/****************************************************************************
 * drivers/usbdev/adb.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <nuttx/nuttx.h>
#include <queue.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/kmalloc.h>

#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#include <nuttx/usb/adb.h>

#include <nuttx/fs/fs.h>
#include <fcntl.h>
#include <poll.h>

#ifdef CONFIG_USBADB_BOARD_SERIALSTR
#include <nuttx/board.h>
#endif

#ifdef CONFIG_USBADB_COMPOSITE
#  include <nuttx/usb/composite.h>
#  include "composite.h"
#endif

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* FIXME use minor for char device npath */

#define USBADB_CHARDEV_PATH "/dev/adb0"

/* USB Controller */

#ifdef CONFIG_USBDEV_SELFPOWERED
#  define USBADB_SELFPOWERED USB_CONFIG_ATTR_SELFPOWER
#else
#  define USBADB_SELFPOWERED (0)
#endif

#ifdef CONFIG_USBDEV_REMOTEWAKEUP
#  define USBADB_REMOTEWAKEUP USB_CONFIG_ATTR_WAKEUP
#else
#  define USBADB_REMOTEWAKEUP (0)
#endif

/* Buffer big enough for any of our descriptors (the config descriptor is the
 * biggest).
 */

#define USBADB_MXDESCLEN           (64)
#define USBADB_MAXSTRLEN           (USBADB_MXDESCLEN-2)

/* Device descriptor values */

#define USBADB_VERSIONNO           (0x0101) /* Device version number 1.1 (BCD) */

/* String language */

#define USBADB_STR_LANGUAGE        (0x0409) /* en-us */

/* Descriptor strings.  If there serial device is part of a composite device
 * then the manufacturer, product, and serial number strings will be provided
 * by the composite logic.
 */

#ifndef CONFIG_USBADB_COMPOSITE
#  define USBADB_MANUFACTURERSTRID (1)
#  define USBADB_PRODUCTSTRID      (2)
#  define USBADB_SERIALSTRID       (3)
#  define USBADB_CONFIGSTRID       (4)
#  define USBADB_INTERFACESTRID    (5)
#else
#  define USBADB_INTERFACESTRID    (1)
#  define USBADB_NSTRIDS           (1)
#endif

#define USBADB_NCONFIGS          (1)
#define USBADB_CONFIGID          (1)
#define USBADB_CONFIGIDNONE      (0)

/* Length of ADB descriptor */

#define USBADB_DESC_TOTALLEN 32

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* Manage char device non blocking io */

typedef struct adb_char_waiter_sem_s
{
  sem_t sem;
  FAR struct adb_char_waiter_sem_s *next;
} adb_char_waiter_sem_t;

/* Container to support a list of requests */

struct usbadb_wrreq_s
{
  FAR sq_entry_t node;          /* Implements a singly linked list */
  FAR struct usbdev_req_s *req; /* The contained request */
};

struct usbadb_rdreq_s
{
  FAR sq_entry_t node;          /* Implements a singly linked list */
  FAR struct usbdev_req_s *req; /* The contained request */
  uint16_t offset;              /* Offset to valid data in the RX request */
};

/* This structure describes the internal state of the driver */

struct usbdev_adb_s
{
  FAR struct usbdev_s *usbdev;      /* usbdev driver pointer */
#ifdef CONFIG_USBADB_COMPOSITE
  struct usbdev_devinfo_s devinfo;
#endif

  FAR struct usbdev_ep_s  *epbulkin;  /* Bulk IN endpoint structure */
  FAR struct usbdev_ep_s  *epbulkout; /* Bulk OUT endpoint structure */

  FAR struct usbdev_req_s *ctrlreq;   /* Preallocated control request */

  uint8_t config;                     /* USB Configuration number */

  struct sq_queue_s txfree;           /* Available write request containers */
  struct sq_queue_s rxpending;        /* Pending read request containers */

  /* Pre-allocated request containers. The write requests will be
   * linked in a free list (txfree), and used to send requests to
   * EPBULKIN; Read requests will be queued in the EBULKOUT.
   */

  struct usbadb_wrreq_s wrreqs[CONFIG_USBADB_NWRREQS];
  struct usbadb_rdreq_s rdreqs[CONFIG_USBADB_NRDREQS];

  /* Char device driver */

  sem_t                 exclsem; /* Enforces device exclusive access */
  adb_char_waiter_sem_t *rdsems; /* List of blocking readers */
  adb_char_waiter_sem_t *wrsems; /* List of blocking writers */
  uint8_t               crefs;   /* Count of opened instances */
  FAR struct pollfd *fds[CONFIG_USBADB_NPOLLWAITERS];
};

struct adb_driver_s
{
  struct usbdevclass_driver_s drvr;
  struct usbdev_adb_s dev;
};

