forked from luck/tmp_suning_uos_patched
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:
parent
256dbcd80f
commit
977dcfdc60
|
@ -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
|
||||
* immediately. HC should be working on them.
|
||||
*
|
||||
* - ED_IDLE: when there's no TD queue. there's no reason for the HC
|
||||
* to care about this ED; safe to disable the endpoint.
|
||||
* - ED_IDLE: when there's no TD queue or the HC isn't running.
|
||||
*
|
||||
* When finish_unlinks() runs later, after SOF interrupt, it will often
|
||||
* 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;
|
||||
__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
|
||||
* 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
|
||||
* have modified this list. normally it's just prepending
|
||||
* entries (which we'd ignore), but paranoia won't hurt.
|
||||
*/
|
||||
*last = ed->ed_next;
|
||||
ed->ed_next = NULL;
|
||||
modified = 0;
|
||||
|
||||
/* 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))
|
||||
goto rescan_this;
|
||||
|
||||
/* 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);
|
||||
|
||||
/* but if there's work queued, reschedule */
|
||||
if (!list_empty (&ed->td_list)) {
|
||||
if (ohci->rh_state == OHCI_RH_RUNNING)
|
||||
ed_schedule (ohci, ed);
|
||||
/*
|
||||
* If no TDs are queued, take ED off the ed_rm_list.
|
||||
* Otherwise, if the HC is running, reschedule.
|
||||
* If not, leave it on the list for further dequeues.
|
||||
*/
|
||||
if (list_empty(&ed->td_list)) {
|
||||
*last = ed->ed_next;
|
||||
ed->ed_next = NULL;
|
||||
} else if (ohci->rh_state == OHCI_RH_RUNNING) {
|
||||
*last = ed->ed_next;
|
||||
ed->ed_next = NULL;
|
||||
ed_schedule(ohci, ed);
|
||||
} else {
|
||||
last = &ed->ed_next;
|
||||
}
|
||||
|
||||
if (modified)
|
||||
|
|
Loading…
Reference in New Issue
Block a user