/****************************************************************************
 * drivers/usbdev/composite.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 <sys/types.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>

#if defined(CONFIG_BOARD_USBDEV_SERIALSTR) || defined(CONFIG_BOARD_USBDEV_PIDVID)
#  include <nuttx/board.h>
#endif

#include "composite.h"

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

/* The internal version of the class driver */

struct composite_driver_s
{
  struct usbdevclass_driver_s  drvr;
  FAR struct composite_dev_s  *dev;
};

/* This is what is allocated */

struct composite_alloc_s
{
  struct composite_dev_s       dev;
  struct composite_driver_s    drvr;
};

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

/* USB helpers **************************************************************/

static void    composite_ep0incomplete(FAR struct usbdev_ep_s *ep,
                 FAR struct usbdev_req_s *req);
static int     composite_classsetup(FAR struct composite_dev_s *priv,
                 FAR struct usbdev_s *dev,
                 FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout,
                 size_t outlen);

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

static int     composite_bind(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static void    composite_unbind(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static int     composite_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    composite_disconnect(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static void    composite_suspend(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static void    composite_resume(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);

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

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

static const struct usbdevclass_driverops_s g_driverops =
{
  composite_bind,       /* bind */
  composite_unbind,     /* unbind */
  composite_setup,      /* setup */
  composite_disconnect, /* disconnect */
  composite_suspend,    /* suspend */
  composite_resume,     /* resume */
};

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

/****************************************************************************
 * Name: composite_ep0incomplete
 *
 * Description:
 *   Handle completion of the composite driver's EP0 control operations
 *
 ****************************************************************************/

static void composite_ep0incomplete(FAR struct usbdev_ep_s *ep,
                                    FAR struct usbdev_req_s *req)
{
  /* Just check the result of the transfer */

  if (req->result || req->xfrd != req->len)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_REQRESULT),
               (uint16_t)-req->result);
    }
}

/****************************************************************************
 * Name: composite_classsetup
 *
 * Description:
 *   Forward a setup command to the appropriate component device
 *
 ****************************************************************************/

static int composite_classsetup(FAR struct composite_dev_s *priv,
                                FAR struct usbdev_s *dev,
                                FAR const struct usb_ctrlreq_s *ctrl,
                                FAR uint8_t *dataout, size_t outlen)
{
  uint16_t index;
  uint8_t  interface;
  int ret = -EOPNOTSUPP;
  int i;

  index     = GETUINT16(ctrl->index);
  interface = (uint8_t)(index & 0xff);

  for (i = 0; i < priv->ndevices; i++)
    {
      if (interface >= priv->device[i].compdesc.devinfo.ifnobase &&
          interface < (priv->device[i].compdesc.devinfo.ifnobase +
                       priv->device[i].compdesc.devinfo.ninterfaces))
        {
          return CLASS_SETUP(priv->device[i].dev, dev, ctrl,
                             dataout, outlen);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: composite_msftdescriptor
 *
 * Description:
 *   Assemble the Microsoft OS descriptor from the COMPATIBLE_ID's given
 *   in each device's composite_devdesc_s.
 *
 ****************************************************************************/

#ifdef CONFIG_COMPOSITE_MSFT_OS_DESCRIPTORS
static int composite_msftdescriptor(FAR struct composite_dev_s *priv,
                                    FAR struct usbdev_s *dev,
                                    FAR const struct usb_ctrlreq_s *ctrl,
                                    FAR struct usbdev_req_s *ctrl_rsp,
                                    FAR bool *dispatched)
{
  if (ctrl->index[0] == MSFTOSDESC_INDEX_FUNCTION)
    {
      /* Function descriptor is common to whole device */

      FAR struct usb_msft_os_feature_desc_s *response =
        (FAR struct usb_msft_os_feature_desc_s *)ctrl_rsp->buf;
      int i;

      memset(response, 0, sizeof(*response));

      for (i = 0; i < priv->ndevices; i++)
        {
          if (priv->device[i].compdesc.msft_compatible_id[0] != 0)
            {
              FAR struct usb_msft_os_function_desc_s *func =
                &response->function[response->count];

              memset(func, 0, sizeof(*func));
              func->firstif = priv->device[i].compdesc.devinfo.ifnobase;
              func->nifs    = priv->device[i].compdesc.devinfo.ninterfaces;
              memcpy(func->compatible_id,
                     priv->device[i].compdesc.msft_compatible_id,
                     sizeof(func->compatible_id));
              memcpy(func->sub_id, priv->device[i].compdesc.msft_sub_id,
                     sizeof(func->sub_id));

              response->count++;
            }
        }

      if (response->count > 0)
        {
          size_t total_len     = sizeof(struct usb_msft_os_feature_desc_s) +
                                 (response->count - 1) *
                                 sizeof(struct usb_msft_os_function_desc_s);
          response->len[0]     = (total_len >> 0) & 0xff;
          response->len[1]     = (total_len >> 8) & 0xff;
          response->len[2]     = (total_len >> 16) & 0xff;
          response->len[3]     = (total_len >> 24) & 0xff;
          response->version[1] = 0x01;
          response->index[0]   = MSFTOSDESC_INDEX_FUNCTION;

          return total_len;
        }
      else
        {
          return 0;
        }
    }
  else if (ctrl->index[0] == MSFTOSDESC_INDEX_EXTPROP ||
           ctrl->index[0] == ctrl->value[0])
    {
      /* Extended properties are per-interface, pass the request to
       * subdevice.  NOTE: The documentation in OS_Desc_Ext_Prop.docx seems
       * a bit incorrect here, the interface is in ctrl->value low byte.
       * Also WinUSB driver has limitation that index[0] will not be correct
       * if trying to read descriptors using e.g. libusb xusb.exe.
       */

      uint8_t interface = ctrl->value[0];
      int ret = -ENOTSUP;
      int i;

      for (i = 0; i < priv->ndevices; i++)
        {
          if (interface >= priv->device[i].compdesc.devinfo.ifnobase &&
              interface < (priv->device[i].compdesc.devinfo.ifnobase +
                           priv->device[i].compdesc.devinfo.ninterfaces))
            {
              ret = CLASS_SETUP(priv->device[i].dev, dev, ctrl, NULL, 0);
              *dispatched = true;
              break;
            }
        }

      return ret;
    }
  else
    {
      return -ENOTSUP;
    }
}
#endif

/****************************************************************************
 * Name: composite_mkcfgdesc
 *
 * Description:
 *   Construct the configuration descriptor
 *
 ****************************************************************************/

static int16_t composite_mkcfgdesc(FAR struct usbdevclass_driver_s *driver,
                                   FAR uint8_t *buf,
                                   uint8_t speed, uint8_t type)
{
  FAR struct composite_dev_s *priv =
    ((FAR struct composite_driver_s *)driver)->dev;
  FAR struct usb_cfgdesc_s *cfgdesc;
  int16_t len;
  int16_t total;
  int i;

  /* Configuration descriptor for the composite device */

  memcpy(buf, priv->descs->cfgdesc, sizeof(struct usb_cfgdesc_s));

  cfgdesc = (FAR struct usb_cfgdesc_s *)buf;
  cfgdesc->ninterfaces = priv->ninterfaces;
  cfgdesc->type = type;

  /* Increment the size and buf to point right behind the information
   * filled in
   */

  total = USB_SIZEOF_CFGDESC;
  buf += USB_SIZEOF_CFGDESC;

  /* Copy all contained interface descriptors into the buffer too */

  for (i = 0; i < priv->ndevices; i++)
    {
      FAR struct composite_devobj_s *devobj = &priv->device[i];

      len = devobj->compdesc.mkconfdesc(buf,
                                        &devobj->compdesc.devinfo,
                                        speed, type);
      total += len;
      buf += len;
    }

  cfgdesc->totallen[0] = LSBYTE(total);
  cfgdesc->totallen[1] = MSBYTE(total);

  return total;
}

/****************************************************************************
 * Name: composite_mkstrdesc
 *
 * Description:
 *   Construct a string descriptor
 *
 ****************************************************************************/

static int composite_mkstrdesc(FAR struct usbdevclass_driver_s *driver,
                               uint8_t id, FAR struct usb_strdesc_s *outdesc)
{
  FAR struct composite_dev_s *priv =
    ((FAR struct composite_driver_s *)driver)->dev;
  FAR const struct usbdev_strdescs_s *strdescs = priv->descs->strdescs;
  FAR const struct usbdev_strdesc_s *strdesc;
  FAR uint8_t *data = (FAR uint8_t *)(outdesc + 1);
  int i;

  if (id == 0)
    {
      outdesc->len = 4;
      outdesc->type = USB_DESC_TYPE_STRING;
      data[0] = LSBYTE(strdescs->language);
      data[1] = MSBYTE(strdescs->language);
      return 4;
    }

#ifdef CONFIG_COMPOSITE_MSFT_OS_DESCRIPTORS
  if (id == USB_REQ_GETMSFTOSDESCRIPTOR)
    {
      /* Note: Windows has a habit of caching this response,
       * so if you want to enable/disable it you'll usually
       * need to change the device serial number afterwards.
       */

      static const uint8_t msft_response[16] =
        {
          'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0,
          '0', 0, USB_REQ_GETMSFTOSDESCRIPTOR, 0
        };

      outdesc->len = 18;
      outdesc->type = USB_DESC_TYPE_STRING;
      memcpy(data, msft_response, 16);
      return outdesc->len;
    }
#endif

  for (strdesc = strdescs->strdesc;
       strdesc != NULL && strdesc->string != NULL; strdesc++)
    {
      if (strdesc->id == id)
        {
          FAR const char *strval = strdesc->string;
          int ndata;
          int len;

#ifdef CONFIG_BOARD_USBDEV_SERIALSTR
          if (strdesc->id == COMPOSITE_SERIALSTRID)
            {
              strval = board_usbdev_serialstr();
            }
#endif

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

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

  for (i = 0; i < priv->ndevices; i++)
    {
      if (id >
          priv->device[i].compdesc.devinfo.strbase &&
          id <=
          priv->device[i].compdesc.devinfo.strbase +
          priv->device[i].compdesc.devinfo.nstrings)
        {
          return priv->device[i].compdesc.mkstrdesc(
                   id -
                   priv->device[i].compdesc.devinfo.strbase,
                   outdesc);
        }
    }

  return -EINVAL;
}

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

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

static int composite_bind(FAR struct usbdevclass_driver_s *driver,
                          FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv =
    ((FAR struct composite_driver_s *)driver)->dev;

  int ret;
  int i;

  usbtrace(TRACE_CLASSBIND, 0);

  /* Bind the structures */

  priv->usbdev   = dev;

  /* Save the reference to our private data structure in EP0 so that it
   * can be recovered in ep0 completion events.
   */

  dev->ep0->priv = priv;

  /* Preallocate one control request */

  priv->ctrlreq = usbdev_allocreq(dev->ep0, priv->cfgdescsize);
  if (priv->ctrlreq == NULL)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_ALLOCCTRLREQ), 0);
      ret = -ENOMEM;
      goto errout;
    }

  /* Initialize the pre-allocated control request */

  priv->ctrlreq->callback = composite_ep0incomplete;

  /* Then bind each of the constituent class drivers */

  for (i = 0; i < priv->ndevices; i++)
    {
      ret = CLASS_BIND(priv->device[i].dev, dev);
      if (ret < 0)
        {
          goto errout;
        }
    }

  /* Report if we are selfpowered */

#ifdef CONFIG_USBDEV_SELFPOWERED
  DEV_SETSELFPOWERED(dev);
#endif

  /* And pull-up the data line for the soft connect function */

  DEV_CONNECT(dev);
  return OK;

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

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

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

  usbtrace(TRACE_CLASSUNBIND, 0);

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

  /* Extract reference to private data */

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

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

  /* Make sure that we are not already unbound */

  if (priv != NULL)
    {
      int i;

      /* Unbind the constituent class drivers */

      flags = spin_lock_irqsave_nopreempt(&priv->lock);
      for (i = 0; i < priv->ndevices; i++)
        {
          CLASS_UNBIND(priv->device[i].dev, dev);
        }

      /* Free the pre-allocated control request */

      priv->config = COMPOSITE_CONFIGIDNONE;
      if (priv->ctrlreq != NULL)
        {
          usbdev_freereq(dev->ep0, priv->ctrlreq);
          priv->ctrlreq = NULL;
        }

      spin_unlock_irqrestore_nopreempt(&priv->lock, flags);
    }
}

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

static int composite_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)
{
  FAR struct composite_dev_s *priv;
  FAR struct usbdev_req_s *ctrlreq;
  uint16_t value;
  uint16_t index;
  uint16_t len;
  bool dispatched = false;
  int ret = -EOPNOTSUPP;
  uint8_t recipient;

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

  /* Extract a reference to private data */

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

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

  ctrlreq   = priv->ctrlreq;

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

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

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

  recipient = ctrl->type & USB_REQ_RECIPIENT_MASK;

  if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD &&
      recipient == USB_REQ_RECIPIENT_DEVICE)
    {
      /**********************************************************************
       * Standard Requests
       **********************************************************************/

      switch (ctrl->req)
        {
        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])
              {
              case USB_DESC_TYPE_DEVICE:
                {
                  ret = usbdev_copy_devdesc(ctrlreq->buf,
                                            priv->descs->devdesc,
                                            dev->speed);

#ifdef CONFIG_BOARD_USBDEV_PIDVID
                  {
                    uint16_t pid = board_usbdev_pid();
                    uint16_t vid = board_usbdev_vid();
                    FAR struct usb_devdesc_s *p_desc =
                               (FAR struct usb_devdesc_s *)ctrlreq->buf;

                    p_desc->vendor[0] = LSBYTE(vid);
                    p_desc->vendor[1] = MSBYTE(vid);

                    p_desc->product[0] = LSBYTE(pid);
                    p_desc->product[1] = MSBYTE(pid);
                  }
#endif
                }
                break;

#ifdef CONFIG_USBDEV_DUALSPEED
              case USB_DESC_TYPE_DEVICEQUALIFIER:
                {
                  ret = USB_SIZEOF_QUALDESC;
                  memcpy(ctrlreq->buf, priv->descs->qualdesc, ret);
                }
                break;

              case USB_DESC_TYPE_OTHERSPEEDCONFIG:
#endif

              case USB_DESC_TYPE_CONFIG:
                {
                    ret = composite_mkcfgdesc(driver, ctrlreq->buf,
                                              dev->speed, ctrl->value[1]);
                }
                break;

              case USB_DESC_TYPE_STRING:
                {
                  /* value == string index. Zero is the language ID. */

                  uint8_t strid = ctrl->value[0];
                  FAR struct usb_strdesc_s *buf =
                             (FAR struct usb_strdesc_s *)ctrlreq->buf;

                  ret = composite_mkstrdesc(driver, strid, buf);
                }
                break;

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

        case USB_REQ_SETCONFIGURATION:
          {
            if (ctrl->type == 0)
              {
                int i;

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

                    ret = OK;
                    break;
                  }

                /* Save the configuration and inform the constituent
                 * classes
                 */

                for (i = 0; i < priv->ndevices; i++)
                  {
                    ret = CLASS_SETUP(priv->device[i].dev,
                                      dev,
                                      ctrl,
                                      dataout,
                                      outlen);
                  }

                priv->config = value;
              }
          }
          break;

        case USB_REQ_GETCONFIGURATION:
          {
            if (ctrl->type == USB_DIR_IN)
              {
                ctrlreq->buf[0] = priv->config;
                ret = 1;
              }
          }
          break;

        case USB_REQ_SETINTERFACE:
          {
            if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE &&
                priv->config != COMPOSITE_CONFIGIDNONE)
              {
                ret = composite_classsetup(priv, dev, ctrl, dataout, outlen);
                dispatched = true;
              }
          }
          break;

        case USB_REQ_GETINTERFACE:
          {
            if (ctrl->type == (USB_DIR_IN | USB_REQ_RECIPIENT_INTERFACE) &&
                priv->config == COMPOSITE_CONFIGIDNONE)
              {
                ret = composite_classsetup(priv, dev, ctrl, dataout, outlen);
                dispatched = true;
              }
           }
           break;

        default:
          usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_UNSUPPORTEDSTDREQ),
                   ctrl->req);
          break;
        }
    }
#ifdef CONFIG_COMPOSITE_MSFT_OS_DESCRIPTORS
  else if (ctrl->req == USB_REQ_GETMSFTOSDESCRIPTOR &&
           (ctrl->type & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN &&
           (ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_VENDOR)
    {
      ret = composite_msftdescriptor(priv, dev, ctrl, ctrlreq, &dispatched);
    }
#endif
  else if (recipient == USB_REQ_RECIPIENT_INTERFACE ||
           recipient == USB_REQ_RECIPIENT_ENDPOINT)
    {
      /**********************************************************************
       * Non-Standard Class Requests
       **********************************************************************/

       /* Class implementations should handle their own interface and
        * endpoint requests.
        */

      ret = composite_classsetup(priv, dev, ctrl, dataout, outlen);
      dispatched = true;
    }

  /* Respond to the setup command if (1) data was returned, and (2) the
   * request was NOT successfully dispatched to the component class driver.
   * On an error return value (ret < 0), the USB driver will stall EP0.
   */

  if (ret >= 0 && !dispatched)
    {
      /* Setup the request */

      ctrlreq->len   = MIN(len, ret);

      /* Only when ret is less than len do zero length packet
       * need to be sent
       */

      ctrlreq->flags = ret < len ? USBDEV_REQFLAGS_NULLPKT : 0;

      /* And submit the request to the USB controller driver */

      ret = EP_SUBMIT(dev->ep0, ctrlreq);
      if (ret < 0)
        {
          usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_EPRESPQ),
                  (uint16_t)-ret);
          ctrlreq->result = OK;
          composite_ep0incomplete(dev->ep0, ctrlreq);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: composite_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 composite_disconnect(FAR struct usbdevclass_driver_s *driver,
                                 FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv;
  irqstate_t flags;
  int i;

  usbtrace(TRACE_CLASSDISCONNECT, 0);

#ifdef CONFIG_DEBUG_FEATURES
  if (!driver || !dev)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_INVALIDARG), 0);
      return;
    }