struct adb_cfgdesc_s
{
#ifndef CONFIG_USBADB_COMPOSITE
  struct usb_cfgdesc_s cfgdesc;        /* Configuration descriptor */
#endif
  struct usb_ifdesc_s  ifdesc;         /* ADB interface descriptor */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* USB class device *********************************************************/

static int     usbclass_bind(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static void    usbclass_unbind(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static int     usbclass_setup(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev,
                 FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout,
                 size_t outlen);
static void    usbclass_disconnect(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);

static void    usbclass_suspend(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static void    usbclass_resume(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);

static FAR struct usbdev_req_s *usbclass_allocreq(FAR struct usbdev_ep_s *ep,
                                                  uint16_t len);

/* Char device Operations ***************************************************/

static int adb_char_open(FAR struct file *filep);
static int adb_char_close(FAR struct file *filep);

static ssize_t adb_char_read(FAR struct file *filep, FAR char *buffer,
                               size_t len);
static ssize_t adb_char_write(FAR struct file *filep,
                                FAR const char *buffer, size_t len);
static int adb_char_ioctl(FAR struct file *filep, int cmd,
                            unsigned long arg);
static int adb_char_poll(FAR struct file *filep, FAR struct pollfd *fds,
                       bool setup);

static void adb_char_notify_readers(FAR struct usbdev_adb_s *priv);
static void adb_char_pollnotify(FAR struct usbdev_adb_s *dev,
                                pollevent_t eventset);

static void adb_char_on_connect(FAR struct usbdev_adb_s *priv, int connect);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* USB class device *********************************************************/

static const struct usbdevclass_driverops_s g_adb_driverops =
{
  usbclass_bind,       /* bind */
  usbclass_unbind,     /* unbind */
  usbclass_setup,      /* setup */
  usbclass_disconnect, /* disconnect */
  usbclass_suspend,    /* suspend */
  usbclass_resume      /* resume */
};

/* Char device **************************************************************/

static const struct file_operations g_adb_fops =
{
  adb_char_open,  /* open */
  adb_char_close, /* close */
  adb_char_read,  /* read */
  adb_char_write, /* write */
  NULL,           /* seek */
  adb_char_ioctl, /* ioctl */
  adb_char_poll   /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  , NULL          /* unlink */
#endif
};

/* USB descriptor ***********************************************************/

#ifndef CONFIG_USBADB_COMPOSITE
static const struct usb_devdesc_s g_adb_devdesc =
{
  .len = USB_SIZEOF_DEVDESC,         /* Descriptor length */
  .type = USB_DESC_TYPE_DEVICE,      /* Descriptor type */
  .usb =                             /* USB version */
  {
    LSBYTE(0x0200),
    MSBYTE(0x0200)
  },
  .classid = 0,                               /* Device class */
  .subclass = 0,                              /* Device sub-class */
  .protocol = 0,                              /* Device protocol */
  .mxpacketsize = CONFIG_USBADB_EP0MAXPACKET, /* Max packet size (ep0) */
  .vendor =                                   /* Vendor ID */
  {
    LSBYTE(CONFIG_USBADB_VENDORID),
    MSBYTE(CONFIG_USBADB_VENDORID)
  },
  .product =                         /* Product ID */
  { LSBYTE(CONFIG_USBADB_PRODUCTID),
    MSBYTE(CONFIG_USBADB_PRODUCTID)
  },
  .device =                          /* Device ID */
  { LSBYTE(USBADB_VERSIONNO),
    MSBYTE(USBADB_VERSIONNO)
  },
  .imfgr = USBADB_MANUFACTURERSTRID, /* Manufacturer */
  .iproduct = USBADB_PRODUCTSTRID,   /* Product */
  .serno = USBADB_SERIALSTRID,       /* Serial number */
  .nconfigs = 1                      /* Number of configurations */
};
#endif

static const struct adb_cfgdesc_s g_adb_cfgdesc =
{
#ifndef CONFIG_USBADB_COMPOSITE
  {
    .len          = USB_SIZEOF_CFGDESC,   /* Descriptor length    */
    .type         = USB_DESC_TYPE_CONFIG, /* Descriptor type      */
    .totallen     =
    {
      LSBYTE(USBADB_DESC_TOTALLEN),       /* LS Total length      */
      MSBYTE(USBADB_DESC_TOTALLEN)        /* MS Total length      */
    },
    .ninterfaces  = 1,                    /* Number of interfaces */
    .cfgvalue     = 1,                    /* Configuration value  */
    .icfg         = USBADB_CONFIGSTRID,   /* Configuration        */
    .attr         = USB_CONFIG_ATTR_ONE |
                    USBADB_SELFPOWERED  |
                    USBADB_REMOTEWAKEUP,  /* Attributes           */

    .mxpower      = (CONFIG_USBDEV_MAXPOWER + 1) / 2 /* Max power (mA/2) */
  },
#endif
  {
    .len            = USB_SIZEOF_IFDESC,
    .type           = USB_DESC_TYPE_INTERFACE,
    .ifno           = 0,
    .alt            = 0,
    .neps           = 2,
    .classid        = USB_CLASS_VENDOR_SPEC,
    .subclass       = 0x42,
    .protocol       = 0x01,
    .iif            = USBADB_INTERFACESTRID
  }
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: usbclass_allocreq
 *
 * Description:
 *   Allocate request buffer for a specified endpoint.
 *
 ****************************************************************************/

static FAR struct usbdev_req_s *usbclass_allocreq(FAR struct usbdev_ep_s *ep,
                                                  uint16_t len)
{
  FAR struct usbdev_req_s *req;

  req = EP_ALLOCREQ(ep);
  if (req != NULL)
    {
      req->len = len;
      req->buf = EP_ALLOCBUFFER(ep, len);

      if (req->buf == NULL)
        {
          EP_FREEREQ(ep, req);
          req = NULL;
        }
    }

  return req;
}

/****************************************************************************
 * Name: usbclass_copy_epdesc
 *
 * Description:
 *   Copies the requested Endpoint Description into the buffer given.
 *   Returns the number of Bytes filled in ( sizeof(struct usb_epdesc_s) ).
 *
 ****************************************************************************/

static int usbclass_copy_epdesc(int epid, FAR struct usb_epdesc_s *epdesc,
                                FAR struct usbdev_devinfo_s *devinfo,
                                bool hispeed)
{
#ifndef CONFIG_USBDEV_DUALSPEED
  UNUSED(hispeed);
#endif

  epdesc->len  = USB_SIZEOF_EPDESC;           /* Descriptor length */
  epdesc->type = USB_DESC_TYPE_ENDPOINT;      /* Descriptor type */
  epdesc->attr = USB_EP_ATTR_XFER_BULK |      /* Endpoint attributes */
                 USB_EP_ATTR_NO_SYNC   |
                 USB_EP_ATTR_USAGE_DATA;
  epdesc->interval = 0;                       /* Interval */

  if (epid == USBADB_EP_BULKIN_IDX) /* Bulk IN endpoint */
    {
      /* Endpoint address */

#ifdef CONFIG_USBADB_COMPOSITE
      epdesc->addr = USB_EPIN(devinfo->epno[USBADB_EP_BULKIN_IDX]);
#else
      epdesc->addr = USB_EPIN(CONFIG_USBADB_EPBULKIN);
#endif

#ifdef CONFIG_USBDEV_DUALSPEED
      if (hispeed)
        {
          /* Maximum packet size (high speed) */

          epdesc->mxpacketsize[0] = LSBYTE(CONFIG_USBADB_EPBULKIN_HSSIZE);
          epdesc->mxpacketsize[1] = MSBYTE(CONFIG_USBADB_EPBULKIN_HSSIZE);
        }
      else
#endif
        {
          /* Maximum packet size (full speed) */

          epdesc->mxpacketsize[0] = LSBYTE(CONFIG_USBADB_EPBULKIN_FSSIZE);
          epdesc->mxpacketsize[1] = MSBYTE(CONFIG_USBADB_EPBULKIN_FSSIZE);
        }
    }
  else /* USBADB_EP_BULKOUT_IDX: Bulk OUT endpoint */
    {
      /* Endpoint address */

#ifdef CONFIG_USBADB_COMPOSITE
      epdesc->addr = USB_EPOUT(devinfo->epno[USBADB_EP_BULKOUT_IDX]);
#else
      epdesc->addr = USB_EPOUT(CONFIG_USBADB_EPBULKOUT);
#endif

#ifdef CONFIG_USBDEV_DUALSPEED
      if (hispeed)
        {
          /* Maximum packet size (high speed) */

          epdesc->mxpacketsize[0] = LSBYTE(CONFIG_USBADB_EPBULKOUT_HSSIZE);
          epdesc->mxpacketsize[1] = MSBYTE(CONFIG_USBADB_EPBULKOUT_HSSIZE);
        }
      else
#endif
        {
          /* Maximum packet size (full speed) */

          epdesc->mxpacketsize[0] = LSBYTE(CONFIG_USBADB_EPBULKOUT_FSSIZE);
          epdesc->mxpacketsize[1] = MSBYTE(CONFIG_USBADB_EPBULKOUT_FSSIZE);
        }
    }

  return sizeof(struct usb_epdesc_s);
}

/****************************************************************************
 * Name: usb_adb_submit_rdreq
 *
 * Description:
 *   Submits the bulk OUT read request. Takes care not to submit the request
 *   when the RX packet buffer is already in use.
 *
 * Input Parameters:
 *   priv: pointer to ADB device driver structure
 *
 * Returned Value:
 *   The return value of the EP_SUBMIT operation
 *
 ****************************************************************************/

static int usb_adb_submit_rdreq(FAR struct usbdev_adb_s *priv,
                                FAR struct usbadb_rdreq_s *rdcontainer)
{
  FAR struct usbdev_req_s *req;
  FAR struct usbdev_ep_s *ep;
  int ret;

  DEBUGASSERT(priv != NULL && rdcontainer != NULL);

  req      = rdcontainer->req;
  DEBUGASSERT(req != NULL);

  /* Requeue the read request */

  ep       = priv->epbulkout;
  req->len = ep->maxpacket;
  ret      = EP_SUBMIT(ep, req);
  if (ret != OK)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT),
                              (uint16_t)-req->result);
    }

  return ret;
}

/****************************************************************************
 * Name: usb_adb_wrcomplete
 *
 * Description:
 *   Handle completion of write request.  This function probably executes
 *   in the context of an interrupt handler.
 *
 ****************************************************************************/

static void usb_adb_wrcomplete(FAR struct usbdev_ep_s *ep,
                               FAR struct usbdev_req_s *req)
{
  FAR struct usbadb_wrreq_s *wrcontainer;
  FAR struct usbdev_adb_s *priv;
  irqstate_t flags;

  /* Sanity check */

#ifdef CONFIG_DEBUG_FEATURES
  if (!ep || !ep->priv || !req || !req->priv)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return;
    }
#endif

  /* Extract references to private data */

  priv        = (FAR struct usbdev_adb_s *)ep->priv;
  wrcontainer = (FAR struct usbadb_wrreq_s *)req->priv;

  /* Return the write request to the free list */

  flags = enter_critical_section();
  sq_addlast(&wrcontainer->node, &priv->txfree);

  /* Check for termination condition */

  switch (req->result)
    {
    case OK: /* Normal completion */
      {
        usbtrace(TRACE_CLASSWRCOMPLETE, priv->nwrq);

        /* Notify all waiting writers that write req is available */

        adb_char_waiter_sem_t *cur_sem = priv->wrsems;
        while (cur_sem != NULL)
          {
            nxsem_post(&cur_sem->sem);
            cur_sem = cur_sem->next;
          }

        priv->wrsems = NULL;

        /* Notify all poll/select waiters */

        adb_char_pollnotify(priv, POLLOUT);
      }
      break;

    case -ESHUTDOWN: /* Disconnection */
      {
        usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRSHUTDOWN), priv->nwrq);
      }
      break;

