Skip to content

Commit

Permalink
USB: EHCI: rescan the queue after an unlink
Browse files Browse the repository at this point in the history
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: Alan Stern <stern@rowland.harvard.edu>
CC: David Brownell <david-b@pacbell.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Alan Stern authored and Greg Kroah-Hartman committed Sep 23, 2009
1 parent 04c4ab1 commit 3a44494
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 12 deletions.
18 changes: 13 additions & 5 deletions drivers/usb/host/ehci-hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
end_unlink_async(ehci);

/* if it's not linked then there's nothing to do */
if (qh->qh_state != QH_STATE_LINKED)
;
/* If the QH isn't linked then there's nothing we can do
* 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 */
else if (ehci->reclaim) {
if (ehci->reclaim) {
struct ehci_qh *last;

for (last = ehci->reclaim;
Expand Down Expand Up @@ -1001,6 +1007,7 @@ ehci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
qh->qh_state = QH_STATE_IDLE;
switch (qh->qh_state) {
case QH_STATE_LINKED:
case QH_STATE_COMPLETING:
for (tmp = ehci->async->qh_next.qh;
tmp && tmp != qh;
tmp = tmp->qh_next.qh)
Expand Down Expand Up @@ -1065,7 +1072,8 @@ ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
usb_settoggle(qh->dev, epnum, is_out, 0);
if (!list_empty(&qh->qtd_list)) {
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
* while the QH is active. Unlink it now;
Expand Down
43 changes: 36 additions & 7 deletions drivers/usb/host/ehci-q.c
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,13 @@ static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh);
static unsigned
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;
int last_status = -EINPROGRESS;
int last_status;
int stopped;
unsigned count = 0;
u8 state;
__le32 halt = HALT_BIT(ehci);
const __le32 halt = HALT_BIT(ehci);
struct ehci_qh_hw *hw = qh->hw;

if (unlikely (list_empty (&qh->qtd_list)))
Expand All @@ -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.
*
* 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;
qh->qh_state = QH_STATE_COMPLETING;
stopped = (state == QH_STATE_IDLE);

rescan:
last = NULL;
last_status = -EINPROGRESS;
qh->needs_rescan = 0;

/* remove de-activated QTDs from front of queue.
* after faults (including short reads), cleanup this urb
* then let the queue advance.
Expand Down Expand Up @@ -507,6 +516,21 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
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 */
qh->qh_state = state;

Expand Down Expand Up @@ -535,8 +559,10 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
& hw->hw_info2) != 0) {
intr_deschedule (ehci, qh);
(void) qh_schedule (ehci, qh);
} else
unlink_async (ehci, qh);
} else {
/* Tell the caller to start an unlink */
qh->needs_rescan = 1;
}
break;
/* otherwise, unlink already started */
}
Expand Down Expand Up @@ -916,6 +942,8 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
if (unlikely(qh->clearing_tt))
return;

WARN_ON(qh->qh_state != QH_STATE_IDLE);

/* (re)start the async schedule? */
head = ehci->async;
timer_action_done (ehci, TIMER_ASYNC_OFF);
Expand All @@ -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 */
if (qh->qh_state == QH_STATE_IDLE)
qh_refresh (ehci, qh);
qh_refresh(ehci, qh);

/* splice right after start */
qh->qh_next = head->qh_next;
Expand Down Expand Up @@ -1220,6 +1247,8 @@ static void scan_async (struct ehci_hcd *ehci)
qh = qh_get (qh);
qh->stamp = ehci->stamp;
temp = qh_completions (ehci, qh);
if (qh->needs_rescan)
unlink_async(ehci, qh);
qh_put (qh);
if (temp != 0) {
goto rescan;
Expand Down
1 change: 1 addition & 0 deletions drivers/usb/host/ehci.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ struct ehci_qh {
u32 refcount;
unsigned stamp;

u8 needs_rescan; /* Dequeue during giveback */
u8 qh_state;
#define QH_STATE_LINKED 1 /* HC sees this */
#define QH_STATE_UNLINK 2 /* HC may still see this */
Expand Down

0 comments on commit 3a44494

Please sign in to comment.