#endif

  /* Extract reference to private data */

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

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

  /* Reset the configuration and inform the constituent class drivers of
   * the disconnection.
   */

  flags = spin_lock_irqsave_nopreempt(&priv->lock);

  for (i = 0; i < priv->ndevices; i++)
    {
      CLASS_DISCONNECT(priv->device[i].dev, dev);
    }

  priv->config = COMPOSITE_CONFIGIDNONE;
  spin_unlock_irqrestore_nopreempt(&priv->lock, flags);

  /* Perform the soft connect function so that we will we can be
   * re-enumerated.
   */

  DEV_CONNECT(dev);
}

/****************************************************************************
 * Name: composite_suspend
 *
 * Description:
 *   Invoked on a USB suspend event.
 *
 ****************************************************************************/

static void composite_suspend(FAR struct usbdevclass_driver_s *driver,
                              FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv;
  irqstate_t flags;
  int i;

  usbtrace(TRACE_CLASSSUSPEND, 0);

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

  /* Extract reference to private data */

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

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

  /* Forward the suspend event to the constituent devices */

  flags = spin_lock_irqsave_nopreempt(&priv->lock);

  for (i = 0; i < priv->ndevices; i++)
    {
      CLASS_SUSPEND(priv->device[i].dev, priv->usbdev);
    }

  spin_unlock_irqrestore_nopreempt(&priv->lock, flags);
}

