Skip to content

Commit 977dcfd

Browse files
AlanSterngregkh
authored andcommitted
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 <[email protected]> CC: <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 256dbcd commit 977dcfd

File tree

1 file changed

+29
-17
lines changed

1 file changed

+29
-17
lines changed

drivers/usb/host/ohci-q.c

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,7 @@ static void periodic_unlink (struct ohci_hcd *ohci, struct ed *ed)
311311
* - ED_OPER: when there's any request queued, the ED gets rescheduled
312312
* immediately. HC should be working on them.
313313
*
314-
* - ED_IDLE: when there's no TD queue. there's no reason for the HC
315-
* to care about this ED; safe to disable the endpoint.
314+
* - ED_IDLE: when there's no TD queue or the HC isn't running.
316315
*
317316
* When finish_unlinks() runs later, after SOF interrupt, it will often
318317
* complete one or more URB unlinks before making that state change.
@@ -954,6 +953,10 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
954953
int completed, modified;
955954
__hc32 *prev;
956955

956+
/* Is this ED already invisible to the hardware? */
957+
if (ed->state == ED_IDLE)
958+
goto ed_idle;
959+
957960
/* only take off EDs that the HC isn't using, accounting for
958961
* frame counter wraps and EDs with partially retired TDs
959962
*/
@@ -983,12 +986,20 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
983986
}
984987
}
985988

989+
/* ED's now officially unlinked, hc doesn't see */
990+
ed->state = ED_IDLE;
991+
if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
992+
ohci->eds_scheduled--;
993+
ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
994+
ed->hwNextED = 0;
995+
wmb();
996+
ed->hwINFO &= ~cpu_to_hc32(ohci, ED_SKIP | ED_DEQUEUE);
997+
ed_idle:
998+
986999
/* reentrancy: if we drop the schedule lock, someone might
9871000
* have modified this list. normally it's just prepending
9881001
* entries (which we'd ignore), but paranoia won't hurt.
9891002
*/
990-
*last = ed->ed_next;
991-
ed->ed_next = NULL;
9921003
modified = 0;
9931004

9941005
/* unlink urbs as requested, but rescan the list after
@@ -1046,19 +1057,20 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
10461057
if (completed && !list_empty (&ed->td_list))
10471058
goto rescan_this;
10481059

1049-
/* ED's now officially unlinked, hc doesn't see */
1050-
ed->state = ED_IDLE;
1051-
if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
1052-
ohci->eds_scheduled--;
1053-
ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
1054-
ed->hwNextED = 0;
1055-
wmb ();
1056-
ed->hwINFO &= ~cpu_to_hc32 (ohci, ED_SKIP | ED_DEQUEUE);
1057-
1058-
/* but if there's work queued, reschedule */
1059-
if (!list_empty (&ed->td_list)) {
1060-
if (ohci->rh_state == OHCI_RH_RUNNING)
1061-
ed_schedule (ohci, ed);
1060+
/*
1061+
* If no TDs are queued, take ED off the ed_rm_list.
1062+
* Otherwise, if the HC is running, reschedule.
1063+
* If not, leave it on the list for further dequeues.
1064+
*/
1065+
if (list_empty(&ed->td_list)) {
1066+
*last = ed->ed_next;
1067+
ed->ed_next = NULL;
1068+
} else if (ohci->rh_state == OHCI_RH_RUNNING) {
1069+
*last = ed->ed_next;
1070+
ed->ed_next = NULL;
1071+
ed_schedule(ohci, ed);
1072+
} else {
1073+
last = &ed->ed_next;
10621074
}
10631075

10641076
if (modified)

0 commit comments

Comments
 (0)