Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b18ffd49 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman
Browse files

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: default avatarAlan Stern <stern@rowland.harvard.edu>
CC: David Brownell <david-b@pacbell.net>
Tested-by: default avatarDavid <david@unsolicited.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 5effabbe
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ static const struct hc_driver ehci_au1xxx_hc_driver = {
	.urb_enqueue		= ehci_urb_enqueue,
	.urb_dequeue		= ehci_urb_dequeue,
	.endpoint_disable	= ehci_endpoint_disable,
	.endpoint_reset		= ehci_endpoint_reset,

	/*
	 * scheduling support
+1 −0
Original line number Diff line number Diff line
@@ -309,6 +309,7 @@ static const struct hc_driver ehci_fsl_hc_driver = {
	.urb_enqueue = ehci_urb_enqueue,
	.urb_dequeue = ehci_urb_dequeue,
	.endpoint_disable = ehci_endpoint_disable,
	.endpoint_reset = ehci_endpoint_reset,

	/*
	 * scheduling support
+45 −0
Original line number Diff line number Diff line
@@ -1024,6 +1024,51 @@ ehci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
	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);
+1 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ static const struct hc_driver ixp4xx_ehci_hc_driver = {
	.urb_enqueue		= ehci_urb_enqueue,
	.urb_dequeue		= ehci_urb_dequeue,
	.endpoint_disable	= ehci_endpoint_disable,
	.endpoint_reset		= ehci_endpoint_reset,
	.get_frame_number	= ehci_get_frame,
	.hub_status_data	= ehci_hub_status_data,
	.hub_control		= ehci_hub_control,
+1 −0
Original line number Diff line number Diff line
@@ -149,6 +149,7 @@ static const struct hc_driver ehci_orion_hc_driver = {
	.urb_enqueue = ehci_urb_enqueue,
	.urb_dequeue = ehci_urb_dequeue,
	.endpoint_disable = ehci_endpoint_disable,
	.endpoint_reset = ehci_endpoint_reset,

	/*
	 * scheduling support
Loading