    default: /* Some other error occurred */
      {
        usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED),
                 (uint16_t)-req->result);
      }
      break;
    }

  leave_critical_section(flags);
}

/****************************************************************************
 * Name: usb_adb_rdcomplete
 *
 * Description:
 *   Handle completion of read request on the bulk OUT endpoint.
 *
 ****************************************************************************/

static void usb_adb_rdcomplete(FAR struct usbdev_ep_s *ep,
                               FAR struct usbdev_req_s *req)
{
  FAR struct usbadb_rdreq_s *rdcontainer;
  FAR struct usbdev_adb_s *priv;
  irqstate_t flags;

  /* Sanity check */

#ifdef CONFIG_DEBUG_FEATURES
  if (!ep || !ep->priv || !req)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return;
    }
#endif

  /* Extract references to private data */

  priv        = (FAR struct usbdev_adb_s *)ep->priv;
  rdcontainer = (FAR struct usbadb_rdreq_s *)req->priv;

  /* Process the received data unless this is some unusual condition */

  switch (req->result)
    {
    case 0: /* Normal completion */

      usbtrace(TRACE_CLASSRDCOMPLETE, priv->nrdq);

      /* Restart request due to either no reader or
       * empty frame received.
       */

      if (priv->crefs == 0)
        {
          uwarn("drop frame\n");
          goto restart_req;
        }

      if (req->xfrd <= 0)
        {
          goto restart_req;
        }

      /* Queue request and notify readers */

      flags = enter_critical_section();

      /* Put request on RX pending queue */

      rdcontainer->offset = 0;
      sq_addlast(&rdcontainer->node, &priv->rxpending);

      adb_char_notify_readers(priv);

      leave_critical_section(flags);
      return;

    case -ESHUTDOWN: /* Disconnection */
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0);
      return;

    default: /* Some other error occurred */
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED),
               (uint16_t)-req->result);
      goto restart_req;
    };

restart_req:

  /* Restart request */

  usb_adb_submit_rdreq(priv, rdcontainer);
}

/****************************************************************************
 * Name: usbclass_resetconfig
 *
 * Description:
 *   Mark the device as not configured and disable all endpoints.
 *
 ****************************************************************************/

static void usbclass_resetconfig(FAR struct usbdev_adb_s *priv)
{
  /* Are we configured? */

  if (priv->config != USBADB_CONFIGIDNONE)
    {
      /* Yes.. but not anymore */

      adb_char_on_connect(priv, 0);

      /* Disable endpoints.  This should force completion of all pending
       * transfers.
       */

      EP_DISABLE(priv->epbulkin);
      EP_DISABLE(priv->epbulkout);
    }

  priv->config = USBADB_CONFIGIDNONE;
}

/****************************************************************************
 * Name: usbclass_setconfig
 *
 * Description:
 *   Set the device configuration by allocating and configuring endpoints and
 *   by allocating and queue read and write requests.
 *
 ****************************************************************************/

static int usbclass_setconfig(FAR struct usbdev_adb_s *priv, uint8_t config)
{
  struct usb_epdesc_s epdesc;
  bool hispeed = false;
  int i;
  int ret = 0;

#ifdef CONFIG_DEBUG_FEATURES
  if (priv == NULL)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return -EIO;
    }
#endif

#ifdef CONFIG_USBDEV_DUALSPEED
  hispeed = (priv->usbdev->speed == USB_SPEED_HIGH);
#endif

  if (config == priv->config)
    {
      /* Already configured -- Do nothing */

      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALREADYCONFIGURED), 0);
      return 0;
    }

  /* Discard the previous configuration data */

  usbclass_resetconfig(priv);

  /* Was this a request to simply discard the current configuration? */

  if (config == USBADB_CONFIGIDNONE)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0);
      return 0;
    }

  /* We only accept one configuration */

  if (config != USBADB_CONFIGID)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGIDBAD), 0);
      return -EINVAL;
    }

  /* Configure the IN bulk endpoint */

#ifdef CONFIG_USBADB_COMPOSITE
  usbclass_copy_epdesc(USBADB_EP_BULKIN_IDX, &epdesc,
                       &priv->devinfo, hispeed);
#else
  usbclass_copy_epdesc(USBADB_EP_BULKIN_IDX, &epdesc, NULL, hispeed);
#endif

  ret = EP_CONFIGURE(priv->epbulkin, &epdesc, false);

  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0);
      goto errout;
    }

  priv->epbulkin->priv = priv;

  /* Configure the OUT bulk endpoint */

#ifdef CONFIG_USBADB_COMPOSITE
  usbclass_copy_epdesc(USBADB_EP_BULKOUT_IDX, &epdesc,
                       &priv->devinfo, hispeed);
#else
  usbclass_copy_epdesc(USBADB_EP_BULKOUT_IDX, &epdesc, NULL, hispeed);
