Module Name: src
Committed By: skrll
Date: Mon May 30 06:46:50 UTC 2016
Modified Files:
src/sys/dev/usb [nick-nhusb]: ohci.c ohcivar.h
Log Message:
Restructure the abort code for TD based transfers (ctrl, bulk, intr).
In PR/22646 some TDs can be on the done queue when the abort start and,
if this is the case, they need to processed after the WDH interrupt.
Instead of waiting for WDH we release TDs that have been touched by the
HC and replace them with new ones. Once WDH happens the floating TDs
will be returned to the free list.
To generate a diff of this commit:
cvs rdiff -u -r1.254.2.75 -r1.254.2.76 src/sys/dev/usb/ohci.c
cvs rdiff -u -r1.55.6.14 -r1.55.6.15 src/sys/dev/usb/ohcivar.h
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/dev/usb/ohci.c
diff -u src/sys/dev/usb/ohci.c:1.254.2.75 src/sys/dev/usb/ohci.c:1.254.2.76
--- src/sys/dev/usb/ohci.c:1.254.2.75 Sun May 29 08:44:31 2016
+++ src/sys/dev/usb/ohci.c Mon May 30 06:46:50 2016
@@ -1,4 +1,4 @@
-/* $NetBSD: ohci.c,v 1.254.2.75 2016/05/29 08:44:31 skrll Exp $ */
+/* $NetBSD: ohci.c,v 1.254.2.76 2016/05/30 06:46:50 skrll Exp $ */
/*
* Copyright (c) 1998, 2004, 2005, 2012 The NetBSD Foundation, Inc.
@@ -41,7 +41,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ohci.c,v 1.254.2.75 2016/05/29 08:44:31 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ohci.c,v 1.254.2.76 2016/05/30 06:46:50 skrll Exp $");
#ifdef _KERNEL_OPT
#include "opt_usb.h"
@@ -494,6 +494,7 @@ ohci_alloc_std(ohci_softc_t *sc)
memset(&std->td, 0, sizeof(ohci_td_t));
std->nexttd = NULL;
std->xfer = NULL;
+ std->held = NULL;
return std;
}
@@ -541,14 +542,17 @@ ohci_alloc_std_chain(ohci_softc_t *sc, s
DPRINTFN(8, "xfer %p nstd %d", xfer, nstd, 0, 0);
- for (size_t j = 0; j < ox->ox_nstd;) {
+ for (size_t j = 0; j < ox->ox_nstd; j++) {
ohci_soft_td_t *cur = ohci_alloc_std(sc);
if (cur == NULL)
goto nomem;
- ox->ox_stds[j++] = cur;
+ ox->ox_stds[j] = cur;
+ cur->held = &ox->ox_stds[j];
cur->xfer = xfer;
cur->flags = 0;
+ DPRINTFN(10, "xfer=%p new std=%p held at %p", ox, cur,
+ cur->held, 0);
}
return 0;
@@ -794,6 +798,8 @@ ohci_init(ohci_softc_t *sc)
for (i = 0; i < OHCI_HASH_SIZE; i++)
LIST_INIT(&sc->sc_hash_itds[i]);
+ TAILQ_INIT(&sc->sc_abortingxfers);
+
sc->sc_xferpool = pool_cache_init(sizeof(struct ohci_xfer), 0, 0, 0,
"ohcixfer", NULL, IPL_USB, NULL, NULL, NULL);
@@ -1325,12 +1331,26 @@ ohci_intr1(ohci_softc_t *sc)
*/
softint_schedule(sc->sc_rhsc_si);
}
+ if (eintrs & OHCI_SF) {
+ struct ohci_xfer *ox, *tmp;
+ TAILQ_FOREACH_SAFE(ox, &sc->sc_abortingxfers, ox_abnext, tmp) {
+ DPRINTFN(10, "SF %p xfer %p", sc, ox, 0, 0);
+ ox->ox_abintrs &= ~OHCI_SF;
+ KASSERT(ox->ox_abintrs == 0);
+ TAILQ_REMOVE(&sc->sc_abortingxfers, ox, ox_abnext);
+ }
+ cv_broadcast(&sc->sc_softwake_cv);
+
+ KASSERT(TAILQ_EMPTY(&sc->sc_abortingxfers));
+ DPRINTFN(10, "end SOF %p", sc, 0, 0, 0);
+ /* Don't remove OHIC_SF from eintrs so it is blocked below */
+ }
if (eintrs != 0) {
/* Block unprocessed interrupts. */
OWRITE4(sc, OHCI_INTERRUPT_DISABLE, eintrs);
sc->sc_eintrs &= ~eintrs;
- DPRINTF("sc %p blocking intrs 0x%x", sc, eintrs, 0, 0);
+ DPRINTF("sc %p blocking/removing intrs 0x%x", sc, eintrs, 0, 0);
}
return 1;
@@ -1381,12 +1401,22 @@ ohci_softintr(void *v)
struct ohci_pipe *opipe;
int len, cc;
int i, j, actlen, iframes, uedir;
- ohci_physaddr_t done;
+ ohci_physaddr_t done = 0;
KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock));
OHCIHIST_FUNC(); OHCIHIST_CALLED();
+ /*
+ * Only read hccadone if WDH is set - we might get here from places
+ * other than an interrupt
+ */
+ if (!(OREAD4(sc, OHCI_INTERRUPT_STATUS) & OHCI_WDH)) {
+ DPRINTFN(10, "no WDH %p", sc, 0, 0, 0);
+ return;
+ }
+
+ DPRINTFN(10, "WDH %p", sc, 0, 0, 0);
usb_syncmem(&sc->sc_hccadma, offsetof(struct ohci_hcca, hcca_done_head),
sizeof(sc->sc_hcca->hcca_done_head),
BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD);
@@ -1440,8 +1470,8 @@ ohci_softintr(void *v)
for (std = sdone; std; std = stdnext) {
xfer = std->xfer;
stdnext = std->dnext;
- DPRINTFN(10, "std=%p xfer=%p hcpriv=%p", std, xfer,
- xfer ? xfer->ux_hcpriv : 0, 0);
+ DPRINTFN(10, "std=%p xfer=%p hcpriv=%p dnext=%p", std, xfer,
+ xfer ? xfer->ux_hcpriv : 0, stdnext);
if (xfer == NULL) {
/*
* xfer == NULL: There seems to be no xfer associated
@@ -1451,13 +1481,25 @@ ohci_softintr(void *v)
*/
continue;
}
+ if (std->held == NULL) {
+ DPRINTFN(10, "std=%p held is null", std, 0, 0, 0);
+ ohci_hash_rem_td(sc, std);
+ ohci_free_std_locked(sc, std);
+ continue;
+ }
+ /*
+ * Make sure the timeout handler didn't run or ran to the end
+ * and set the transfer status.
+ */
+ callout_halt(&xfer->ux_callout, &sc->sc_lock);
+
if (xfer->ux_status == USBD_CANCELLED ||
xfer->ux_status == USBD_TIMEOUT) {
DPRINTF("cancel/timeout %p", xfer, 0, 0, 0);
+
/* Handled by abort routine. */
continue;
}
- callout_stop(&xfer->ux_callout);
len = std->len;
if (std->td.td_cbp != 0)
@@ -1586,11 +1628,6 @@ ohci_softintr(void *v)
}
}
- if (sc->sc_softwake) {
- sc->sc_softwake = 0;
- cv_broadcast(&sc->sc_softwake_cv);
- }
-
DPRINTFN(10, "done", 0, 0, 0, 0);
}
@@ -1876,22 +1913,31 @@ ohci_timeout(void *addr)
{
struct usbd_xfer *xfer = addr;
ohci_softc_t *sc = OHCI_XFER2SC(xfer);
+ bool timeout = false;
OHCIHIST_FUNC(); OHCIHIST_CALLED();
DPRINTF("xfer=%p", xfer, 0, 0, 0);
+ mutex_enter(&sc->sc_lock);
if (sc->sc_dying) {
- mutex_enter(&sc->sc_lock);
ohci_abort_xfer(xfer, USBD_TIMEOUT);
mutex_exit(&sc->sc_lock);
return;
}
- /* Execute the abort in a process context. */
- usb_init_task(&xfer->ux_aborttask, ohci_timeout_task, addr,
- USB_TASKQ_MPSAFE);
- usb_add_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask,
- USB_TASKQ_HC);
+ if (xfer->ux_status != USBD_CANCELLED) {
+ xfer->ux_status = USBD_TIMEOUT;
+ timeout = true;
+ }
+ mutex_exit(&sc->sc_lock);
+
+ if (timeout) {
+ /* Execute the abort in a process context. */
+ usb_init_task(&xfer->ux_aborttask, ohci_timeout_task, addr,
+ USB_TASKQ_MPSAFE);
+ usb_add_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask,
+ USB_TASKQ_HC);
+ }
}
void
@@ -2063,6 +2109,7 @@ ohci_open(struct usbd_pipe *pipe)
goto bad;
opipe->tail.itd = sitd;
+ sitd->held = &opipe->tail.itd;
tdphys = sitd->physaddr;
fmt = OHCI_ED_FORMAT_ISO;
if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN)
@@ -2075,6 +2122,7 @@ ohci_open(struct usbd_pipe *pipe)
goto bad;
opipe->tail.td = std;
+ std->held = &opipe->tail.td;
tdphys = std->physaddr;
fmt = OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD;
}
@@ -2181,16 +2229,25 @@ ohci_close_pipe(struct usbd_pipe *pipe,
}
/*
- * Abort a device request.
- * If this routine is called at splusb() it guarantees that the request
- * will be removed from the hardware scheduling and that the callback
- * for it will be called with USBD_CANCELLED status.
+ * Cancel or timeour a device request. We have two cases to deal with
+ *
+ * 1) A driver wants to stop scheduled or inflight transfers
+ * 2) A transfer has timed out
+ *
* It's impossible to guarantee that the requested transfer will not
- * have happened since the hardware runs concurrently.
- * If the transaction has already happened we rely on the ordinary
- * interrupt processing to process it.
- * XXX This is most probably wrong.
- * XXXMRG this doesn't make sense anymore.
+ * have (partially) happened since the hardware runs concurrently.
+ *
+ * Transfer state is protected by the bus lock and we set the transfer status
+ * as soon as either of the above happens (with bus lock held).
+ *
+ * Then we arrange for the hardware to tells us that it is not still
+ * processing the TDs by setting the sKip bit and requesting a SOF interrupt
+ *
+ * Once we see the SOF interrupt we can check the transfer TDs/iTDs to see if
+ * they've been processed and either
+ * a) if they're unused recover them for later use, or
+ * b) if they've been used allocate new TD/iTDs to replace those
+ * used. The softint handler will free the old ones.
*/
void
ohci_abort_xfer(struct usbd_xfer *xfer, usbd_status status)
@@ -2211,7 +2268,7 @@ ohci_abort_xfer(struct usbd_xfer *xfer,
if (sc->sc_dying) {
/* If we're dying, just do the software part. */
- xfer->ux_status = status; /* make software ignore it */
+ KASSERT(xfer->ux_status == status);
callout_halt(&xfer->ux_callout, &sc->sc_lock);
usb_transfer_complete(xfer);
return;
@@ -2238,29 +2295,60 @@ ohci_abort_xfer(struct usbd_xfer *xfer,
xfer->ux_hcflags |= UXFER_ABORTING;
/*
- * Step 1: Make interrupt routine and hardware ignore xfer.
+ * Step 1: When cancelling a transfer make sure the timeout handler
+ * didn't run or ran to the end and saw the USBD_CANCELLED status.
+ * Otherwise we must have got here via a timeout.
+ *
+ * If we timed out then
+ */
+ if (status == USBD_CANCELLED) {
+ xfer->ux_status = status;
+ callout_halt(&xfer->ux_callout, &sc->sc_lock);
+ } else {
+ KASSERT(xfer->ux_status == USBD_TIMEOUT);
+ }
+
+ /*
+ * Step 2: Unless the endpoint is already halted, we set the endpoint
+ * descriptor sKip bit and wait for hardware to complete processing.
+ *
+ * This includes ensuring that any TDs of the transfer that got onto
+ * the done list are also removed. We ensure this by waiting for
+ * both a WDH and SOF interrupt.
*/
- xfer->ux_status = status; /* make software ignore it */
- callout_stop(&xfer->ux_callout);
DPRINTFN(1, "stop ed=%p", sed, 0, 0, 0);
usb_syncmem(&sed->dma, sed->offs + offsetof(ohci_ed_t, ed_flags),
sizeof(sed->ed.ed_flags),
BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD);
- sed->ed.ed_flags |= HTOO32(OHCI_ED_SKIP); /* force hardware skip */
- usb_syncmem(&sed->dma, sed->offs + offsetof(ohci_ed_t, ed_flags),
- sizeof(sed->ed.ed_flags),
- BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
+ if (!(sed->ed.ed_flags & OHCI_HALTED)) {
+ /* force hardware skip */
+ DPRINTFN(1, "pausing ed=%p", sed, 0, 0, 0);
+ sed->ed.ed_flags |= HTOO32(OHCI_ED_SKIP);
+ usb_syncmem(&sed->dma,
+ sed->offs + offsetof(ohci_ed_t, ed_flags),
+ sizeof(sed->ed.ed_flags),
+ BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
- /*
- * Step 2: Wait until we know hardware has finished any possible
- * use of the xfer. Also make sure the soft interrupt routine
- * has run.
- */
- /* Hardware finishes in 1ms */
- usb_delay_ms_locked(opipe->pipe.up_dev->ud_bus, 20, &sc->sc_lock);
- sc->sc_softwake = 1;
- usb_schedsoftintr(&sc->sc_bus);
- cv_wait(&sc->sc_softwake_cv, &sc->sc_lock);
+ DPRINTFN(10, "WDH %p xfer %p", sc, xfer, 0, 0);
+ struct ohci_xfer *ox = OHCI_XFER2OXFER(xfer);
+ ox->ox_abintrs = OHCI_SF;
+ TAILQ_INSERT_TAIL(&sc->sc_abortingxfers, ox, ox_abnext);
+
+ OWRITE4(sc, OHCI_INTERRUPT_STATUS, OHCI_SF);
+ sc->sc_eintrs |= OHCI_SF;
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_SF);
+ /*
+ * Step 2: Wait until we know hardware has finished any possible
+ * use of the xfer.
+ */
+ while (ox->ox_abintrs != 0) {
+ DPRINTFN(10, "WDH %p xfer %p intrs %#x", sc, xfer,
+ ox->ox_abintrs, 0);
+ cv_wait(&sc->sc_softwake_cv, &sc->sc_lock);
+ }
+ } else {
+ DPRINTFN(1, "halted ed=%p", sed, 0, 0, 0);
+ }
/*
* Step 3: Remove any vestiges of the xfer from the hardware.
@@ -2282,12 +2370,34 @@ ohci_abort_xfer(struct usbd_xfer *xfer,
}
DPRINTF("--- dump end ---", 0, 0, 0, 0);
#endif
+
+#define OHCI_CC_ACCESSED_P(x) (((x) & OHCI_CC_NOT_ACCESSED_MASK) != OHCI_CC_NOT_ACCESSED)
headp = O32TOH(sed->ed.ed_headp) & OHCI_HEADMASK;
hit = 0;
for (; p->xfer == xfer; p = n) {
hit |= headp == p->physaddr;
n = p->nexttd;
- ohci_hash_rem_td(sc, p);
+
+ int cc = OHCI_TD_GET_CC(O32TOH(p->td.td_flags));
+ if (!OHCI_CC_ACCESSED_P(cc)) {
+ ohci_hash_rem_td(sc, p);
+ continue;
+ }
+ DPRINTFN(10, "std=%p has been touched by HC", p, 0, 0, 0);
+
+ mutex_exit(&sc->sc_lock);
+ ohci_soft_td_t *std = ohci_alloc_std(sc);
+ if (std == NULL) {
+ /* XXX What to do??? */
+ panic("hmm");
+ }
+ mutex_enter(&sc->sc_lock);
+
+ DPRINTFN(10, "new std=%p now held at %p", std, p->held, 0, 0);
+ *(p->held) = std;
+ std->held = p->held;
+ std->xfer = xfer;
+ p->held = NULL;
}
/* Zap headp register if hardware pointed inside the xfer. */
if (hit) {
@@ -2620,6 +2730,11 @@ ohci_device_ctrl_init(struct usbd_xfer *
ox->ox_setup = setup;
ox->ox_stat = stat;
ox->ox_nstd = 0;
+ setup->held = &ox->ox_setup;
+ stat->held = &ox->ox_stat;
+
+ DPRINTFN(10, "xfer=%p setup=%p held at %p", ox, setup, setup->held, 0);
+ DPRINTFN(10, "xfer=%p stat= %p held at %p", ox, stat, stat->held, 0);
/* Set up data transaction */
if (len != 0) {
@@ -2723,13 +2838,19 @@ ohci_device_ctrl_start(struct usbd_xfer
setup = opipe->tail.td;
opipe->tail.td = ox->ox_setup;
ox->ox_setup = setup;
+ setup->held = &ox->ox_setup;
+
+ DPRINTFN(10, "xfer=%p new setup=%p held at %p", ox, setup, setup->held, 0);
stat = ox->ox_stat;
/* point at sentinel */
tail = opipe->tail.td;
+ tail->held = &opipe->tail.td;
sed = opipe->sed;
+ DPRINTFN(10, "xfer=%p new tail=%p held at %p", ox, tail, tail->held, 0);
+
KASSERTMSG(OHCI_ED_GET_FA(O32TOH(sed->ed.ed_flags)) == dev->ud_addr,
"address ED %d pipe %d\n",
OHCI_ED_GET_FA(O32TOH(sed->ed.ed_flags)), dev->ud_addr);
@@ -2990,13 +3111,17 @@ ohci_device_bulk_start(struct usbd_xfer
data = opipe->tail.td;
opipe->tail.td = ox->ox_stds[0];
ox->ox_stds[0] = data;
+ data->held = &ox->ox_stds[0];
ohci_reset_std_chain(sc, xfer, len, isread, data, &last);
+ DPRINTFN(10, "xfer=%p new data=%p held at %p", ox, data, data->held, 0);
/* point at sentinel */
tail = opipe->tail.td;
memset(&tail->td, 0, sizeof(tail->td));
+ tail->held = &opipe->tail.td;
tail->nexttd = NULL;
tail->xfer = NULL;
+ DPRINTFN(10, "xfer=%p new tail=%p held at %p", ox, tail, tail->held, 0);
usb_syncmem(&tail->dma, tail->offs, sizeof(tail->td),
BUS_DMASYNC_PREWRITE);
xfer->ux_hcpriv = data;
@@ -3187,13 +3312,17 @@ ohci_device_intr_start(struct usbd_xfer
data = opipe->tail.td;
opipe->tail.td = ox->ox_stds[0];
ox->ox_stds[0] = data;
+ data->held = &ox->ox_stds[0];
ohci_reset_std_chain(sc, xfer, len, isread, data, &last);
+ DPRINTFN(10, "xfer=%p new data=%p held at %p", ox, data, data->held, 0);
/* point at sentinel */
tail = opipe->tail.td;
memset(&tail->td, 0, sizeof(tail->td));
+ tail->held = &opipe->tail.td;
tail->nexttd = NULL;
tail->xfer = NULL;
+ DPRINTFN(10, "xfer=%p new tail=%p held at %p", ox, tail, tail->held, 0);
usb_syncmem(&tail->dma, tail->offs, sizeof(tail->td),
BUS_DMASYNC_PREWRITE);
xfer->ux_hcpriv = data;
@@ -3392,8 +3521,10 @@ ohci_device_isoc_init(struct usbd_xfer *
goto fail;
}
ox->ox_sitds[i] = sitd;
+ sitd->held = &ox->ox_sitds[i];
sitd->xfer = xfer;
sitd->flags = 0;
+// DPRINTFN(10, "xfer=%p new tail=%p held at %p", ox, tail, tail->held, 0);
}
return 0;
@@ -3487,6 +3618,7 @@ ohci_device_isoc_enter(struct usbd_xfer
sitd = opipe->tail.itd;
opipe->tail.itd = ox->ox_sitds[0];
ox->ox_sitds[0] = sitd;
+ sitd->held = &ox->ox_sitds[0];
buf = DMAADDR(&xfer->ux_dmabuf, 0);
bp0 = OHCI_PAGE(buf);
@@ -3536,6 +3668,7 @@ ohci_device_isoc_enter(struct usbd_xfer
/* point at sentinel */
tail = opipe->tail.itd;
memset(&tail->itd, 0, sizeof(tail->itd));
+ tail->held = &opipe->tail.itd;
tail->nextitd = NULL;
tail->xfer = NULL;
usb_syncmem(&tail->dma, tail->offs, sizeof(tail->itd),
Index: src/sys/dev/usb/ohcivar.h
diff -u src/sys/dev/usb/ohcivar.h:1.55.6.14 src/sys/dev/usb/ohcivar.h:1.55.6.15
--- src/sys/dev/usb/ohcivar.h:1.55.6.14 Sun May 29 08:44:31 2016
+++ src/sys/dev/usb/ohcivar.h Mon May 30 06:46:50 2016
@@ -1,4 +1,4 @@
-/* $NetBSD: ohcivar.h,v 1.55.6.14 2016/05/29 08:44:31 skrll Exp $ */
+/* $NetBSD: ohcivar.h,v 1.55.6.15 2016/05/30 06:46:50 skrll Exp $ */
/*
* Copyright (c) 1998 The NetBSD Foundation, Inc.
@@ -50,6 +50,7 @@ typedef struct ohci_soft_td {
ohci_td_t td;
struct ohci_soft_td *nexttd; /* mirrors nexttd in TD */
struct ohci_soft_td *dnext; /* next in done list */
+ struct ohci_soft_td **held; /* where the ref to this std is held */
ohci_physaddr_t physaddr;
usb_dma_t dma;
int offs;
@@ -68,6 +69,7 @@ typedef struct ohci_soft_itd {
ohci_itd_t itd;
struct ohci_soft_itd *nextitd; /* mirrors nexttd in ITD */
struct ohci_soft_itd *dnext; /* next in done list */
+ struct ohci_soft_itd **held; /* where the ref to this sitd is held */
ohci_physaddr_t physaddr;
usb_dma_t dma;
int offs;
@@ -108,6 +110,8 @@ typedef struct ohci_softc {
LIST_HEAD(, ohci_soft_td) sc_hash_tds[OHCI_HASH_SIZE];
LIST_HEAD(, ohci_soft_itd) sc_hash_itds[OHCI_HASH_SIZE];
+ TAILQ_HEAD(, ohci_xfer) sc_abortingxfers;
+
int sc_noport;
int sc_endian;
@@ -118,7 +122,6 @@ typedef struct ohci_softc {
int sc_flags;
#define OHCIF_SUPERIO 0x0001
- char sc_softwake;
kcondvar_t sc_softwake_cv;
ohci_soft_ed_t *sc_freeeds;
@@ -145,6 +148,8 @@ typedef struct ohci_softc {
struct ohci_xfer {
struct usbd_xfer xfer;
+ uint32_t ox_abintrs;
+ TAILQ_ENTRY(ohci_xfer) ox_abnext;
/* ctrl */
ohci_soft_td_t *ox_setup;
ohci_soft_td_t *ox_stat;