mirror of https://gitee.com/openkylin/linux.git
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
|
* - 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)
|
||||||
|
|
Loading…
Reference in New Issue