[PATCH] fix data toggle mismatch, after driver unbinding without toggle reset

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The previous patch (as1197), to not reset the data toggle during driver 
unbinding when an interface is already in altsetting 0, has introduced an 
intermittent loss of packets due to data toggle mismatch.  Specifically, the 
ehci and ohci drivers do not use the device toggle field to actually track the 
current toggle; they use hardware to track the device toggle.  So avoiding the 
call to usb_settoggle() in usb_endpoint_enable() isn't enough.

I think there is an easy way to fix this, and a hard way.  The hard way (and 
more correct way IMHO) is to make the data toggles HCD-internal, which is what 
they really should be.  The only interface that drivers or the USB core needs is 
the ability to reset the data toggle after a set interface or a clear halt.  
This would be a relatively large change, affecting all the HC drivers, the USB 
core, and many of the USB device drivers.

The easy way is what the patch below does.  Currently, dev->toggle has 2 
different meanings:
-For uhci, it is the actual toggle value
-For ehci and ohci, it is a flag to indicate if the toggle should be reset 
This patch changes it to have 3 meanings:
-For uhci, it is the actual toggle value
-For ehci and ohci while there is a qh or ed present for an endpoint, it is a 
flag to indicate if the toggle should be reset
-For ehci and ohci when the endpoint is disabled, it stores the last toggle 
value, which is restored when the endpoint is re-enabled and a new qh or ed is 
created for the endpoint.  If the toggle really should be reset, it happens 
normally during the call to usb_enable_endpoint().

I don't know enough about the other HCD drivers to say if they use hardware 
toggling or not.  If they do, then they will need to similarly keep track of the 
last toggle or there will occasionally be a lost packet due to toggle mismatch.

Signed-off-by: Dan Streetman <ddstreet@xxxxxxxx>


diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 4725d15..7c8545f 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -927,6 +927,7 @@ ehci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
 	struct ehci_hcd		*ehci = hcd_to_ehci (hcd);
 	unsigned long		flags;
 	struct ehci_qh		*qh, *tmp;
+	int			epnum, is_out, toggle;
 
 	/* ASSERT:  any requests/urbs are being unlinked */
 	/* ASSERT:  nobody can be submitting urbs for this any more */
@@ -937,6 +938,11 @@ rescan:
 	if (!qh)
 		goto done;
 
+	epnum = ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+	is_out = !(ep->desc.bEndpointAddress & USB_DIR_IN);
+	toggle = (qh->hw_token & cpu_to_hc32 (ehci, QTD_TOGGLE)) != 0;
+	usb_settoggle(qh->dev, epnum, is_out, toggle);
+
 	/* endpoints can be iso streams.  for now, we don't
 	 * accelerate iso completions ... so spin a while.
 	 */
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 3712b92..e09bef3 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -819,6 +819,8 @@ done:
 	qh->qh_state = QH_STATE_IDLE;
 	qh->hw_info1 = cpu_to_hc32(ehci, info1);
 	qh->hw_info2 = cpu_to_hc32(ehci, info2);
+	if (usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input))
+		qh->hw_token |= cpu_to_hc32(ehci, QTD_TOGGLE);
 	usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1);
 	qh_refresh (ehci, qh);
 	return qh;
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 65a9609..a0cf489 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -314,6 +314,7 @@ ohci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
 	unsigned long		flags;
 	struct ed		*ed = ep->hcpriv;
 	unsigned		limit = 1000;
+	int			epnum, is_out, toggle;
 
 	/* ASSERT:  any requests/urbs are being unlinked */
 	/* ASSERT:  nobody can be submitting urbs for this any more */
@@ -332,6 +333,11 @@ sanitize:
 		finish_unlinks (ohci, 0);
 	}
 
+	epnum = ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+	is_out = !(ep->desc.bEndpointAddress & USB_DIR_IN);
+	toggle = (ed->hwHeadP & cpu_to_hc32(ohci, ED_C)) != 0;
+	usb_settoggle(ed->dev, epnum, is_out, toggle);
+
 	switch (ed->state) {
 	case ED_UNLINK:		/* wait for hw to finish? */
 		/* major IRQ delivery trouble loses INTR_SF too... */
diff --git a/drivers/usb/host/ohci-mem.c b/drivers/usb/host/ohci-mem.c
index 2f20d3d..d4ecacf 100644
--- a/drivers/usb/host/ohci-mem.c
+++ b/drivers/usb/host/ohci-mem.c
@@ -133,6 +133,8 @@ ed_alloc (struct ohci_hcd *hc, gfp_t mem_flags)
 static void
 ed_free (struct ohci_hcd *hc, struct ed *ed)
 {
+	if (ed->dev)
+		usb_put_dev (ed->dev);
 	dma_pool_free (hc->ed_cache, ed, ed->dma);
 }
 
diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
index c2d80f8..201222b 100644
--- a/drivers/usb/host/ohci-q.c
+++ b/drivers/usb/host/ohci-q.c
@@ -393,7 +393,7 @@ static struct ed *ed_get (
 
 	if (!(ed = ep->hcpriv)) {
 		struct td	*td;
-		int		is_out;
+		int		epnum, is_out;
 		u32		info;
 
 		ed = ed_alloc (ohci, GFP_ATOMIC);
@@ -415,8 +415,13 @@ static struct ed *ed_get (
 		ed->hwHeadP = ed->hwTailP;	/* ED_C, ED_H zeroed */
 		ed->state = ED_IDLE;
 
+		epnum = ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
 		is_out = !(ep->desc.bEndpointAddress & USB_DIR_IN);
 
+		if (usb_gettoggle(udev, epnum, is_out))
+			ed->hwHeadP |= cpu_to_hc32(ohci, ED_C);
+		usb_settoggle(udev, epnum, is_out, 1);
+
 		/* FIXME usbcore changes dev->devnum before SET_ADDRESS
 		 * suceeds ... otherwise we wouldn't need "pipe".
 		 */
@@ -446,6 +451,8 @@ static struct ed *ed_get (
 		}
 		ed->hwINFO = cpu_to_hc32(ohci, info);
 
+		ed->dev = usb_get_dev(udev);
+
 		ep->hcpriv = ed;
 	}
 
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index 222011f..16933a9 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -66,6 +66,9 @@ struct ed {
 
 	/* HC may see EDs on rm_list until next frame (frame_no == tick) */
 	u16			tick;
+
+	/* Access to USB device (for toggle) */
+	struct usb_device	*dev;
 } __attribute__ ((aligned(16)));
 
 #define ED_MASK	((u32)~0x0f)		/* strip hw status in low addr bits */
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux