/****************************************************************************
 * drivers/usbhost/usbhost_composite.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 <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/usb/usbhost.h>

#include "usbhost_composite.h"

#ifdef CONFIG_USBHOST_COMPOSITE

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

/* This is the size of a large, allocated temporary buffer that we will use
 * to construct custom configuration descriptors for each member class.
 */

#define CUSTOM_CONFIG_BUFSIZE  \
  (USB_SIZEOF_CFGDESC + 3 * USB_SIZEOF_IFDESC + 9 * USB_SIZEOF_EPDESC)

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

/* This structure describes one component class of the composite */

struct usbhost_member_s
{
  /* This the classobject returned by each contained class */

  FAR struct usbhost_class_s *usbclass;

  /* This is the information that we need to do the registry lookup for this
   * class member.
   */

  struct usbhost_id_s id;

  /* This information will be needed to construct a meaningful configuration
   * for CLASS_CONNSET()
   */

  uint8_t firstif;     /* First interface */
  uint8_t nifs;        /* Number of interfaces */
};

/* This structure contains the internal, private state of the USB host
 * CDC/ACM class.
 */

struct usbhost_composite_s
{
  /* This is the externally visible portion of the state.  The usbclass must
   * the first element of the structure.  It is then cast compatible with
   * struct usbhost_composite_s.
   */

  struct usbhost_class_s usbclass;

  /* Class specific data follows */

  uint16_t nclasses;   /* Number of component classes in the composite */

  /* The following points to an allocated array of type struct
   * usbhost_member_s.  Element element of the array corresponds to one
   * component class in the composite.
   */

  FAR struct usbhost_member_s *members;
};

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

/* struct usbhost_class_s methods */

static int  usbhost_connect(FAR struct usbhost_class_s *usbclass,
              FAR const uint8_t *configdesc, int desclen);
static int  usbhost_disconnected(FAR struct usbhost_class_s *usbclass);

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

/****************************************************************************
 * Name: usbhost_disconnect_all
 *
 * Description:
 *   Disconnect all contained class instances.
 *
 * Input Parameters:
 *   priv - Reference to private, composite container state structure.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void usbhost_disconnect_all(FAR struct usbhost_composite_s *priv)
{
  FAR struct usbhost_member_s *member;
  int i;

  /* Loop, processing each class that has been included into the composite */

  for (i = 0; i < priv->nclasses; i++)
    {
      member = &priv->members[i];

      /* Has this member been included to the composite? */

      if (member->usbclass != NULL)
        {
          /* Yes.. disconnect it, freeing all of the class resources */

          CLASS_DISCONNECTED(member->usbclass);
          member->usbclass = NULL;
        }
    }
}

/****************************************************************************
 * Name: usbhost_connect
 *
 * Description:
 *   This function implements the connect() method of struct
 *   usbhost_class_s.  This method is a callback into the class
 *   implementation from the common enumeration logic.  It is normally used
 *   to provide the device's configuration descriptor to the class so that
 *   the class may initialize properly.  That calling sequence is:
 *
 *   1. usbhost_enumerate()
 *   2. usbhost_classbind()
 *   3. CLASS_CONNECT()
 *
 *   However, that applies only to the Non-composite device.
 *   usbhost_classbind() is not called for the composite device and, hence,
 *   this method is never called.  Rather, the composite logic calls
 *   CLASS_CONNECT() for each member of the composite in a calling sequence
 *   like:
 *
 *   1. usbhost_enumerate()
 *   2. usbhost_composite()
 *   3. Call CLASS_CONNECT() for each composite member
 *
 * Input Parameters:
 *   usbclass - The USB host class entry previously obtained from a call to
 *     create().
 *   configdesc - A pointer to a uint8_t buffer container the configuration
 *     descriptor.
 *   desclen - The length in bytes of the configuration descriptor.
 *
 * Returned Value:
 *   On success, zero (OK) is returned. On a failure, a negated errno value
 *   is returned indicating the nature of the failure
 *
 *   NOTE that the class instance remains valid upon return with a failure.
 *   It is the responsibility of the higher level enumeration logic to call
 *   CLASS_DISCONNECTED to free up the class driver resources.
 *
 * Assumptions:
 *   - This function will *not* be called from an interrupt handler.
 *   - If this function returns an error, the USB host controller driver
 *     must call to DISCONNECTED method to recover from the error
 *
 ****************************************************************************/

