Commit 3a44494e authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: EHCI: rescan the queue after an unlink

This patch (as1280) fixes an obscure bug in ehci-hcd's dequeuing logic
for async URBs.  If a later URB is unlinked and the completion
routine unlinks an earlier URB, then the earlier URB won't be given
back in a timely manner because the endpoint queue isn't rescanned as
it should be.

Similar bugs occur if an endpoint is reset or a halt is cleared while
a completion routine is running, because the subroutines don't test
for the COMPLETING state.

All these problems are solved by adding a new needs_rescan flag to the
ehci_qh structure.  If the flag is set while scanning through an idle
QH, the scan will be repeated.  If the QH isn't idle then an unlink
cycle will be initiated, and the proper action will be taken when it
becomes idle.

Also, an unnecessary test is removed from qh_link_async(): That
routine is never called if the QH's state isn't IDLE.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
CC: David Brownell <david-b@pacbell.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 04c4ab17
...@@ -863,12 +863,18 @@ static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -863,12 +863,18 @@ static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
if (!HC_IS_RUNNING(ehci_to_hcd(ehci)->state) && ehci->reclaim) if (!HC_IS_RUNNING(ehci_to_hcd(ehci)->state) && ehci->reclaim)
end_unlink_async(ehci); end_unlink_async(ehci);
/* if it's not linked then there's nothing to do */ /* If the QH isn't linked then there's nothing we can do
if (qh->qh_state != QH_STATE_LINKED) * unless we were called during a giveback, in which case
; * qh_completions() has to deal with it.
*/
if (qh->qh_state != QH_STATE_LINKED) {
if (qh->qh_state == QH_STATE_COMPLETING)
qh->needs_rescan = 1;
return;
}
/* defer till later if busy */ /* defer till later if busy */
else if (ehci->reclaim) { if (ehci->reclaim) {
struct ehci_qh *last; struct ehci_qh *last;
for (last = ehci->reclaim; for (last = ehci->reclaim;
...@@ -1001,6 +1007,7 @@ rescan: ...@@ -1001,6 +1007,7 @@ rescan:
qh->qh_state = QH_STATE_IDLE; qh->qh_state = QH_STATE_IDLE;
switch (qh->qh_state) { switch (qh->qh_state) {
case QH_STATE_LINKED: case QH_STATE_LINKED:
case QH_STATE_COMPLETING:
for (tmp = ehci->async->qh_next.qh; for (tmp = ehci->async->qh_next.qh;
tmp && tmp != qh; tmp && tmp != qh;
tmp = tmp->qh_next.qh) tmp = tmp->qh_next.qh)
...@@ -1065,7 +1072,8 @@ ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) ...@@ -1065,7 +1072,8 @@ ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
usb_settoggle(qh->dev, epnum, is_out, 0); usb_settoggle(qh->dev, epnum, is_out, 0);
if (!list_empty(&qh->qtd_list)) { if (!list_empty(&qh->qtd_list)) {
WARN_ONCE(1, "clear_halt for a busy endpoint\n"); WARN_ONCE(1, "clear_halt for a busy endpoint\n");
} else if (qh->qh_state == QH_STATE_LINKED) { } else if (qh->qh_state == QH_STATE_LINKED ||
qh->qh_state == QH_STATE_COMPLETING) {
/* The toggle value in the QH can't be updated /* The toggle value in the QH can't be updated
* while the QH is active. Unlink it now; * while the QH is active. Unlink it now;
......
...@@ -310,13 +310,13 @@ static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh); ...@@ -310,13 +310,13 @@ static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh);
static unsigned static unsigned
qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
{ {
struct ehci_qtd *last = NULL, *end = qh->dummy; struct ehci_qtd *last, *end = qh->dummy;
struct list_head *entry, *tmp; struct list_head *entry, *tmp;
int last_status = -EINPROGRESS; int last_status;
int stopped; int stopped;
unsigned count = 0; unsigned count = 0;
u8 state; u8 state;
__le32 halt = HALT_BIT(ehci); const __le32 halt = HALT_BIT(ehci);
struct ehci_qh_hw *hw = qh->hw; struct ehci_qh_hw *hw = qh->hw;
if (unlikely (list_empty (&qh->qtd_list))) if (unlikely (list_empty (&qh->qtd_list)))
...@@ -327,11 +327,20 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -327,11 +327,20 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
* they add urbs to this qh's queue or mark them for unlinking. * they add urbs to this qh's queue or mark them for unlinking.
* *
* NOTE: unlinking expects to be done in queue order. * NOTE: unlinking expects to be done in queue order.
*
* It's a bug for qh->qh_state to be anything other than
* QH_STATE_IDLE, unless our caller is scan_async() or
* scan_periodic().
*/ */
state = qh->qh_state; state = qh->qh_state;
qh->qh_state = QH_STATE_COMPLETING; qh->qh_state = QH_STATE_COMPLETING;
stopped = (state == QH_STATE_IDLE); stopped = (state == QH_STATE_IDLE);
rescan:
last = NULL;
last_status = -EINPROGRESS;
qh->needs_rescan = 0;
/* remove de-activated QTDs from front of queue. /* remove de-activated QTDs from front of queue.
* after faults (including short reads), cleanup this urb * after faults (including short reads), cleanup this urb
* then let the queue advance. * then let the queue advance.
...@@ -507,6 +516,21 @@ halt: ...@@ -507,6 +516,21 @@ halt:
ehci_qtd_free (ehci, last); ehci_qtd_free (ehci, last);
} }
/* Do we need to rescan for URBs dequeued during a giveback? */
if (unlikely(qh->needs_rescan)) {
/* If the QH is already unlinked, do the rescan now. */
if (state == QH_STATE_IDLE)
goto rescan;
/* Otherwise we have to wait until the QH is fully unlinked.
* Our caller will start an unlink if qh->needs_rescan is
* set. But if an unlink has already started, nothing needs
* to be done.
*/
if (state != QH_STATE_LINKED)
qh->needs_rescan = 0;
}
/* restore original state; caller must unlink or relink */ /* restore original state; caller must unlink or relink */
qh->qh_state = state; qh->qh_state = state;
...@@ -535,8 +559,10 @@ halt: ...@@ -535,8 +559,10 @@ halt:
& hw->hw_info2) != 0) { & hw->hw_info2) != 0) {
intr_deschedule (ehci, qh); intr_deschedule (ehci, qh);
(void) qh_schedule (ehci, qh); (void) qh_schedule (ehci, qh);
} else } else {
unlink_async (ehci, qh); /* Tell the caller to start an unlink */
qh->needs_rescan = 1;
}
break; break;
/* otherwise, unlink already started */ /* otherwise, unlink already started */
} }
...@@ -916,6 +942,8 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -916,6 +942,8 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
if (unlikely(qh->clearing_tt)) if (unlikely(qh->clearing_tt))
return; return;
WARN_ON(qh->qh_state != QH_STATE_IDLE);
/* (re)start the async schedule? */ /* (re)start the async schedule? */
head = ehci->async; head = ehci->async;
timer_action_done (ehci, TIMER_ASYNC_OFF); timer_action_done (ehci, TIMER_ASYNC_OFF);
...@@ -934,8 +962,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -934,8 +962,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
} }
/* clear halt and/or toggle; and maybe recover from silicon quirk */ /* clear halt and/or toggle; and maybe recover from silicon quirk */
if (qh->qh_state == QH_STATE_IDLE) qh_refresh(ehci, qh);
qh_refresh (ehci, qh);
/* splice right after start */ /* splice right after start */
qh->qh_next = head->qh_next; qh->qh_next = head->qh_next;
...@@ -1220,6 +1247,8 @@ rescan: ...@@ -1220,6 +1247,8 @@ rescan:
qh = qh_get (qh); qh = qh_get (qh);
qh->stamp = ehci->stamp; qh->stamp = ehci->stamp;
temp = qh_completions (ehci, qh); temp = qh_completions (ehci, qh);
if (qh->needs_rescan)
unlink_async(ehci, qh);
qh_put (qh); qh_put (qh);
if (temp != 0) { if (temp != 0) {
goto rescan; goto rescan;
......
...@@ -341,6 +341,7 @@ struct ehci_qh { ...@@ -341,6 +341,7 @@ struct ehci_qh {
u32 refcount; u32 refcount;
unsigned stamp; unsigned stamp;
u8 needs_rescan; /* Dequeue during giveback */
u8 qh_state; u8 qh_state;
#define QH_STATE_LINKED 1 /* HC sees this */ #define QH_STATE_LINKED 1 /* HC sees this */
#define QH_STATE_UNLINK 2 /* HC may still see this */ #define QH_STATE_UNLINK 2 /* HC may still see this */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment