USB: OHCI: don't lose track of EDs when a controller dies

This patch fixes a bug in ohci-hcd.  When an URB is unlinked, the
corresponding Endpoint Descriptor is added to the ed_rm_list and taken
off the hardware schedule.  Once the ED is no longer visible to the
hardware, finish_unlinks() handles the URBs that were unlinked or have
completed.  If any URBs remain attached to the ED, the ED is added
back to the hardware schedule -- but only if the controller is
running.

This fails when a controller dies.  A non-empty ED does not get added
back to the hardware schedule and does not remain on the ed_rm_list;
ohci-hcd loses track of it.  The remaining URBs cannot be unlinked,
which causes the USB stack to hang.

The patch changes finish_unlinks() so that non-empty EDs remain on
the ed_rm_list if the controller isn't running.  This requires moving
some of the existing code around, to avoid modifying the ED's hardware
fields more than once.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
CC: <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Alan Stern 2014-07-17 16:34:29 -04:00 committed by Greg Kroah-Hartman
parent 256dbcd80f
commit 977dcfdc60
1 changed files with 29 additions and 17 deletions

View File

@ -311,8 +311,7 @@ static void periodic_unlink (struct ohci_hcd *ohci, struct ed *ed)
* - ED_OPER: when there's any request queued, the ED gets rescheduled * - ED_OPER: when there's any request queued, the ED gets rescheduled
* immediately. HC should be working on them. * immediately. HC should be working on them.
* *
* - ED_IDLE: when there's no TD queue. there's no reason for the HC * - ED_IDLE: when there's no TD queue or the HC isn't running.
* to care about this ED; safe to disable the endpoint.
* *
* When finish_unlinks() runs later, after SOF interrupt, it will often * When finish_unlinks() runs later, after SOF interrupt, it will often
* complete one or more URB unlinks before making that state change. * complete one or more URB unlinks before making that state change.
@ -954,6 +953,10 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
int completed, modified; int completed, modified;
__hc32 *prev; __hc32 *prev;
/* Is this ED already invisible to the hardware? */
if (ed->state == ED_IDLE)
goto ed_idle;
/* only take off EDs that the HC isn't using, accounting for /* only take off EDs that the HC isn't using, accounting for
* frame counter wraps and EDs with partially retired TDs * frame counter wraps and EDs with partially retired TDs
*/ */
@ -983,12 +986,20 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
} }
} }
/* ED's now officially unlinked, hc doesn't see */
ed->state = ED_IDLE;
if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
ohci->eds_scheduled--;
ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
ed->hwNextED = 0;
wmb();
ed->hwINFO &= ~cpu_to_hc32(ohci, ED_SKIP | ED_DEQUEUE);
ed_idle:
/* reentrancy: if we drop the schedule lock, someone might /* reentrancy: if we drop the schedule lock, someone might
* have modified this list. normally it's just prepending * have modified this list. normally it's just prepending
* entries (which we'd ignore), but paranoia won't hurt. * entries (which we'd ignore), but paranoia won't hurt.
*/ */
*last = ed->ed_next;
ed->ed_next = NULL;
modified = 0; modified = 0;
/* unlink urbs as requested, but rescan the list after /* unlink urbs as requested, but rescan the list after
@ -1046,19 +1057,20 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
if (completed && !list_empty (&ed->td_list)) if (completed && !list_empty (&ed->td_list))
goto rescan_this; goto rescan_this;
/* ED's now officially unlinked, hc doesn't see */ /*
ed->state = ED_IDLE; * If no TDs are queued, take ED off the ed_rm_list.
if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT) * Otherwise, if the HC is running, reschedule.
ohci->eds_scheduled--; * If not, leave it on the list for further dequeues.
ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H); */
ed->hwNextED = 0; if (list_empty(&ed->td_list)) {
wmb (); *last = ed->ed_next;
ed->hwINFO &= ~cpu_to_hc32 (ohci, ED_SKIP | ED_DEQUEUE); ed->ed_next = NULL;
} else if (ohci->rh_state == OHCI_RH_RUNNING) {
/* but if there's work queued, reschedule */ *last = ed->ed_next;
if (!list_empty (&ed->td_list)) { ed->ed_next = NULL;
if (ohci->rh_state == OHCI_RH_RUNNING)
ed_schedule(ohci, ed); ed_schedule(ohci, ed);
} else {
last = &ed->ed_next;
} }
if (modified) if (modified)