static int usbhost_connect(FAR struct usbhost_class_s *usbclass,
                           FAR const uint8_t *configdesc, int desclen)
{
  return -ENOSYS;
}

/****************************************************************************
 * Name: usbhost_disconnected
 *
 * Description:
 *   This function implements the disconnected() method of struct
 *   usbhost_class_s.  This method is a callback into the class
 *   implementation.  It is used to inform the class that the USB device has
 *   been disconnected.
 *
 * Input Parameters:
 *   usbclass - The USB host class entry previously obtained from a call to
 *     create().
 *
 * Returned Value:
 *   On success, zero (OK) is returned. On a failure, a negated errno value
 *   is returned indicating the nature of the failure
 *
 * Assumptions:
 *   This function may be called from an interrupt handler.
 *
 ****************************************************************************/

static int usbhost_disconnected(struct usbhost_class_s *usbclass)
{
  FAR struct usbhost_composite_s *priv =
    (FAR struct usbhost_composite_s *)usbclass;

  DEBUGASSERT(priv != NULL);

  /* Forward the disconnect event to each contained class in the composite. */

  usbhost_disconnect_all(priv);

  /* Free the allocate array of composite members */

  if (priv->members != NULL)
    {
      kmm_free(priv->members);
    }

  /* The destroy the composite container itself */

  kmm_free(priv);
  return OK;
}

/****************************************************************************
 * Name: usbhost_copyinterface
 *
 * Description:
 *   Find an interface descriptor and copy it along with all of its
 *   following endpoint and cs interface descriptors.
 *
 * Input Parameters:
 *   ifno       - The interface ID to find.
 *   configdesc - The original configuration descriptor that contains the
 *                the interface descriptor.
 *   desclen    - the length of configdesc.
 *   buffer     - The buffer in which to return the descriptors
 *   buflen     - The length of buffer
 *
 * Returned Value:
 *   On success, the number of bytes copied is returned. On a failure, a
 *   negated errno value is returned indicating the nature of the failure:
 *
 *   -ENOENT: Did not find interface descriptor
 *   -EINVAL: Did not find all endpoint descriptors
 *   -ENOSPC: Provided buffer too small to hold all found descriptors
 *
 ****************************************************************************/