#endif
  ret = EP_CONFIGURE(priv->epbulkout, &epdesc, true);

  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTCONFIGFAIL), 0);
      goto errout;
    }

  priv->epbulkout->priv = priv;

  /* Queue read requests in the bulk OUT endpoint */

  for (i = 0; i < CONFIG_USBADB_NRDREQS; i++)
    {
      priv->rdreqs[i].req->callback = usb_adb_rdcomplete;
      ret = usb_adb_submit_rdreq(priv, &priv->rdreqs[i]);
      if (ret != OK)
        {
          /* TODO cancel submitted requests */

          goto errout;
        }
    }

  /* We are successfully configured. Char device is now active */

  priv->config = config;
  adb_char_on_connect(priv, 1);
  return OK;

errout:
  usbclass_resetconfig(priv);
  return ret;
}

/****************************************************************************
 * Name: usbclass_ep0incomplete
 *
 * Description:
 *   Handle completion of EP0 control operations
 *
 ****************************************************************************/

static void usbclass_ep0incomplete(FAR struct usbdev_ep_s *ep,
                                 FAR struct usbdev_req_s *req)
{
  if (req->result || req->xfrd != req->len)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT),
               (uint16_t)-req->result);
    }
}

#ifdef CONFIG_USBDEV_DUALSPEED
static int16_t usbclass_mkcfgdesc(FAR uint8_t *buf,
                                  FAR struct usbdev_devinfo_s *devinfo,
                                  uint8_t speed, uint8_t type);
#else
static int16_t usbclass_mkcfgdesc(FAR uint8_t *buf,
                                  FAR struct usbdev_devinfo_s *devinfo)
#endif
{
  bool hispeed = false;
  FAR struct usb_epdesc_s *epdesc;
  FAR struct adb_cfgdesc_s *dest;

#ifdef CONFIG_USBDEV_DUALSPEED
  hispeed = (speed == USB_SPEED_HIGH);

  /* Check for switches between high and full speed */

  if (type == USB_DESC_TYPE_OTHERSPEEDCONFIG)
    {
      hispeed = !hispeed;
    }
#endif

  dest = (FAR struct adb_cfgdesc_s *)buf;
  epdesc = (FAR struct usb_epdesc_s *)(buf + sizeof(g_adb_cfgdesc));

  memcpy(dest, &g_adb_cfgdesc, sizeof(g_adb_cfgdesc));

#ifdef CONFIG_USBADB_COMPOSITE
  usbclass_copy_epdesc(USBADB_EP_BULKIN_IDX, &epdesc[0], devinfo, hispeed);
  usbclass_copy_epdesc(USBADB_EP_BULKOUT_IDX, &epdesc[1], devinfo, hispeed);
#else
  usbclass_copy_epdesc(USBADB_EP_BULKIN_IDX, &epdesc[0], NULL, hispeed);
  usbclass_copy_epdesc(USBADB_EP_BULKOUT_IDX, &epdesc[1], NULL, hispeed);
#endif

#ifdef CONFIG_USBADB_COMPOSITE
  /* For composite device, apply possible offset to the interface numbers */

  dest->ifdesc.ifno = devinfo->ifnobase;
  dest->ifdesc.iif  = devinfo->strbase + USBADB_INTERFACESTRID;
#endif

  return sizeof(g_adb_cfgdesc)+2*USB_SIZEOF_EPDESC;
}

static int usbclass_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc)
{
  FAR uint8_t *data = (FAR uint8_t *)(strdesc + 1);
  FAR const char *str;
  int len;
  int ndata;
  int i;

  switch (id)
    {
#ifndef CONFIG_USBADB_COMPOSITE
    case 0:
      {
        /* Descriptor 0 is the language id */

        strdesc->len  = 4;
        strdesc->type = USB_DESC_TYPE_STRING;
        data[0] = LSBYTE(USBADB_STR_LANGUAGE);
        data[1] = MSBYTE(USBADB_STR_LANGUAGE);
        return 4;
      }

    case USBADB_MANUFACTURERSTRID:
      str = CONFIG_USBADB_VENDORSTR;
      break;

    case USBADB_PRODUCTSTRID:
      str = CONFIG_USBADB_PRODUCTSTR;
      break;

    case USBADB_SERIALSTRID:
#ifdef CONFIG_USBADB_BOARD_SERIALSTR
      str = board_usbdev_serialstr();
#else
      str = CONFIG_USBADB_SERIALSTR;
#endif
      break;

    case USBADB_CONFIGSTRID:
      str = CONFIG_USBADB_CONFIGSTR;
      break;
#endif

    /* Composite driver removes offset before calling mkstrdesc() */

    case USBADB_INTERFACESTRID:
      str = CONFIG_USBADB_INTERFACESTR;
      break;

    default:
      return -EINVAL;
    }

  /* The string is utf16-le.  The poor man's utf-8 to utf16-le
   * conversion below will only handle 7-bit en-us ascii
   */

  len = strlen(str);
  if (len > (USBADB_MAXSTRLEN / 2))
    {
      len = (USBADB_MAXSTRLEN / 2);
    }

  for (i = 0, ndata = 0; i < len; i++, ndata += 2)
    {
      data[ndata]     = str[i];
      data[ndata + 1] = 0;
    }

  strdesc->len  = ndata + 2;
  strdesc->type = USB_DESC_TYPE_STRING;
  return strdesc->len;
}

/****************************************************************************
 * USB Class Driver Methods
 ****************************************************************************/

/****************************************************************************
 * Name: usbclass_bind
 *
 * Description:
 *   Invoked when the driver is bound to a USB device driver
 *
 ****************************************************************************/

static int usbclass_bind(FAR struct usbdevclass_driver_s *driver,
                         FAR struct usbdev_s *dev)
{
  int ret;
  int i;
  uint16_t reqlen;
  irqstate_t flags;
  FAR struct usbdev_adb_s *priv = &((FAR struct adb_driver_s *)driver)->dev;

  usbtrace(TRACE_CLASSBIND, 0);

  priv->ctrlreq = usbclass_allocreq(dev->ep0, USBADB_MXDESCLEN);
  if (priv->ctrlreq == NULL)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0);
      return -ENOMEM;
    }

  priv->ctrlreq->callback = usbclass_ep0incomplete;

  /* Pre-allocate all endpoints... the endpoints will not be functional
   * until the SET CONFIGURATION request is processed in usbclass_setconfig.
   * This is done here because there may be calls to kmm_malloc and the SET
   * CONFIGURATION processing probably occurs within interrupt handling
   * logic where kmm_malloc calls will fail.
   */

  /* Pre-allocate the IN bulk endpoint */

  priv->epbulkin = DEV_ALLOCEP(dev,
#ifdef CONFIG_USBADB_COMPOSITE
      USB_EPIN(priv->devinfo.epno[USBADB_EP_BULKIN_IDX]),
#else
      USB_EPIN(CONFIG_USBADB_EPBULKIN),
#endif
      true,
      USB_EP_ATTR_XFER_BULK);

  if (!priv->epbulkin)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0);
      ret = -ENODEV;
      goto errout;
    }

  priv->epbulkin->priv = priv;

  /* Pre-allocate the OUT bulk endpoint */

  priv->epbulkout = DEV_ALLOCEP(dev,
#ifdef CONFIG_USBADB_COMPOSITE
      USB_EPOUT(priv->devinfo.epno[USBADB_EP_BULKOUT_IDX]),
#else
      USB_EPOUT(CONFIG_USBADB_EPBULKOUT),
#endif
      false,
      USB_EP_ATTR_XFER_BULK);

  if (!priv->epbulkout)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTALLOCFAIL), 0);
      ret = -ENODEV;
      goto errout;
    }

  priv->epbulkout->priv = priv;

  /* Pre-allocate read requests. The buffer size is one full packet. */