/****************************************************************************
 * Name: composite_resume
 *
 * Description:
 *   Invoked on a USB resume event.
 *
 ****************************************************************************/

static void composite_resume(FAR struct usbdevclass_driver_s *driver,
                             FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv = NULL;
  irqstate_t flags;
  int i;

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

  /* Extract reference to private data */

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

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

  /* Forward the resume event to the constituent devices */

  flags = spin_lock_irqsave_nopreempt(&priv->lock);

  for (i = 0; i < priv->ndevices; i++)
    {
      CLASS_RESUME(priv->device[i].dev, priv->usbdev);
    }

  spin_unlock_irqrestore_nopreempt(&priv->lock, flags);
}

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

/****************************************************************************
 * Name: composite_initialize
 *
 * Description:
 *   Register USB composite device as configured.  This function will call
 *   board-specific implementations in order to obtain the class objects for
 *   each of the members of the composite.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   A non-NULL "handle" is returned on success.  This handle may be used
 *   later with composite_uninitialize() in order to removed the composite
 *   device.  This handle is the (untyped) internal representation of the
 *   the class driver instance.
 *
 *   NULL is returned on any failure.
 *
 ****************************************************************************/

FAR void *composite_initialize(FAR const struct usbdev_devdescs_s *devdescs,
                               FAR struct composite_devdesc_s *pdevices,
                               uint8_t ndevices)
{
  FAR const struct usbdev_strdesc_s *strdesc;
  FAR struct composite_alloc_s *alloc;
  FAR struct composite_dev_s *priv;
  FAR struct composite_driver_s *drvr;
  int ret;
  int i;

  DEBUGASSERT(pdevices != NULL && ndevices <= NUM_DEVICES_TO_HANDLE);

  /* Allocate the structures needed */

  alloc = (FAR struct composite_alloc_s *)
    kmm_malloc(sizeof(struct composite_alloc_s));

  if (!alloc)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_ALLOCDEVSTRUCT), 0);
      return NULL;
    }

  /* Convenience pointers into the allocated blob */

  priv = &alloc->dev;
  drvr = &alloc->drvr;

  /* Initialize the USB composite driver structure */

  memset(priv, 0, sizeof(struct composite_dev_s));

  /* Initialize USB device descriptor */

  priv->descs       = devdescs;
  priv->cfgdescsize = USB_SIZEOF_CFGDESC;
  priv->ninterfaces = 0;
  spin_lock_init(&priv->lock);

  /* Get the constituent class driver objects */

  for (i = 0; i < ndevices; i++)
    {
      FAR struct composite_devobj_s *devobj = &priv->device[i];

      devobj->compdesc = pdevices[i];

      ret =
        devobj->compdesc.classobject(devobj->compdesc.minor,
                                     &devobj->compdesc.devinfo,
                                     &devobj->dev);
      if (ret < 0)
        {
          usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_CLASSOBJECT),
                   (uint16_t)-ret);
          goto errout_with_alloc;
        }

      priv->cfgdescsize += devobj->compdesc.cfgdescsize;
      priv->ninterfaces += devobj->compdesc.devinfo.ninterfaces;
    }

  /* Update cfgdescsize based on the longest string descriptor */

