| /**************************************************************************** |
| * 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; |
| } |
| } |