#ifdef CONFIG_USBDEV_DUALSPEED
  reqlen = CONFIG_USBADB_EPBULKOUT_HSSIZE;
#else
  reqlen = CONFIG_USBADB_EPBULKOUT_FSSIZE;
#endif

  for (i = 0; i < CONFIG_USBADB_NRDREQS; i++)
    {
      FAR struct usbadb_rdreq_s *rdcontainer;

      rdcontainer      = &priv->rdreqs[i];
      rdcontainer->req = usbclass_allocreq(priv->epbulkout, reqlen);
      if (rdcontainer->req == NULL)
        {
          usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM);
          ret = -ENOMEM;
          goto errout;
        }

      rdcontainer->offset        = 0;
      rdcontainer->req->priv     = rdcontainer;
      rdcontainer->req->callback = usb_adb_rdcomplete;
    }

  /* Pre-allocate write requests. The buffer size is one full packet. */

#ifdef CONFIG_USBDEV_DUALSPEED
  reqlen = CONFIG_USBADB_EPBULKIN_HSSIZE;
#else
  reqlen = CONFIG_USBADB_EPBULKIN_FSSIZE;
#endif

  for (i = 0; i < CONFIG_USBADB_NWRREQS; i++)
    {
      FAR struct usbadb_wrreq_s *wrcontainer;

      wrcontainer      = &priv->wrreqs[i];
      wrcontainer->req = usbclass_allocreq(priv->epbulkin, reqlen);
      if (wrcontainer->req == NULL)
        {
          usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRALLOCREQ), -ENOMEM);
          ret = -ENOMEM;
          goto errout;
        }

      wrcontainer->req->priv     = wrcontainer;
      wrcontainer->req->callback = usb_adb_wrcomplete;

      flags = enter_critical_section();
      sq_addlast(&wrcontainer->node, &priv->txfree);
      leave_critical_section(flags);
    }

  /* Report if we are selfpowered (unless we are part of a
   * composite device)
   */

#ifndef CONFIG_USBADB_COMPOSITE
#ifdef CONFIG_USBDEV_SELFPOWERED
  DEV_SETSELFPOWERED(dev);
#endif

  /* And pull-up the data line for the soft connect function (unless we are
   * part of a composite device)
   */

  DEV_CONNECT(dev);
#endif
  return OK;

errout:
  usbclass_unbind(driver, dev);
  return ret;
}

/****************************************************************************
 * Name: usbclass_unbind
 *
 * Description:
 *    Invoked when the driver is unbound from a USB device driver
 *
 ****************************************************************************/

static void usbclass_unbind(FAR struct usbdevclass_driver_s *driver,
                            FAR struct usbdev_s *dev)
{
  usbtrace(TRACE_CLASSUNBIND, 0);

  #warning Missing logic
}

/****************************************************************************
 * Name: usbclass_setup
 *
 * Description:
 *   Invoked for ep0 control requests.  This function probably executes
 *   in the context of an interrupt handler.
 *
 ****************************************************************************/

static int usbclass_setup(FAR struct usbdevclass_driver_s *driver,
                          FAR struct usbdev_s *dev,
                          FAR const struct usb_ctrlreq_s *ctrl,
                          FAR uint8_t *dataout, size_t outlen)
{
  uint16_t value;
  uint16_t len;
  int ret = -EOPNOTSUPP;

  FAR struct usbdev_adb_s *priv;
  FAR struct usbdev_req_s *ctrlreq;

#ifdef CONFIG_DEBUG_FEATURES
  if (!driver || !dev || !dev->ep0 || !ctrl)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return -EIO;
    }
#endif

  /* Extract reference to private data */

  usbtrace(TRACE_CLASSSETUP, ctrl->req);
  priv = &((FAR struct adb_driver_s *)driver)->dev;

#ifdef CONFIG_DEBUG_FEATURES
  if (!priv || !priv->ctrlreq)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
      return -ENODEV;
    }
#endif

  ctrlreq = priv->ctrlreq;

  /* Extract the little-endian 16-bit values to host order */

  value = GETUINT16(ctrl->value);
  len   = GETUINT16(ctrl->len);

  uinfo("type=%02x req=%02x value=%04x index=%04x len=%04x\n",
        ctrl->type, ctrl->req, value, GETUINT16(ctrl->index), len);

  switch (ctrl->type & USB_REQ_TYPE_MASK)
    {
      case USB_REQ_TYPE_STANDARD:
        {
          switch (ctrl->req)
            {
#ifndef CONFIG_USBADB_COMPOSITE
            case USB_REQ_GETDESCRIPTOR:
              {
                /* The value field specifies the descriptor type in the
                 * MS byte and the descriptor index in the LS byte
                 * (order is little endian)
                 */

                switch (ctrl->value[1])
                  {
                  /* If the device is used in as part of a composite
                   * device, then the device descriptor is provided by logic
                   * in the composite device implementation.
                   */

                  case USB_DESC_TYPE_DEVICE:
                    {
                      ret = USB_SIZEOF_DEVDESC;
                      memcpy(ctrlreq->buf, &g_adb_devdesc, ret);
                    }
                    break;

                  case USB_DESC_TYPE_DEVICEQUALIFIER:
                    break;
                  case USB_DESC_TYPE_OTHERSPEEDCONFIG:
                    break;

                  /* If the serial device is used in as part of a composite
                   * device, then the configuration descriptor is provided by
                   * logic in the composite device implementation.
                   */

                  case USB_DESC_TYPE_CONFIG:
                    {
                      ret = usbclass_mkcfgdesc(ctrlreq->buf, NULL);
                    }
                    break;

                  /* If the serial device is used in as part of a composite
                   * device, then the language string descriptor is provided
                   * by logic in the composite device implementation.
                   */

                  case USB_DESC_TYPE_STRING:
                    {
                      /* index == language code. */

                      ret =
                      usbclass_mkstrdesc(ctrl->value[0],
                                        (FAR struct usb_strdesc_s *)
                                          ctrlreq->buf);
                    }
                    break;

                  default:
                    {
                      usbtrace(
                        TRACE_CLSERROR(USBSER_TRACEERR_GETUNKNOWNDESC),
                        value);
                    }
                    break;
                  }
              }
              break;

            /* If the serial device is used in as part of a composite device,
             * then the overall composite class configuration is managed by
             * logic in the composite device implementation.
             */

            case USB_REQ_GETCONFIGURATION:
              {
                if (ctrl->type == USB_DIR_IN)
                  {
                    *(FAR uint8_t *)ctrlreq->buf = priv->config;
                    ret = 1;
                  }
              }
              break;
#endif /* !CONFIG_USBADB_COMPOSITE */

            case USB_REQ_SETCONFIGURATION:
              {
                if (ctrl->type == 0)
                  {
                    ret = usbclass_setconfig(priv, value);
                  }
              }
              break;

            default:
              usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDSTDREQ),
                       ctrl->req);
              break;
            }
        }

      case USB_REQ_TYPE_CLASS:
        {
          /* ADB-Specific Requests */

          usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ),
                  ctrl->req);
          break;
        }

      default:
        {
          usbtrace(
            TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDTYPE),
            ctrl->type);
        }
    }