#ifdef CONFIG_BOARD_USBDEV_SERIALSTR
  ret = sizeof(struct usb_strdesc_s) + strlen(board_usbdev_serialstr()) * 2;
  if (priv->cfgdescsize < ret)
    {
      priv->cfgdescsize = ret;
    }
#endif

  strdesc = devdescs->strdescs->strdesc;
  for (i = 0; strdesc[i].string != NULL; i++)
    {
      ret = sizeof(struct usb_strdesc_s) + strlen(strdesc[i].string) * 2;
      if (priv->cfgdescsize < ret)
        {
          priv->cfgdescsize = ret;
        }
    }

  priv->ndevices = ndevices;

  /* Initialize the USB class driver structure */
#if defined(CONFIG_USBDEV_SUPERSPEED)
  drvr->drvr.speed = USB_SPEED_SUPER;
#elif defined(CONFIG_USBDEV_DUALSPEED)
  drvr->drvr.speed = USB_SPEED_HIGH;
#else
  drvr->drvr.speed = USB_SPEED_FULL;
#endif
  drvr->drvr.ops   = &g_driverops;
  drvr->dev        = priv;

  /* Register the USB composite class driver */

  ret = usbdev_register(&drvr->drvr);
  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_DEVREGISTER),
               (uint16_t)-ret);
      goto errout_with_alloc;
    }

  return (FAR void *)alloc;

