USB: EHCI: update toggle state for linked QHs

This patch (as1245) fixes a bug in ehci-hcd.  When an URB is queued
for an endpoint whose QH is already in the LINKED state, the QH
doesn't get refreshed.  As a result, if usb_clear_halt() was called
during the time that the QH was linked but idle, the data toggle value
in the QH doesn't get reset.

The symptom is that after a clear_halt, data gets lost and transfers
time out.  This problem is starting to show up now because the
"ehci-hcd unlink speedups" patch causes QHs with no queued URBs to
remain linked for a suitable time.

The patch utilizes the new endpoint_reset mechanism to fix the
problem.  When an endpoint is reset, the new method forcibly unlinks
the QH (if necessary) and safely updates the toggle value.  This
allows qh_update() to be simplified and avoids using usb_device's
toggle bits in a rather unintuitive way.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
CC: David Brownell <david-b@pacbell.net>
Tested-by: David <david@unsolicited.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Alan Stern
2009-05-27 18:21:56 -04:00
committed by Greg Kroah-Hartman
parent 5effabbe9e
commit b18ffd49e8
9 changed files with 53 additions and 18 deletions

View File

@@ -1024,6 +1024,51 @@ done:
return;
}
static void
ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct ehci_qh *qh;
int eptype = usb_endpoint_type(&ep->desc);
if (eptype != USB_ENDPOINT_XFER_BULK && eptype != USB_ENDPOINT_XFER_INT)
return;
rescan:
spin_lock_irq(&ehci->lock);
qh = ep->hcpriv;
/* For Bulk and Interrupt endpoints we maintain the toggle state
* in the hardware; the toggle bits in udev aren't used at all.
* When an endpoint is reset by usb_clear_halt() we must reset
* the toggle bit in the QH.
*/
if (qh) {
if (!list_empty(&qh->qtd_list)) {
WARN_ONCE(1, "clear_halt for a busy endpoint\n");
} else if (qh->qh_state == QH_STATE_IDLE) {
qh->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE);
} else {
/* It's not safe to write into the overlay area
* while the QH is active. Unlink it first and
* wait for the unlink to complete.
*/
if (qh->qh_state == QH_STATE_LINKED) {
if (eptype == USB_ENDPOINT_XFER_BULK) {
unlink_async(ehci, qh);
} else {
intr_deschedule(ehci, qh);
(void) qh_schedule(ehci, qh);
}
}
spin_unlock_irq(&ehci->lock);
schedule_timeout_uninterruptible(1);
goto rescan;
}
}
spin_unlock_irq(&ehci->lock);
}
static int ehci_get_frame (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);