summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2011-07-19 14:01:23 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2011-08-04 21:58:41 -0700
commitd10a6cb264a9a60097e46c93146259fd36ea4bf4 (patch)
tree9d8474b2e83139c4ae4f78505ab0d3a2958645b6
parente151a2a6651a0a46fc03a845614b23c3b630042e (diff)
downloadlwn-d10a6cb264a9a60097e46c93146259fd36ea4bf4.tar.gz
lwn-d10a6cb264a9a60097e46c93146259fd36ea4bf4.zip
EHCI: fix direction handling for interrupt data toggles
commit e04f5f7e423018bcec84c11af2058cdce87816f3 upstream. This patch (as1480) fixes a rather obscure bug in ehci-hcd. The qh_update() routine needs to know the number and direction of the endpoint corresponding to its QH argument. The number can be taken directly from the QH data structure, but the direction isn't stored there. The direction is taken instead from the first qTD linked to the QH. However, it turns out that for interrupt transfers, qh_update() gets called before the qTDs are linked to the QH. As a result, qh_update() computes a bogus direction value, which messes up the endpoint toggle handling. Under the right combination of circumstances this causes usb_reset_endpoint() not to work correctly, which causes packets to be dropped and communications to fail. Now, it's silly for the QH structure not to have direct access to all the descriptor information for the corresponding endpoint. Ultimately it may get a pointer to the usb_host_endpoint structure; for now, adding a copy of the direction flag solves the immediate problem. This allows the Spyder2 color-calibration system (a low-speed USB device that sends all its interrupt data packets with the toggle set to 0 and hance requires constant use of usb_reset_endpoint) to work when connected through a high-speed hub. Thanks to Graeme Gill for supplying the hardware that allowed me to track down this bug. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Reported-by: Graeme Gill <graeme@argyllcms.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/host/ehci-q.c3
-rw-r--r--drivers/usb/host/ehci.h1
2 files changed, 3 insertions, 1 deletions
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 9bf3c0d983c4..0917e3a32465 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -103,7 +103,7 @@ qh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd)
if (!(hw->hw_info1 & cpu_to_hc32(ehci, 1 << 14))) {
unsigned is_out, epnum;
- is_out = !(qtd->hw_token & cpu_to_hc32(ehci, 1 << 8));
+ is_out = qh->is_out;
epnum = (hc32_to_cpup(ehci, &hw->hw_info1) >> 8) & 0x0f;
if (unlikely (!usb_gettoggle (qh->dev, epnum, is_out))) {
hw->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE);
@@ -946,6 +946,7 @@ done:
hw = qh->hw;
hw->hw_info1 = cpu_to_hc32(ehci, info1);
hw->hw_info2 = cpu_to_hc32(ehci, info2);
+ qh->is_out = !is_input;
usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1);
qh_refresh (ehci, qh);
return qh;
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 86e546778997..989e0a8e01f5 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -375,6 +375,7 @@ struct ehci_qh {
#define NO_FRAME ((unsigned short)~0) /* pick new start */
struct usb_device *dev; /* access to TT */
+ unsigned is_out:1; /* bulk or intr OUT */
unsigned clearing_tt:1; /* Clear-TT-Buf in progress */
};