#ifndef CONFIG_USBADB_COMPOSITE
  /* Respond to the setup command if data was returned.  On an error return
   * value (ret < 0), the USB driver will stall.
   */

  if (ret >= 0)
    {
      ctrlreq->len   = (len < ret) ? len : ret;
      ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;

      /* Send the response -- either directly to the USB controller or
       * indirectly in the case where this class is a member of a composite
       * device.
       */

      ret = EP_SUBMIT(dev->ep0, ctrlreq);

      if (ret < 0)
        {
          usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPRESPQ), (uint16_t)-ret);
          ctrlreq->result = OK;
          usbclass_ep0incomplete(dev->ep0, ctrlreq);
        }
    }
#else
  /* Composite should send only one request for USB_REQ_SETCONFIGURATION.
   * Hence ADB driver cannot submit to ep0; composite has to handle it.
   */

  #warning composite_ep0submit() seems broken so skip it in case of composite
#endif /* !CONFIG_USBADB_COMPOSITE */

  /* Returning a negative value will cause a STALL */

  return ret;
}

/****************************************************************************
 * Name: usbclass_disconnect
 *
 * Description:
 *   Invoked after all transfers have been stopped, when the host is
 *   disconnected.  This function is probably called from the context of an
 *   interrupt handler.
 *
 ****************************************************************************/

static void usbclass_disconnect(FAR struct usbdevclass_driver_s *driver,
                                FAR struct usbdev_s *dev)
{
  FAR struct usbdev_adb_s *priv;
  irqstate_t flags;

  usbtrace(TRACE_CLASSDISCONNECT, 0);

#ifdef CONFIG_DEBUG_FEATURES
  if (!driver || !dev || !dev->ep0)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return;
    }
#endif

  /* Extract reference to private data */

  priv = &((FAR struct adb_driver_s *)driver)->dev;

#ifdef CONFIG_DEBUG_FEATURES
  if (!priv)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
      return;
    }
#endif

  /* FIXME do we have to lock interrupts here ? */

  flags = enter_critical_section();

  /* Reset the configuration */

  usbclass_resetconfig(priv);

  leave_critical_section(flags);

  /* Perform the soft connect function so that we will we can be
   * re-enumerated (unless we are part of a composite device)
   */

#ifndef CONFIG_USBDEV_COMPOSITE
  DEV_CONNECT(dev);
#endif
}

/****************************************************************************
 * Name: usbclass_suspend
 *
 * Description:
 *   Handle the USB suspend event.
 *
 ****************************************************************************/

static void usbclass_suspend(FAR struct usbdevclass_driver_s *driver,
                             FAR struct usbdev_s *dev)
{
  FAR struct usbdev_adb_s *priv = &((FAR struct adb_driver_s *)driver)->dev;

  usbtrace(TRACE_CLASSSUSPEND, 0);

  if (priv->config != USBADB_CONFIGIDNONE)
    {
      adb_char_on_connect(priv, 0);
    }
}

/****************************************************************************
 * Name: usbclass_resume
 *
 * Description:
 *   Handle the USB resume event.
 *
 ****************************************************************************/

static void usbclass_resume(FAR struct usbdevclass_driver_s *driver,
                            FAR struct usbdev_s *dev)
{
  FAR struct usbdev_adb_s *priv = &((FAR struct adb_driver_s *)driver)->dev;

  usbtrace(TRACE_CLASSRESUME, 0);

  if (priv->config != USBADB_CONFIGIDNONE)
    {
      adb_char_on_connect(priv, 1);
    }
}

/****************************************************************************
 * Name: usbclass_classobject
 *
 * Description:
 *   Register USB driver and return the class object.
 *
 * Returned Value:
 *   0 on success, negative error code on failure.
 *
 ****************************************************************************/

static int usbclass_classobject(int minor,
                                FAR struct usbdev_devinfo_s *devinfo,
                                FAR struct usbdevclass_driver_s **classdev)
{
  int ret;
  FAR struct adb_driver_s *alloc;

  alloc = (FAR struct adb_driver_s *)
    kmm_zalloc(sizeof(struct adb_driver_s));

  if (!alloc)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0);
      return -ENOMEM;
    }

  /* Initialize the USB class driver structure */

#ifdef CONFIG_USBDEV_DUALSPEED
  alloc->drvr.speed          = USB_SPEED_HIGH;
#else
  alloc->drvr.speed          = USB_SPEED_FULL;
#endif

  alloc->drvr.ops   = &g_adb_driverops;

  sq_init(&alloc->dev.rxpending);
  sq_init(&alloc->dev.txfree);

#ifdef CONFIG_USBADB_COMPOSITE
  /* Save the caller provided device description (composite only) */

  memcpy(&alloc->dev.devinfo, devinfo,
         sizeof(struct usbdev_devinfo_s));
#endif

  /* Initialize the char device structure */

  nxsem_init(&alloc->dev.exclsem, 0, 1);
  alloc->dev.crefs = 0;

  /* Register char device driver */

  /* FIXME use minor in device name */

  ret = register_driver(USBADB_CHARDEV_PATH, &g_adb_fops, 0666, &alloc->dev);
  if (ret < 0)
    {
      uerr("Failed to register char device");
      goto exit_free_driver;
    }

  *classdev = &alloc->drvr;
  return OK;

exit_free_driver:
  kmm_free(alloc);
  return ret;
}

/****************************************************************************
 * Name: usbclass_uninitialize
 *
 * Description:
 *   Free allocated memory
 *
 * Returned Value:
 *   0 on success, negative error code on failure.
 *
 ****************************************************************************/

static void usbclass_uninitialize(FAR struct usbdevclass_driver_s *classdev)
{
  FAR struct adb_driver_s *alloc = container_of(
    classdev, FAR struct adb_driver_s, drvr);

  #warning FIXME Maybe missing logic here

  unregister_driver(USBADB_CHARDEV_PATH);

  kmm_free(alloc);
}

/****************************************************************************
 * Char Device Driver Methods
 ****************************************************************************/

/****************************************************************************
 * Name: adb_char_notify_readers
 *
 * Description:
 *   Notify threads waiting to read device. This function must be called
 *   with interrupt disabled.
 *
 ****************************************************************************/

static void adb_char_notify_readers(FAR struct usbdev_adb_s *priv)
{
  /* Notify all of the waiting readers */

  adb_char_waiter_sem_t *cur_sem = priv->rdsems;
  while (cur_sem != NULL)
    {
      nxsem_post(&cur_sem->sem);
      cur_sem = cur_sem->next;
    }

  priv->rdsems = NULL;

  /* Notify all poll/select waiters */

  adb_char_pollnotify(priv, POLLIN);
}