static int usbhost_copyinterface(uint8_t ifno, FAR const uint8_t *configdesc,
                                 int desclen, FAR uint8_t *buffer,
                                 int buflen)
{
  FAR struct usb_desc_s   *desc;
  FAR struct usb_ifdesc_s *ifdesc;
  int retsize;
  int offset;
  int neps;
  int len;

  /* Make sure that the buffer will hold at least the interface descriptor */

  if (buflen < USB_SIZEOF_IFDESC)
    {
      return -ENOSPC;
    }

  /* Search for the interface */

  for (offset = 0, retsize = 0;
       offset < desclen - sizeof(struct usb_desc_s);
       offset += len)
    {
      desc = (FAR struct usb_desc_s *)&configdesc[offset];
      len  = desc->len;

      /* Is this an interface descriptor? */

      if (desc->type == USB_DESC_TYPE_INTERFACE)
        {
          ifdesc = (FAR struct usb_ifdesc_s *)&configdesc[offset];

          /* Is it the one we are looking for? */

          if (ifdesc->ifno == ifno && ifdesc->neps != 0)
            {
              /* Yes.. return the interface descriptor */

              memcpy(buffer, desc, len);
              buffer  += len;
              buflen  -= len;
              retsize += len;

              /* Make sure that the buffer will hold at least the endpoint
               * descriptors.
               */

              neps = ifdesc->neps;
              if (buflen < neps * USB_SIZEOF_EPDESC)
                {
                  return -ENOSPC;
                }

              /* The CS and endpoint descriptors should immediately
               * follow the interface descriptor.
               */

              for (offset += len;
                   offset < desclen - sizeof(struct usb_desc_s);
                   offset += len)
                {
                  desc = (FAR struct usb_desc_s *)&configdesc[offset];
                  len  = desc->len;

                  /* Is this a class-specific interface descriptor?  */

                  if (desc->type == USB_DESC_TYPE_CSINTERFACE)
                    {
                      /* Yes... return the descriptor */

                      if (buflen < len)
                        {
                          return -ENOSPC;
                        }

                      memcpy(buffer, desc, len);
                      buffer  += len;
                      buflen  -= len;
                      retsize += len;
                    }

                  /* Is this an endpoint descriptor?  */

                  else if (desc->type == USB_DESC_TYPE_ENDPOINT)
                    {
                      /* Yes.. return the endpoint descriptor */

                      if (buflen < len)
                        {
                          return -ENOSPC;
                        }

                      memcpy(buffer, desc, len);
                      buffer  += len;
                      buflen  -= len;
                      retsize += len;

                      /* And reduce the number of endpoints we are looking
                       * for.
                       */

                      if (--neps <= 0)
                        {
                          /* That is all of them!  Return the total size
                           * copied.
                           */

                          return retsize;
                        }
                    }

                  /* The endpoint descriptors following the interface
                   * descriptor should all be contiguous.  But we will
                   * complain only if another interface descriptor is
                   * encountered before all of the endpoint descriptors have
                   * been found.
                   */

                  else if (desc->type == USB_DESC_TYPE_INTERFACE)
                    {
                      break;
                    }
                }

              /* Did not find all of the interface descriptors */

              return -EINVAL;
            }
        }
    }

  /* Could not find the interface descriptor */

  return -ENOENT;
}

/****************************************************************************
 * Name: usbhost_createconfig
 *
 * Description:
 *   Create a custom configuration for a member class.
 *
 * Input Parameters:
 *   configdesc - The original configuration descriptor that contains the
 *                the interface descriptor.
 *   desclen    - the length of configdesc.
 *   buffer     - The buffer in which to return the descriptors
 *   buflen     - The length of buffer
 *
 * Returned Value:
 *   On success, the size of the new configuration descriptor is returned.
 *   On a failure, a negated errno value is returned indicating the nature
 *   of the failure:
 *
 *   -ENOENT: Did not find interface descriptor
 *   -EINVAL: Did not find all endpoint descriptors
 *
 ****************************************************************************/

static int usbhost_createconfig(FAR struct usbhost_member_s *member,
                                FAR const uint8_t *configdesc, int desclen,
                                FAR uint8_t *buffer, int buflen)
{
  FAR struct usb_cfgdesc_s *cfgdesc;
  int cfgsize;
  int ifsize;
  int ifno;
  int nifs;

  /* Copy and modify the original configuration descriptor */

  if (buflen < USB_SIZEOF_CFGDESC)
    {
      return -ENOSPC;
    }

  memcpy(buffer, configdesc, USB_SIZEOF_CFGDESC);
  cfgdesc = (FAR struct usb_cfgdesc_s *)buffer;
  cfgsize = USB_SIZEOF_CFGDESC;
  buffer += USB_SIZEOF_CFGDESC;
  buflen -= USB_SIZEOF_CFGDESC;

  /* Modify the copied configuration descriptor */

  cfgdesc->len         = USB_SIZEOF_CFGDESC;
  cfgdesc->ninterfaces = member->nifs;

  /* Then copy all of the interfaces to the configuration buffer */

  for (nifs = 0, ifno = member->firstif; nifs < member->nifs; nifs++, ifno++)
    {
      ifsize = usbhost_copyinterface(ifno, configdesc, desclen,
                                     buffer, buflen);
      if (ifsize < 0)
        {
          uerr("ERROR: Failed to copy interface: %d\n", ifsize);
          return ifsize;
        }

      /* Update sizes and pointers */

      cfgsize += ifsize;
      buffer  += ifsize;
      buflen  -= ifsize;
    }

  /* Set the totallen of the configuration descriptor and return success */

  cfgdesc->totallen[0] = cfgsize & 0xff; /* Little endian always */
  cfgdesc->totallen[1] = cfgsize >> 8;
  return cfgsize;
}

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

