From 519461f843727ce5480786efb95c76c5883c1c9b Mon Sep 17 00:00:00 2001
From: Josef Karthauser <joe@FreeBSD.org>
Date: Sun, 20 Jan 2002 23:38:33 +0000
Subject: [PATCH] Merge from NetBSD:

    uhci.c:	-r1.124
    uhcireg.h:	-r1.13

	date: 2000/08/13 18:20:14;  author: augustss;  state: Exp;
	Fix race condition when unlinking xfers.  Thanks to IWAMOTO Toshihiro
	<iwamoto@sat.t.u-tokyo.ac.jp> for analyzing the problem and suggesting a fix.
	Fixes PR 10662.
---
 sys/dev/usb/uhci.c    | 35 ++++++++++++++++++++++++++++++++++-
 sys/dev/usb/uhcireg.h |  8 +++++++-
 2 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/sys/dev/usb/uhci.c b/sys/dev/usb/uhci.c
index aa4bacbca851..036773e86205 100644
--- a/sys/dev/usb/uhci.c
+++ b/sys/dev/usb/uhci.c
@@ -954,9 +954,23 @@ uhci_remove_hs_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh)
 
 	DPRINTFN(10, ("uhci_remove_hs_ctrl: sqh=%p\n", sqh));
 	uhci_rem_loop(sc);
+	/*
+	 * The T bit should be set in the elink of the QH so that the HC
+	 * doesn't follow the pointer.  This condition may fail if the
+	 * the transferred packet was short so that the QH still points
+	 * at the last used TD.
+	 * In this case we set the T bit and wait a little for the HC
+	 * to stop looking at the TD.
+	 */
+	if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) {
+		sqh->qh.qh_elink = htole32(UHCI_PTR_T);
+		delay(UHCI_QH_REMOVE_DELAY);
+	}
+
 	pqh = uhci_find_prev_qh(sc->sc_hctl_start, sqh);
 	pqh->hlink = sqh->hlink;
 	pqh->qh.qh_hlink = sqh->qh.qh_hlink;
+	delay(UHCI_QH_REMOVE_DELAY);
 	if (sc->sc_hctl_end == sqh)
 		sc->sc_hctl_end = pqh;
 }
@@ -987,9 +1001,15 @@ uhci_remove_ls_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh)
 	SPLUSBCHECK;
  
 	DPRINTFN(10, ("uhci_remove_ls_ctrl: sqh=%p\n", sqh));
+	/* See comment in uhci_remove_hs_ctrl() */
+	if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) {
+		sqh->qh.qh_elink = htole32(UHCI_PTR_T);
+		delay(UHCI_QH_REMOVE_DELAY);
+	}
 	pqh = uhci_find_prev_qh(sc->sc_lctl_start, sqh);
 	pqh->hlink = sqh->hlink;
 	pqh->qh.qh_hlink = sqh->qh.qh_hlink;
+	delay(UHCI_QH_REMOVE_DELAY);
 	if (sc->sc_lctl_end == sqh)
 		sc->sc_lctl_end = pqh;
 }
@@ -1022,9 +1042,15 @@ uhci_remove_bulk(uhci_softc_t *sc, uhci_soft_qh_t *sqh)
 
 	DPRINTFN(10, ("uhci_remove_bulk: sqh=%p\n", sqh));
 	uhci_rem_loop(sc);
+	/* See comment in uhci_remove_hs_ctrl() */
+	if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) {
+		sqh->qh.qh_elink = htole32(UHCI_PTR_T);
+		delay(UHCI_QH_REMOVE_DELAY);
+	}
 	pqh = uhci_find_prev_qh(sc->sc_bulk_start, sqh);
 	pqh->hlink = sqh->hlink;
 	pqh->qh.qh_hlink = sqh->qh.qh_hlink;
+	delay(UHCI_QH_REMOVE_DELAY);
 	if (sc->sc_bulk_end == sqh)
 		sc->sc_bulk_end = pqh;
 }
@@ -2538,6 +2564,12 @@ uhci_remove_intr(uhci_softc_t *sc, int n, uhci_soft_qh_t *sqh)
 
 	DPRINTFN(4, ("uhci_remove_intr: n=%d sqh=%p\n", n, sqh));
 
+	/* See comment in uhci_remove_ctrl() */
+	if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) {
+		sqh->qh.qh_elink = htole32(UHCI_PTR_T);
+		delay(UHCI_QH_REMOVE_DELAY);
+	}
+
 	for (pqh = vf->hqh; pqh->hlink != sqh; pqh = pqh->hlink)
 #if defined(DIAGNOSTIC) || defined(UHCI_DEBUG)		
 		if (le32toh(pqh->qh.qh_hlink) & UHCI_PTR_T) {
@@ -2549,6 +2581,7 @@ uhci_remove_intr(uhci_softc_t *sc, int n, uhci_soft_qh_t *sqh)
 #endif
 	pqh->hlink       = sqh->hlink;
 	pqh->qh.qh_hlink = sqh->qh.qh_hlink;
+	delay(UHCI_QH_REMOVE_DELAY);
 	if (vf->eqh == sqh)
 		vf->eqh = pqh;
 	vf->bandwidth--;
@@ -3086,7 +3119,7 @@ uhci_root_ctrl_start(usbd_xfer_handle xfer)
 			delay(100);
 			x = UREAD2(sc, port);
 			UWRITE2(sc, port, x  | UHCI_PORTSC_PE);
-			delay(100);
+			usb_delay_ms(&sc->sc_bus, 10); /* XXX */
 			DPRINTFN(3,("uhci port %d reset, status = 0x%04x\n",
 				    index, UREAD2(sc, port)));
 			sc->sc_isreset = 1;
diff --git a/sys/dev/usb/uhcireg.h b/sys/dev/usb/uhcireg.h
index ab3c9e75afbd..c8c547987ae1 100644
--- a/sys/dev/usb/uhcireg.h
+++ b/sys/dev/usb/uhcireg.h
@@ -1,4 +1,4 @@
-/*	$NetBSD: usb/uhcireg.h,v 1.12 2000/07/23 19:43:38 augustss Exp $	*/
+/*	$NetBSD: usb/uhcireg.h,v 1.13 2000/08/13 18:20:15 augustss Exp $	*/
 /*	$FreeBSD$	*/
 
 /*
@@ -121,6 +121,12 @@ typedef u_int32_t uhci_physaddr_t;
 #define UHCI_PTR_QH		0x00000002
 #define UHCI_PTR_VF		0x00000004
 
+/* 
+ * Wait this long after a QH has been removed.  This gives that HC a
+ * chance to stop looking at it before it's recycled.
+ */
+#define UHCI_QH_REMOVE_DELAY	5
+
 /*
  * The Queue Heads and Transfer Descriptors are accessed
  * by both the CPU and the USB controller which run