/****************************************************************************
 * Name: adb_char_pollnotify
 *
 * Description:
 *   Notify threads waiting for device event. This function must be called
 *   with interrupt disabled.
 *
 ****************************************************************************/

static void adb_char_pollnotify(FAR struct usbdev_adb_s *dev,
                                pollevent_t eventset)
{
  FAR struct pollfd *fds;
  int i;

  for (i = 0; i < CONFIG_USBADB_NPOLLWAITERS; i++)
    {
      fds = dev->fds[i];
      if (fds)
        {
          fds->revents |= eventset & (fds->events | POLLERR | POLLHUP);

          if (fds->revents != 0)
            {
              nxsem_post(fds->sem);
            }
        }
    }
}

/****************************************************************************
 * Name: adb_char_open
 *
 * Description:
 *   Open adb device. Only one open() instance is supported.
 *
 ****************************************************************************/

static int adb_char_open(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct usbdev_adb_s *priv = inode->i_private;
  int ret;

  /* Get exclusive access to the device structures */

  ret = nxsem_wait(&priv->exclsem);
  if (ret < 0)
    {
      return ret;
    }

  finfo("entry: <%s> %d\n", inode->i_name, priv->crefs);

  priv->crefs += 1;

  assert(priv->crefs != 0);

  nxsem_post(&priv->exclsem);
  return ret;
}

/****************************************************************************
 * Name: adb_char_close
 *
 * Description:
 *   Close adb device.
 *
 ****************************************************************************/

static int adb_char_close(FAR struct file *filep)
{
  int ret;
  FAR struct inode *inode = filep->f_inode;
  FAR struct usbdev_adb_s *priv = inode->i_private;

  /* Get exclusive access to the device structures */

  ret = nxsem_wait(&priv->exclsem);
  if (ret < 0)
    {
      return ret;
    }

  finfo("entry: <%s> %d\n", inode->i_name, priv->crefs);

  priv->crefs -= 1;

  assert(priv->crefs >= 0);

  nxsem_post(&priv->exclsem);
  return OK;
}

/****************************************************************************
 * Name: adb_char_blocking_io
 *
 * Description:
 *   Handle read/write blocking io.
 *
 ****************************************************************************/

static int adb_char_blocking_io(FAR struct usbdev_adb_s *priv,
                                FAR adb_char_waiter_sem_t *sem,
                                FAR adb_char_waiter_sem_t **slist,
                                FAR struct sq_queue_s *queue)
{
  int ret;
  irqstate_t flags;

  flags = enter_critical_section();

  if (!sq_empty(queue))
    {
      /* Queue not empty after all */

      leave_critical_section(flags);
      return 0;
    }

  /* Register waiter semaphore */

  sem->next = *slist;
  *slist = sem;

  leave_critical_section(flags);

  nxsem_post(&priv->exclsem);

  /* Wait for USB device to notify */

  ret = nxsem_wait(&sem->sem);

  if (ret < 0)
    {
      /* Interrupted wait, unregister semaphore
       * TODO ensure that exclsem wait does not fail (ECANCELED)
       */

      nxsem_wait_uninterruptible(&priv->exclsem);

      flags = enter_critical_section();

      adb_char_waiter_sem_t *cur_sem = *slist;

      if (cur_sem == sem)
        {
          *slist = sem->next;
        }
      else
        {
          while (cur_sem)
            {
              if (cur_sem->next == sem)
                {
                  cur_sem->next = sem->next;
                  break;
                }
            }
        }

      leave_critical_section(flags);
      nxsem_post(&priv->exclsem);
      return ret;
    }

  return nxsem_wait(&priv->exclsem);
}

/****************************************************************************
 * Name: adb_char_read
 *
 * Description:
 *   Read adb device.
 *
 ****************************************************************************/

static ssize_t adb_char_read(FAR struct file *filep, FAR char *buffer,
                               size_t len)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct usbdev_adb_s *priv = inode->i_private;
  ssize_t ret;
  size_t retlen;
  irqstate_t flags;

  assert(len > 0 && buffer != NULL);

  if (priv->config == USBADB_CONFIGIDNONE)
    {
      /* USB device not connected */

      return -EPIPE;
    }

  ret = nxsem_wait(&priv->exclsem);
  if (ret < 0)
    {
      return ret;
    }

  /* Check for available data */

  if (sq_empty(&priv->rxpending))
    {
      if (filep->f_oflags & O_NONBLOCK)
        {
          nxsem_post(&priv->exclsem);
          return -EAGAIN;
        }

      adb_char_waiter_sem_t sem;
      nxsem_init(&sem.sem, 0, 0);
      nxsem_set_protocol(&sem.sem, SEM_PRIO_NONE);

      do
        {
          /* RX queue seems empty. Check again with interrupts disabled */

          ret = adb_char_blocking_io(
            priv, &sem, &priv->rdsems, &priv->rxpending);
          if (ret < 0)
            {
              nxsem_destroy(&sem.sem);
              return ret;
            }
        }
      while (sq_empty(&priv->rxpending));

      /* RX queue not empty and exclsem locked so we are the only reader */

      nxsem_destroy(&sem.sem);
    }

  /* Device ready for read */

  retlen = 0;

  while (!sq_empty(&priv->rxpending) && len > 0)
    {
      FAR struct usbadb_rdreq_s *rdcontainer;
      uint16_t reqlen;

      /* Process each packet in the priv->rxpending list */

      rdcontainer = container_of(
        sq_peek(&priv->rxpending),
        struct usbadb_rdreq_s,
        node);

      reqlen = rdcontainer->req->xfrd - rdcontainer->offset;

      if (reqlen > len)
        {
          /* Output buffer full */

          memcpy(&buffer[retlen],
                 &rdcontainer->req->buf[rdcontainer->offset],
                 len);
          rdcontainer->offset += len;
          retlen += len;
          break;
        }

      memcpy(&buffer[retlen],
             &rdcontainer->req->buf[rdcontainer->offset],
             reqlen);
      retlen += reqlen;
      len -= reqlen;

      /* The entire packet was processed and may be removed from the
       * pending RX list.
       */

      /* FIXME use atomic queue primitives ? */

      flags = enter_critical_section();
      sq_remfirst(&priv->rxpending);
      leave_critical_section(flags);

      ret = usb_adb_submit_rdreq(priv, rdcontainer);

      if (ret != OK)
        {
          /* TODO handle error */

          PANIC();
        }
    }

  nxsem_post(&priv->exclsem);
  return retlen;
}

/****************************************************************************
 * Name: adb_char_write
 *
 * Description:
 *   Write adb device.
 *
 ****************************************************************************/

