summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/dev/usb/ehci.c32
-rw-r--r--sys/dev/usb/ehci_pci.c4
-rw-r--r--sys/dev/usb/ehcivar.h2
3 files changed, 38 insertions, 0 deletions
diff --git a/sys/dev/usb/ehci.c b/sys/dev/usb/ehci.c
index eeb47a4..5324c5e0 100644
--- a/sys/dev/usb/ehci.c
+++ b/sys/dev/usb/ehci.c
@@ -153,6 +153,7 @@ Static void ehci_check_intr(ehci_softc_t *, struct ehci_xfer *);
Static void ehci_idone(struct ehci_xfer *);
Static void ehci_timeout(void *);
Static void ehci_timeout_task(void *);
+Static void ehci_intrlist_timeout(void *);
Static usbd_status ehci_allocm(struct usbd_bus *, usb_dma_t *, u_int32_t);
Static void ehci_freem(struct usbd_bus *, usb_dma_t *);
@@ -489,6 +490,7 @@ ehci_init(ehci_softc_t *sc)
EOWRITE4(sc, EHCI_ASYNCLISTADDR, sqh->physaddr | EHCI_LINK_QH);
usb_callout_init(sc->sc_tmo_pcd);
+ usb_callout_init(sc->sc_tmo_intrlist);
lockinit(&sc->sc_doorbell_lock, PZERO, "ehcidb", 0, 0);
@@ -692,6 +694,12 @@ ehci_softintr(void *v)
ehci_check_intr(sc, ex);
}
+ /* Schedule a callout to catch any dropped transactions. */
+ if ((sc->sc_flags & EHCI_SCFLG_LOSTINTRBUG) &&
+ !LIST_EMPTY(&sc->sc_intrhead))
+ usb_callout(sc->sc_tmo_intrlist, hz / 5, ehci_intrlist_timeout,
+ sc);
+
#ifdef USB_USE_SOFTINTR
if (sc->sc_softwake) {
sc->sc_softwake = 0;
@@ -942,6 +950,7 @@ ehci_detach(struct ehci_softc *sc, int flags)
EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
EOWRITE4(sc, EHCI_USBCMD, 0);
EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET);
+ usb_uncallout(sc->sc_tmo_intrlist, ehci_intrlist_timeout, sc);
usb_uncallout(sc->sc_tmo_pcd, ehci_pcd_enable, sc);
#if defined(__NetBSD__) || defined(__OpenBSD__)
@@ -2700,6 +2709,29 @@ ehci_timeout_task(void *addr)
splx(s);
}
+/*
+ * Some EHCI chips from VIA seem to trigger interrupts before writing back the
+ * qTD status, or miss signalling occasionally under heavy load. If the host
+ * machine is too fast, we we can miss transaction completion - when we scan
+ * the active list the transaction still seems to be active. This generally
+ * exhibits itself as a umass stall that never recovers.
+ *
+ * We work around this behaviour by setting up this callback after any softintr
+ * that completes with transactions still pending, giving us another chance to
+ * check for completion after the writeback has taken place.
+ */
+void
+ehci_intrlist_timeout(void *arg)
+{
+ ehci_softc_t *sc = arg;
+ int s = splusb();
+
+ DPRINTFN(3, ("ehci_intrlist_timeout\n"));
+ usb_schedsoftintr(&sc->sc_bus);
+
+ splx(s);
+}
+
/************************/
Static usbd_status
diff --git a/sys/dev/usb/ehci_pci.c b/sys/dev/usb/ehci_pci.c
index 9918efc..b03d94c 100644
--- a/sys/dev/usb/ehci_pci.c
+++ b/sys/dev/usb/ehci_pci.c
@@ -373,6 +373,10 @@ ehci_pci_attach(device_t self)
return ENXIO;
}
+ /* Enable workaround for dropped interrupts as required */
+ if (pci_get_vendor(self) == PCI_EHCI_VENDORID_VIA)
+ sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG;
+
/*
* Find companion controllers. According to the spec they always
* have lower function numbers so they should be enumerated already.
diff --git a/sys/dev/usb/ehcivar.h b/sys/dev/usb/ehcivar.h
index f7a2029..e1b852d 100644
--- a/sys/dev/usb/ehcivar.h
+++ b/sys/dev/usb/ehcivar.h
@@ -94,6 +94,7 @@ struct ehci_soft_islot {
#define EHCI_COMPANION_MAX 8
#define EHCI_SCFLG_DONEINIT 0x0001 /* ehci_init() has been called. */
+#define EHCI_SCFLG_LOSTINTRBUG 0x0002 /* workaround for VIA chipsets */
typedef struct ehci_softc {
struct usbd_bus sc_bus; /* base device */
@@ -153,6 +154,7 @@ typedef struct ehci_softc {
struct lock sc_doorbell_lock;
usb_callout_t sc_tmo_pcd;
+ usb_callout_t sc_tmo_intrlist;
#if defined(__NetBSD__) || defined(__OpenBSD__)
device_ptr_t sc_child; /* /dev/usb# device */
OpenPOWER on IntegriCloud