text: /****************************************************************************
   * drivers/usbhost/usbhost_cdcacm.c
   *
   *   Copyright (C) 2015-2016 Gregory Nutt. All rights reserved.
   *   Author: Gregory Nutt <gnutt@nuttx.org>
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   * 3. Neither the name NuttX nor the names of its contributors may be
   *    used to endorse or promote products derived from this software
   *    without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
   * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
   * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   *
   ****************************************************************************/
  
  /****************************************************************************
   * Included Files
   ****************************************************************************/
  
  #include <nuttx/config.h>
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <semaphore.h>
  #include <assert.h>
  #include <errno.h>
  #include <debug.h>
  
  #include <nuttx/irq.h>
  #include <nuttx/kmalloc.h>
  #include <nuttx/arch.h>
  #include <nuttx/wqueue.h>
  #include <nuttx/clock.h>
  #include <nuttx/serial/serial.h>
  
  #include <nuttx/usb/usb.h>
  #include <nuttx/usb/usbhost.h>
  #include <nuttx/usb/cdc.h>
  #include <nuttx/usb/cdcacm.h>
  #include <nuttx/usb/usbhost_devaddr.h>
  
  #ifdef CONFIG_USBHOST_CDCACM
  
  /****************************************************************************
   * Pre-processor Definitions
   ****************************************************************************/
  
  /* Configuration ************************************************************/
  
  #ifndef CONFIG_USBHOST
  #  warning USB host support not enabled (CONFIG_USBHOST)
  #endif
  
  #ifdef CONFIG_USBHOST_BULK_DISABLE
  #  warning USB bulk endpoint support is disabled (CONFIG_USBHOST_BULK_DISABLE)
  #endif
  
  #ifdef CONFIG_USBHOST_INT_DISABLE
  #  warning USB interrupt endpoint support is disabled (CONFIG_USBHOST_INT_DISABLE)
  #endif
  
  #if !defined(CONFIG_SCHED_WORKQUEUE)
  #  warning Worker thread support is required (CONFIG_SCHED_WORKQUEUE)
  #else
  #  ifndef CONFIG_SCHED_HPWORK
  #    warning High priority work thread support is required (CONFIG_SCHED_HPWORK)
  #  endif
  #  ifndef CONFIG_SCHED_LPWORK
  #    warning Low priority work thread support is required (CONFIG_SCHED_LPWORK)
  #  endif
  #  if CONFIG_SCHED_LPNTHREADS < 2
  #    warning Multiple low priority work threads recommended for performance (CONFIG_SCHED_LPNTHREADS > 1)
  #  endif
  #endif
  
  #ifndef CONFIG_USBHOST_ASYNCH
  #  warning Asynchronous transfer support is required (CONFIG_USBHOST_ASYNCH)
  #endif
  
  #ifndef CONFIG_SERIAL_REMOVABLE
  #  warning Removable serial device support is required (CONFIG_SERIAL_REMOVABLE)
  #endif
  
  #ifdef CONFIG_USBHOST_CDCACM_NTDELAY
  #  define USBHOST_CDCACM_NTDELAY MSEC2TICK(CONFIG_USBHOST_CDCACM_NTDELAY)
  #else
  #  define USBHOST_CDCACM_NTDELAY MSEC2TICK(200)
  #endif
  
  #ifdef CONFIG_USBHOST_CDCACM_RXDELAY
  #  define USBHOST_CDCACM_RXDELAY MSEC2TICK(CONFIG_USBHOST_CDCACM_RXDELAY)
  #else
  #  define USBHOST_CDCACM_RXDELAY MSEC2TICK(200)
  #endif
  
  #ifdef CONFIG_USBHOST_CDCACM_TXDELAY
  #  define USBHOST_CDCACM_TXDELAY MSEC2TICK(CONFIG_USBHOST_CDCACM_TXDELAY)
  #else
  #  define USBHOST_CDCACM_TXDELAY MSEC2TICK(200)
  #endif
  
  /* Supported protocol */
  
  #define HAVE_CLASS_REQUESTS 1
  #define HAVE_INTIN_ENDPOINT 1
  #define HAVE_CTRL_INTERFACE 1
  
  #if defined(CONFIG_USBHOST_CDCACM_REDUCED)
  #  undef CONFIG_USBHOST_CDCACM_BULKONLY
  #  undef CONFIG_USBHOST_CDCACM_COMPLIANT
  #  undef HAVE_INTIN_ENDPOINT
  #elif defined(CONFIG_USBHOST_CDCACM_BULKONLY)
  #  undef CONFIG_USBHOST_CDCACM_COMPLIANT
  #  undef HAVE_CLASS_REQUESTS
  #  undef HAVE_INTIN_ENDPOINT
  #  undef HAVE_CTRL_INTERFACE
  #endif
  
  /* If the create() method is called by the USB host device driver from an
   * interrupt handler, then it will be unable to call kmm_malloc() in order to
   * allocate a new class instance.  If the create() method is called from the
   * interrupt level, then class instances must be pre-allocated.
   */
  
  #ifndef CONFIG_USBHOST_CDCACM_NPREALLOC
  #  define CONFIG_USBHOST_CDCACM_NPREALLOC 0
  #endif
  
  #if CONFIG_USBHOST_CDCACM_NPREALLOC > 32
  #  error Currently limited to 32 devices /dev/ttyACM[n]
  #endif
  
  #ifndef CONFIG_USBHOST_CDCACM_RXBUFSIZE
  #  define CONFIG_USBHOST_CDCACM_RXBUFSIZE 128
  #endif
  
  #ifndef CONFIG_USBHOST_CDCACM_TXBUFSIZE
  #  define CONFIG_USBHOST_CDCACM_TXBUFSIZE 128
  #endif
  
  /* Initial line coding */
  
  #ifndef CONFIG_USBHOST_CDCACM_BAUD
  #  define CONFIG_USBHOST_CDCACM_BAUD 115200
  #endif
  
  #ifndef CONFIG_USBHOST_CDCACM_PARITY
  #  define CONFIG_USBHOST_CDCACM_PARITY 0
  #endif
  
  #ifndef CONFIG_USBHOST_CDCACM_BITS
  #  define CONFIG_USBHOST_CDCACM_BITS 8
  #endif
  
  #ifndef CONFIG_USBHOST_CDCACM_2STOP
  #  define CONFIG_USBHOST_CDCACM_2STOP 0
  #endif
  
  /* Driver support ***********************************************************/
  /* This format is used to construct the /dev/sd[n] device driver path.  It
   * defined here so that it will be used consistently in all places.
   */
  
  #define DEV_FORMAT             "/dev/ttyACM%d"
  #define DEV_NAMELEN            16
  
  #define MAX_NOTIFICATION       32
  
  /* Used in usbhost_connect() */
  
  #define USBHOST_DATAIF_FOUND   0x01      /* Data interface found */
  #define USBHOST_BULKIN_FOUND   0x02      /* Bulk IN interface found */
  #define USBHOST_BULKOUT_FOUND  0x04      /* Bulk OUT interface found */
  
  #if defined(CONFIG_USBHOST_CDCACM_BULKONLY)
  #  define USBHOST_MINFOUND     0x07      /* Minimum things needed */
  #  define USBHOST_ALLFOUND     0x07      /* All configuration things */
  #elif defined(CONFIG_USBHOST_CDCACM_REDUCED)
  #  define USBHOST_CTRLIF_FOUND 0x08      /* Control interface found */
  
  #  define USBHOST_MINFOUND     0x07      /* Minimum things needed */
  #  define USBHOST_HAVE_CTRLIF  0x08      /* Needed for control interface */
  #  define USBHOST_ALLFOUND     0x0f      /* All configuration things */
  #else
  #  define USBHOST_CTRLIF_FOUND 0x08      /* Control interface found */
  #  define USBHOST_INTIN_FOUND  0x10      /* Interrupt IN interface found */
  
  #  define USBHOST_MINFOUND     0x07      /* Minimum things needed */
  #  define USBHOST_HAVE_CTRLIF  0x18      /* Needed for control interface */
  #  define USBHOST_ALLFOUND     0x1f      /* All configuration things */
  #endif
  
  #define USBHOST_MAX_CREFS      INT16_MAX /* Max cref count before signed overflow */
  
  /****************************************************************************
   * Private Types
   ****************************************************************************/
  
  /* This structure contains the internal, private state of the USB host
   * CDC/ACM class.
   */
  
  struct usbhost_cdcacm_s
  {
    /* This is the externally visible portion of the state.  The usbclass must
     * the first element of the structure.  It is then cast compatible with
     * struct usbhost_cdcacm_s.
     */
  
    struct usbhost_class_s usbclass;
  
    /* This is the standard of the lower-half serial interface.  It is not
     * the first element of the structure, but includes a pointer back to the
     * the beginning of this structure.
     */
  
    struct uart_dev_s uartdev;
  
    /* The remainder of the fields are provide to the CDC/ACM class */
  
    volatile bool  disconnected;   /* TRUE: Device has been disconnected */
    bool           stop2;          /* True: 2 stop bits (for line coding) */
    bool           dsr;            /* State of transmission carrier */
    bool           dcd;            /* State of receiver carrier detection */
    bool           txena;          /* True: TX "interrupts" enabled */
    bool           rxena;          /* True: RX "interrupts" enabled */
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
    bool           iflow;          /* True: Input flow control (RTS) enabled */
    bool           rts;            /* True: Input flow control is in effect */
  #endif
  #ifdef CONFIG_SERIAL_OFLOWCONTROL
    bool           oflow;          /* True: Output flow control (CTS) enabled */
  #endif
    uint8_t        minor;          /* Minor number identifying the /dev/ttyACM[n] device */
    uint8_t        dataif;         /* Data interface number */
  #ifdef HAVE_CTRL_INTERFACE
    uint8_t        ctrlif;         /* Control interface number */
  #endif
    uint8_t        nbits;          /* Number of bits (for line encoding) */
    uint8_t        parity;         /* Parity (for line encoding) */
    uint16_t       pktsize;        /* Allocated size of transfer buffers */
    uint16_t       nrxbytes;       /* Number of bytes in the RX packet buffer */
    uint16_t       rxndx;          /* Index to the next byte in the RX packet buffer */
    int16_t        crefs;          /* Reference count on the driver instance */
    int16_t        nbytes;         /* The number of bytes actually transferred */
    sem_t          exclsem;        /* Used to maintain mutual exclusive access */
    struct work_s  ntwork;         /* For asynchronous notification work */
    struct work_s  rxwork;         /* For RX packet work */
    struct work_s  txwork;         /* For TX packet work */
    FAR uint8_t   *ctrlreq;        /* Allocated ctrl request structure */
    FAR uint8_t   *linecode;       /* The allocated buffer for line encoding */
    FAR uint8_t   *notification;   /* The allocated buffer for async notifications */
    FAR uint8_t   *inbuf;          /* Allocated RX buffer for the Bulk IN endpoint */
    FAR uint8_t   *outbuf;         /* Allocated TX buffer for the Bulk OUT endpoint */
    uint32_t       baud;           /* Current baud for line coding */
    usbhost_ep_t   bulkin;         /* Bulk IN endpoint */
    usbhost_ep_t   bulkout;        /* Bulk OUT endpoint */
  #ifdef HAVE_INTIN_ENDPOINT
    usbhost_ep_t   intin;          /* Interrupt IN endpoint (optional) */
  #endif
  
    /* This is the serial data buffer */
  
    char           rxbuffer[CONFIG_USBHOST_CDCACM_RXBUFSIZE];
    char           txbuffer[CONFIG_USBHOST_CDCACM_TXBUFSIZE];
  };
  
  /* This is how struct usbhost_cdcacm_s looks to the free list logic */
  
  struct usbhost_freestate_s
  {
    FAR struct usbhost_freestate_s *flink;
  };
  
  /****************************************************************************
   * Private Function Prototypes
   ****************************************************************************/
  
  /* Semaphores */
  
  static void usbhost_takesem(sem_t *sem);
  #define usbhost_givesem(s) sem_post(s);
  
  /* Memory allocation services */
  
  static FAR struct usbhost_cdcacm_s *usbhost_allocclass(void);
  static void usbhost_freeclass(FAR struct usbhost_cdcacm_s *usbclass);
  
  /* Device name management */
  
  static int  usbhost_devno_alloc(FAR struct usbhost_cdcacm_s *priv);
  static void usbhost_devno_free(FAR struct usbhost_cdcacm_s *priv);
  static inline void usbhost_mkdevname(FAR struct usbhost_cdcacm_s *priv,
                FAR char *devname);
  
  /* CDC/ACM request helpers */
  
  #ifdef HAVE_CTRL_INTERFACE
  static int  usbhost_linecoding_send(FAR struct usbhost_cdcacm_s *priv);
  #ifdef HAVE_INTIN_ENDPOINT
  static void usbhost_notification_work(FAR void *arg);
  static void usbhost_notification_callback(FAR void *arg, ssize_t nbytes);
  #endif
  #endif
  
  /* UART buffer data transfer */
  
  static void usbhost_txdata_work(FAR void *arg);
  static void usbhost_rxdata_work(FAR void *arg);
  
  /* Worker thread actions */
  
  static void usbhost_destroy(FAR void *arg);
  
  /* Helpers for usbhost_connect() */
  
  static int  usbhost_cfgdesc(FAR struct usbhost_cdcacm_s *priv,
                FAR const uint8_t *configdesc, int desclen);
  
  /* (Little Endian) Data helpers */
  
  static inline uint16_t usbhost_getle16(const uint8_t *val);
  static inline uint16_t usbhost_getbe16(const uint8_t *val);
  static inline void usbhost_putle16(uint8_t *dest, uint16_t val);
  #ifdef HAVE_CTRL_INTERFACE
  static void usbhost_putle32(uint8_t *dest, uint32_t val);
  #endif
  
  /* Transfer descriptor memory management */
  
  static int  usbhost_alloc_buffers(FAR struct usbhost_cdcacm_s *priv);
  static void usbhost_free_buffers(FAR struct usbhost_cdcacm_s *priv);
  
  /* struct usbhost_registry_s methods */
  
  static struct usbhost_class_s *usbhost_create(
                FAR struct usbhost_hubport_s *hport,
                FAR const struct usbhost_id_s *id);
  
  /* struct usbhost_class_s methods */
  
  static int  usbhost_connect(FAR struct usbhost_class_s *usbclass,
                FAR const uint8_t *configdesc, int desclen);
  static int  usbhost_disconnected(FAR struct usbhost_class_s *usbclass);
  
  /* Serial driver lower-half interfaces */
  
  static int  usbhost_setup(FAR struct uart_dev_s *uartdev);
  static void usbhost_shutdown(FAR struct uart_dev_s *uartdev);
  static int  usbhost_attach(FAR struct uart_dev_s *uartdev);
  static void usbhost_detach(FAR struct uart_dev_s *uartdev);
  static int  usbhost_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
  static void usbhost_rxint(FAR struct uart_dev_s *uartdev, bool enable);
  static bool usbhost_rxavailable(FAR struct uart_dev_s *uartdev);
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
  static bool usbhost_rxflowcontrol(FAR struct uart_dev_s *uartdev,
                unsigned int nbuffered, bool upper);
  #endif
  static void usbhost_txint(FAR struct uart_dev_s *uartdev, bool enable);
  static bool usbhost_txready(FAR struct uart_dev_s *uartdev);
  static bool usbhost_txempty(FAR struct uart_dev_s *uartdev);
  
  /****************************************************************************
   * Private Data
   ****************************************************************************/
  
  /* This structure provides the registry entry ID information that will  be
   * used to associate the USB host CDC/ACM class to a connected USB
   * device.
   */
  
  static const const struct usbhost_id_s g_id =
  {
    USB_CLASS_CDC,          /* base     */
    CDC_SUBCLASS_NONE,      /* subclass */
    CDC_PROTO_NONE,         /* proto    */
    0,                      /* vid      */
    0                       /* pid      */
  };
  
  /* This is the USB host CDC/ACM class's registry entry */
  
  static struct usbhost_registry_s g_cdcacm =
  {
    NULL,                   /* flink    */
    usbhost_create,         /* create   */
    1,                      /* nids     */
    &g_id                   /* id[]     */
  };
  
  /* Serial driver lower half interface */
  
  static const struct uart_ops_s g_uart_ops =
  {
    usbhost_setup,         /* setup */
    usbhost_shutdown,      /* shutdown */
    usbhost_attach,        /* attach */
    usbhost_detach,        /* detach */
    usbhost_ioctl,         /* ioctl */
    NULL           ,       /* receive */
    usbhost_rxint,         /* rxinit */
    usbhost_rxavailable,   /* rxavailable */
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
    usbhost_rxflowcontrol, /* rxflowcontrol */
  #endif
    NULL,                  /* send */
    usbhost_txint,         /* txinit */
    usbhost_txready,       /* txready */
    usbhost_txempty        /* txempty */
  };
  
  /* This is an array of pre-allocated USB host CDC/ACM class instances */
  
  #if CONFIG_USBHOST_CDCACM_NPREALLOC > 0
  static struct usbhost_cdcacm_s g_prealloc[CONFIG_USBHOST_CDCACM_NPREALLOC];
  #endif
  
  /* This is a list of free, pre-allocated USB host CDC/ACM class instances */
  
  #if CONFIG_USBHOST_CDCACM_NPREALLOC > 0
  static FAR struct usbhost_freestate_s *g_freelist;
  #endif
  
  /* This is a bitmap that is used to allocate device minor numbers /dev/ttyACM[n]. */
  
  static uint32_t g_devinuse;
  
  /****************************************************************************
   * Private Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: usbhost_takesem
   *
   * Description:
   *   This is just a wrapper to handle the annoying behavior of semaphore
   *   waits that return due to the receipt of a signal.
   *
   ****************************************************************************/
  
  static void usbhost_takesem(sem_t *sem)
  {
    /* Take the semaphore (perhaps waiting) */
  
    while (sem_wait(sem) != 0)
      {
        /* The only case that an error should occur here is if the wait was
         * awakened by a signal.
         */
  
        ASSERT(errno == EINTR);
      }
  }
  
  /****************************************************************************
   * Name: usbhost_allocclass
   *
   * Description:
   *   This is really part of the logic that implements the create() method
   *   of struct usbhost_registry_s.  This function allocates memory for one
   *   new class instance.
   *
   * Input Parameters:
   *   None
   *
   * Returned Value:
   *   On success, this function will return a non-NULL instance of struct
   *   usbhost_class_s.  NULL is returned on failure; this function will
   *   will fail only if there are insufficient resources to create another
   *   USB host class instance.
   *
   ****************************************************************************/
  
  #if CONFIG_USBHOST_CDCACM_NPREALLOC > 0
  static FAR struct usbhost_cdcacm_s *usbhost_allocclass(void)
  {
    FAR struct usbhost_freestate_s *entry;
    irqstate_t flags;
  
    /* We may be executing from an interrupt handler so we need to take one of
     * our pre-allocated class instances from the free list.
     */
  
    flags = enter_critical_section();
    entry = g_freelist;
    if (entry)
      {
        g_freelist = entry->flink;
      }
  
    leave_critical_section(flags);
    uinfo("Allocated: %p\n", entry);
    return (FAR struct usbhost_cdcacm_s *)entry;
  }
  #else
  static FAR struct usbhost_cdcacm_s *usbhost_allocclass(void)
  {
    FAR struct usbhost_cdcacm_s *priv;
  
    /* We are not executing from an interrupt handler so we can just call
     * kmm_malloc() to get memory for the class instance.
     */
  
    DEBUGASSERT(!up_interrupt_context());
    priv = (FAR struct usbhost_cdcacm_s *)kmm_malloc(sizeof(struct usbhost_cdcacm_s));
    uinfo("Allocated: %p\n", priv);
    return priv;
  }
  #endif
  
  /****************************************************************************
   * Name: usbhost_freeclass
   *
   * Description:
   *   Free a class instance previously allocated by usbhost_allocclass().
   *
   * Input Parameters:
   *   usbclass - A reference to the class instance to be freed.
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  #if CONFIG_USBHOST_CDCACM_NPREALLOC > 0
  static void usbhost_freeclass(FAR struct usbhost_cdcacm_s *usbclass)
  {
    FAR struct usbhost_freestate_s *entry = (FAR struct usbhost_freestate_s *)usbclass;
    irqstate_t flags;
    DEBUGASSERT(entry != NULL);
  
    uinfo("Freeing: %p\n", entry);
  
    /* Just put the pre-allocated class structure back on the freelist */
  
    flags = enter_critical_section();
    entry->flink = g_freelist;
    g_freelist = entry;
    leave_critical_section(flags);
  }
  #else
  static void usbhost_freeclass(FAR struct usbhost_cdcacm_s *usbclass)
  {
    DEBUGASSERT(usbclass != NULL);
  
    /* Free the class instance (calling sched_kfree() in case we are executing
     * from an interrupt handler.
     */
  
    uinfo("Freeing: %p\n", usbclass);
    sched_kfree(usbclass);
  }
  #endif
  
  /****************************************************************************
   * Name: usbhost_devno_alloc
   *
   * Description:
   *   Allocate a unique /dev/ttyACM[n] minor number in the range 0-31.
   *
   ****************************************************************************/
  
  static int usbhost_devno_alloc(FAR struct usbhost_cdcacm_s *priv)
  {
    irqstate_t flags;
    int devno;
  
    flags = enter_critical_section();
    for (devno = 0; devno < 32; devno++)
      {
        uint32_t bitno = 1 << devno;
        if ((g_devinuse & bitno) == 0)
          {
            g_devinuse |= bitno;
            priv->minor = devno;
            leave_critical_section(flags);
            return OK;
          }
      }
  
    leave_critical_section(flags);
    return -EMFILE;
  }
  
  /****************************************************************************
   * Name: usbhost_devno_free
   *
   * Description:
   *   Free a /dev/ttyACM[n] minor number so that it can be used.
   *
   ****************************************************************************/
  
  static void usbhost_devno_free(FAR struct usbhost_cdcacm_s *priv)
  {
    int devno = priv->minor;
  
    if (devno >= 0 && devno < 32)
      {
        irqstate_t flags = enter_critical_section();
        g_devinuse &= ~(1 << devno);
        leave_critical_section(flags);
      }
  }
  
  /****************************************************************************
   * Name: usbhost_mkdevname
   *
   * Description:
   *   Format a /dev/ttyACM[n] device name given a minor number.
   *
   ****************************************************************************/
  
  static inline void usbhost_mkdevname(FAR struct usbhost_cdcacm_s *priv,
                                       FAR char *devname)
  {
    (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->minor);
  }
  
  /****************************************************************************
   * Name: usbhost_linecoding_send
   *
   * Description:
   *   Format and send the on EP0.
   *
   * Input Parameters:
   *   arg - A reference to the class instance to be destroyed.
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  #ifdef HAVE_CTRL_INTERFACE
  static int usbhost_linecoding_send(FAR struct usbhost_cdcacm_s *priv)
  {
    FAR struct usbhost_hubport_s *hport;
    FAR struct cdc_linecoding_s *linecode;
    FAR struct usb_ctrlreq_s *ctrlreq;
    int ret;
  
    hport = priv->usbclass.hport;
    DEBUGASSERT(hport);
  
    /* Initialize the line coding structure */
  
    linecode         = (FAR struct cdc_linecoding_s *)priv->linecode;
    usbhost_putle32(linecode->baud, priv->baud);
    linecode->stop   = (priv->stop2) ? 2 : 0;
    linecode->parity = priv->parity;
    linecode->nbits  = priv->nbits;
  
    /* Initialize the control request */
  
    ctrlreq          = (FAR struct usb_ctrlreq_s *)priv->ctrlreq;
    ctrlreq->type    = USB_DIR_OUT | USB_REQ_TYPE_CLASS | USB_REQ_RECIPIENT_INTERFACE;
    ctrlreq->req     = ACM_SET_LINE_CODING;
  
    usbhost_putle16(ctrlreq->value, 0);
    usbhost_putle16(ctrlreq->index, priv->ctrlif);
    usbhost_putle16(ctrlreq->len,   SIZEOF_CDC_LINECODING);
  
    /* And send the request */
  
    ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, priv->linecode);
    if (ret < 0)
      {
        uerr("ERROR: DRVR_CTRLOUT failed: %d\n", ret);
      }
  
    return ret;
  }
  #endif
  
  /****************************************************************************
   * Name: usbhost_notification_work
   *
   * Description:
   *   Handle receipt of an asynchronous notification from the CDC/ACM device
   *
   * Input Parameters:
   *   arg - The argument provided with the asynchronous I/O was setup
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   Probably called from an interrupt handler.
   *
   ****************************************************************************/
  
  #ifdef HAVE_INTIN_ENDPOINT
  static void usbhost_notification_work(FAR void *arg)
  {
    FAR struct usbhost_cdcacm_s *priv;
    FAR struct usbhost_hubport_s *hport;
    FAR struct cdc_notification_s *inmsg;
    uint16_t value;
    uint16_t index;
    uint16_t len;
    int ret;
  
    priv = (FAR struct usbhost_cdcacm_s *)arg;
    DEBUGASSERT(priv);
  
    hport = priv->usbclass.hport;
    DEBUGASSERT(hport);
  
    /* Are we still connected? */
  
    if (!priv->disconnected && priv->intin)
      {
        /* Yes.. Was an interrupt IN message received correctly? */
  
        if (priv->nbytes >= 0)
          {
            /* Yes.. decode the notification */
  
            inmsg = (FAR struct cdc_notification_s *)priv->notification;
            value = usbhost_getle16(inmsg->value);
            index = usbhost_getle16(inmsg->index);
            len   = usbhost_getle16(inmsg->len);
  
            uinfo("type: %02x notification: %02x value: %04x index: %04x len: %04x\n",
                   inmsg->type, inmsg->notification, value, index, len);
  
            /* We care only about the SerialState notification */
  
            if ((inmsg->type         == (USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS |
                                         USB_REQ_RECIPIENT_INTERFACE)) &&
                (inmsg->notification == ACM_SERIAL_STATE) &&
                (value               == 0) &&
                (index               == priv->ctrlif) &&
                (len                 == 2))
              {
                uint16_t state = usbhost_getle16(inmsg->data);
  
                /* And we care only about the state of the DCD and DSR in the
                 * the notification.
                 */
  
                priv->dcd = ((state & CDC_UART_RXCARRIER) != 0);
                priv->dsr = ((state & CDC_UART_TXCARRIER) != 0);
  
                uinfo("ACM_SERIAL_STATE: DCD=%d DSR=%d\n",
                      priv->dcd, priv->dsr);
              }
          }
  
        /* Setup to receive the next line status change event */
  
        ret = DRVR_ASYNCH(hport->drvr, priv->intin,
                          (FAR uint8_t *)priv->notification,
                          MAX_NOTIFICATION, usbhost_notification_callback,
                          priv);
        if (ret < 0)
          {
            uerr("ERROR: DRVR_ASYNCH failed: %d\n", ret);
          }
      }
  }
  #endif
  
  /****************************************************************************
   * Name: usbhost_notification_callback
   *
   * Description:
   *   Handle receipt of line status from the CDC/ACM device
   *
   * Input Parameters:
   *   arg - The argument provided with the asynchronous I/O was setup
   *   nbytes - The number of bytes actually transferred (or a negated errno
   *     value;
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   Probably called from an interrupt handler.
   *
   ****************************************************************************/
  
  #ifdef HAVE_INTIN_ENDPOINT
  static void usbhost_notification_callback(FAR void *arg, ssize_t nbytes)
  {
    FAR struct usbhost_cdcacm_s *priv = (FAR struct usbhost_cdcacm_s *)arg;
    uint32_t delay = 0;
  
    DEBUGASSERT(priv);
  
    /* Are we still connected? */
  
    if (!priv->disconnected)
      {
        /* Check for a failure.  On higher end host controllers, the
         * asynchronous transfer will pend until data is available (OHCI and
         * EHCI).  On lower end host controllers (like STM32 and EFM32), the
         * transfer will fail immediately when the device NAKs the first
         * attempted interrupt IN transfer (with nbytes == -EAGAIN).  In that
         * case (or in the case of other errors), we must fall back to
         * polling.
         */
  
        DEBUGASSERT(nbytes >= INT16_MIN && nbytes <= INT16_MAX);
        priv->nbytes = (int16_t)nbytes;
  
        if (nbytes < 0)
          {
            /* This debug output is good to know, but really a nuisance for
             * those configurations where we have to fall back to polling.
             * FIX:  Don't output the message is the result is -EAGAIN.
             */
  
  #if defined(CONFIG_DEBUG_USB) && !defined(CONFIG_DEBUG_INFO)
            if (nbytes != -EAGAIN)
  #endif
              {
                uerr("ERROR: Transfer failed: %d\n", nbytes);
              }
  
            /* We don't know the nature of the failure, but we need to do all
             * that we can do to avoid a CPU hog error loop.
             *
             * Use the low-priority work queue and delay polling for the next
             * event.  We want to use as little CPU bandwidth as possible in
             * this case.
             */
  
            delay = USBHOST_CDCACM_NTDELAY;
          }
  
        /* Make sure that the  work structure available.  There is a remote
         * chance that this may collide with a device disconnection event.
         */
  
        if (work_available(&priv->ntwork))
          {
            (void)work_queue(HPWORK, &priv->ntwork,
                             (worker_t)usbhost_notification_work,
                             priv, delay);
          }
      }
  }
  #endif
  
  /************************************************************************************
   * UART buffer data transfer
   ************************************************************************************/
  /****************************************************************************
   * Name: usbhost_txdata_work
   *
   * Description:
   *   Send more OUT data to the attached CDC/ACM device.
   *
   * Input Parameters:
   *   arg - A reference to the CDC/ACM class private data
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void usbhost_txdata_work(FAR void *arg)
  {
    FAR struct usbhost_cdcacm_s *priv;
    FAR struct usbhost_hubport_s *hport;
    FAR struct uart_dev_s *uartdev;
    FAR struct uart_buffer_s *txbuf;
    ssize_t nwritten;
    int txndx;
    int txtail;
    int ret;
  
    priv = (FAR struct usbhost_cdcacm_s *)arg;
    DEBUGASSERT(priv);
  
    hport = priv->usbclass.hport;
    DEBUGASSERT(hport);
  
    uartdev = &priv->uartdev;
    txbuf   = &uartdev->xmit;
  
    /* Do nothing if TX transmission is disabled */
  
    if (!priv->txena)
      {
        /* Terminate the work now *without* rescheduling */
  
        return;
      }
  
    /* Loop until The UART TX buffer is empty (or we become disconnected) */
  
    txtail = txbuf->tail;
    txndx  = 0;
  
    while (txtail != txbuf->head && priv->txena && !priv->disconnected)
      {
        /* Copy data from the UART TX buffer until either 1) the UART TX
         * buffer has been emptied, or 2) the Bulk OUT buffer is full.
         */
  
        txndx = 0;
        while (txtail != txbuf->head && txndx < priv->pktsize)
          {
            /* Copy the next byte */
  
            priv->outbuf[txndx] = txbuf->buffer[txtail];
  
            /* Increment counters and indices */
  
            txndx++;
            if (++txtail > txbuf->size)
              {
               txtail = 0;
              }
          }
  
        /* Save the updated tail pointer so that it cannot be sent again */
  
        txbuf->tail = txtail;
  
        /* Bytes were removed from the TX buffer.  Inform any waiters that
         * there is space available in the TX buffer.
         */
  
        uart_datasent(uartdev);
  
        /* Send the filled TX buffer to the CDC/ACM device */
  
        nwritten = DRVR_TRANSFER(hport->drvr, priv->bulkout,
                                 priv->outbuf, txndx);
        if (nwritten < 0)
          {
            /* The most likely reason for a failure is that CDC/ACM device
             * NAK'ed our packet OR that the device has been disconnected.
             *
             * Just break out of the loop, rescheduling the work (unless
             * the device is disconnected).
             */
  
            uerr("ERROR: DRVR_TRANSFER for packet failed: %d\n", (int)nwritten);
            break;
          }
      }
  
    /* We get here because: 1) the UART TX buffer is empty and there is
     * nothing more to send, 2) the CDC/ACM device was not ready to accept our
     * data, or the device is no longer available.
     *
     * If the last packet sent was and even multiple of the packet size, then
     * we need to send a zero length packet (ZLP).
     */
  
    if (txndx == priv->pktsize && !priv->disconnected)
      {
        /* Send the ZLP to the CDC/ACM device */
  
        nwritten = DRVR_TRANSFER(hport->drvr, priv->bulkout,
                                 priv->outbuf, 0);
        if (nwritten < 0)
          {
            /* The most likely reason for a failure is that CDC/ACM device
             * NAK'ed our packet.
             */
  
            uerr("ERROR: DRVR_TRANSFER for ZLP failed: %d\n", (int)nwritten);
          }
      }
  
    /* Check again if TX reception is enabled and that the device is still
     * connected.  These states could have changed since we started the
     * transfer.
     */
  
    if (priv->txena && !priv->disconnected)
      {
        /* Schedule TX data work to occur after a delay. */
  
        ret = work_queue(LPWORK, &priv->txwork, usbhost_txdata_work, priv,
                         USBHOST_CDCACM_TXDELAY);
        DEBUGASSERT(ret >= 0);
        UNUSED(ret);
      }
  }
  
  /****************************************************************************
   * Name: usbhost_rxdata_work
   *
   * Description:
   *   Get more IN data from the attached CDC/ACM device.
   *
   * Input Parameters:
   *   arg - A reference to the CDC/ACM class private data
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void usbhost_rxdata_work(FAR void *arg)
  {
    FAR struct usbhost_cdcacm_s *priv;
    FAR struct usbhost_hubport_s *hport;
    FAR struct uart_dev_s *uartdev;
    FAR struct uart_buffer_s *rxbuf;
    ssize_t nread;
    int nxfrd;
    int nexthead;
    int rxndx;
    int ret;
  
    priv    = (FAR struct usbhost_cdcacm_s *)arg;
    DEBUGASSERT(priv);
  
    hport   = priv->usbclass.hport;
    DEBUGASSERT(hport);
  
    uartdev = &priv->uartdev;
    rxbuf   = &uartdev->recv;
  
    /* Get the index in the RX packet buffer where we will take the first
     * byte of data.
     */
  
    rxndx    = priv->rxndx;
    nxfrd    = 0;
  
    /* Get the index to the value of the UART RX buffer head AFTER the
     * first character has been stored.  We need to know this in order
     * to test if the UART RX buffer is full.
     */
  
    nexthead = rxbuf->head + 1;
    if (nexthead >= rxbuf->size)
      {
        nexthead = 0;
      }
  
    /* Loop until either:
     *
     * 1. The UART RX buffer is full
     * 2. There is no more data available from the CDC/ACM device
     * 3. RX rec
     */
  
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
    while (priv->rxena && priv->rts && !priv->disconnected)
  #else
    while (priv->rxena && !priv->disconnected)
  #endif
      {
        /* Stop now if there is no room for another character in the RX buffer. */
  
        if (nexthead == rxbuf->tail)
          {
            /* Break out of the loop, rescheduling the work */
  
            break;
          }
  
        /* Do we have any buffer RX data to transfer? */
  
        if (priv->nrxbytes < 1)
          {
            /* No.. Read more data from the CDC/ACM device */
  
            nread = DRVR_TRANSFER(hport->drvr, priv->bulkin,
                                  priv->inbuf, priv->pktsize);
            if (nread < 0)
              {
                /* The most likely reason for a failure is that the has no
                 * data available now and NAK'ed the IN token OR that the
                 * transfer was cancelled because the device was disconnected.
                 *
                 * Just break out of the loop, rescheduling the work (if the
                 * device was not disconnected.
                 */
  
                uerr("ERROR: DRVR_TRANSFER for packet failed: %d\n", (int)nread);
                break;
              }
  
            /* Save the number of bytes read.  This might be zero if
             * a Zero Length Packet (ZLP) is received.  The ZLP is
             * part of the bulk transfer protocol, but otherwise of
             * no interest to us.
             */
  
            DEBUGASSERT(nread <= priv->pktsize);
            priv->nrxbytes = (uint16_t)nread;
            rxndx          = 0;
  
            /* Ignore ZLPs */
  
            if (nread == 0)
              {
                continue;
              }
          }
  
        /* Transfer one byte from the RX packet buffer into UART RX buffer */
  
        rxbuf->buffer[rxbuf->head] = priv->inbuf[rxndx];
        nxfrd++;
  
        /* Save the updated indices */
  
        rxbuf->head = nexthead;
        priv->rxndx = rxndx;
  
        /* Update the head point for for the next pass through the loop
         * handling. If nexthead incremented to rxbuf->tail, then the
         * RX buffer will and we will exit the loop at the top.
         */
  
        if (++nexthead >= rxbuf->size)
          {
             nexthead = 0;
          }
  
        /* Increment the index in the USB IN packet buffer.  If the
         * index becomes equal to the number of bytes in the buffer, then
         * we have consumed all of the RX data.
         */
  
        if (++rxndx >= priv->nrxbytes)
          {
            /* In that case set the number of bytes in the buffer to zero.
             * This will force re-reading on the next time through the loop.
             */
  
            priv->nrxbytes = 0;
            priv->rxndx    = 0;
  
            /* Inform any waiters there there is new incoming data available. */
  
            uart_datareceived(uartdev);
            nxfrd = 0;
          }
      }
  
    /* We break out to here:  1) the UART RX buffer is full, 2) the CDC/ACM
     * device is not ready to provide us with more serial data, or 3) the
     * device has been disconnected.
     *
     * Check if the device is still available:  RX enabled, no RX flow
     * control in effect, and that the device is not disconnected. These
     * states could have changed since we started the transfer.
     */
  
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
    if (priv->rxena && priv->rts && work_available(&priv->rxwork) && !priv->disconnected)
  #else
    if (priv->rxena && work_available(&priv->rxwork) && !priv->disconnected)
  #endif
      {
        /* Schedule RX data reception work to occur after a delay.  This will
         * affect our responsive in certain cases.  The delayed work, however,
         * will be cancelled and replaced with immediate work when the upper
         * layer demands more data.
         */
  
        ret = work_queue(LPWORK, &priv->rxwork, usbhost_rxdata_work, priv,
                         USBHOST_CDCACM_RXDELAY);
        DEBUGASSERT(ret >= 0);
        UNUSED(ret);
      }
  
    /* If any bytes were added to the buffer, inform any waiters there there
     * is new incoming data available.
     */
  
    if (nxfrd)
      {
        uart_datareceived(uartdev);
      }
  }
  
  /****************************************************************************
   * Name: usbhost_destroy
   *
   * Description:
   *   The USB CDC/ACM device has been disconnected and the reference count
   *   on the USB host class instance has gone to 1.. Time to destroy the USB
   *   host class instance.
   *
   * Input Parameters:
   *   arg - A reference to the class instance to be destroyed.
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void usbhost_destroy(FAR void *arg)
  {
    FAR struct usbhost_cdcacm_s *priv = (FAR struct usbhost_cdcacm_s *)arg;
    FAR struct usbhost_hubport_s *hport;
    char devname[DEV_NAMELEN];
  
    DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL);
    hport = priv->usbclass.hport;
  
    uinfo("crefs: %d\n", priv->crefs);
  
    /* Unregister the serial lower half driver */
  
    usbhost_mkdevname(priv, devname);
  #warning Missing logic
  
    /* Release the device name used by this connection */
  
    usbhost_devno_free(priv);
  
    /* Free the allocated endpoints */
  
    if (priv->bulkout)
      {
        DRVR_EPFREE(hport->drvr, priv->bulkout);
      }
  
    if (priv->bulkin)
      {
        DRVR_EPFREE(hport->drvr, priv->bulkin);
      }
  
  #ifdef HAVE_INTIN_ENDPOINT
    if (priv->intin)
      {
        DRVR_EPFREE(hport->drvr, priv->intin);
      }
  #endif
  
    /* Free any transfer buffers */
  
    usbhost_free_buffers(priv);
  
    /* Destroy the semaphores */
  
    sem_destroy(&priv->exclsem);
  
    /* Disconnect the USB host device */
  
    DRVR_DISCONNECT(hport->drvr, hport);
  
    /* Free the function address assigned to this device */
  
    usbhost_devaddr_destroy(hport, hport->funcaddr);
    hport->funcaddr = 0;
  
    /* And free the class instance.  */
  
    usbhost_freeclass(priv);
  }
  
  /****************************************************************************
   * Name: usbhost_cfgdesc
   *
   * Description:
   *   This function implements the connect() method of struct
   *   usbhost_class_s.  This method is a callback into the class
   *   implementation.  It is used to provide the device's configuration
   *   descriptor to the class so that the class may initialize properly
   *
   * Input Parameters:
   *   priv - The USB host class instance.
   *   configdesc - A pointer to a uint8_t buffer container the configuration
   *     descriptor.
   *   desclen - The length in bytes of the configuration descriptor.
   *
   * Returned Value:
   *   On success, zero (OK) is returned. On a failure, a negated errno value is
   *   returned indicating the nature of the failure
   *
   * Assumptions:
   *   This function will *not* be called from an interrupt handler.
   *
   ****************************************************************************/
  
  static int usbhost_cfgdesc(FAR struct usbhost_cdcacm_s *priv,
                             FAR const uint8_t *configdesc, int desclen)
  {
    FAR struct usbhost_hubport_s *hport;
    FAR struct usb_cfgdesc_s *cfgdesc;
    FAR struct usb_desc_s *desc;
    FAR struct usbhost_epdesc_s bindesc;
    FAR struct usbhost_epdesc_s boutdesc;
    FAR struct usbhost_epdesc_s iindesc;
    int remaining;
    uint8_t found  = 0;
    uint8_t currif = 0;
    int ret;
  
    DEBUGASSERT(priv != NULL && priv->usbclass.hport &&
                configdesc != NULL && desclen >= sizeof(struct usb_cfgdesc_s));
    hport = priv->usbclass.hport;
  
    /* Keep the compiler from complaining about uninitialized variables */
  
    memset(&bindesc, 0, sizeof(struct usbhost_epdesc_s));
    memset(&boutdesc, 0, sizeof(struct usbhost_epdesc_s));
    memset(&iindesc, 0, sizeof(struct usbhost_epdesc_s));
  
    /* Verify that we were passed a configuration descriptor */
  
    cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc;
    if (cfgdesc->type != USB_DESC_TYPE_CONFIG)
      {
        return -EINVAL;
      }
  
    /* Get the total length of the configuration descriptor (little endian).
     * It might be a good check to get the number of interfaces here too.
     */
  
    remaining = (int)usbhost_getle16(cfgdesc->totallen);
  
    /* Skip to the next entry descriptor */
  
    configdesc += cfgdesc->len;
    remaining  -= cfgdesc->len;
  
    /* Loop where there are more descriptors to examine */
  
    while (remaining >= sizeof(struct usb_desc_s))
      {
        /* What is the next descriptor? */
  
        desc = (FAR struct usb_desc_s *)configdesc;
        switch (desc->type)
          {
          /* Interface descriptor. The CDC/ACM device may include two
           * interfaces:
           *
           * 1) A data interface which consists of two endpoints (bulk in +
           *    bulk out) and
           * 2) A control interface which consists of one interrupt in
           *    endpoint.
           */
  
          case USB_DESC_TYPE_INTERFACE:
            {
              FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *)configdesc;
  
              uinfo("Interface descriptor: class: %d subclass: %d proto: %d\n",
                    ifdesc->classid, ifdesc->subclass, ifdesc->protocol);
              DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC);
  
              /* Check for the CDC/ACM data interface */
  
              if (ifdesc->classid == USB_CLASS_CDC_DATA &&
                  (found & USBHOST_DATAIF_FOUND) == 0)
                {
                  /* Save the data interface number and mark that the data
                   * interface found has been found.
                   */
  
                  priv->dataif  = ifdesc->ifno;
                  found        |= USBHOST_DATAIF_FOUND;
                  currif        = USBHOST_DATAIF_FOUND;
                }
  #ifdef HAVE_CTRL_INTERFACE
              else if (ifdesc->classid == USB_CLASS_CDC &&
                       (found & USBHOST_CTRLIF_FOUND) == 0)
                {
                  /* Otherwise, tentatively assume that this is the control
                   * interface.
                   */
  
                  priv->ctrlif  = ifdesc->ifno;
                  currif        = USBHOST_CTRLIF_FOUND;
                }
  #endif
              else
                {
                  /* Its something else */
  
                  currif        = 0;
                }
            }
            break;
  
          /* Endpoint descriptor.  We expect two bulk endpoints, an IN and an
           * OUT.
           */
  
          case USB_DESC_TYPE_ENDPOINT:
            {
              FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)configdesc;
  
              uinfo("Endpoint descriptor: currif: %02x attr: %02x\n",
                    currif, epdesc->attr);
              DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC);
  
              /* Check for a bulk endpoint. */
  
              if (currif == USBHOST_DATAIF_FOUND &&
                  (epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == USB_EP_ATTR_XFER_BULK)
                {
                  /* Yes.. it is a bulk endpoint.  IN or OUT? */
  
                  if (USB_ISEPOUT(epdesc->addr))
                    {
                      /* It is an OUT bulk endpoint.  There should be only one
                       * bulk OUT endpoint.
                       */
  
                      if ((found & USBHOST_BULKOUT_FOUND) != 0)
                        {
                          /* Oops.. more than one endpoint.  We don't know
                           * what to do with this.
                           */
  
                          return -EINVAL;
                        }
  
                      found |= USBHOST_BULKOUT_FOUND;
  
                      /* Save the bulk OUT endpoint information */
  
                      boutdesc.hport        = hport;
                      boutdesc.addr         = epdesc->addr & USB_EP_ADDR_NUMBER_MASK;
                      boutdesc.in           = false;
                      boutdesc.xfrtype      = USB_EP_ATTR_XFER_BULK;
                      boutdesc.interval     = epdesc->interval;
                      boutdesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize);
  
                      uinfo("Bulk OUT EP addr:%d mxpacketsize:%d\n",
                            boutdesc.addr, boutdesc.mxpacketsize);
                    }
                  else
                    {
                      /* It is an IN bulk endpoint.  There should be only one
                       * bulk IN endpoint.
                       */
  
                      if ((found & USBHOST_BULKIN_FOUND) != 0)
                        {
                          /* Oops.. more than one endpoint.  We don't know
                           * what to do with this.
                           */
  
                          return -EINVAL;
                        }
  
                      found |= USBHOST_BULKIN_FOUND;
  
                      /* Save the bulk IN endpoint information */
  
                      bindesc.hport        = hport;
                      bindesc.addr         = epdesc->addr & USB_EP_ADDR_NUMBER_MASK;
                      bindesc.in           = 1;
                      bindesc.xfrtype      = USB_EP_ATTR_XFER_BULK;
                      bindesc.interval     = epdesc->interval;
                      bindesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize);
                      uinfo("Bulk IN EP addr:%d mxpacketsize:%d\n",
                            bindesc.addr, bindesc.mxpacketsize);
                    }
                }
  
  #ifdef HAVE_CTRL_INTERFACE
              /* Check for an interrupt IN endpoint. */
  
              else if (currif == USBHOST_CTRLIF_FOUND &&
                       (epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == USB_EP_ATTR_XFER_INT)
                {
                  /* Yes.. it is a interrupt endpoint.  IN or OUT? */
  
                  if (USB_ISEPIN(epdesc->addr))
                    {
  #ifdef HAVE_INTIN_ENDPOINT
                      /* It is an IN interrupt endpoint.  There should be only one
                       * interrupt IN endpoint.
                       */
  
                      if ((found & USBHOST_INTIN_FOUND) != 0)
                        {
                          /* Oops.. more than one.  We don't know what to do
                           * with this.
                           */
  
                          return -EINVAL;
                        }
  
                      /* Let's pick this interface as the one and only control
                       * interface.
                       */
  
                      found |= (USBHOST_CTRLIF_FOUND | USBHOST_INTIN_FOUND);
  
                      /* Save the bulk OUT endpoint information */
  
                      iindesc.hport        = hport;
                      iindesc.addr         = epdesc->addr & USB_EP_ADDR_NUMBER_MASK;
                      iindesc.in           = false;
                      iindesc.xfrtype      = USB_EP_ATTR_XFER_INT;
                      iindesc.interval     = epdesc->interval;
                      iindesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize);
  
                      uinfo("Interrupt IN EP addr:%d mxpacketsize:%d\n",
                            boutdesc.addr, boutdesc.mxpacketsize);
  #else
                      found |= USBHOST_CTRLIF_FOUND;
  #endif
                    }
                }
  #endif
            }
            break;
  
          /* Other descriptors are just ignored for now */
  
          default:
            break;
          }
  
        /* If we found everything we need with this interface, then break out
         * of the loop early.
         */
  
        if (found == USBHOST_ALLFOUND)
          {
            break;
          }
  
        /* Increment the address of the next descriptor */
  
        configdesc += desc->len;
        remaining  -= desc->len;
      }
  
    /* Sanity checking... did we find all of things that we needed for the
     * basic CDC/ACM data itnerface? NOTE: that the Control interface with
     * the Interrupt IN endpoint is optional.
     */
  
    if ((found & USBHOST_MINFOUND) != USBHOST_MINFOUND)
      {
        uerr("ERROR: Found DATA IF:%s BULK IN:%s BULK OUT:%s\n",
             (found & USBHOST_DATAIF_FOUND)  != 0  ? "YES" : "NO",
             (found & USBHOST_BULKIN_FOUND)  != 0 ? "YES" : "NO",
             (found & USBHOST_BULKOUT_FOUND) != 0 ? "YES" : "NO");
        return -EINVAL;
      }
  
    /* We are good... Allocate the endpoints */
  
    ret = DRVR_EPALLOC(hport->drvr, &boutdesc, &priv->bulkout);
    if (ret < 0)
      {
        uerr("ERROR: Failed to allocate Bulk OUT endpoint\n");
        return ret;
      }
  
    ret = DRVR_EPALLOC(hport->drvr, &bindesc, &priv->bulkin);
    if (ret < 0)
      {
        uerr("ERROR: Failed to allocate Bulk IN endpoint\n");
        (void)DRVR_EPFREE(hport->drvr, priv->bulkout);
        return ret;
      }
  
  #ifdef HAVE_INTIN_ENDPOINT
    /* The control interface with interrupt IN endpoint is optional */
  
    if ((found & USBHOST_HAVE_CTRLIF) == USBHOST_HAVE_CTRLIF)
      {
        ret = DRVR_EPALLOC(hport->drvr, &iindesc, &priv->intin);
        if (ret < 0)
          {
            uerr("ERROR: Failed to allocate Interrupt IN endpoint\n");
            priv->intin = NULL;
          }
      }
  #endif
  
    uinfo("Endpoints allocated\n");
    return OK;
  }
  
  /****************************************************************************
   * Name: usbhost_getle16
   *
   * Description:
   *   Get a (possibly unaligned) 16-bit little endian value.
   *
   * Input Parameters:
   *   val - A pointer to the first byte of the little endian value.
   *
   * Returned Value:
   *   A uint16_t representing the whole 16-bit integer value
   *
   ****************************************************************************/
  
  static inline uint16_t usbhost_getle16(const uint8_t *val)
  {
    return (uint16_t)val[1] << 8 | (uint16_t)val[0];
  }
  
  /****************************************************************************
   * Name: usbhost_getbe16
   *
   * Description:
   *   Get a (possibly unaligned) 16-bit big endian value.
   *
   * Input Parameters:
   *   val - A pointer to the first byte of the big endian value.
   *
   * Returned Value:
   *   A uint16_t representing the whole 16-bit integer value
   *
   ****************************************************************************/
  
  static inline uint16_t usbhost_getbe16(const uint8_t *val)
  {
    return (uint16_t)val[0] << 8 | (uint16_t)val[1];
  }
  
  /****************************************************************************
   * Name: usbhost_putle16
   *
   * Description:
   *   Put a (possibly unaligned) 16-bit little endian value.
   *
   * Input Parameters:
   *   dest - A pointer to the first byte to save the little endian value.
   *   val - The 16-bit value to be saved.
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void usbhost_putle16(uint8_t *dest, uint16_t val)
  {
    dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */
    dest[1] = val >> 8;
  }
  
  /****************************************************************************
   * Name: usbhost_putle32
   *
   * Description:
   *   Put a (possibly unaligned) 32-bit little endian value.
   *
   * Input Parameters:
   *   dest - A pointer to the first byte to save the little endian value.
   *   val - The 32-bit value to be saved.
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  #ifdef HAVE_CTRL_INTERFACE
  static void usbhost_putle32(uint8_t *dest, uint32_t val)
  {
    /* Little endian means LS halfword first in byte stream */
  
    usbhost_putle16(dest, (uint16_t)(val & 0xffff));
    usbhost_putle16(dest+2, (uint16_t)(val >> 16));
  }
  #endif
  
  /****************************************************************************
   * Name: usbhost_alloc_buffers
   *
   * Description:
   *   Allocate transfer buffer memory.
   *
   * Input Parameters:
   *   priv - A reference to the class instance.
   *
   * Returned Value:
   *   On sucess, zero (OK) is returned.  On failure, an negated errno value
   *   is returned to indicate the nature of the failure.
   *
   ****************************************************************************/
  
  static int usbhost_alloc_buffers(FAR struct usbhost_cdcacm_s *priv)
  {
    FAR struct usbhost_hubport_s *hport;
    size_t maxlen;
    int ret;
  
    DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL &&
                priv->linecode == NULL);
    hport = priv->usbclass.hport;
  
    /* Allocate memory for control requests */
  
    ret = DRVR_ALLOC(hport->drvr, (FAR uint8_t **)&priv->ctrlreq, &maxlen);
    if (ret < 0)
      {
        uerr("ERROR: DRVR_ALLOC of ctrlreq failed: %d\n", ret);
        goto errout;
      }
  
    DEBUGASSERT(maxlen >= sizeof(struct usb_ctrlreq_s));
  
    /* Allocate buffer for sending line coding data. */
  
    ret = DRVR_IOALLOC(hport->drvr, &priv->linecode,
                       sizeof(struct cdc_linecoding_s));
    if (ret < 0)
      {
        uerr("ERROR: DRVR_IOALLOC of line coding failed: %d (%d bytes)\n",
             ret, sizeof(struct cdc_linecoding_s));
        goto errout;
      }
  
  #ifdef HAVE_INTIN_ENDPOINT
    /* Allocate (optional) buffer for receiving line status data. */
  
    if (priv->intin)
      {
        ret = DRVR_IOALLOC(hport->drvr, &priv->notification, MAX_NOTIFICATION);
        if (ret < 0)
          {
            uerr("ERROR: DRVR_IOALLOC of line status failed: %d (%d bytes)\n",
                 ret, MAX_NOTIFICATION);
            goto errout;
          }
      }
  #endif
  
    /* Set the size of Bulk IN and OUT buffers to the max packet size */
  
    priv->pktsize = (hport->speed == USB_SPEED_HIGH) ? 512 : 64;
  
    /* Allocate a RX buffer for Bulk IN transfers */
  
    ret = DRVR_IOALLOC(hport->drvr, &priv->inbuf, priv->pktsize);
    if (ret < 0)
      {
        uerr("ERROR: DRVR_IOALLOC of Bulk IN buffer failed: %d (%d bytes)\n",
             ret, priv->pktsize);
        goto errout;
      }
  
    /* Allocate a TX buffer for Bulk IN transfers */
  
    ret = DRVR_IOALLOC(hport->drvr, &priv->outbuf, priv->pktsize);
    if (ret < 0)
      {
        uerr("ERROR: DRVR_IOALLOC of Bulk OUT buffer failed: %d (%d bytes)\n",
             ret, priv->pktsize);
        goto errout;
      }
  
    return OK;
  
  errout:
    usbhost_free_buffers(priv);
    return ret;
  }
  
  /****************************************************************************
   * Name: usbhost_free_buffers
   *
   * Description:
   *   Free transfer buffer memory.
   *
   * Input Parameters:
   *   priv - A reference to the class instance.
   *
   * Returned Value:
   *   None
   *
   ****************************************************************************/
  
  static void usbhost_free_buffers(FAR struct usbhost_cdcacm_s *priv)
  {
    FAR struct usbhost_hubport_s *hport;
  
    DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL);
    hport = priv->usbclass.hport;
  
    if (priv->ctrlreq)
      {
        (void)DRVR_FREE(hport->drvr, priv->ctrlreq);
      }
  
    if (priv->linecode)
      {
        (void)DRVR_IOFREE(hport->drvr, priv->linecode);
      }
  
    if (priv->notification)
      {
        (void)DRVR_IOFREE(hport->drvr, priv->notification);
      }
  
    if (priv->inbuf)
      {
        (void)DRVR_IOFREE(hport->drvr, priv->inbuf);
      }
  
    if (priv->outbuf)
      {
        (void)DRVR_IOFREE(hport->drvr, priv->outbuf);
      }
  
    priv->pktsize      = 0;
    priv->ctrlreq      = NULL;
    priv->linecode     = NULL;
    priv->notification = NULL;
    priv->inbuf        = NULL;
    priv->outbuf       = NULL;
  }
  
  /****************************************************************************
   * struct usbhost_registry_s methods
   ****************************************************************************/
  
  /****************************************************************************
   * Name: usbhost_create
   *
   * Description:
   *   This function implements the create() method of struct usbhost_registry_s.
   *   The create() method is a callback into the class implementation.  It is
   *   used to (1) create a new instance of the USB host class state and to (2)
   *   bind a USB host driver "session" to the class instance.  Use of this
   *   create() method will support environments where there may be multiple
   *   USB ports and multiple USB devices simultaneously connected.
   *
   * Input Parameters:
   *   hport - The hub port that manages the new class instance.
   *   id - In the case where the device supports multiple base classes,
   *     subclasses, or protocols, this specifies which to configure for.
   *
   * Returned Value:
   *   On success, this function will return a non-NULL instance of struct
   *   usbhost_class_s that can be used by the USB host driver to communicate
   *   with the USB host class.  NULL is returned on failure; this function
   *   will fail only if the hport input parameter is NULL or if there are
   *   insufficient resources to create another USB host class instance.
   *
   ****************************************************************************/
  
  static FAR struct usbhost_class_s *
  usbhost_create(FAR struct usbhost_hubport_s *hport,
                 FAR const struct usbhost_id_s *id)
  {
    FAR struct usbhost_cdcacm_s *priv;
    FAR struct uart_dev_s *uartdev;
  
    /* Allocate a USB host CDC/ACM class instance */
  
    priv = usbhost_allocclass();
    if (priv)
      {
        /* Initialize the allocated CDC/ACM class instance */
  
        memset(priv, 0, sizeof(struct usbhost_cdcacm_s));
  
        /* Assign a device number to this class instance */
  
        if (usbhost_devno_alloc(priv) == OK)
          {
           /* Initialize class method function pointers */
  
            priv->usbclass.hport        = hport;
            priv->usbclass.connect      = usbhost_connect;
            priv->usbclass.disconnected = usbhost_disconnected;
  
            /* The initial reference count is 1... One reference is held by the driver */
  
            priv->crefs = 1;
  
            /* Initialize semaphores (this works okay in the interrupt context) */
  
            sem_init(&priv->exclsem, 0, 1);
  
            /* Set up the serial lower-half interface */
  
  
            uartdev              = &priv->uartdev;
            uartdev->recv.size   = CONFIG_USBHOST_CDCACM_RXBUFSIZE;
            uartdev->recv.buffer = priv->rxbuffer;
            uartdev->xmit.size   = CONFIG_USBHOST_CDCACM_TXBUFSIZE;
            uartdev->xmit.buffer = priv->txbuffer;
            uartdev->ops         = &g_uart_ops;
            uartdev->priv        = priv;
  
            /* Set up the initial line status */
  
            priv->baud           = CONFIG_USBHOST_CDCACM_BAUD;
            priv->nbits          = CONFIG_USBHOST_CDCACM_BITS;
            priv->parity         = CONFIG_USBHOST_CDCACM_PARITY;
            priv->stop2          = CONFIG_USBHOST_CDCACM_2STOP;
  
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
            priv->rts            = true;
  #endif
  
            /* Return the instance of the USB CDC/ACM class */
  
            return &priv->usbclass;
          }
      }
  
    /* An error occurred. Free the allocation and return NULL on all failures */
  
    if (priv)
      {
        usbhost_freeclass(priv);
      }
  
    return NULL;
  }
  
  /****************************************************************************
   * struct usbhost_class_s methods
   ****************************************************************************/
  /****************************************************************************
   * Name: usbhost_connect
   *
   * Description:
   *   This function implements the connect() method of struct
   *   usbhost_class_s.  This method is a callback into the class
   *   implementation.  It is used to provide the device's configuration
   *   descriptor to the class so that the class may initialize properly
   *
   * Input Parameters:
   *   usbclass - The USB host class entry previously obtained from a call to
   *     create().
   *   configdesc - A pointer to a uint8_t buffer container the configuration
   *     descriptor.
   *   desclen - The length in bytes of the configuration descriptor.
   *
   * Returned Value:
   *   On success, zero (OK) is returned. On a failure, a negated errno value is
   *   returned indicating the nature of the failure
   *
   *   NOTE that the class instance remains valid upon return with a failure.  It is
   *   the responsibility of the higher level enumeration logic to call
   *   CLASS_DISCONNECTED to free up the class driver resources.
   *
   * Assumptions:
   *   - This function will *not* be called from an interrupt handler.
   *   - If this function returns an error, the USB host controller driver
   *     must call to DISCONNECTED method to recover from the error
   *
   ****************************************************************************/
  
  static int usbhost_connect(FAR struct usbhost_class_s *usbclass,
                             FAR const uint8_t *configdesc, int desclen)
  {
    FAR struct usbhost_cdcacm_s *priv = (FAR struct usbhost_cdcacm_s *)usbclass;
  #ifdef HAVE_INTIN_ENDPOINT
    FAR struct usbhost_hubport_s *hport;
  #endif
    char devname[DEV_NAMELEN];
    int ret;
  
    DEBUGASSERT(priv != NULL &&
                configdesc != NULL &&
                desclen >= sizeof(struct usb_cfgdesc_s));
  
  #ifdef HAVE_INTIN_ENDPOINT
    hport = priv->usbclass.hport;
    DEBUGASSERT(hport);
  #endif
  
    /* Get exclusive access to the device structure */
  
    usbhost_takesem(&priv->exclsem);
  
    /* Increment the reference count.  This will prevent usbhost_destroy() from
     * being called asynchronously if the device is removed.
     */
  
    priv->crefs++;
    DEBUGASSERT(priv->crefs == 2);
  
    /* Parse the configuration descriptor to get the bulk I/O endpoints */
  
    ret = usbhost_cfgdesc(priv, configdesc, desclen);
    if (ret < 0)
      {
        uerr("ERROR: usbhost_cfgdesc() failed: %d\n", ret);
        goto errout;
      }
  
    /* Set aside a transfer buffer for exclusive use by the CDC/ACM driver */
  
    ret = usbhost_alloc_buffers(priv);
    if (ret < 0)
      {
        uerr("ERROR: Failed to allocate transfer buffer\n");
        goto errout;
      }
  
  #ifdef HAVE_CTRL_INTERFACE
    /* Send the initial line encoding */
  
    ret = usbhost_linecoding_send(priv);
    if (ret < 0)
      {
        uerr("ERROR: usbhost_linecoding_send() failed: %d\n", ret);
      }
  #endif
  
    /* Register the lower half serial instance with the upper half serial
     * driver */
  
    usbhost_mkdevname(priv, devname);
    uinfo("Register device: %s\n", devname);
  
    ret = uart_register(devname, &priv->uartdev);
    if (ret < 0)
      {
        uerr("ERROR: uart_register() failed: %d\n", ret);
        goto errout;
      }
  
  #ifdef HAVE_INTIN_ENDPOINT
    /* Do we have an interrupt IN endpoint? */
  
    if (priv->intin)
      {
        /* Begin monitoring of port status change events */
  
        uinfo("Start notification monitoring\n");
        ret = DRVR_ASYNCH(hport->drvr, priv->intin,
                          (FAR uint8_t *)priv->notification,
                          MAX_NOTIFICATION, usbhost_notification_callback,
                          priv);
        if (ret < 0)
          {
            uerr("ERROR: DRVR_ASYNCH failed: %d\n", ret);
          }
      }
  #endif
  
  errout:
    /* Decrement the reference count.  We incremented the reference count
     * above so that usbhost_destroy() could not be called.  We now have to
     * be concerned about asynchronous modification of crefs because the
     * serial driver has been registered.
     */
  
    DEBUGASSERT(priv->crefs >= 2);
    priv->crefs--;
  
    /* Release the semaphore... there is a race condition here.
     * Decrementing the reference count and releasing the semaphore
     * allows usbhost_destroy() to execute (on the worker thread);
     * the class driver instance could get destroyed before we are
     * ready to handle it!
     */
  
    usbhost_givesem(&priv->exclsem);
    return ret;
  }
  
  /****************************************************************************
   * Name: usbhost_disconnected
   *
   * Description:
   *   This function implements the disconnected() method of struct
   *   usbhost_class_s.  This method is a callback into the class
   *   implementation.  It is used to inform the class that the USB device has
   *   been disconnected.
   *
   * Input Parameters:
   *   usbclass - The USB host class entry previously obtained from a call to
   *     create().
   *
   * Returned Value:
   *   On success, zero (OK) is returned. On a failure, a negated errno value
   *   is returned indicating the nature of the failure
   *
   * Assumptions:
   *   This function may be called from an interrupt handler.
   *
   ****************************************************************************/
  
  static int usbhost_disconnected(struct usbhost_class_s *usbclass)
  {
    FAR struct usbhost_cdcacm_s *priv = (FAR struct usbhost_cdcacm_s *)usbclass;
    FAR struct usbhost_hubport_s *hport;
    irqstate_t flags;
    int ret;
  
    DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL);
    hport = priv->usbclass.hport;
  
    /* Set an indication to any users of the CDC/ACM device that the device
     * is no longer available.
     */
  
    flags              = enter_critical_section();
    priv->disconnected = true;
  
    /* Let the upper half driver know that serial device is no longer
     * connected.
     */
  
    uart_connected(&priv->uartdev, false);
  
    /* Cancel any ongoing Bulk transfers */
  
    ret = DRVR_CANCEL(hport->drvr, priv->bulkin);
    if (ret < 0)
      {
       uerr("ERROR: Bulk IN DRVR_CANCEL failed: %d\n", ret);
      }
  
    ret = DRVR_CANCEL(hport->drvr, priv->bulkout);
    if (ret < 0)
      {
       uerr("ERROR: Bulk OUT DRVR_CANCEL failed: %d\n", ret);
      }
  
  #ifdef HAVE_INTIN_ENDPOINT
    /* Cancel any pending asynchronous I/O */
  
    if (priv->intin)
      {
        int ret = DRVR_CANCEL(hport->drvr, priv->intin);
        if (ret < 0)
          {
           uerr("ERROR: Interrupt IN DRVR_CANCEL failed: %d\n", ret);
          }
      }
  #endif
  
    /* Now check the number of references on the class instance.  If it is one,
     * then we can free the class instance now.  Otherwise, we will have to
     * wait until the holders of the references free them by closing the
     * serial driver.
     */
  
    uinfo("crefs: %d\n", priv->crefs);
    if (priv->crefs == 1)
      {
        /* Destroy the class instance.  If we are executing from an interrupt
         * handler, then defer the destruction to the worker thread.
         * Otherwise, destroy the instance now.
         */
  
        if (up_interrupt_context())
          {
            /* Destroy the instance on the worker thread. */
  
            uinfo("Queuing destruction: worker %p->%p\n",
                  priv->ntwork.worker, usbhost_destroy);
  
            DEBUGASSERT(work_available(&priv->ntwork));
            (void)work_queue(HPWORK, &priv->ntwork, usbhost_destroy, priv, 0);
          }
        else
          {
            /* Do the work now */
  
            usbhost_destroy(priv);
          }
      }
  
    leave_critical_section(flags);
    return OK;
  }
  
  /****************************************************************************
   * Serial Lower-Half Interfaces
   ****************************************************************************/
  /****************************************************************************
   * Name: usbhost_setup
   *
   * Description:
   *   Configure the USART baud, bits, parity, etc. This method is called the
   *   first time that the serial port is opened.
   *
   ****************************************************************************/
  
  static int usbhost_setup(FAR struct uart_dev_s *uartdev)
  {
    FAR struct usbhost_cdcacm_s *priv;
    irqstate_t flags;
    int ret;
  
    uinfo("Entry\n");
    DEBUGASSERT(uartdev && uartdev->priv);
    priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv;
  
    /* Make sure that we have exclusive access to the private data structure */
  
    DEBUGASSERT(priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS);
    usbhost_takesem(&priv->exclsem);
  
    /* Check if the CDC/ACM device is still connected.  We need to disable
     * interrupts momentarily to assure that there are no asynchronous
     * isconnect events.
     */
  
    flags = enter_critical_section();
    if (priv->disconnected)
      {
        /* No... the block driver is no longer bound to the class.  That means that
         * the USB CDC/ACM device is no longer connected.  Refuse any further
         * attempts to open the driver.
         */
  
        ret = -ENODEV;
      }
    else
      {
        /* Otherwise, just increment the reference count on the driver */
  
        priv->crefs++;
        ret = OK;
      }
  
    leave_critical_section(flags);
    usbhost_givesem(&priv->exclsem);
    return ret;
  }
  
  /****************************************************************************
   * Name: usbhost_shutdown
   *
   * Description:
   *   Disable the USART.  This method is called when the serial
   *   port is closed
   *
   ****************************************************************************/
  
  static void usbhost_shutdown(FAR struct uart_dev_s *uartdev)
  {
    FAR struct usbhost_cdcacm_s *priv;
    irqstate_t flags;
  
    uinfo("Entry\n");
    DEBUGASSERT(uartdev && uartdev->priv);
    priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv;
  
    /* Decrement the reference count on the block driver */
  
    DEBUGASSERT(priv->crefs > 1);
    usbhost_takesem(&priv->exclsem);
    priv->crefs--;
  
    /* Release the semaphore.  The following operations when crefs == 1 are
     * safe because we know that there is no outstanding open references to
     * the block driver.
     */
  
    usbhost_givesem(&priv->exclsem);
  
    /* We need to disable interrupts momentarily to assure that there are
     * no asynchronous disconnect events.
     */
  
    flags = enter_critical_section();
  
    /* Check if the USB CDC/ACM device is still connected.  If the
     * CDC/ACM device is not connected and the reference count just
     * decremented to one, then unregister the block driver and free
     * the class instance.
     */
  
    if (priv->crefs <= 1 && priv->disconnected)
      {
        /* Destroy the class instance */
  
        DEBUGASSERT(priv->crefs == 1);
        usbhost_destroy(priv);
      }
  
    leave_critical_section(flags);
  }
  
  /****************************************************************************
   * Name: usbhost_attach
   *
   * Description:
   *   Configure the USART to operation in interrupt driven mode.  This method is
   *   called when the serial port is opened.  Normally, this is just after the
   *   the setup() method is called, however, the serial console may operate in
   *   a non-interrupt driven mode during the boot phase.
   *
   *   RX and TX interrupts are not enabled when by the attach method (unless the
   *   hardware supports multiple levels of interrupt enabling).  The RX and TX
   *   interrupts are not enabled until the txint() and rxint() methods are called.
   *
   ****************************************************************************/
  
  static int usbhost_attach(FAR struct uart_dev_s *uartdev)
  {
    return OK;
  }
  
  /****************************************************************************
   * Name: usbhost_detach
   *
   * Description:
   *   Detach USART interrupts.  This method is called when the serial port is
   *   closed normally just before the shutdown method is called.  The exception
   *   is the serial console which is never shutdown.
   *
   ****************************************************************************/
  
  static void usbhost_detach(FAR struct uart_dev_s *uartdev)
  {
  }
  
  /****************************************************************************
   * Name: usbhost_ioctl
   *
   * Description:
   *   All ioctl calls will be routed through this method
   *
   ****************************************************************************/
  
  static int usbhost_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
  {
    struct inode *inode;
    struct usbhost_cdcacm_s *priv;
    int ret;
  
    uinfo("Entry\n");
    DEBUGASSERT(filep && filep->f_inode);
    inode = filep->f_inode;
  
    DEBUGASSERT(inode && inode->i_private);
    priv  = (FAR struct usbhost_cdcacm_s *)inode->i_private;
  
    /* Check if the CDC/ACM device is still connected */
  
    if (priv->disconnected)
      {
        /* No... the serial device has been disconnecgted.  Refuse to process
         * any ioctl commands.
         */
  
        ret = -ENODEV;
      }
    else
      {
        /* Process the IOCTL by command */
  
        usbhost_takesem(&priv->exclsem);
        switch (cmd)
          {
  #ifdef CONFIG_SERIAL_TIOCSERGSTRUCT
          case TIOCSERGSTRUCT:
            {
              FAR struct usbhost_cdcacm_s *user =
                (FAR struct usbhost_cdcacm_s *)arg;
              if (!user)
                {
                  ret = -EINVAL;
                }
              else
                {
                  memcpy(user, uartdev, sizeof(struct usbhost_cdcacm_s));
                }
            }
            break;
  #endif
  
  #ifdef CONFIG_SERIAL_TERMIOS
          case TCGETS:
            {
              FAR struct termios *termiosp = (FAR struct termios *)arg;
  
              if (!termiosp)
                {
                  ret = -EINVAL;
                  break;
                }
  
              cfsetispeed(termiosp, priv->baud);
  
              termiosp->c_cflag =
                ((priv->parity != 0) ? PARENB : 0) |
                ((priv->parity == 1) ? PARODD : 0) |
                ((priv->stop2) ? CSTOPB : 0)
  #ifdef CONFIG_SERIAL_OFLOWCONTROL
                | ((priv->oflow) ? CCTS_OFLOW : 0)
  #endif
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
                | ((priv->iflow) ? CRTS_IFLOW : 0)
  #endif
                ;
  
              switch (priv->bits)
                {
                case 5:
                  termiosp->c_cflag |= CS5;
                  break;
  
                case 6:
                  termiosp->c_cflag |= CS6;
                  break;
  
                case 7:
                  termiosp->c_cflag |= CS7;
                  break;
  
                default: /* 16-bits? */
                case 8:
                  termiosp->c_cflag |= CS8;
                  break;
                }
            }
            break;
  
          case TCSETS:
            {
              FAR struct termios *termiosp = (FAR struct termios *)arg;
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
              bool iflow;
  #endif
  
              if (!termiosp)
                {
                  ret = -EINVAL;
                  break;
                }
  
              if (termiosp->c_cflag & PARENB)
                {
                  priv->parity = (termiosp->c_cflag & PARODD) ? 1 : 2;
                }
              else
                 {
                  priv->parity = 0;
                }
  
              priv->stopbits2 = (termiosp->c_cflag & CSTOPB) != 0;
  #ifdef CONFIG_SERIAL_OFLOWCONTROL
              priv->oflow = (termiosp->c_cflag & CCTS_OFLOW) != 0;
  #endif
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
              iflow       = priv->iflow;
              priv->iflow = (termiosp->c_cflag & CRTS_IFLOW) != 0;
  #endif
  
              switch (termiosp->c_flag & CSIZE)
                {
                case C5:
                  priv->bits = 5;
                  break;
  
                case C6:
                  priv->bits = 6;
                  break;
  
                case C7:
                  priv->bits = 7;
                  break;
  
                default:
                case C8:
                  priv->bits = 8;
                  break;
                }
  
              /* Note that only cfgetispeed is used because we have knowledge
               * that only one speed is supported.
               */
  
              priv->baud = cfgetispeed(termiosp);
  
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
              /* Set RTS if input flow control changed */
  
              if (iflow != !priv->iflow)
                {
                   priv->rts = true;
                }
  #endif
  
  #ifdef HAVE_CTRL_INTERFACE
              /* Effect the changes immediately - note that we do not implement
               * TCSADRAIN / TCSAFLUSH
               */
  
              ret = usbhost_linecoding_send(priv);
  #endif
            }
            break;
  #endif /* CONFIG_SERIAL_TERMIOS */
  
  #ifdef CONFIG_USBHOST_CDCACM_BREAKS
          case TIOCSBRK:  /* BSD compatibility: Turn break on, unconditionally */
            {
  #warning Missing logic
            }
            break;
  
          case TIOCCBRK:  /* BSD compatibility: Turn break off, unconditionally */
            {
  #warning Missing logic
            }
            break;
  #endif
  
          default:
            ret = -ENOTTY;
            break;
          }
  
        usbhost_givesem(&priv->exclsem);
      }
  
    return ret;
  }
  
  /****************************************************************************
   * Name: usbhost_rxint
   *
   * Description:
   *   Call to enable or disable RX interrupts
   *
   ****************************************************************************/
  
  static void usbhost_rxint(FAR struct uart_dev_s *uartdev, bool enable)
  {
    FAR struct usbhost_cdcacm_s *priv;
    int ret;
  
    DEBUGASSERT(uartdev && uartdev->priv);
    priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv;
  
    /* Are we enabling or disabling RX reception? */
  
    if (enable && !priv->rxena)
      {
        /* Cancel any pending, delayed RX data reception work */
  
        (void)work_cancel(LPWORK, &priv->rxwork);
  
        /* Restart immediate RX data reception work (unless RX flow control
         * is in* effect.
         */
  
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
        if (priv->rts)
  #endif
          {
            ret = work_queue(LPWORK, &priv->rxwork,
                             usbhost_rxdata_work, priv, 0);
            DEBUGASSERT(ret >= 0);
            UNUSED(ret);
          }
      }
    else if (!enable && priv->rxena)
      {
        /* Cancel any pending RX data reception work */
  
        (void)work_cancel(LPWORK, &priv->rxwork);
      }
  
    /* Save the new RX enable state */
  
    priv->rxena = enable;
  }
  
  /****************************************************************************
   * Name: usbhost_rxavailable
   *
   * Description:
   *   Return true if the receive buffer is not empty
   *
   ****************************************************************************/
  
  static bool usbhost_rxavailable(FAR struct uart_dev_s *uartdev)
  {
  
    FAR struct usbhost_cdcacm_s *priv;
  
    DEBUGASSERT(uartdev && uartdev->priv);
    priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv;
    return (priv->nrxbytes > 0);
  }
  
  /****************************************************************************
   * Name: usbhost_rxflowcontrol
   *
   * Description:
   *   Called when Rx buffer is full (or exceeds configured watermark levels
   *   if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is defined).
   *   Return true if UART activated RX flow control to block more incoming
   *   data
   *
   * Input parameters:
   *   uartdev   - UART device instance
   *   nbuffered - the number of characters currently buffered
   *               (if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is
   *               not defined the value will be 0 for an empty buffer or the
   *               defined buffer size for a full buffer)
   *   upper     - true indicates the upper watermark was crossed where
   *               false indicates the lower watermark has been crossed
   *
   * Returned Value:
   *   true if RX flow control activated.
   *
   ****************************************************************************/
  
  #ifdef CONFIG_SERIAL_IFLOWCONTROL
  static bool usbhost_rxflowcontrol(FAR struct uart_dev_s *uartdev,
                                    unsigned int nbuffered, bool upper)
  {
    FAR struct usbhost_cdcacm_s *priv;
    bool newrts;
    int ret;
  
    DEBUGASSERT(uartdev && uartdev->priv);
    priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv
  
    /* Is RX flow control enabled? */
  
    if (!priv->iflow)
      {
        /* Now.. make sure that RTS is set */
  
        priv->rts = true;
        return false;
      }
  
    /* Are we enabling or disabling RX flow control? */
  
    if (priv->rts && upper)
      {
        /* RX flow control is not in effect (RTS is true) but we have just
         * crossed the upper threshold, meaning that we should now clear
         * RTS.
         */
  
         priv->rts = false;
  
         /* Cancel any pending RX data reception work */
  
        (void)work_cancel(LPWORK, &priv->rxwork)
        return true;
      }
    else if (!priv->rts && !upper)
      {
        /* RX flow control is in effect (RTS is false) and we have just
         * crossed the lower threshold, meaning that we should now set
         * RTS.
         */
  
         priv->rts = true;
  
        /* Restart RX data reception work flow unless RX reception is
         * disabled.
         */
  
        if (priv->rxena && work_available(&priv->rxwork))
          {
            ret = work_queue(LPWORK, &priv->rxwork,
                             usbhost_rxdata_work, priv, 0);
            DEBUGASSERT(ret >= 0);
            UNUSED(ret);
          }
  
        return false;
      }
  }
  #endif
  
  /****************************************************************************
   * Name: usbhost_txint
   *
   * Description:
   *   Call to enable or disable TX interrupts
   *
   ****************************************************************************/
  
  static void usbhost_txint(FAR struct uart_dev_s *uartdev, bool enable)
  {
    FAR struct usbhost_cdcacm_s *priv;
    int ret;
  
    DEBUGASSERT(uartdev && uartdev->priv);
    priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv;
  
    /* Are we enabling or disabling TX transmission? */
  
    if (enable && !priv->txena)
      {
        /* Cancel any pending, delayed TX data transmission work */
  
        (void)work_cancel(LPWORK, &priv->txwork);
  
        /* Restart immediate TX data transmission work */
  
        ret = work_queue(LPWORK, &priv->txwork,
                         usbhost_txdata_work, priv, 0);
        DEBUGASSERT(ret >= 0);
        UNUSED(ret);
      }
    else if (!enable && priv->txena)
      {
        /* Cancel any pending TX data transmission work */
  
        (void)work_cancel(LPWORK, &priv->txwork);
      }
  
    /* Save the new RX enable state */
  
    priv->txena = enable;
  }
  
  /****************************************************************************
   * Name: usbhost_txready
   *
   * Description:
   *   Return true if the transmit data register is not full.
   *
   ****************************************************************************/
  
  static bool usbhost_txready(FAR struct uart_dev_s *uartdev)
  {
    return true;
  }
  
  /****************************************************************************
   * Name: usbhost_txempty
   *
   * Description:
   *   Return true if the transmit data buffer is empty
   *
   ****************************************************************************/
  
  static bool usbhost_txempty(FAR struct uart_dev_s *uartdev)
  {
    return true;
  }
  
  /****************************************************************************
   * Public Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: usbhost_cdcacm_initialize
   *
   * Description:
   *   Initialize the USB host CDC/ACM class.  This function should be called
   *   be platform-specific code in order to initialize and register support
   *   for the USB host CDC/ACM class.
   *
   * Input Parameters:
   *   None
   *
   * Returned Value:
   *   On success this function will return zero (OK);  A negated errno value
   *   will be returned on failure.
   *
   ****************************************************************************/
  
  int usbhost_cdcacm_initialize(void)
  {
    /* If we have been configured to use pre-allocated CDC/ACM class instances,
     * then place all of the pre-allocated USB host CDC/ACM class instances
     * into a free list.
     */
  
  #if CONFIG_USBHOST_CDCACM_NPREALLOC > 0
    FAR struct usbhost_freestate_s *entry;
    int i;
  
    g_freelist = NULL;
    for (i = 0; i < CONFIG_USBHOST_CDCACM_NPREALLOC; i++)
      {
        entry        = (FAR struct usbhost_freestate_s *)&g_prealloc[i];
        entry->flink = g_freelist;
        g_freelist   = entry;
      }
  #endif
  
    /* Advertise our availability to support (certain) CDC/ACM devices */
  
    return usbhost_registerclass(&g_cdcacm);
  }
  
  #endif  /* CONFIG_USBHOST_CDCACM */
  