static ssize_t adb_char_write(FAR struct file *filep,
                                FAR const char *buffer, size_t len)
{
  int ret;
  int wlen;
  FAR struct usbdev_req_s *req;
  FAR struct usbadb_wrreq_s *wrcontainer;
  FAR struct inode *inode = filep->f_inode;
  FAR struct usbdev_adb_s *priv = inode->i_private;

  irqstate_t flags;

  if (priv->config == USBADB_CONFIGIDNONE)
    {
      /* USB device not connected */

      return -EPIPE;
    }

  ret = nxsem_wait(&priv->exclsem);
  if (ret < 0)
    {
      return ret;
    }

  /* Check for available write request */

  if (sq_empty(&priv->txfree))
    {
      if (filep->f_oflags & O_NONBLOCK)
        {
          ret = -EAGAIN;
          goto errout;
        }

      adb_char_waiter_sem_t sem;
      nxsem_init(&sem.sem, 0, 0);
      nxsem_set_protocol(&sem.sem, SEM_PRIO_NONE);

      do
        {
          /* TX queue seems empty. Check again with interrupts disabled */

          ret = adb_char_blocking_io(
            priv, &sem, &priv->wrsems, &priv->txfree);
          if (ret < 0)
            {
              nxsem_destroy(&sem.sem);
              return ret;
            }
        }
      while (sq_empty(&priv->txfree));

      nxsem_destroy(&sem.sem);
    }

  /* Device ready for write */

  wlen = 0;

  while (len > 0 && !sq_empty(&priv->txfree))
    {
      int cur_len;

      /* Get available TX request slot */

      flags = enter_critical_section();

      wrcontainer = container_of(
        sq_remfirst(&priv->txfree),
        struct usbadb_wrreq_s,
        node);

      leave_critical_section(flags);

      req = wrcontainer->req;

      /* Fill the request with data */

      if (len > priv->epbulkin->maxpacket)
        {
          cur_len = priv->epbulkin->maxpacket;
        }
      else
        {
          cur_len = len;
        }

      memcpy(req->buf, &buffer[wlen], cur_len);

      /* Then submit the request to the endpoint */

      req->len     = cur_len;
      req->flags   = 0;
      req->priv    = wrcontainer;
      ret          = EP_SUBMIT(priv->epbulkin, req);

      if (ret != OK)
        {
          /* TODO add tx request back in txfree queue */

          usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL),
                   (uint16_t)-ret);
          PANIC();
          break;
        }

      wlen += cur_len;
      len -= cur_len;
    }

  assert(wlen > 0);
  ret = wlen;

errout:
  nxsem_post(&priv->exclsem);
  return ret;
}

static int adb_char_ioctl(FAR struct file *filep, int cmd,
                            unsigned long arg)
{
  return -EINVAL;
}

static int adb_char_poll(FAR struct file *filep, FAR struct pollfd *fds,
                       bool setup)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct usbdev_adb_s *priv = inode->i_private;
  int ret;
  int i;
  pollevent_t eventset;
  irqstate_t flags;

  ret = nxsem_wait(&priv->exclsem);
  if (ret < 0)
    {
      return ret;
    }

  ret = OK;

  if (!setup)
    {
      /* This is a request to tear down the poll. */

      FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;

      /* Remove all memory of the poll setup */

      *slot     = NULL;
      fds->priv = NULL;
      goto errout;
    }

  /* FIXME only parts of this function required interrupt disabled */

  flags = enter_critical_section();

  /* This is a request to set up the poll. Find an available
   * slot for the poll structure reference
   */

  for (i = 0; i < CONFIG_USBADB_NPOLLWAITERS; i++)
    {
      /* Find an available slot */

      if (!priv->fds[i])
        {
          /* Bind the poll structure and this slot */

          priv->fds[i] = fds;
          fds->priv   = &priv->fds[i];
          break;
        }
    }

  if (i >= CONFIG_USBADB_NPOLLWAITERS)
    {
      fds->priv = NULL;
      ret       = -EBUSY;
      goto exit_leave_critical;
    }

  eventset = 0;

  /* Notify the POLLOUT event if at least one request is available */

  if (!sq_empty(&priv->txfree))
    {
      eventset |= POLLOUT;
    }

  /* Notify the POLLIN event if at least one read request is pending */

  if (!sq_empty(&priv->rxpending))
    {
      eventset |= POLLIN;
    }

  if (eventset)
    {
      adb_char_pollnotify(priv, eventset);
    }

exit_leave_critical:
  leave_critical_section(flags);
errout:
  nxsem_post(&priv->exclsem);
  return ret;
}

static void adb_char_on_connect(FAR struct usbdev_adb_s *priv, int connect)
{
  irqstate_t flags;
  adb_char_waiter_sem_t *cur_sem;

  flags = enter_critical_section();

  if (connect)
    {
      /* Notify poll/select with POLLIN */

      adb_char_pollnotify(priv, POLLIN);
    }
  else
    {
      /* Notify all of the char device waiting readers */

      cur_sem = priv->rdsems;
      while (cur_sem != NULL)
        {
          nxsem_post(&cur_sem->sem);
          cur_sem = cur_sem->next;
        }

      priv->rdsems = NULL;

      /* Notify all of the char device waiting writers */

      cur_sem = priv->wrsems;
      while (cur_sem != NULL)
        {
          nxsem_post(&cur_sem->sem);
          cur_sem = cur_sem->next;
        }

      priv->wrsems = NULL;

      /* Notify all poll/select waiters that a hangup occurred */

      adb_char_pollnotify(priv, (POLLERR | POLLHUP));
    }

    leave_critical_section(flags);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: usbdev_adb_initialize
 *
 * Description:
 *   Initialize the Android Debug Bridge USB device driver.
 *
 * Returned Value:
 *   0 on success, -errno on failure
 *
 ****************************************************************************/

#ifndef CONFIG_USBADB_COMPOSITE
int usbdev_adb_initialize(void)
{
  int ret;
  FAR struct usbdevclass_driver_s *classdev;
  FAR struct adb_driver_s *drvr;

  ret = usbclass_classobject(0, NULL, &classdev);
  if (ret)
    {
      nerr("usbclass_classobject failed: %d\n", ret);
      return ret;
    }

  drvr = (FAR struct adb_driver_s *)classdev;

  ret = usbdev_register(&drvr->drvr);
  if (ret)
    {
      nerr("usbdev_register failed: %d\n", ret);
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_DEVREGISTER), (uint16_t)-ret);
      usbclass_uninitialize(classdev);
      return ret;
    }

  return OK;
}
#endif

/****************************************************************************
 * Name: usbdev_adb_get_composite_devdesc
 *
 * Description:
 *   Helper function to fill in some constants into the composite
 *   configuration struct.
 *
 * Input Parameters:
 *     dev - Pointer to the configuration struct we should fill
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#if defined(CONFIG_USBDEV_COMPOSITE) && defined(CONFIG_USBADB_COMPOSITE)
void usbdev_adb_get_composite_devdesc(struct composite_devdesc_s *dev)
{
  memset(dev, 0, sizeof(struct composite_devdesc_s));

  dev->mkconfdesc          = usbclass_mkcfgdesc;
  dev->mkstrdesc           = usbclass_mkstrdesc;
  dev->classobject         = usbclass_classobject;
  dev->uninitialize        = usbclass_uninitialize;
  dev->nconfigs            = USBADB_NCONFIGS;
  dev->configid            = 1;
  dev->cfgdescsize         = sizeof(g_adb_cfgdesc)+2*USB_SIZEOF_EPDESC;
  dev->devinfo.ninterfaces = 1;
  dev->devinfo.nstrings    = USBADB_NSTRIDS;
  dev->devinfo.nendpoints  = USBADB_NUM_EPS;
}
#endif