/****************************************************************************
 * Name: usbhost_composite
 *
 * Description:
 *   As the final steps in the device enumeration sequence this function
 *   will be called in order to determine (1) determine if the device is
 *   a composite device, and if so, (2) create the composite class which
 *   contains all of the individual class instances making up the composite.
 *
 * Input Parameters:
 *   hport      - The downstream port to which the (potential) composite
 *                device has been connected.
 *   configdesc - The full configuration descriptor
 *   desclen    - The length of the configuration descriptor
 *   id         - Lookup information extracted from the device descriptor.
 *                for the case of the composite devices, we need only the
 *                vid and pid.
 *   usbclass   - If the class driver for the device is successful located
 *                and bound to the hub port, the allocated class instance
 *                is returned into this caller-provided memory location.
 *
 * Returned Value:
 *   Zero (OK) is returned if (1) the device was determined to be a
 *   composite device and (2) the composite class wrapper was successfully
 *   created and bound to the HCD.  A negated errno value is returned on
 *   any failure.  The value -ENOENT, in particular means that the attached
 *   device is not a composite device.  Other values would indicate other
 *   various, unexpected failures.
 *
 ****************************************************************************/

int usbhost_composite(FAR struct usbhost_hubport_s *hport,
                      FAR const uint8_t *configdesc, int desclen,
                      FAR struct usbhost_id_s *id,
                      FAR struct usbhost_class_s **usbclass)
{
  FAR struct usbhost_composite_s *priv;
  FAR struct usbhost_member_s *member;
  FAR const struct usbhost_registry_s *reg;
  FAR struct usb_desc_s *desc;
  FAR uint8_t *cfgbuffer;
  uint32_t mergeset;
  uint16_t nintfs;
  uint16_t nmerged;
  uint16_t nclasses;
  int cfgsize;
  int offset;
  int ret;
  int i;

  /* Determine if this a composite device has been connected to the
   * downstream port.
   *
   * First look at there device descriptor information.  A composite
   * device is only possible if:
   *
   * 1. Manufacturers of composite devices typically assign a value of zero
   *    to the device class (bDeviceClass), subclass (bDeviceSubClass), and
   *    protocol (bDeviceProtocol) fields in the device descriptor, as
   *    specified by the Universal Serial Bus Specification. This allows
   *    the manufacturer to associate each individual interface with a
   *    different device class and protocol.
   *
   * 2. The USB-IF core team has devised a special class and protocol code
   *    set that notifies the operating system that one or more IADs are
   *    present in device firmware. A device's device descriptor must have
   *    the values that appear in the following table:
   *
   *   bDeviceClass    0xEF
   *   bDeviceSubClass 0x02
   *   bDeviceProtocol 0x01
   */

  if (id->base != USB_CLASS_PER_INTERFACE && id->base != USB_CLASS_MISC)
    {
      return -ENOENT;
    }

  /* First, count the number of interface descriptors (nintfs) and the
   * number of interfaces that are associated to one device via IAD
   * descriptor (nmerged).
   */

  mergeset = 0;
  nintfs   = 0;
  nmerged  = 0;

  for (offset = 0; offset < desclen - sizeof(struct usb_desc_s); )
    {
      desc = (FAR struct usb_desc_s *)&configdesc[offset];
      int len = desc->len;

      if (offset + len <= desclen)
        {
          /* Is this an interface descriptor? */

          if (desc->type == USB_DESC_TYPE_INTERFACE)
            {
#ifdef CONFIG_DEBUG_ASSERTIONS
              FAR struct usb_ifdesc_s *ifdesc =
                (FAR struct usb_ifdesc_s *)desc;

              DEBUGASSERT(ifdesc->ifno < 32);
#endif
              /* Increment the count of interfaces */

              nintfs++;
            }

          /* Check for IAD descriptors that will be used when it is
           * necessary to associate multiple interfaces with a single
           * class driver.
           */

          else if (desc->type == USB_DESC_TYPE_INTERFACEASSOCIATION)
            {
              FAR struct usb_iaddesc_s *iad =
                (FAR struct usb_iaddesc_s *)desc;
              uint32_t mask;

              /* Keep count of the number of interfaces that will be merged */

              nmerged += (iad->nifs - 1);

              /* Keep track of which interfaces will be merged */

              DEBUGASSERT(iad->firstif + iad->nifs < 32);
              mask      = (1 << iad->nifs) - 1;
              mergeset |= mask << iad->firstif;
            }
        }

      offset += len;
    }

  if (nintfs < 2)
    {
      /* Only one interface descriptor.  Can't be a composite device */

      return -ENOENT;
    }

#if 0 /* I think not needed, the device descriptor classid check should handle this */
  /* Special case:  Some NON-composite device have more than on interface:  CDC/ACM
   * and MSC both may have two interfaces.
   */

  if (nintfs < 3 && nmerged == 0)
    {
      /* Do the special case checks */
#warning Missing logic
    }
#endif

  /* The total number of classes is then the number of interfaces minus the
   * number of interfaces merged via the IAD descriptor.
   */

  if (nintfs <= nmerged)
    {
      /* Should not happen.  Means a bug. */

      return -EINVAL;
    }

  nclasses = nintfs - nmerged;

  /* Allocate the composite class container */

  priv = (FAR struct usbhost_composite_s *)
    kmm_zalloc(sizeof(struct usbhost_composite_s));

  if (priv == NULL)
    {
      uerr("ERROR: Failed to allocate class container\n");
      return -ENOMEM;
    }

  priv->members = (FAR struct usbhost_member_s *)
    kmm_zalloc(nclasses * sizeof(struct usbhost_member_s));

  if (priv->members == NULL)
    {
      uerr("ERROR: Failed to allocate class members\n");
      ret = -ENOMEM;
      goto errout_with_container;
    }

  /* Initialize the non-zero elements of the class container */

  priv->usbclass.hport        = hport;
  priv->usbclass.connect      = usbhost_connect;
  priv->usbclass.disconnected = usbhost_disconnected;
  priv->nclasses              = nclasses;

  /* Re-parse the configuration descriptor and save the CLASS ID information
   * in the member structure:  If the interface is defined by an interface
   * descriptor, then we have to use the info in the interface descriptor;
   * If the interface has a IAD, we have to use info in the IAD.
   */

  for (i = 0, offset = 0; offset < desclen - sizeof(struct usb_desc_s); )
    {
      desc = (FAR struct usb_desc_s *)&configdesc[offset];
      int len = desc->len;

      if (offset + len <= desclen)
        {
          /* Is this an interface descriptor? */

          if (desc->type == USB_DESC_TYPE_INTERFACE)
            {
              FAR struct usb_ifdesc_s *ifdesc =
                (FAR struct usb_ifdesc_s *)desc;

              /* Was the interface merged via an IAD descriptor? */

              DEBUGASSERT(ifdesc->ifno < 32);
              if ((mergeset & (1 << ifdesc->ifno)) == 0)
                {
                  /* No, this interface was not merged.  Save the registry
                   * lookup information from the interface descriptor.
                   */

                  member              = (FAR struct usbhost_member_s *)
                                         &priv->members[i];
                  member->id.base     = ifdesc->classid;
                  member->id.subclass = ifdesc->subclass;
                  member->id.proto    = ifdesc->protocol;
                  member->id.vid      = id->vid;
                  member->id.pid      = id->pid;

                  member->firstif     = ifdesc->ifno;
                  member->nifs        = 1;

                  /* Increment the member index */

                  i++;
                }
            }

          /* Check for IAD descriptors that will be used when it is
           * necessary to associate multiple interfaces with a single
           * device.
           */

          else if (desc->type == USB_DESC_TYPE_INTERFACEASSOCIATION)
            {
              FAR struct usb_iaddesc_s *iad =
                (FAR struct usb_iaddesc_s *)desc;

              /* Yes.. Save the registry lookup information from the IAD. */

              member              = (FAR struct usbhost_member_s *)
                                    &priv->members[i];
              member->id.base     = iad->classid;
              member->id.subclass = iad->subclass;
              member->id.proto    = iad->protocol;
              member->id.vid      = id->vid;
              member->id.pid      = id->pid;

              member->firstif     = iad->firstif;
              member->nifs        = iad->nifs;

              /* Increment the member index */

              i++;
            }
        }

      offset += len;
    }

  /* If everything worked, the final index must be the same as the pre-
   * calculated number of member classes.
   */

  DEBUGASSERT(i == nclasses);

  /* Allocate a temporary buffer in which we can construct a custom
   * configuration descriptor for each member class.
   */

  cfgbuffer = (FAR uint8_t *)kmm_malloc(CUSTOM_CONFIG_BUFSIZE);
  if (cfgbuffer == NULL)
    {
      uerr("ERROR: Failed to allocate configuration buffer");
      ret = -ENOMEM;
      goto errout_with_members;
    }

  /* Now loop, performing the registry lookup and initialization of each
   * member class in the composite.
   */

  for (i = 0; i < nclasses; i++)
    {
       member = &priv->members[i];

      /* Is there is a class implementation registered to support this
       * device.
       */

      reg = usbhost_findclass(&member->id);
      if (reg == NULL)
        {
          uerr("ERROR: usbhost_findclass failed\n");
#ifdef CONFIG_USBHOST_COMPOSITE_STRICT
          ret = -EINVAL;
          goto errout_with_cfgbuffer;
#else
          continue;
#endif
        }

      /* Yes.. there is a class for this device.  Get an instance of its
       * interface.
       */

      member->usbclass = CLASS_CREATE(reg, hport, id);
      if (member->usbclass == NULL)
        {
          uerr("ERROR: CLASS_CREATE failed\n");
          ret = -ENOMEM;
          goto errout_with_cfgbuffer;
        }

      /* Construct a custom configuration descriptor for this member */

      cfgsize = usbhost_createconfig(member, configdesc, desclen,
                                     cfgbuffer, CUSTOM_CONFIG_BUFSIZE);
      if (cfgsize < 0)
        {
          uerr("ERROR: Failed to create the custom configuration: %d\n",
               cfgsize);
          ret = cfgsize;
          goto errout_with_cfgbuffer;
        }

      /* Call the newly instantiated classes connect() method provide it
       * with the configuration information that it needs to initialize
       * properly.
       */

      ret = CLASS_CONNECT(member->usbclass, cfgbuffer, cfgsize);
      if (ret < 0)
        {
          /* On failure, call the class disconnect method of each contained
           * class which should then free the allocated usbclass instance.
           */

          uerr("ERROR: CLASS_CONNECT failed: %d\n", ret);
          goto errout_with_cfgbuffer;
        }
    }

  /* Free the temporary buffer */

  kmm_free(cfgbuffer);

  /* Return our USB class structure */

  *usbclass = &priv->usbclass;
  return OK;

errout_with_cfgbuffer:
  kmm_free(cfgbuffer);

errout_with_members:
  /* On an failure, call the class disconnect method of each contained
   * class which should then free the allocated usbclass instance.
   */

  usbhost_disconnect_all(priv);

  /* Free the allocate array of composite members */

  if (priv->members != NULL)
    {
      kmm_free(priv->members);
    }

errout_with_container:

  /* Then free the composite container itself */

  kmm_free(priv);
  return ret;
}

#endif /* CONFIG_USBHOST_COMPOSITE */
