USB hub: Add a configuration option to fallback to polling mode if the the HCD async method does not work. Don't call DRVR_CANCEL twice, and certainly not from the interrupt level. Add so checks so that we do not do some normal asynchronous actions if the hub has been disconnected

This commit is contained in:
Gregory Nutt 2015-04-30 11:30:01 -06:00
parent f36b833955
commit 1380945a13
2 changed files with 80 additions and 17 deletions

View file

@ -51,6 +51,32 @@ config USBHOST_HUB
---help---
Select this option to build in support for USB hubs.
if USBHOST_HUB
config USBHOST_HUB_POLLMSEC
int "USB Hub Polling Interval (MSec)"
default 400
---help---
On higher end host controllers (OHCI and EHCI), the asynchronous,
interrupt IN transfers will pend until data is available from the
hub. 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 result == EGAIN) and the hub
class driver will fall back to polling the hub.
For the case of the higher end controllers, this polling interval
is not critical since it would really only be used in the event of
failures to communicate with the hub.
But for the lower end host controllers, the asynchronous transfers
are ineffective and this polling interval becomes a critical
parameter that must be tuned to tradeoff CPU usage with
responsiveness to hub-related events (It would, I suppose, be more
efficient to use synchronous transfers with these lower end host
controllers).
endif # USBHOST_HUB
config USBHOST_MSC
bool "Mass Storage Class Support"
default n

View file

@ -668,6 +668,7 @@ static void usbhost_hub_event(FAR void *arg)
FAR struct usbhost_hubpriv_s *priv;
FAR struct usb_ctrlreq_s *ctrlreq;
struct usb_portstatus_s portstatus;
irqstate_t flags;
uint16_t status;
uint16_t change;
uint16_t mask;
@ -680,6 +681,16 @@ static void usbhost_hub_event(FAR void *arg)
hubclass = (FAR struct usbhost_class_s *)arg;
priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv;
/* Has the hub been disconnected? */
if (priv->disconnected)
{
uvdbg("Disconnected\n");
return;
}
/* No.. then set up to process the hub event */
DEBUGASSERT(priv->ctrlreq);
ctrlreq = priv->ctrlreq;
@ -954,6 +965,16 @@ static void usbhost_hub_event(FAR void *arg)
udbg("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 = irqsave();
if (!priv->disconnected)
{
/* Wait for the next hub event */
ret = DRVR_ASYNCH(hport->drvr, priv->intin, (FAR uint8_t *)priv->buffer,
@ -964,6 +985,9 @@ static void usbhost_hub_event(FAR void *arg)
}
}
irqrestore(flags);
}
/****************************************************************************
* Name: usbhost_disconnect_event
*
@ -994,6 +1018,8 @@ static void usbhost_disconnect_event(FAR void *arg)
irqstate_t flags;
int port;
uvdbg("Disconnecting\n");
DEBUGASSERT(hubclass != NULL && hubclass->hport != NULL);
priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv;
hport = hubclass->hport;
@ -1005,7 +1031,6 @@ static void usbhost_disconnect_event(FAR void *arg)
*/
flags = irqsave();
priv->disconnected = true;
/* Cancel any pending transfers on the interrupt IN pipe */
@ -1132,7 +1157,13 @@ static void usbhost_callback(FAR void *arg, int result)
hubclass = (FAR struct usbhost_class_s *)arg;
priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv;
/* Check for a failure */
/* 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 result == EGAIN). In that case (or in the case of
* other errors), we must fall back to polling.
*/
if (result != OK)
{
@ -1161,7 +1192,7 @@ static void usbhost_callback(FAR void *arg, int result)
* collide with a hub disconnection event.
*/
if (work_available(&priv->work))
if (work_available(&priv->work) && !priv->disconnected)
{
(void)work_queue(LPWORK, &priv->work, (worker_t)usbhost_hub_event,
hubclass, delay);
@ -1395,25 +1426,31 @@ static int usbhost_connect(FAR struct usbhost_class_s *hubclass,
static int usbhost_disconnected(struct usbhost_class_s *hubclass)
{
FAR struct usbhost_hubpriv_s *priv;
FAR struct usbhost_hubport_s *hport;
irqstate_t flags;
int ret;
uvdbg("Disconnected\n");
/* Execute the disconnect action from the worker thread. */
DEBUGASSERT(hubclass != NULL && hubclass->hport != NULL);
DEBUGASSERT(hubclass != NULL);
priv = &((FAR struct usbhost_hubclass_s *)hubclass)->hubpriv;
hport = hubclass->hport;
/* Cancel any pending transfers on the interrupt IN pipe */
DRVR_CANCEL(hport->drvr, priv->intin);
/* NOTE: There may be pending HUB work associated with hub interrupt
* pipe events. That work may be overwritten and lost by this action.
/* Mark the driver disconnected. This will cause the callback to ignore
* any subsequent completions of asynchronous transfers.
*/
flags = irqsave();
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.
*/
(void)work_cancel(LPWORK, &priv->work);
/* Schedule the disconnection work */
ret = work_queue(LPWORK, &priv->work,
(worker_t)usbhost_disconnect_event, hubclass, 0);
irqrestore(flags);