aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2013-03-01 10:50:08 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-03-04 19:45:33 -0500
commit6402c796d3b4205d3d7296157956c5100a05d7d6 (patch)
treeeeb53dbcd07afb41d4991d84e87f6ecb78e433d5
parentea5301aa130a07e8d0f8bd7d7d3124f45e592208 (diff)
USB: EHCI: work around silicon bug in Intel's EHCI controllers
This patch (as1660) works around a hardware problem present in some (if not all) Intel EHCI controllers. After a QH has been unlinked from the async schedule and the corresponding IAA interrupt has occurred, the controller is not supposed access the QH and its qTDs. There certainly shouldn't be any more DMA writes to those structures. Nevertheless, Intel's controllers have been observed to perform a final writeback to the QH's overlay region and to the most recent qTD. For more information and a test program to determine whether this problem is present in a particular controller, see http://marc.info/?l=linux-usb&m=135492071812265&w=2 http://marc.info/?l=linux-usb&m=136182570800963&w=2 This patch works around the problem by always waiting for two IAA cycles when unlinking an async QH. The extra IAA delay gives the controller time to perform its final writeback. Surprisingly enough, the effects of this silicon bug have gone undetected until quite recently. More through luck than anything else, it hasn't caused any apparent problems. However, it does interact badly with the path that follows this one, so it needs to be addressed. This is the first part of a fix for the regression reported at: https://bugs.launchpad.net/bugs/1088733 Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Tested-by: Stephen Thirlwall <sdt@dr.com> CC: <stable@vger.kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/usb/host/ehci-hcd.c6
-rw-r--r--drivers/usb/host/ehci-q.c18
2 files changed, 16 insertions, 8 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index b416a3fc9959..5726cb144abf 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -748,11 +748,9 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
748 /* guard against (alleged) silicon errata */ 748 /* guard against (alleged) silicon errata */
749 if (cmd & CMD_IAAD) 749 if (cmd & CMD_IAAD)
750 ehci_dbg(ehci, "IAA with IAAD still set?\n"); 750 ehci_dbg(ehci, "IAA with IAAD still set?\n");
751 if (ehci->async_iaa) { 751 if (ehci->async_iaa)
752 COUNT(ehci->stats.iaa); 752 COUNT(ehci->stats.iaa);
753 end_unlink_async(ehci); 753 end_unlink_async(ehci);
754 } else
755 ehci_dbg(ehci, "IAA with nothing unlinked?\n");
756 } 754 }
757 755
758 /* remote wakeup [4.3.1] */ 756 /* remote wakeup [4.3.1] */
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index fd252f0cfb3a..7bf2b4eeb9ce 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -1170,7 +1170,7 @@ static void single_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh)
1170 struct ehci_qh *prev; 1170 struct ehci_qh *prev;
1171 1171
1172 /* Add to the end of the list of QHs waiting for the next IAAD */ 1172 /* Add to the end of the list of QHs waiting for the next IAAD */
1173 qh->qh_state = QH_STATE_UNLINK; 1173 qh->qh_state = QH_STATE_UNLINK_WAIT;
1174 if (ehci->async_unlink) 1174 if (ehci->async_unlink)
1175 ehci->async_unlink_last->unlink_next = qh; 1175 ehci->async_unlink_last->unlink_next = qh;
1176 else 1176 else
@@ -1213,9 +1213,19 @@ static void start_iaa_cycle(struct ehci_hcd *ehci, bool nested)
1213 1213
1214 /* Do only the first waiting QH (nVidia bug?) */ 1214 /* Do only the first waiting QH (nVidia bug?) */
1215 qh = ehci->async_unlink; 1215 qh = ehci->async_unlink;
1216 ehci->async_iaa = qh; 1216
1217 ehci->async_unlink = qh->unlink_next; 1217 /*
1218 qh->unlink_next = NULL; 1218 * Intel (?) bug: The HC can write back the overlay region
1219 * even after the IAA interrupt occurs. In self-defense,
1220 * always go through two IAA cycles for each QH.
1221 */
1222 if (qh->qh_state == QH_STATE_UNLINK_WAIT) {
1223 qh->qh_state = QH_STATE_UNLINK;
1224 } else {
1225 ehci->async_iaa = qh;
1226 ehci->async_unlink = qh->unlink_next;
1227 qh->unlink_next = NULL;
1228 }
1219 1229
1220 /* Make sure the unlinks are all visible to the hardware */ 1230 /* Make sure the unlinks are all visible to the hardware */
1221 wmb(); 1231 wmb();