errout_with_alloc:
  kmm_free(alloc);
  return NULL;
}

/****************************************************************************
 * Name: composite_uninitialize
 *
 * Description:
 *   Un-initialize the USB composite driver.  The handle is the USB composite
 *   class' device object as was returned by composite_initialize().  This
 *   function will call  board-specific implementations in order to free the
 *   class objects for each of the members of the composite.
 *
 * Input Parameters:
 *   handle - The handle returned by a previous call to
 *            composite_initialize().
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void composite_uninitialize(FAR void *handle)
{
  FAR struct composite_alloc_s *alloc =
                               (FAR struct composite_alloc_s *)handle;
  FAR struct composite_dev_s *priv;
  int i;

  DEBUGASSERT(alloc != NULL);

  priv = &alloc->dev;

  /* Then unregister and destroy the composite class */

  usbdev_unregister(&alloc->drvr.drvr);

  /* Uninitialization each of the member classes and clean up
   * all memory resources
   */

  for (i = 0; i < priv->ndevices; i++)
    {
      priv->device[i].compdesc.uninitialize(priv->device[i].dev);
    }

  /* Then free the composite driver state structure itself */

  kmm_free(priv);
}

/****************************************************************************
 * Name: composite_ep0submit
 *
 * Description:
 *   Members of the composite cannot send on EP0 directly because EP0 is
 *   is "owned" by the composite device.  Instead, when configured as members
 *   of a composite device, those classes should call this method so that
 *   the composite device can send on EP0 onbehalf of the class.
 *
 ****************************************************************************/

int composite_ep0submit(FAR struct usbdevclass_driver_s *driver,
                        FAR struct usbdev_s *dev,
                        FAR struct usbdev_req_s *ctrlreq,
                        FAR const struct usb_ctrlreq_s *ctrl)
{
  bool ep0submit = true;

  /* Some EP0 responses must be send only once from the composite class */

  if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD)
    {
      if (ctrl->req == USB_REQ_SETCONFIGURATION)
        {
          ep0submit = false;
        }
    }

  if (ep0submit)
    {
      return EP_SUBMIT(dev->ep0, ctrlreq);
    }
  else
    {
      return 0;
    }
}