| /**************************************************************************** |
| * drivers/usbhost/usbhost_hub.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 <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/clock.h> |
| |
| #include <nuttx/usb/usb.h> |
| #include <nuttx/usb/usbhost.h> |
| #include <nuttx/usb/hub.h> |
| #include <nuttx/usb/usbhost_devaddr.h> |
| |
| #ifdef CONFIG_USBHOST_HUB |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration ************************************************************/ |
| |
| /* It is necessary to perform work on the low-priority work queue (vs. the |
| * high priority work queue) because: |
| * |
| * 1. Deferred work requires some delays and waiting, and |
| * 2. There may be dependencies between the waiting and driver interrupt |
| * related work. Since that interrupt related work will performed on the |
| * high priority work queue, there would be the likelihood of deadlocks |
| * if you wait for events on the high priority work thread that can only |
| * occur if the high priority work thread is available to post those |
| * events. |
| */ |
| |
| #if !defined(CONFIG_SCHED_WORKQUEUE) |
| # error Work queue support is required (CONFIG_SCHED_WORKQUEUE) |
| #elif !defined(CONFIG_SCHED_LPWORK) |
| # error Low-priority work queue support is required (CONFIG_SCHED_LPWORK) |
| #endif |
| |
| #ifndef CONFIG_USBHOST_ASYNCH |
| # error Asynchronous transfer support is required (CONFIG_USBHOST_ASYNCH) |
| #endif |
| |
| #ifndef CONFIG_USBHOST_HUB_POLLMSEC |
| # define CONFIG_USBHOST_HUB_POLLMSEC 400 |
| #endif |
| |
| /* Perform polling actions with a delay on the low priority work queue, if |
| * configured |
| */ |
| |
| #define POLL_DELAY MSEC2TICK(CONFIG_USBHOST_HUB_POLLMSEC) |
| |
| /* Used in usbhost_cfgdesc() */ |
| |
| #define USBHOST_IFFOUND 0x01 /* Required I/F descriptor found */ |
| #define USBHOST_EPINFOUND 0x02 /* Required interrupt IN EP descriptor found */ |
| #define USBHOST_ALLFOUND (USBHOST_IFFOUND | USBHOST_EPINFOUND) |
| |
| /* Maximum size of an interrupt IN transfer */ |
| |
| #define INTIN_BUFSIZE ((USBHUB_MAX_PORTS + 8) >> 3) |
| |
| /* Convert 0-based index to port number. */ |
| |
| #define PORT_NO(x) ((x) + 1) |
| |
| /* Convert port number to 0-based index. */ |
| |
| #define PORT_INDX(x) ((x) - 1) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* This structure contains the internal, private state of the USB host |
| * hub class. |
| */ |
| |
| struct usbhost_hubpriv_s |
| { |
| FAR struct usb_ctrlreq_s *ctrlreq; /* Allocated control request */ |
| FAR uint8_t *buffer; /* Allocated buffer */ |
| |
| uint8_t ifno; /* Interface number */ |
| uint8_t nports; /* Number of ports */ |
| uint8_t lpsm; /* Logical power switching mode */ |
| uint8_t ocmode; /* Over current protection mode */ |
| uint8_t ctrlcurrent; /* Control current */ |
| volatile bool disconnected; /* TRUE: Device has been disconnected */ |
| bool compounddev; /* Hub is part of compound device */ |
| bool indicator; /* Port indicator */ |
| uint16_t pwrondelay; /* Power on wait time in ms */ |
| struct work_s work; /* Used for deferred callback work */ |
| usbhost_ep_t intin; /* Interrupt IN endpoint */ |
| |
| /* Hub port descriptors */ |
| |
| struct usbhost_hubport_s hport[USBHUB_MAX_PORTS]; |
| }; |
| |
| /* This represents the hub class structure. It must be cast compatible |
| * with struct usbhost_class_s. |
| */ |
| |
| struct usbhost_hubclass_s |
| { |
| struct usbhost_class_s hubclass; /* Publicly visible class data */ |
| struct usbhost_hubpriv_s hubpriv; /* Private class data */ |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Helpers for usbhost_connect() */ |
| |
| static inline int usbhost_cfgdesc(FAR struct usbhost_class_s *hubclass, |
| FAR const uint8_t *configdesc, int desclen); |
| static inline int usbhost_hubdesc(FAR struct usbhost_class_s *hubclass); |
| static inline int usbhost_hubpwr(FAR struct usbhost_hubpriv_s *priv, |
| FAR struct usbhost_hubport_s *hport, |
| bool on); |
| static void usbhost_hub_event(FAR void *arg); |
| static void usbhost_disconnect_event(FAR void *arg); |
| |
| /* (Little Endian) Data helpers */ |
| |
| static inline uint16_t usbhost_getle16(const uint8_t *val); |
| static void usbhost_putle16(uint8_t *dest, uint16_t val); |
| static void usbhost_callback(FAR void *arg, ssize_t nbytes); |
| |
| /* struct usbhost_registry_s methods */ |
| |
| static FAR struct usbhost_class_s *usbhost_create( |
| FAR struct usbhost_hubport_s *hport, |
| FAR const struct usbhost_id_s *id); |
| |
| /* struct usbhost_class_s methods */ |
| |
| static int usbhost_connect(FAR struct usbhost_class_s *hubclass, |
| FAR const uint8_t *configdesc, int desclen); |
| static int usbhost_disconnected(FAR struct usbhost_class_s *hubclass); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* This structure provides the registry entry ID information that will be |
| * used to associate the USB host hub class to a connected USB hub. |
| */ |
| |
| static const struct usbhost_id_s g_id[2] = |
| { |
| { |
| USB_CLASS_HUB, /* base */ |
| 0, /* subclass */ |
| 0, /* proto FS hub */ |
| 0, /* vid */ |
| 0 /* pid */ |
| }, |
| { |
| USB_CLASS_HUB, /* base */ |
| 0, /* subclass */ |
| 1, /* proto HS hub */ |
| 0, /* vid */ |
| 0 /* pid */ |
| } |
| }; |
| |
| /* This is the USB host hub class's registry entry */ |
| |
| static struct usbhost_registry_s g_hub = |
| { |
| NULL, /* flink */ |
| usbhost_create, /* create */ |
| 2, /* nids */ |
| g_id /* id[] */ |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_hport_deactivate |
| * |
| * Description: |
| * Free a hub resource previously allocated by usbhost_hport_activate(). |
| * |
| * Input Parameters: |
| * hport - A reference to the hub port instance to be freed. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void usbhost_hport_deactivate(FAR struct usbhost_hubport_s *hport) |
| { |
| uinfo("Deactivating: %s port %d\n", |
| ROOTHUB(hport) ? "Root" : "Hub", PORT_NO(hport->port)); |
| |
| /* Don't free the control pipe of root hub ports! */ |
| |
| if (!ROOTHUB(hport) && hport->ep0 != NULL) |
| { |
| /* Free the control endpoint */ |
| |
| DRVR_EPFREE(hport->drvr, hport->ep0); |
| hport->ep0 = NULL; |
| } |
| |
| /* Free the function address if one has been assigned */ |
| |
| usbhost_devaddr_destroy(hport, hport->funcaddr); |
| hport->funcaddr = 0; |
| |
| /* If this is a downstream hub port, then there should be no class driver |
| * associated with it. |
| */ |
| |
| DEBUGASSERT(ROOTHUB(hport) || hport->devclass == NULL); |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_hport_activate |
| * |
| * Description: |
| * Activate a hub port by assigning it a control endpoint. This actions |
| * only occur when a device is connected to the hub endpoint. |
| * |
| * Input Parameters: |
| * hport - The hub port to be activated. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; a negated errno value is returned |
| * on any failure. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_hport_activate(FAR struct usbhost_hubport_s *hport) |
| { |
| struct usbhost_epdesc_s epdesc; |
| int ret; |
| |
| uinfo("Activating port %d\n", PORT_NO(hport->port)); |
| |
| epdesc.hport = hport; |
| epdesc.addr = 0; |
| epdesc.in = false; |
| epdesc.xfrtype = USB_EP_ATTR_XFER_CONTROL; |
| epdesc.interval = 0; |
| epdesc.mxpacketsize = (hport->speed == USB_SPEED_HIGH) ? 64 : 8; |
| |
| ret = DRVR_EPALLOC(hport->drvr, &epdesc, &hport->ep0); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to allocate ep0: %d\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_cfgdesc |
| * |
| * Description: |
| * This function implements the connect() method of struct |
| * usbhost_class_s. This method is a callback into the class |
| * implementation. It is used to provide the device's configuration |
| * descriptor to the class so that the class may initialize properly |
| * |
| * Input Parameters: |
| * hubclass - The USB host class instance. |
| * configdesc - A pointer to a uint8_t buffer container the configuration |
| * descriptor. |
| * desclen - The length in bytes of the configuration descriptor. |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On a failure, a negated errno value |
| * is returned indicating the nature of the failure |
| * |
| * Assumptions: |
| * This function will *not* be called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static inline int usbhost_cfgdesc(FAR struct usbhost_class_s *hubclass, |
| FAR const uint8_t *configdesc, int desclen) |
| { |
| FAR struct usbhost_hubpriv_s *priv; |
| FAR struct usbhost_hubport_s *hport; |
| FAR struct usb_cfgdesc_s *cfgdesc; |
| FAR struct usb_desc_s *desc; |
| FAR struct usbhost_epdesc_s intindesc; |
| int remaining; |
| uint8_t found = 0; |
| int ret; |
| |
| DEBUGASSERT(hubclass != NULL); |
| priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv; |
| |
| DEBUGASSERT(hubclass->hport); |
| hport = hubclass->hport; |
| |
| DEBUGASSERT(configdesc != NULL && desclen >= sizeof(struct usb_cfgdesc_s)); |
| |
| /* Initialize the interrupt IN endpoint information (only to prevent |
| * compiler complaints) |
| */ |
| |
| intindesc.hport = hport; |
| intindesc.addr = 0; |
| intindesc.in = true; |
| intindesc.xfrtype = USB_EP_ATTR_XFER_INT; |
| intindesc.interval = 0; |
| intindesc.mxpacketsize = 0; |
| |
| /* Verify that we were passed a configuration descriptor */ |
| |
| cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc; |
| if (cfgdesc->type != USB_DESC_TYPE_CONFIG) |
| { |
| return -EINVAL; |
| } |
| |
| /* Get the total length of the configuration descriptor (little endian). |
| * It might be a good check to get the number of interfaces here too. |
| */ |
| |
| remaining = (int)usbhost_getle16(cfgdesc->totallen); |
| |
| /* Skip to the next entry descriptor */ |
| |
| configdesc += cfgdesc->len; |
| remaining -= cfgdesc->len; |
| |
| /* Loop where there are more descriptors to examine */ |
| |
| while (remaining >= sizeof(struct usb_desc_s)) |
| { |
| /* What is the next descriptor? */ |
| |
| desc = (FAR struct usb_desc_s *)configdesc; |
| switch (desc->type) |
| { |
| /* Interface descriptor. We really should get the number of endpoints |
| * from this descriptor too. |
| */ |
| |
| case USB_DESC_TYPE_INTERFACE: |
| { |
| FAR struct usb_ifdesc_s *ifdesc = |
| (FAR struct usb_ifdesc_s *)configdesc; |
| |
| uinfo("Interface descriptor\n"); |
| DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC); |
| |
| /* Save the interface number and mark ONLY the interface found */ |
| |
| priv->ifno = ifdesc->ifno; |
| found = USBHOST_IFFOUND; |
| } |
| break; |
| |
| /* Endpoint descriptor. Here, we expect one interrupt IN endpoints. */ |
| |
| case USB_DESC_TYPE_ENDPOINT: |
| { |
| FAR struct usb_epdesc_s *epdesc = |
| (FAR struct usb_epdesc_s *)configdesc; |
| |
| uinfo("Endpoint descriptor\n"); |
| DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC); |
| |
| /* Check for an interrupt endpoint. */ |
| |
| if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == |
| USB_EP_ATTR_XFER_INT) |
| { |
| /* Yes.. it is a interrupt endpoint. IN or OUT? */ |
| |
| if (USB_ISEPOUT(epdesc->addr)) |
| { |
| /* It is an OUT interrupt endpoint. Ignore */ |
| |
| uinfo("Interrupt OUT EP addr:%d mxpacketsize:%d\n", |
| (epdesc->addr & USB_EP_ADDR_NUMBER_MASK), |
| usbhost_getle16(epdesc->mxpacketsize)); |
| } |
| else |
| { |
| /* It is an IN interrupt endpoint. */ |
| |
| found |= USBHOST_EPINFOUND; |
| |
| /* Save the interrupt IN endpoint information */ |
| |
| intindesc.addr = epdesc->addr & |
| USB_EP_ADDR_NUMBER_MASK; |
| intindesc.interval = epdesc->interval; |
| intindesc.mxpacketsize = usbhost_getle16( |
| epdesc->mxpacketsize); |
| |
| uinfo("Interrupt IN EP:"); |
| uinfo(" addr=%d interval=%d mxpacketsize=%d\n", |
| intindesc.addr, intindesc.interval, |
| intindesc.mxpacketsize); |
| } |
| } |
| } |
| break; |
| |
| /* Other descriptors are just ignored for now */ |
| |
| default: |
| break; |
| } |
| |
| /* If we found everything we need with this interface, then break out |
| * of the loop early. |
| */ |
| |
| if (found == USBHOST_ALLFOUND) |
| { |
| break; |
| } |
| |
| /* Increment the address of the next descriptor */ |
| |
| configdesc += desc->len; |
| remaining -= desc->len; |
| } |
| |
| /* Sanity checking... did we find all of things that we need? */ |
| |
| if (found != USBHOST_ALLFOUND) |
| { |
| uerr("ERROR: Found IF=%s EPIN=%s\n", |
| (found & USBHOST_IFFOUND) != 0 ? "YES" : "NO", |
| (found & USBHOST_EPINFOUND) != 0 ? "YES" : "NO"); |
| return -EINVAL; |
| } |
| |
| /* We are good... Allocate the interrupt IN endpoint */ |
| |
| ret = DRVR_EPALLOC(hport->drvr, &intindesc, &priv->intin); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to allocate Interrupt IN endpoint: %d\n", ret); |
| DRVR_EPFREE(hport->drvr, priv->intin); |
| return ret; |
| } |
| |
| uinfo("Endpoint allocated\n"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_hubdesc |
| * |
| * Description: |
| * This function implements the connect() method of struct |
| * usbhost_class_s. This method is a callback into the class |
| * implementation. It is used to provide the device's configuration |
| * descriptor to the class so that the class may initialize properly |
| * |
| * Input Parameters: |
| * hubclass - The USB host class instance. |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On a failure, a negated errno value |
| * is returned indicating the nature of the failure |
| * |
| * Assumptions: |
| * This function will *not* be called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static inline int usbhost_hubdesc(FAR struct usbhost_class_s *hubclass) |
| { |
| FAR struct usbhost_hubpriv_s *priv; |
| FAR struct usbhost_hubport_s *hport; |
| FAR struct usb_ctrlreq_s *ctrlreq; |
| FAR struct usb_hubdesc_s *hubdesc; |
| uint16_t hubchar; |
| int ret; |
| size_t maxlen; |
| |
| uinfo("Read hub descriptor\n"); |
| |
| DEBUGASSERT(hubclass != NULL); |
| priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv; |
| |
| DEBUGASSERT(hubclass->hport); |
| hport = hubclass->hport; |
| |
| /* Get the hub descriptor */ |
| |
| ctrlreq = priv->ctrlreq; |
| DEBUGASSERT(ctrlreq); |
| |
| ctrlreq->type = USB_REQ_DIR_IN | USBHUB_REQ_TYPE_HUB; |
| ctrlreq->req = USBHUB_REQ_GETDESCRIPTOR; |
| usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_HUB << 8)); |
| usbhost_putle16(ctrlreq->index, 0); |
| usbhost_putle16(ctrlreq->len, USB_SIZEOF_HUBDESC); |
| |
| ret = DRVR_ALLOC(hport->drvr, (FAR uint8_t **)&hubdesc, &maxlen); |
| if (ret < 0) |
| { |
| uerr("ERROR: DRVR_ALLOC failed: %d\n", ret); |
| return ret; |
| } |
| |
| ret = DRVR_CTRLIN(hport->drvr, hport->ep0, |
| ctrlreq, (FAR uint8_t *)hubdesc); |
| if (ret < 0) |
| { |
| DRVR_FREE(hport->drvr, (FAR uint8_t *)hubdesc); |
| uerr("ERROR: Failed to read hub descriptor: %d\n", ret); |
| return ret; |
| } |
| |
| priv->nports = hubdesc->nports; |
| |
| hubchar = usbhost_getle16(hubdesc->characteristics); |
| priv->lpsm = (hubchar & USBHUB_CHAR_LPSM_MASK) >> |
| USBHUB_CHAR_LPSM_SHIFT; |
| priv->compounddev = (hubchar & USBHUB_CHAR_COMPOUND) ? true : false; |
| priv->ocmode = (hubchar & USBHUB_CHAR_OCPM_MASK) >> |
| USBHUB_CHAR_OCPM_SHIFT; |
| priv->indicator = (hubchar & USBHUB_CHAR_PORTIND) ? true : false; |
| |
| priv->pwrondelay = (2 * hubdesc->pwrondelay); |
| priv->ctrlcurrent = hubdesc->ctrlcurrent; |
| |
| uinfo("Hub Descriptor:\n"); |
| uinfo(" bDescLength: %d\n", hubdesc->len); |
| uinfo(" bDescriptorType: 0x%02x\n", hubdesc->type); |
| uinfo(" bNbrPorts: %d\n", hubdesc->nports); |
| uinfo(" wHubCharacteristics: 0x%04x\n", |
| usbhost_getle16(hubdesc->characteristics)); |
| uinfo(" lpsm: %d\n", priv->lpsm); |
| uinfo(" compounddev: %s\n", priv->compounddev ? "TRUE" : "FALSE"); |
| uinfo(" ocmode: %d\n", priv->ocmode); |
| uinfo(" indicator: %s\n", priv->indicator ? "TRUE" : "FALSE"); |
| uinfo(" bPwrOn2PwrGood: %d\n", hubdesc->pwrondelay); |
| uinfo(" pwrondelay: %d\n", priv->pwrondelay); |
| uinfo(" bHubContrCurrent: %d\n", hubdesc->ctrlcurrent); |
| uinfo(" DeviceRemovable: %d\n", hubdesc->devattached); |
| uinfo(" PortPwrCtrlMask: %d\n", hubdesc->pwrctrlmask); |
| |
| DRVR_FREE(hport->drvr, (FAR uint8_t *)hubdesc); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_hubpwr |
| * |
| * Description: |
| * Self-powered hubs may have power switches that control delivery of |
| * power downstream facing ports but it is not required. Bus-powered hubs |
| * are required to have power switches. A hub with power switches can |
| * switch power to all ports as a group/gang, to each port individually, or |
| * have an arbitrary number of gangs of one or more ports. |
| * |
| * A hub indicates whether or not it supports power switching by the |
| * setting of the Logical Power Switching Mode field in |
| * wHubCharacteristics. |
| * If a hub supports per-port power switching, then the power to a port is |
| * turned on when a SetPortFeature(PORT_POWER) request is received for the |
| * port. Port power is turned off when the port is in the Powered-off or |
| * Not Configured states. If a hub supports ganged power switching, then |
| * the power to all ports in a gang is turned on when any port in a gang |
| * receives a SetPortFeature(PORT_POWER) request. The power to a gang is |
| * not turned off unless all ports in a gang are in the Powered-off or Not |
| * Configured states. Note, the power to a port is not turned on by a |
| * SetPortFeature(PORT_POWER) if both C_HUB_LOCAL_POWER and Local Power |
| * Status (in wHubStatus) are set to 1B at the time when the request is |
| * executed and the PORT_POWER feature would be turned on. |
| * |
| * Input Parameters: |
| * priv - The USB hub private data |
| * hport - The port on the parent hub where the this hub is connected. |
| * on - True: enable power; false: Disable power |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On a failure, a negated errno value |
| * is returned indicating the nature of the failure |
| * |
| * Assumptions: |
| * This function will *not* be called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_hubpwr(FAR struct usbhost_hubpriv_s *priv, |
| FAR struct usbhost_hubport_s *hport, |
| bool on) |
| { |
| FAR struct usb_ctrlreq_s *ctrlreq; |
| uint16_t req; |
| int port; |
| int ret; |
| |
| /* Are we enabling or disabling power? */ |
| |
| if (on) |
| { |
| req = USBHUB_REQ_SETFEATURE; |
| } |
| else |
| { |
| req = USBHUB_REQ_CLEARFEATURE; |
| } |
| |
| /* Enable/disable power to all downstream ports */ |
| |
| ctrlreq = priv->ctrlreq; |
| DEBUGASSERT(ctrlreq); |
| |
| for (port = 1; port <= priv->nports; port++) |
| { |
| ctrlreq->type = USBHUB_REQ_TYPE_PORT; |
| ctrlreq->req = req; |
| usbhost_putle16(ctrlreq->value, USBHUB_PORT_FEAT_POWER); |
| usbhost_putle16(ctrlreq->index, port); |
| usbhost_putle16(ctrlreq->len, 0); |
| |
| ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, NULL); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to power %s port %d: %d\n", |
| on ? "UP" : "DOWN", port, ret); |
| return ret; |
| } |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_hub_event |
| * |
| * Description: |
| * Handle a hub event. |
| * |
| * Input Parameters: |
| * xfer - The USB host class instance. |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On a failure, a negated errno value |
| * is returned indicating the nature of the failure |
| * |
| * Assumptions: |
| * This function will *not* be called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static void usbhost_hub_event(FAR void *arg) |
| { |
| FAR struct usbhost_class_s *hubclass; |
| FAR struct usbhost_hubport_s *hport; |
| FAR struct usbhost_hubport_s *connport; |
| FAR struct usbhost_hubpriv_s *priv; |
| FAR struct usb_ctrlreq_s *ctrlreq; |
| FAR struct usb_portstatus_s *portstatus; |
| irqstate_t flags; |
| uint16_t status; |
| uint16_t change; |
| uint16_t mask; |
| uint16_t feat; |
| uint8_t statuschange; |
| int port; |
| int ret; |
| size_t maxlen; |
| |
| DEBUGASSERT(arg != NULL); |
| hubclass = (FAR struct usbhost_class_s *)arg; |
| priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv; |
| |
| /* Has the hub been disconnected? */ |
| |
| if (priv->disconnected) |
| { |
| uinfo("Disconnected\n"); |
| return; |
| } |
| |
| /* No.. then set up to process the hub event */ |
| |
| DEBUGASSERT(priv->ctrlreq); |
| ctrlreq = priv->ctrlreq; |
| |
| DEBUGASSERT(hubclass->hport); |
| hport = hubclass->hport; |
| |
| statuschange = priv->buffer[0]; |
| uinfo("StatusChange: %02x\n", statuschange); |
| |
| ret = DRVR_ALLOC(hport->drvr, (FAR uint8_t **)&portstatus, &maxlen); |
| if (ret < 0) |
| { |
| uerr("ERROR: DRVR_ALLOC failed: %d\n", ret); |
| return; |
| } |
| |
| /* Check for status change on any port */ |
| |
| for (port = 1; port <= priv->nports; port++) |
| { |
| /* Check if port status has changed */ |
| |
| if ((statuschange & (1 << port)) == 0) |
| { |
| continue; |
| } |
| |
| uinfo("Port %d status change\n", port); |
| |
| /* Port status changed, check what happened */ |
| |
| statuschange &= ~(1 << port); |
| |
| /* Read hub port status */ |
| |
| ctrlreq->type = USB_REQ_DIR_IN | USBHUB_REQ_TYPE_PORT; |
| ctrlreq->req = USBHUB_REQ_GETSTATUS; |
| usbhost_putle16(ctrlreq->value, 0); |
| usbhost_putle16(ctrlreq->index, port); |
| usbhost_putle16(ctrlreq->len, USB_SIZEOF_PORTSTS); |
| |
| ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, |
| (FAR uint8_t *)portstatus); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to read port %d status: %d\n", port, ret); |
| continue; |
| } |
| |
| status = usbhost_getle16(portstatus->status); |
| change = usbhost_getle16(portstatus->change); |
| |
| /* First, clear all change bits */ |
| |
| mask = 1; |
| feat = USBHUB_PORT_FEAT_CCONNECTION; |
| while (change) |
| { |
| if (change & mask) |
| { |
| ctrlreq->type = USBHUB_REQ_TYPE_PORT; |
| ctrlreq->req = USBHUB_REQ_CLEARFEATURE; |
| usbhost_putle16(ctrlreq->value, feat); |
| usbhost_putle16(ctrlreq->index, port); |
| usbhost_putle16(ctrlreq->len, 0); |
| |
| ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, NULL); |
| if (ret < 0) |
| { |
| uerr("ERROR:"); |
| uerr(" Failed to clear port %d change mask %04x: %d\n", |
| port, mask, ret); |
| } |
| |
| change &= (~mask); |
| } |
| |
| mask <<= 1; |
| feat++; |
| } |
| |
| change = usbhost_getle16(portstatus->change); |
| |
| /* Handle connect or disconnect, no power management */ |
| |
| if ((change & USBHUB_PORT_STAT_CCONNECTION) != 0) |
| { |
| uint16_t debouncetime = 0; |
| uint16_t debouncestable = 0; |
| uint16_t connection = 0xffff; |
| |
| uinfo("Port %d status %04x change %04x\n", port, status, change); |
| |
| /* Debounce */ |
| |
| while (debouncetime < 1500) |
| { |
| ctrlreq->type = USB_REQ_DIR_IN | USBHUB_REQ_TYPE_PORT; |
| ctrlreq->req = USBHUB_REQ_GETSTATUS; |
| usbhost_putle16(ctrlreq->value, 0); |
| usbhost_putle16(ctrlreq->index, port); |
| usbhost_putle16(ctrlreq->len, USB_SIZEOF_PORTSTS); |
| |
| ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, |
| (FAR uint8_t *)portstatus); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to get port %d status: %d\n", |
| port, ret); |
| break; |
| } |
| |
| status = usbhost_getle16(portstatus->status); |
| change = usbhost_getle16(portstatus->change); |
| |
| if ((change & USBHUB_PORT_STAT_CCONNECTION) == 0 && |
| (status & USBHUB_PORT_STAT_CONNECTION) == connection) |
| { |
| debouncestable += 25; |
| if (debouncestable >= 100) |
| { |
| uinfo("Port %d debouncestable=%d\n", |
| port, debouncestable); |
| break; |
| } |
| } |
| else |
| { |
| debouncestable = 0; |
| connection = status & USBHUB_PORT_STAT_CONNECTION; |
| } |
| |
| if ((change & USBHUB_PORT_STAT_CCONNECTION) != 0) |
| { |
| ctrlreq->type = USBHUB_REQ_TYPE_PORT; |
| ctrlreq->req = USBHUB_REQ_CLEARFEATURE; |
| usbhost_putle16(ctrlreq->value, |
| USBHUB_PORT_FEAT_CCONNECTION); |
| usbhost_putle16(ctrlreq->index, port); |
| usbhost_putle16(ctrlreq->len, 0); |
| |
| DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, NULL); |
| } |
| |
| debouncetime += 25; |
| nxsig_usleep(25 * 1000); |
| } |
| |
| if (ret < 0 || debouncetime >= 1500) |
| { |
| uerr("ERROR: Failed to debounce port %d: %d\n", port, ret); |
| continue; |
| } |
| |
| if ((status & USBHUB_PORT_STAT_CONNECTION) != 0) |
| { |
| /* Device connected to a port on the hub */ |
| |
| uinfo("Connection on port %d\n", port); |
| |
| ctrlreq->type = USBHUB_REQ_TYPE_PORT; |
| ctrlreq->req = USBHUB_REQ_SETFEATURE; |
| usbhost_putle16(ctrlreq->value, USBHUB_PORT_FEAT_RESET); |
| usbhost_putle16(ctrlreq->index, port); |
| usbhost_putle16(ctrlreq->len, 0); |
| |
| ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, NULL); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to reset port %d: %d\n", port, ret); |
| continue; |
| } |
| |
| nxsig_usleep(100 * 1000); |
| |
| ctrlreq->type = USB_REQ_DIR_IN | USBHUB_REQ_TYPE_PORT; |
| ctrlreq->req = USBHUB_REQ_GETSTATUS; |
| usbhost_putle16(ctrlreq->value, 0); |
| usbhost_putle16(ctrlreq->index, port); |
| usbhost_putle16(ctrlreq->len, USB_SIZEOF_PORTSTS); |
| |
| ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, |
| (FAR uint8_t *)portstatus); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to get port %d status: %d\n", |
| port, ret); |
| continue; |
| } |
| |
| status = usbhost_getle16(portstatus->status); |
| change = usbhost_getle16(portstatus->change); |
| |
| uinfo("port %d status %04x change %04x after reset\n", |
| port, status, change); |
| |
| if ((status & USBHUB_PORT_STAT_RESET) == 0 && |
| (status & USBHUB_PORT_STAT_ENABLE) != 0) |
| { |
| if ((change & USBHUB_PORT_STAT_CRESET) != 0) |
| { |
| ctrlreq->type = USBHUB_REQ_TYPE_PORT; |
| ctrlreq->req = USBHUB_REQ_CLEARFEATURE; |
| usbhost_putle16(ctrlreq->value, |
| USBHUB_PORT_FEAT_CRESET); |
| usbhost_putle16(ctrlreq->index, port); |
| usbhost_putle16(ctrlreq->len, 0); |
| |
| DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, NULL); |
| } |
| |
| connport = &priv->hport[PORT_INDX(port)]; |
| if ((status & USBHUB_PORT_STAT_HIGH_SPEED) != 0) |
| { |
| connport->speed = USB_SPEED_HIGH; |
| } |
| else if ((status & USBHUB_PORT_STAT_LOW_SPEED) != 0) |
| { |
| connport->speed = USB_SPEED_LOW; |
| } |
| else |
| { |
| connport->speed = USB_SPEED_FULL; |
| } |
| |
| /* Activate the hub port by assigning it a control |
| * endpoint. |
| */ |
| |
| ret = usbhost_hport_activate(connport); |
| if (ret < 0) |
| { |
| uerr("ERROR: usbhost_hport_activate failed: %d\n", |
| ret); |
| } |
| else |
| { |
| /* Inform waiters that a new device has been |
| * connected |
| */ |
| |
| ret = DRVR_CONNECT(connport->drvr, connport, true); |
| if (ret < 0) |
| { |
| uerr("ERROR: DRVR_CONNECT failed: %d\n", ret); |
| usbhost_hport_deactivate(connport); |
| } |
| } |
| } |
| else |
| { |
| uerr("ERROR: Failed to enable port %d\n", port); |
| continue; |
| } |
| } |
| else |
| { |
| /* Device disconnected from a port on the hub. Release port |
| * resources. |
| */ |
| |
| uinfo("Disconnection on port %d\n", port); |
| |
| /* Free any devices classes connect on this hub port */ |
| |
| connport = &priv->hport[PORT_INDX(port)]; |
| if (connport->devclass != NULL) |
| { |
| CLASS_DISCONNECTED(connport->devclass); |
| |
| if (connport->devclass->connect == usbhost_connect) |
| { |
| /* For hubs, the usbhost_disconnect_event function |
| * (triggered by the CLASS_DISCONNECTED call above) |
| * will call usbhost_hport_deactivate for us. We |
| * prevent a crash when a hub is unplugged by skipping |
| * the second unnecessary usbhost_hport_deactivated |
| * call here. |
| */ |
| |
| connport->devclass = NULL; |
| } |
| else |
| { |
| connport->devclass = NULL; |
| |
| /* Free any resources used by the hub port */ |
| |
| usbhost_hport_deactivate(connport); |
| } |
| } |
| else |
| { |
| /* Free any resources used by the hub port */ |
| |
| usbhost_hport_deactivate(connport); |
| } |
| } |
| } |
| else if (change) |
| { |
| uwarn("WARNING: status %04x change %04x not handled\n", |
| status, change); |
| } |
| } |
| |
| /* Free portstatus memory */ |
| |
| DRVR_FREE(hport->drvr, (FAR uint8_t *)portstatus); |
| |
| /* Check for hub status change */ |
| |
| if ((statuschange & 1) != 0) |
| { |
| /* Hub status changed */ |
| |
| uwarn("WARNING: Hub status changed, not handled\n"); |
| } |
| |
| /* The preceding sequence of events may take a significant amount of |
| * time and it is possible that the hub may have been removed while this |
| * logic operated. In any event, we will get here after several failures. |
| * But we do not want to schedule another hub event if the hub has been |
| * removed. |
| */ |
| |
| flags = enter_critical_section(); |
| if (!priv->disconnected) |
| { |
| /* Wait for the next hub event */ |
| |
| ret = DRVR_ASYNCH(hport->drvr, priv->intin, |
| (FAR uint8_t *)priv->buffer, |
| INTIN_BUFSIZE, usbhost_callback, hubclass); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to queue interrupt endpoint: %d\n", ret); |
| } |
| } |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_disconnect_event |
| * |
| * Description: |
| * This function performs the disconnect() actions on the worker thread. |
| * This work was scheduled when by the usbhost_disconnected() method after |
| * the HCD informs use that the device has been disconnected. |
| * |
| * Input Parameters: |
| * class - 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: |
| * Probably called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static void usbhost_disconnect_event(FAR void *arg) |
| { |
| FAR struct usbhost_class_s *hubclass = (FAR struct usbhost_class_s *)arg; |
| FAR struct usbhost_hubpriv_s *priv; |
| FAR struct usbhost_hubport_s *hport; |
| FAR struct usbhost_hubport_s *child; |
| irqstate_t flags; |
| int port; |
| |
| uinfo("Disconnecting\n"); |
| |
| DEBUGASSERT(hubclass != NULL && hubclass->hport != NULL); |
| priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv; |
| hport = hubclass->hport; |
| |
| uinfo("Destroying hub on port %d\n", PORT_NO(hport->port)); |
| |
| /* Set an indication to any users of the device that the device is no |
| * longer available. |
| */ |
| |
| flags = enter_critical_section(); |
| |
| /* Cancel any pending transfers on the interrupt IN pipe */ |
| |
| DRVR_CANCEL(hport->drvr, priv->intin); |
| |
| /* Cancel any pending port status change events */ |
| |
| work_cancel(LPWORK, &priv->work); |
| |
| /* Free the allocated control request */ |
| |
| DRVR_FREE(hport->drvr, (FAR uint8_t *)priv->ctrlreq); |
| |
| /* Free buffer for status change (INT) endpoint */ |
| |
| DRVR_IOFREE(hport->drvr, priv->buffer); |
| |
| /* Destroy the interrupt IN endpoint */ |
| |
| DRVR_EPFREE(hport->drvr, priv->intin); |
| |
| /* Release per-port resources */ |
| |
| for (port = 0; port < USBHUB_MAX_PORTS; port++) |
| { |
| /* Free any devices classes connect on this hub port */ |
| |
| child = &priv->hport[port]; |
| if (child->devclass != NULL) |
| { |
| CLASS_DISCONNECTED(child->devclass); |
| child->devclass = NULL; |
| } |
| |
| /* Free any resources used by the hub port */ |
| |
| usbhost_hport_deactivate(child); |
| } |
| |
| /* Deactivate the parent hub port (unless it is the root hub port) */ |
| |
| usbhost_hport_deactivate(hport); |
| |
| /* Disconnect the USB host device */ |
| |
| DRVR_DISCONNECT(hport->drvr, hport); |
| |
| /* Free the class instance */ |
| |
| kmm_free(hubclass); |
| hport->devclass = NULL; |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_getle16 |
| * |
| * Description: |
| * Get a (possibly unaligned) 16-bit little endian value. |
| * |
| * Input Parameters: |
| * val - A pointer to the first byte of the little endian value. |
| * |
| * Returned Value: |
| * A uint16_t representing the whole 16-bit integer value |
| * |
| ****************************************************************************/ |
| |
| static inline uint16_t usbhost_getle16(const uint8_t *val) |
| { |
| return (uint16_t)val[1] << 8 | (uint16_t)val[0]; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_putle16 |
| * |
| * Description: |
| * Put a (possibly unaligned) 16-bit little endian value. |
| * |
| * Input Parameters: |
| * dest - A pointer to the first byte to save the little endian value. |
| * val - The 16-bit value to be saved. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void usbhost_putle16(uint8_t *dest, uint16_t val) |
| { |
| dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */ |
| dest[1] = val >> 8; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_callback |
| * |
| * Description: |
| * Handle end of transfer callback. |
| * |
| * Input Parameters: |
| * arg - The argument provided with the asynchronous I/O was setup |
| * nbytes - The number of bytes actually transferred (or a negated errno |
| * value; |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Probably called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static void usbhost_callback(FAR void *arg, ssize_t nbytes) |
| { |
| FAR struct usbhost_class_s *hubclass; |
| FAR struct usbhost_hubpriv_s *priv; |
| uint32_t delay = 0; |
| |
| DEBUGASSERT(arg != NULL); |
| hubclass = (FAR struct usbhost_class_s *)arg; |
| priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv; |
| |
| /* Check for a failure. On higher end host controllers, the asynchronous |
| * transfer will pend until data is available (OHCI and EHCI). On lower |
| * end host controllers (like STM32 and EFM32), the transfer will fail |
| * immediately when the device NAKs the first attempted interrupt IN |
| * transfer (with nbytes == -EAGAIN). In that case (or in the case of |
| * other errors), we must fall back to polling. |
| */ |
| |
| if (nbytes < 0) |
| { |
| /* This debug output is good to know, but really a nuisance for |
| * those configurations where we have to fall back to polling. |
| * FIX: Don't output the message is the result is -EAGAIN. |
| */ |
| |
| #if defined(CONFIG_DEBUG_USB) && !defined(CONFIG_DEBUG_INFO) |
| if (nbytes != -EAGAIN) |
| #endif |
| { |
| uerr("ERROR: Transfer failed: %d\n", (int)nbytes); |
| } |
| |
| /* Indicate there there is nothing to do. So when the work is |
| * performed, nothing will happen other than we will set to receive |
| * the next event. |
| */ |
| |
| priv->buffer[0] = 0; |
| |
| /* We don't know the nature of the failure, but we need to do all that |
| * we can do to avoid a CPU hog error loop. |
| * |
| * Use the low-priority work queue and delay polling for the next |
| * event. We want to use as little CPU bandwidth as possible in this |
| * case. |
| */ |
| |
| delay = POLL_DELAY; |
| } |
| |
| /* The work structure should always be available since hub communications |
| * are serialized. However, there is a remote chance that this may |
| * collide with a hub disconnection event. |
| */ |
| |
| if (work_available(&priv->work) && !priv->disconnected) |
| { |
| work_queue(LPWORK, &priv->work, usbhost_hub_event, |
| hubclass, delay); |
| } |
| } |
| |
| /**************************************************************************** |
| * struct usbhost_registry_s methods |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_create |
| * |
| * Description: |
| * This function implements the create() method of struct |
| * usbhost_registry_s. |
| * The create() method is a callback into the class implementation. It is |
| * used to (1) create a new instance of the USB host class state and to (2) |
| * bind a USB host driver "session" to the class instance. Use of this |
| * create() method will support environments where there may be multiple |
| * USB ports and multiple USB devices simultaneously connected. |
| * |
| * Input Parameters: |
| * hport - The hub port that manages the new class instance. |
| * id - In the case where the device supports multiple base classes, |
| * subclasses, or protocols, this specifies which to configure for. |
| * |
| * Returned Value: |
| * On success, this function will return a non-NULL instance of struct |
| * usbhost_class_s that can be used by the USB host driver to communicate |
| * with the USB host class. NULL is returned on failure; this function |
| * will fail only if the hport input parameter is NULL or if there are |
| * insufficient resources to create another USB host class instance. |
| * |
| ****************************************************************************/ |
| |
| static FAR struct usbhost_class_s * |
| usbhost_create(FAR struct usbhost_hubport_s *hport, |
| FAR const struct usbhost_id_s *id) |
| { |
| FAR struct usbhost_hubclass_s *alloc; |
| FAR struct usbhost_class_s *hubclass; |
| FAR struct usbhost_hubpriv_s *priv; |
| size_t maxlen; |
| int port; |
| int ret; |
| |
| /* Allocate a USB host class instance */ |
| |
| alloc = kmm_zalloc(sizeof(struct usbhost_hubclass_s)); |
| if (alloc == NULL) |
| { |
| return NULL; |
| } |
| |
| /* Initialize the public class structure */ |
| |
| hubclass = &alloc->hubclass; |
| hubclass->hport = hport; |
| hubclass->connect = usbhost_connect; |
| hubclass->disconnected = usbhost_disconnected; |
| |
| /* Initialize the private class structure */ |
| |
| priv = &alloc->hubpriv; |
| |
| /* Allocate memory for control requests */ |
| |
| ret = DRVR_ALLOC(hport->drvr, (FAR uint8_t **)&priv->ctrlreq, &maxlen); |
| if (ret < 0) |
| { |
| uerr("ERROR: DRVR_ALLOC failed: %d\n", ret); |
| goto errout_with_hub; |
| } |
| |
| /* Allocate buffer for status change (INT) endpoint. */ |
| |
| ret = DRVR_IOALLOC(hport->drvr, &priv->buffer, INTIN_BUFSIZE); |
| if (ret < 0) |
| { |
| uerr("ERROR: DRVR_IOALLOC failed: %d\n", ret); |
| goto errout_with_ctrlreq; |
| } |
| |
| /* Initialize per-port data */ |
| |
| for (port = 0; port < USBHUB_MAX_PORTS; port++) |
| { |
| FAR struct usbhost_hubport_s *child; |
| |
| /* Initialize the hub port descriptor */ |
| |
| child = &priv->hport[port]; |
| memset(child, 0, sizeof(struct usbhost_hubport_s)); |
| |
| child->drvr = hport->drvr; |
| child->parent = hport; |
| child->port = port; |
| child->speed = USB_SPEED_FULL; |
| } |
| |
| return hubclass; |
| |
| errout_with_ctrlreq: |
| kmm_free(priv->ctrlreq); |
| |
| errout_with_hub: |
| kmm_free(alloc); |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * struct usbhost_class_s methods |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_connect |
| * |
| * Description: |
| * This function implements the connect() method of struct |
| * usbhost_class_s. This method is a callback into the class |
| * implementation. It is used to provide the device's configuration |
| * descriptor to the class so that the class may initialize properly |
| * |
| * Input Parameters: |
| * class - 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 *hubclass, |
| FAR const uint8_t *configdesc, int desclen) |
| { |
| FAR struct usbhost_hubpriv_s *priv; |
| FAR struct usbhost_hubport_s *hport; |
| int ret; |
| |
| DEBUGASSERT(hubclass != NULL); |
| priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv; |
| |
| DEBUGASSERT(hubclass->hport); |
| hport = hubclass->hport; |
| |
| DEBUGASSERT(configdesc != NULL && desclen >= sizeof(struct usb_cfgdesc_s)); |
| |
| /* Parse the configuration descriptor to get the endpoints */ |
| |
| ret = usbhost_cfgdesc(hubclass, configdesc, desclen); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to parse config descriptor: %d\n", ret); |
| return ret; |
| } |
| |
| /* Read the hub descriptor */ |
| |
| ret = usbhost_hubdesc(hubclass); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| if (priv->nports > USBHUB_MAX_PORTS) |
| { |
| uerr("ERROR: too many downstream ports: %d\n", priv->nports); |
| return -ENOSYS; |
| } |
| |
| /* Enable power to all downstream ports */ |
| |
| ret = usbhost_hubpwr(priv, hport, true); |
| if (ret < 0) |
| { |
| uerr("ERROR: usbhost_hubpwr failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Begin monitoring of port status change events */ |
| |
| ret = DRVR_ASYNCH(hport->drvr, priv->intin, (FAR uint8_t *)priv->buffer, |
| INTIN_BUFSIZE, usbhost_callback, hubclass); |
| if (ret < 0) |
| { |
| uerr("ERROR: DRVR_ASYNCH failed: %d\n", ret); |
| usbhost_hubpwr(priv, hport, false); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_disconnected |
| * |
| * Description: |
| * This function implements the disconnected() method of struct |
| * usbhost_class_s. This method is a callback into the class |
| * implementation. It is used to inform the class that the USB device has |
| * been disconnected. |
| * |
| * Input Parameters: |
| * class - 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: |
| * Probably called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_disconnected(struct usbhost_class_s *hubclass) |
| { |
| FAR struct usbhost_hubpriv_s *priv; |
| irqstate_t flags; |
| int ret; |
| |
| uinfo("Disconnected\n"); |
| |
| /* Execute the disconnect action from the worker thread. */ |
| |
| DEBUGASSERT(hubclass != NULL); |
| priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv; |
| |
| /* Mark the driver disconnected. This will cause the callback to ignore |
| * any subsequent completions of asynchronous transfers. |
| */ |
| |
| flags = enter_critical_section(); |
| priv->disconnected = true; |
| |
| /* Cancel any pending work. There may be pending HUB work associated with |
| * hub interrupt pipe events. That work may be lost by this action. |
| */ |
| |
| work_cancel(LPWORK, &priv->work); |
| |
| /* Schedule the disconnection work */ |
| |
| ret = work_queue(LPWORK, &priv->work, |
| usbhost_disconnect_event, hubclass, 0); |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_hub_initialize |
| * |
| * Description: |
| * Initialize the USB hub class. This function should be called |
| * be platform-specific code in order to initialize and register support |
| * for the USB host storage class. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * On success this function will return zero (OK); A negated errno value |
| * will be returned on failure. |
| * |
| ****************************************************************************/ |
| |
| int usbhost_hub_initialize(void) |
| { |
| /* Advertise our availability to support (certain) mass storage devices */ |
| |
| return usbhost_registerclass(&g_hub); |
| } |
| |
| #endif /* CONFIG_USBHOST_HUB */ |