pull across abort fixes from nick-nhusb. add more abort fixes, using ideas from Taylor and Nick, and myself. changes from nhusb were commited with these logs: -- Move the struct usb_task to struct usbd_xfer for everyone to use. -- Set device transfer status to USBD_IN_PROGRESS if start methods succeeds -- Actually set the transfer status on transfers in ohci_abort_xfer and the controller is dying -- Don't supply the lock to callout_halt when polling as it won't be held -- Improve transfer abort -- Mark device transfers as USBD_IN_PROGRESS appropriately and improve abort handling -- #ifdef DIAGNOSTIC -> KASSERT and add another KASSERT -- additional changes include: - initialise the usb abort task in the HCI allocx routine, so that it can be safely usb_rem_task()'d. - for cancelled aborts, always use callout_halt() and usb_rem_task_wait(). - reorder the phases of the HCI abort_task and make each of them similar. (a future change should consolideate most of this code into a usbd function, but this change doesn't require API changes and thus is good for older branches.) - remove UXFER_ABORTWAIT handling and just always cv_broadcast(). - usb_rem_task() in normal completion handlers, just in case. can't use usb_rem_task_wait() as this is usually in a softintr thread. tested on: - multiple PC systems with several types of devices: ugen/UPS, ucom, umass with disk, ssd and cdrom backends, kbd, ms, using uhci, ehci and xhci. - pegasosII: kbs/ms/umass disk on uhci. - erlite3: sd@umass on dwc2. missing testing: - ohci. Index: dev/usb/ehci.c =================================================================== RCS file: /cvsroot/src/sys/dev/usb/ehci.c,v retrieving revision 1.259 diff -p -u -r1.259 ehci.c --- dev/usb/ehci.c 6 Jun 2018 01:49:09 -0000 1.259 +++ dev/usb/ehci.c 7 Aug 2018 21:19:48 -0000 @@ -412,8 +412,7 @@ ehci_init(ehci_softc_t *sc) mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB); mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_USB); - cv_init(&sc->sc_softwake_cv, "ehciab"); - cv_init(&sc->sc_doorbell, "ehcidi"); + cv_init(&sc->sc_doorbell, "ehcidb"); sc->sc_xferpool = pool_cache_init(sizeof(struct ehci_xfer), 0, 0, 0, "ehcixfer", NULL, IPL_USB, NULL, NULL, NULL); @@ -757,6 +756,7 @@ Static void ehci_doorbell(void *addr) { ehci_softc_t *sc = addr; + EHCIHIST_FUNC(); EHCIHIST_CALLED(); mutex_enter(&sc->sc_lock); cv_broadcast(&sc->sc_doorbell); @@ -859,11 +859,6 @@ ehci_softintr(void *v) !TAILQ_EMPTY(&sc->sc_intrhead)) callout_reset(&sc->sc_tmo_intrlist, hz, ehci_intrlist_timeout, sc); - - if (sc->sc_softwake) { - sc->sc_softwake = 0; - cv_broadcast(&sc->sc_softwake_cv); - } } Static void @@ -945,7 +940,6 @@ ehci_check_qh_intr(ehci_softc_t *sc, str } done: DPRINTFN(10, "ex=%#jx done", (uintptr_t)ex, 0, 0, 0); - callout_stop(&ex->ex_xfer.ux_callout); ehci_idone(ex, cq); } @@ -992,7 +986,6 @@ ehci_check_itd_intr(ehci_softc_t *sc, st return; done: DPRINTF("ex %#jx done", (uintptr_t)ex, 0, 0, 0); - callout_stop(&ex->ex_xfer.ux_callout); ehci_idone(ex, cq); } @@ -1030,7 +1023,6 @@ ehci_check_sitd_intr(ehci_softc_t *sc, s return; DPRINTFN(10, "ex=%#jx done", (uintptr_t)ex, 0, 0, 0); - callout_stop(&(ex->ex_xfer.ux_callout)); ehci_idone(ex, cq); } @@ -1038,19 +1030,26 @@ ehci_check_sitd_intr(ehci_softc_t *sc, s Static void ehci_idone(struct ehci_xfer *ex, ex_completeq_t *cq) { + EHCIHIST_FUNC(); EHCIHIST_CALLED(); struct usbd_xfer *xfer = &ex->ex_xfer; struct ehci_pipe *epipe = EHCI_XFER2EPIPE(xfer); struct ehci_softc *sc = EHCI_XFER2SC(xfer); ehci_soft_qtd_t *sqtd, *fsqtd, *lsqtd; uint32_t status = 0, nstatus = 0; int actlen = 0; + bool polling = sc->sc_bus.ub_usepolling; - EHCIHIST_FUNC(); EHCIHIST_CALLED(); - - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); + KASSERT(polling || mutex_owned(&sc->sc_lock)); DPRINTF("ex=%#jx", (uintptr_t)ex, 0, 0, 0); + /* + * Make sure the timeout handler didn't run or ran to the end + * and set the transfer status. + */ + callout_halt(&xfer->ux_callout, polling ? NULL : &sc->sc_lock); + /* XXX wait */ + usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); if (xfer->ux_status == USBD_CANCELLED || xfer->ux_status == USBD_TIMEOUT) { DPRINTF("aborted xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); @@ -1340,7 +1339,6 @@ ehci_detach(struct ehci_softc *sc, int f kmem_free(sc->sc_softitds, sc->sc_flsize * sizeof(ehci_soft_itd_t *)); cv_destroy(&sc->sc_doorbell); - cv_destroy(&sc->sc_softwake_cv); #if 0 /* XXX destroyed in ehci_pci.c as it controls ehci_intr access */ @@ -1519,6 +1517,10 @@ ehci_allocx(struct usbd_bus *bus, unsign xfer = pool_cache_get(sc->sc_xferpool, PR_WAITOK); if (xfer != NULL) { memset(xfer, 0, sizeof(struct ehci_xfer)); + + /* Initialise this always so we can call remove on it. */ + usb_init_task(&xfer->ux_aborttask, ehci_timeout_task, xfer, + USB_TASKQ_MPSAFE); #ifdef DIAGNOSTIC struct ehci_xfer *ex = EHCI_XFER2EXFER(xfer); ex->ex_isdone = true; @@ -2172,6 +2174,7 @@ ehci_sync_hc(ehci_softc_t *sc) DPRINTF("dying", 0, 0, 0, 0); return; } + /* ask for doorbell */ EOWRITE4(sc, EHCI_USBCMD, EOREAD4(sc, EHCI_USBCMD) | EHCI_CMD_IAAD); DPRINTF("cmd = 0x%08jx sts = 0x%08jx", @@ -3103,20 +3106,24 @@ ehci_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. - * 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. + * Cancel or timeout 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 + * + * 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 QH halted bit and wait for the ehci + * door bell */ Static void ehci_abort_xfer(struct usbd_xfer *xfer, usbd_status status) { + EHCIHIST_FUNC(); EHCIHIST_CALLED(); struct ehci_pipe *epipe = EHCI_XFER2EPIPE(xfer); struct ehci_xfer *exfer = EHCI_XFER2EXFER(xfer); ehci_softc_t *sc = EHCI_XFER2SC(xfer); @@ -3125,48 +3132,48 @@ ehci_abort_xfer(struct usbd_xfer *xfer, ehci_physaddr_t cur; uint32_t qhstatus; int hit; - int wake; - EHCIHIST_FUNC(); EHCIHIST_CALLED(); + KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT), + "invalid status for abort: %d", (int)status); DPRINTF("xfer=%#jx pipe=%#jx", (uintptr_t)xfer, (uintptr_t)epipe, 0, 0); KASSERT(mutex_owned(&sc->sc_lock)); ASSERT_SLEEPABLE(); - if (sc->sc_dying) { - /* If we're dying, just do the software part. */ - xfer->ux_status = status; /* make software ignore it */ - callout_stop(&xfer->ux_callout); - usb_transfer_complete(xfer); - return; - } - /* * If an abort is already in progress then just wait for it to * complete and return. */ if (xfer->ux_hcflags & UXFER_ABORTING) { - DPRINTF("already aborting", 0, 0, 0, 0); -#ifdef DIAGNOSTIC - if (status == USBD_TIMEOUT) - printf("ehci_abort_xfer: TIMEOUT while aborting\n"); -#endif - /* Override the status which might be USBD_TIMEOUT. */ - xfer->ux_status = status; DPRINTF("waiting for abort to finish", 0, 0, 0, 0); - xfer->ux_hcflags |= UXFER_ABORTWAIT; while (xfer->ux_hcflags & UXFER_ABORTING) cv_wait(&xfer->ux_hccv, &sc->sc_lock); - return; + goto done; } 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. + */ + xfer->ux_status = status; + if (status == USBD_CANCELLED) { + callout_halt(&xfer->ux_callout, &sc->sc_lock); + usb_rem_task_wait(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, + USB_TASKQ_HC, &sc->sc_lock); + KASSERT(xfer->ux_status == status); + } + + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + goto dying; + } + + /* + * Step 2: Make interrupt routine and hardware ignore xfer. */ - xfer->ux_status = status; /* make software ignore it */ - callout_stop(&xfer->ux_callout); ehci_del_intr_list(sc, exfer); usb_syncmem(&sqh->dma, @@ -3202,17 +3209,13 @@ ehci_abort_xfer(struct usbd_xfer *xfer, } /* - * Step 2: Wait until we know hardware has finished any possible - * use of the xfer. Also make sure the soft interrupt routine - * has run. + * Step 3: Wait until we know hardware has finished any possible + * use of the xfer. */ ehci_sync_hc(sc); - sc->sc_softwake = 1; - usb_schedsoftintr(&sc->sc_bus); - cv_wait(&sc->sc_softwake_cv, &sc->sc_lock); /* - * Step 3: Remove any vestiges of the xfer from the hardware. + * Step 4: Remove any vestiges of the xfer from the hardware. * The complication here is that the hardware may have executed * beyond the xfer we're trying to abort. So as we're scanning * the TDs of this xfer we check if the hardware points to @@ -3253,17 +3256,17 @@ ehci_abort_xfer(struct usbd_xfer *xfer, } /* - * Step 4: Execute callback. + * Step 5: Notify completion to waiting xfers. */ +dying: #ifdef DIAGNOSTIC exfer->ex_isdone = true; #endif - wake = xfer->ux_hcflags & UXFER_ABORTWAIT; - xfer->ux_hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT); usb_transfer_complete(xfer); - if (wake) { - cv_broadcast(&xfer->ux_hccv); - } + xfer->ux_hcflags &= ~UXFER_ABORTING; + cv_broadcast(&xfer->ux_hccv); +done: + DPRINTFN(14, "end", 0, 0, 0, 0); KASSERT(mutex_owned(&sc->sc_lock)); } @@ -3271,14 +3274,16 @@ ehci_abort_xfer(struct usbd_xfer *xfer, Static void ehci_abort_isoc_xfer(struct usbd_xfer *xfer, usbd_status status) { + EHCIHIST_FUNC(); EHCIHIST_CALLED(); ehci_isoc_trans_t trans_status; struct ehci_xfer *exfer; ehci_softc_t *sc; struct ehci_soft_itd *itd; struct ehci_soft_sitd *sitd; - int i, wake; + int i; - EHCIHIST_FUNC(); EHCIHIST_CALLED(); + KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT), + "invalid status for abort: %d", (int)status); exfer = EHCI_XFER2EXFER(xfer); sc = EHCI_XFER2SC(xfer); @@ -3287,33 +3292,41 @@ ehci_abort_isoc_xfer(struct usbd_xfer *x (uintptr_t)xfer->ux_pipe, 0, 0); KASSERT(mutex_owned(&sc->sc_lock)); + ASSERT_SLEEPABLE(); - if (sc->sc_dying) { - xfer->ux_status = status; - callout_stop(&xfer->ux_callout); - usb_transfer_complete(xfer); - return; - } - + /* + * If an abort is already in progress then just wait for it to + * complete and return. + */ if (xfer->ux_hcflags & UXFER_ABORTING) { - DPRINTF("already aborting", 0, 0, 0, 0); - -#ifdef DIAGNOSTIC - if (status == USBD_TIMEOUT) - printf("ehci_abort_isoc_xfer: TIMEOUT while aborting\n"); -#endif - - xfer->ux_status = status; DPRINTF("waiting for abort to finish", 0, 0, 0, 0); - xfer->ux_hcflags |= UXFER_ABORTWAIT; while (xfer->ux_hcflags & UXFER_ABORTING) cv_wait(&xfer->ux_hccv, &sc->sc_lock); goto done; } xfer->ux_hcflags |= UXFER_ABORTING; + /* + * 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. + */ xfer->ux_status = status; - callout_stop(&xfer->ux_callout); + if (status == USBD_CANCELLED) { + callout_halt(&xfer->ux_callout, &sc->sc_lock); + usb_rem_task_wait(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, + USB_TASKQ_HC, &sc->sc_lock); + KASSERT(xfer->ux_status == status); + } + + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + goto dying; + } + + /* + * Step 2: Make interrupt routine and hardware ignore xfer. + */ ehci_del_intr_list(sc, exfer); if (xfer->ux_pipe->up_dev->ud_speed == USB_SPEED_HIGH) { @@ -3354,53 +3367,46 @@ ehci_abort_isoc_xfer(struct usbd_xfer *x } } - sc->sc_softwake = 1; - usb_schedsoftintr(&sc->sc_bus); - cv_wait(&sc->sc_softwake_cv, &sc->sc_lock); - +dying: #ifdef DIAGNOSTIC exfer->ex_isdone = true; #endif - wake = xfer->ux_hcflags & UXFER_ABORTWAIT; - xfer->ux_hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT); usb_transfer_complete(xfer); - if (wake) { - cv_broadcast(&xfer->ux_hccv); - } + xfer->ux_hcflags &= ~UXFER_ABORTING; + cv_broadcast(&xfer->ux_hccv); done: + DPRINTFN(14, "end", 0, 0, 0, 0); + KASSERT(mutex_owned(&sc->sc_lock)); - return; } Static void ehci_timeout(void *addr) { + EHCIHIST_FUNC(); EHCIHIST_CALLED(); struct usbd_xfer *xfer = addr; - struct ehci_xfer *exfer = EHCI_XFER2EXFER(xfer); - struct usbd_pipe *pipe = xfer->ux_pipe; - struct usbd_device *dev = pipe->up_dev; ehci_softc_t *sc = EHCI_XFER2SC(xfer); - EHCIHIST_FUNC(); EHCIHIST_CALLED(); - - DPRINTF("exfer %#jx", (uintptr_t)exfer, 0, 0, 0); + DPRINTF("xfer %#jx", (uintptr_t)xfer, 0, 0, 0); #ifdef EHCI_DEBUG - if (ehcidebug >= 2) + if (ehcidebug >= 2) { + struct usbd_pipe *pipe = xfer->ux_pipe; usbd_dump_pipe(pipe); + } #endif - if (sc->sc_dying) { - mutex_enter(&sc->sc_lock); - ehci_abort_xfer(xfer, USBD_TIMEOUT); + mutex_enter(&sc->sc_lock); + if (sc->sc_dying || xfer->ux_status != USBD_CANCELLED) { + if (!sc->sc_dying) + xfer->ux_status = USBD_TIMEOUT; mutex_exit(&sc->sc_lock); return; } + mutex_exit(&sc->sc_lock); /* Execute the abort in a process context. */ - usb_init_task(&exfer->ex_aborttask, ehci_timeout_task, xfer, - USB_TASKQ_MPSAFE); - usb_add_task(dev, &exfer->ex_aborttask, USB_TASKQ_HC); + usb_add_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, USB_TASKQ_HC); } Static void @@ -4468,7 +4474,6 @@ ehci_device_fs_isoc_transfer(struct usbd ehci_add_intr_list(sc, exfer); xfer->ux_status = USBD_IN_PROGRESS; - mutex_exit(&sc->sc_lock); return USBD_IN_PROGRESS; @@ -4862,7 +4867,6 @@ ehci_device_isoc_transfer(struct usbd_xf ehci_add_intr_list(sc, exfer); xfer->ux_status = USBD_IN_PROGRESS; - mutex_exit(&sc->sc_lock); return USBD_IN_PROGRESS; Index: dev/usb/ehcivar.h =================================================================== RCS file: /cvsroot/src/sys/dev/usb/ehcivar.h,v retrieving revision 1.44 diff -p -u -r1.44 ehcivar.h --- dev/usb/ehcivar.h 9 Apr 2018 16:21:11 -0000 1.44 +++ dev/usb/ehcivar.h 7 Aug 2018 21:19:48 -0000 @@ -91,7 +91,6 @@ typedef struct ehci_soft_itd { struct ehci_xfer { struct usbd_xfer ex_xfer; - struct usb_task ex_aborttask; TAILQ_ENTRY(ehci_xfer) ex_next; /* list of active xfers */ enum { EX_NONE, @@ -208,8 +207,6 @@ typedef struct ehci_softc { uint8_t sc_istthreshold; /* ISOC Scheduling Threshold (uframes) */ struct usbd_xfer *sc_intrxfer; char sc_isreset[EHCI_MAX_PORTS]; - char sc_softwake; - kcondvar_t sc_softwake_cv; uint32_t sc_eintrs; ehci_soft_qh_t *sc_async_head; Index: dev/usb/motg.c =================================================================== RCS file: /cvsroot/src/sys/dev/usb/motg.c,v retrieving revision 1.21 diff -p -u -r1.21 motg.c --- dev/usb/motg.c 9 Apr 2018 16:21:11 -0000 1.21 +++ dev/usb/motg.c 7 Aug 2018 21:19:48 -0000 @@ -2163,7 +2163,6 @@ motg_device_xfer_abort(struct usbd_xfer if (xfer->ux_hcflags & UXFER_ABORTING) { DPRINTF("already aborting", 0, 0, 0, 0); - xfer->ux_hcflags |= UXFER_ABORTWAIT; while (xfer->ux_hcflags & UXFER_ABORTING) cv_wait(&xfer->ux_hccv, &sc->sc_lock); return; @@ -2197,9 +2196,7 @@ motg_device_xfer_abort(struct usbd_xfer } } xfer->ux_status = USBD_CANCELLED; /* make software ignore it */ - wake = xfer->ux_hcflags & UXFER_ABORTWAIT; - xfer->ux_hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT); usb_transfer_complete(xfer); - if (wake) - cv_broadcast(&xfer->ux_hccv); + xfer->ux_hcflags &= ~UXFER_ABORTING; + cv_broadcast(&xfer->ux_hccv); } Index: dev/usb/ohci.c =================================================================== RCS file: /cvsroot/src/sys/dev/usb/ohci.c,v retrieving revision 1.281 diff -p -u -r1.281 ohci.c --- dev/usb/ohci.c 6 Jun 2018 01:49:09 -0000 1.281 +++ dev/usb/ohci.c 7 Aug 2018 21:19:48 -0000 @@ -382,8 +382,6 @@ ohci_detach(struct ohci_softc *sc, int f callout_halt(&sc->sc_tmo_rhsc, NULL); callout_destroy(&sc->sc_tmo_rhsc); - cv_destroy(&sc->sc_softwake_cv); - mutex_destroy(&sc->sc_lock); mutex_destroy(&sc->sc_intr_lock); @@ -788,7 +786,6 @@ ohci_init(ohci_softc_t *sc) mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB); mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_USB); - cv_init(&sc->sc_softwake_cv, "ohciab"); sc->sc_rhsc_si = softint_establish(SOFTINT_USB | SOFTINT_MPSAFE, ohci_rhsc_softint, sc); @@ -1070,6 +1067,10 @@ ohci_allocx(struct usbd_bus *bus, unsign xfer = pool_cache_get(sc->sc_xferpool, PR_WAITOK); if (xfer != NULL) { memset(xfer, 0, sizeof(struct ohci_xfer)); + + /* Initialise this always so we can call remove on it. */ + usb_init_task(&xfer->ux_aborttask, ohci_timeout_task, xfer, + USB_TASKQ_MPSAFE); #ifdef DIAGNOSTIC xfer->ux_state = XFER_BUSY; #endif @@ -1388,8 +1389,9 @@ ohci_softintr(void *v) int len, cc; int i, j, actlen, iframes, uedir; ohci_physaddr_t done; + bool polling = sc->sc_bus.ub_usepolling; - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); + KASSERT(polling || mutex_owned(&sc->sc_lock)); OHCIHIST_FUNC(); OHCIHIST_CALLED(); @@ -1459,14 +1461,23 @@ ohci_softintr(void *v) */ continue; } + + /* + * Make sure the timeout handler didn't run or ran to the end + * and set the transfer status. + */ + callout_halt(&xfer->ux_callout, polling ? NULL : &sc->sc_lock); + /* XXX wait */ + usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); + if (xfer->ux_status == USBD_CANCELLED || xfer->ux_status == USBD_TIMEOUT) { DPRINTF("cancel/timeout %#jx", (uintptr_t)xfer, 0, 0, 0); + /* Handled by abort routine. */ continue; } - callout_stop(&xfer->ux_callout); len = std->len; if (std->td.td_cbp != 0) @@ -1597,11 +1608,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); } @@ -1890,24 +1896,22 @@ void ohci_timeout(void *addr) { struct usbd_xfer *xfer = addr; - struct ohci_xfer *oxfer = OHCI_XFER2OXFER(xfer); ohci_softc_t *sc = OHCI_XFER2SC(xfer); OHCIHIST_FUNC(); OHCIHIST_CALLED(); - DPRINTF("oxfer=%#jx", (uintptr_t)oxfer, 0, 0, 0); + DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - if (sc->sc_dying) { - mutex_enter(&sc->sc_lock); - ohci_abort_xfer(xfer, USBD_TIMEOUT); + mutex_enter(&sc->sc_lock); + if (sc->sc_dying || xfer->ux_status != USBD_CANCELLED) { + if (!sc->sc_dying) + xfer->ux_status = USBD_TIMEOUT; mutex_exit(&sc->sc_lock); return; } + mutex_exit(&sc->sc_lock); /* Execute the abort in a process context. */ - usb_init_task(&oxfer->abort_task, ohci_timeout_task, addr, - USB_TASKQ_MPSAFE); - usb_add_task(xfer->ux_pipe->up_dev, &oxfer->abort_task, - USB_TASKQ_HC); + usb_add_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, USB_TASKQ_HC); } void @@ -2197,57 +2201,52 @@ 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 timeout 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) { + OHCIHIST_FUNC(); OHCIHIST_CALLED(); struct ohci_pipe *opipe = OHCI_PIPE2OPIPE(xfer->ux_pipe); ohci_softc_t *sc = OHCI_XFER2SC(xfer); ohci_soft_ed_t *sed = opipe->sed; ohci_soft_td_t *p, *n; ohci_physaddr_t headp; int hit; - int wake; - OHCIHIST_FUNC(); OHCIHIST_CALLED(); + KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT), + "invalid status for abort: %d", (int)status); + DPRINTF("xfer=%#jx pipe=%#jx sed=%#jx", (uintptr_t)xfer, (uintptr_t)opipe, (uintptr_t)sed, 0); KASSERT(mutex_owned(&sc->sc_lock)); ASSERT_SLEEPABLE(); - if (sc->sc_dying) { - /* If we're dying, just do the software part. */ - xfer->ux_status = status; /* make software ignore it */ - callout_halt(&xfer->ux_callout, &sc->sc_lock); - usb_transfer_complete(xfer); - return; - } - /* * If an abort is already in progress then just wait for it to * complete and return. */ if (xfer->ux_hcflags & UXFER_ABORTING) { - DPRINTFN(2, "already aborting", 0, 0, 0, 0); -#ifdef DIAGNOSTIC - if (status == USBD_TIMEOUT) - printf("%s: TIMEOUT while aborting\n", __func__); -#endif - /* Override the status which might be USBD_TIMEOUT. */ - xfer->ux_status = status; DPRINTFN(2, "waiting for abort to finish", 0, 0, 0, 0); - xfer->ux_hcflags |= UXFER_ABORTWAIT; while (xfer->ux_hcflags & UXFER_ABORTING) cv_wait(&xfer->ux_hccv, &sc->sc_lock); goto done; @@ -2255,10 +2254,33 @@ 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. + */ + xfer->ux_status = status; + if (status == USBD_CANCELLED) { + callout_halt(&xfer->ux_callout, &sc->sc_lock); + usb_rem_task_wait(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, + USB_TASKQ_HC, &sc->sc_lock); + KASSERT(xfer->ux_status == status); + } + + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + DPRINTFN(4, "xfer %#jx dying %ju", (uintptr_t)xfer, + xfer->ux_status, 0, 0); + goto dying; + } + + /* + * 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=%#jx", (uintptr_t)sed, 0, 0, 0); usb_syncmem(&sed->dma, sed->offs + offsetof(ohci_ed_t, ed_flags), sizeof(sed->ed.ed_flags), @@ -2269,18 +2291,14 @@ ohci_abort_xfer(struct usbd_xfer *xfer, 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. + * Step 3: Wait until we know hardware has finished any possible + * use of the xfer. */ /* 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); /* - * Step 3: Remove any vestiges of the xfer from the hardware. + * Step 4: Remove any vestiges of the xfer from the hardware. * The complication here is that the hardware may have executed * beyond the xfer we're trying to abort. So as we're scanning * the TDs of this xfer we check if the hardware points to @@ -2320,7 +2338,7 @@ ohci_abort_xfer(struct usbd_xfer *xfer, } /* - * Step 4: Turn on hardware again. + * Step 5: Turn on hardware again. */ usb_syncmem(&sed->dma, sed->offs + offsetof(ohci_ed_t, ed_flags), sizeof(sed->ed.ed_flags), @@ -2331,15 +2349,15 @@ ohci_abort_xfer(struct usbd_xfer *xfer, BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); /* - * Step 5: Execute callback. + * Step 6: Notify completion to waiting xfers. */ - wake = xfer->ux_hcflags & UXFER_ABORTWAIT; - xfer->ux_hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT); +dying: usb_transfer_complete(xfer); - if (wake) - cv_broadcast(&xfer->ux_hccv); - + xfer->ux_hcflags &= ~UXFER_ABORTING; + cv_broadcast(&xfer->ux_hccv); done: + DPRINTFN(14, "end", 0, 0, 0, 0); + KASSERT(mutex_owned(&sc->sc_lock)); } @@ -2839,6 +2857,7 @@ ohci_device_ctrl_start(struct usbd_xfer DPRINTF("done", 0, 0, 0, 0); + xfer->ux_status = USBD_IN_PROGRESS; mutex_exit(&sc->sc_lock); return USBD_IN_PROGRESS; @@ -3047,6 +3066,8 @@ ohci_device_bulk_start(struct usbd_xfer callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), ohci_timeout, xfer); } + + xfer->ux_status = USBD_IN_PROGRESS; mutex_exit(&sc->sc_lock); return USBD_IN_PROGRESS; @@ -3232,6 +3253,7 @@ ohci_device_intr_start(struct usbd_xfer usb_syncmem(&sed->dma, sed->offs, sizeof(sed->ed), BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); + xfer->ux_status = USBD_IN_PROGRESS; mutex_exit(&sc->sc_lock); return USBD_IN_PROGRESS; Index: dev/usb/ohcivar.h =================================================================== RCS file: /cvsroot/src/sys/dev/usb/ohcivar.h,v retrieving revision 1.59 diff -p -u -r1.59 ohcivar.h --- dev/usb/ohcivar.h 9 Apr 2018 16:21:11 -0000 1.59 +++ dev/usb/ohcivar.h 7 Aug 2018 21:19:48 -0000 @@ -118,9 +118,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; ohci_soft_td_t *sc_freetds; ohci_soft_itd_t *sc_freeitds; @@ -142,7 +139,6 @@ typedef struct ohci_softc { struct ohci_xfer { struct usbd_xfer xfer; - struct usb_task abort_task; /* ctrl */ ohci_soft_td_t *ox_setup; ohci_soft_td_t *ox_stat; Index: dev/usb/uhci.c =================================================================== RCS file: /cvsroot/src/sys/dev/usb/uhci.c,v retrieving revision 1.280 diff -p -u -r1.280 uhci.c --- dev/usb/uhci.c 9 Apr 2018 16:21:11 -0000 1.280 +++ dev/usb/uhci.c 7 Aug 2018 21:19:48 -0000 @@ -574,8 +574,6 @@ uhci_init(uhci_softc_t *sc) callout_init(&sc->sc_poll_handle, CALLOUT_MPSAFE); - cv_init(&sc->sc_softwake_cv, "uhciab"); - /* Set up the bus struct. */ sc->sc_bus.ub_methods = &uhci_bus_methods; sc->sc_bus.ub_pipesize = sizeof(struct uhci_pipe); @@ -639,8 +637,6 @@ uhci_detach(struct uhci_softc *sc, int f callout_halt(&sc->sc_poll_handle, NULL); callout_destroy(&sc->sc_poll_handle); - cv_destroy(&sc->sc_softwake_cv); - mutex_destroy(&sc->sc_lock); mutex_destroy(&sc->sc_intr_lock); @@ -661,6 +657,9 @@ uhci_allocx(struct usbd_bus *bus, unsign if (xfer != NULL) { memset(xfer, 0, sizeof(struct uhci_xfer)); + /* Initialise this always so we can call remove on it. */ + usb_init_task(&xfer->ux_aborttask, uhci_timeout_task, xfer, + USB_TASKQ_MPSAFE); #ifdef DIAGNOSTIC struct uhci_xfer *uxfer = UHCI_XFER2UXFER(xfer); uxfer->ux_isdone = true; @@ -1422,11 +1421,6 @@ uhci_softintr(void *v) DPRINTF("ux %#jx", (uintptr_t)ux, 0, 0, 0); usb_transfer_complete(&ux->ux_xfer); } - - if (sc->sc_softwake) { - sc->sc_softwake = 0; - cv_broadcast(&sc->sc_softwake_cv); - } } /* Check for an interrupt. */ @@ -1482,8 +1476,6 @@ uhci_check_intr(uhci_softc_t *sc, struct if (!(status & UHCI_TD_ACTIVE)) { done: DPRINTFN(12, "ux=%#jx done", (uintptr_t)ux, 0, 0, 0); - - callout_stop(&xfer->ux_callout); uhci_idone(ux, cqp); return; } @@ -1555,18 +1547,32 @@ uhci_check_intr(uhci_softc_t *sc, struct void uhci_idone(struct uhci_xfer *ux, ux_completeq_t *cqp) { + UHCIHIST_FUNC(); UHCIHIST_CALLED(); struct usbd_xfer *xfer = &ux->ux_xfer; uhci_softc_t *sc __diagused = UHCI_XFER2SC(xfer); struct uhci_pipe *upipe = UHCI_PIPE2UPIPE(xfer->ux_pipe); uhci_soft_td_t *std; uint32_t status = 0, nstatus; + bool polling = sc->sc_bus.ub_usepolling; int actlen; KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); - UHCIHIST_FUNC(); UHCIHIST_CALLED(); DPRINTFN(12, "ux=%#jx", (uintptr_t)ux, 0, 0, 0); + /* + * Make sure the timeout handler didn't run or ran to the end + * and set the transfer status. + */ + callout_halt(&xfer->ux_callout, polling ? NULL : &sc->sc_lock); + /* XXX wait */ + usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); + if (xfer->ux_status == USBD_CANCELLED || + xfer->ux_status == USBD_TIMEOUT) { + DPRINTF("aborted xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); + return; + } + #ifdef DIAGNOSTIC #ifdef UHCI_DEBUG if (ux->ux_isdone) { @@ -1701,26 +1707,23 @@ uhci_idone(struct uhci_xfer *ux, ux_comp void uhci_timeout(void *addr) { + UHCIHIST_FUNC(); UHCIHIST_CALLED(); struct usbd_xfer *xfer = addr; - struct uhci_xfer *uxfer = UHCI_XFER2UXFER(xfer); uhci_softc_t *sc = UHCI_XFER2SC(xfer); - UHCIHIST_FUNC(); UHCIHIST_CALLED(); - - DPRINTF("uxfer %#jx", (uintptr_t)uxfer, 0, 0, 0); + DPRINTF("xfer %#jx", (uintptr_t)xfer, 0, 0, 0); - if (sc->sc_dying) { - mutex_enter(&sc->sc_lock); - uhci_abort_xfer(xfer, USBD_TIMEOUT); + mutex_enter(&sc->sc_lock); + if (sc->sc_dying || xfer->ux_status != USBD_CANCELLED) { + if (!sc->sc_dying) + xfer->ux_status = USBD_TIMEOUT; mutex_exit(&sc->sc_lock); return; } + mutex_exit(&sc->sc_lock); /* Execute the abort in a process context. */ - usb_init_task(&uxfer->ux_aborttask, uhci_timeout_task, xfer, - USB_TASKQ_MPSAFE); - usb_add_task(uxfer->ux_xfer.ux_pipe->up_dev, &uxfer->ux_aborttask, - USB_TASKQ_HC); + usb_add_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, USB_TASKQ_HC); } void @@ -2329,54 +2332,42 @@ uhci_device_bulk_abort(struct usbd_xfer } /* - * 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 timeout 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). + * + * To allow the hardware time to notice we simply wait. */ void uhci_abort_xfer(struct usbd_xfer *xfer, usbd_status status) { + UHCIHIST_FUNC(); UHCIHIST_CALLED(); struct uhci_xfer *ux = UHCI_XFER2UXFER(xfer); struct uhci_pipe *upipe = UHCI_PIPE2UPIPE(xfer->ux_pipe); uhci_softc_t *sc = UHCI_XFER2SC(xfer); uhci_soft_td_t *std; - int wake; - UHCIHIST_FUNC(); UHCIHIST_CALLED(); + KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT), + "invalid status for abort: %d", (int)status); + DPRINTFN(1,"xfer=%#jx, status=%jd", (uintptr_t)xfer, status, 0, 0); KASSERT(mutex_owned(&sc->sc_lock)); ASSERT_SLEEPABLE(); - if (sc->sc_dying) { - /* If we're dying, just do the software part. */ - xfer->ux_status = status; /* make software ignore it */ - callout_stop(&xfer->ux_callout); - usb_transfer_complete(xfer); - return; - } - /* * If an abort is already in progress then just wait for it to * complete and return. */ if (xfer->ux_hcflags & UXFER_ABORTING) { - DPRINTFN(2, "already aborting", 0, 0, 0, 0); -#ifdef DIAGNOSTIC - if (status == USBD_TIMEOUT) - printf("%s: TIMEOUT while aborting\n", __func__); -#endif - /* Override the status which might be USBD_TIMEOUT. */ - xfer->ux_status = status; DPRINTFN(2, "waiting for abort to finish", 0, 0, 0, 0); - xfer->ux_hcflags |= UXFER_ABORTWAIT; while (xfer->ux_hcflags & UXFER_ABORTING) cv_wait(&xfer->ux_hccv, &sc->sc_lock); goto done; @@ -2384,10 +2375,28 @@ uhci_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. + */ + xfer->ux_status = status; + if (status == USBD_CANCELLED) { + callout_halt(&xfer->ux_callout, &sc->sc_lock); + usb_rem_task_wait(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, + USB_TASKQ_HC, &sc->sc_lock); + KASSERT(xfer->ux_status == status); + } + + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + DPRINTFN(4, "xfer %#jx dying %ju", (uintptr_t)xfer, + xfer->ux_status, 0, 0); + goto dying; + } + + /* + * Step 2: Make interrupt routine and hardware ignore xfer. */ - xfer->ux_status = status; /* make software ignore it */ - callout_stop(&xfer->ux_callout); uhci_del_intr_list(sc, ux); DPRINTF("stop ux=%#jx", (uintptr_t)ux, 0, 0, 0); @@ -2404,30 +2413,26 @@ uhci_abort_xfer(struct usbd_xfer *xfer, } /* - * Step 2: Wait until we know hardware has finished any possible - * use of the xfer. Also make sure the soft interrupt routine - * has run. + * Step 3: Wait until we know hardware has finished any possible + * use of the xfer. */ /* Hardware finishes in 1ms */ usb_delay_ms_locked(upipe->pipe.up_dev->ud_bus, 2, &sc->sc_lock); - sc->sc_softwake = 1; - usb_schedsoftintr(&sc->sc_bus); - DPRINTF("cv_wait", 0, 0, 0, 0); - cv_wait(&sc->sc_softwake_cv, &sc->sc_lock); /* - * Step 3: Execute callback. + * Step 4: Notify completion to waiting xfers. */ +dying: DPRINTF("callback", 0, 0, 0, 0); #ifdef DIAGNOSTIC ux->ux_isdone = true; #endif - wake = xfer->ux_hcflags & UXFER_ABORTWAIT; - xfer->ux_hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT); usb_transfer_complete(xfer); - if (wake) - cv_broadcast(&xfer->ux_hccv); + xfer->ux_hcflags &= ~UXFER_ABORTING; + cv_broadcast(&xfer->ux_hccv); done: + DPRINTFN(14, "end", 0, 0, 0, 0); + KASSERT(mutex_owned(&sc->sc_lock)); } Index: dev/usb/uhcivar.h =================================================================== RCS file: /cvsroot/src/sys/dev/usb/uhcivar.h,v retrieving revision 1.54 diff -p -u -r1.54 uhcivar.h --- dev/usb/uhcivar.h 9 Apr 2018 16:21:11 -0000 1.54 +++ dev/usb/uhcivar.h 7 Aug 2018 21:19:48 -0000 @@ -61,7 +61,6 @@ typedef union { struct uhci_xfer { struct usbd_xfer ux_xfer; - struct usb_task ux_aborttask; enum { UX_NONE, UX_CTRL, UX_BULK, UX_INTR, UX_ISOC } ux_type; @@ -152,7 +151,6 @@ typedef struct uhci_softc { kmutex_t sc_lock; kmutex_t sc_intr_lock; - kcondvar_t sc_softwake_cv; uhci_physaddr_t *sc_pframes; usb_dma_t sc_dma; @@ -175,8 +173,6 @@ typedef struct uhci_softc { uint8_t sc_saved_sof; uint16_t sc_saved_frnum; - char sc_softwake; - char sc_isreset; char sc_suspend; char sc_dying; Index: dev/usb/usbdi.c =================================================================== RCS file: /cvsroot/src/sys/dev/usb/usbdi.c,v retrieving revision 1.176 diff -p -u -r1.176 usbdi.c --- dev/usb/usbdi.c 31 Jul 2018 16:44:30 -0000 1.176 +++ dev/usb/usbdi.c 7 Aug 2018 21:19:48 -0000 @@ -912,7 +912,8 @@ usb_transfer_complete(struct usbd_xfer * xfer->ux_actlen); KASSERT(polling || mutex_owned(pipe->up_dev->ud_bus->ub_lock)); - KASSERT(xfer->ux_state == XFER_ONQU); + KASSERTMSG(xfer->ux_state == XFER_ONQU, "xfer %p state is %x", xfer, + xfer->ux_state); KASSERT(pipe != NULL); if (!repeat) { Index: dev/usb/usbdivar.h =================================================================== RCS file: /cvsroot/src/sys/dev/usb/usbdivar.h,v retrieving revision 1.116 diff -p -u -r1.116 usbdivar.h --- dev/usb/usbdivar.h 29 Jun 2018 17:48:24 -0000 1.116 +++ dev/usb/usbdivar.h 7 Aug 2018 21:19:48 -0000 @@ -287,9 +287,9 @@ struct usbd_xfer { void *ux_hcpriv; /* private use by the HC driver */ uint8_t ux_hcflags; /* private use by the HC driver */ #define UXFER_ABORTING 0x01 /* xfer is aborting. */ -#define UXFER_ABORTWAIT 0x02 /* abort completion is being awaited. */ kcondvar_t ux_hccv; /* private use by the HC driver */ + struct usb_task ux_aborttask; struct callout ux_callout; }; Index: dev/usb/xhci.c =================================================================== RCS file: /cvsroot/src/sys/dev/usb/xhci.c,v retrieving revision 1.95 diff -p -u -r1.95 xhci.c --- dev/usb/xhci.c 18 Jul 2018 10:44:17 -0000 1.95 +++ dev/usb/xhci.c 7 Aug 2018 21:19:48 -0000 @@ -1696,52 +1696,52 @@ xhci_close_pipe(struct usbd_pipe *pipe) static void xhci_abort_xfer(struct usbd_xfer *xfer, usbd_status status) { + XHCIHIST_FUNC(); XHCIHIST_CALLED(); struct xhci_softc * const sc = XHCI_XFER2SC(xfer); struct xhci_slot * const xs = xfer->ux_pipe->up_dev->ud_hcpriv; const u_int dci = xhci_ep_get_dci(xfer->ux_pipe->up_endpoint->ue_edesc); - XHCIHIST_FUNC(); XHCIHIST_CALLED(); + KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT), + "invalid status for abort: %d", (int)status); + DPRINTFN(4, "xfer %#jx pipe %#jx status %jd", (uintptr_t)xfer, (uintptr_t)xfer->ux_pipe, status, 0); KASSERT(mutex_owned(&sc->sc_lock)); - - if (sc->sc_dying) { - /* If we're dying, just do the software part. */ - DPRINTFN(4, "xfer %#jx dying %ju", (uintptr_t)xfer, - xfer->ux_status, 0, 0); - xfer->ux_status = status; - callout_stop(&xfer->ux_callout); - usb_transfer_complete(xfer); - return; - } + ASSERT_SLEEPABLE(); /* * If an abort is already in progress then just wait for it to * complete and return. */ if (xfer->ux_hcflags & UXFER_ABORTING) { - DPRINTFN(4, "already aborting", 0, 0, 0, 0); -#ifdef DIAGNOSTIC - if (status == USBD_TIMEOUT) - DPRINTFN(4, "TIMEOUT while aborting", 0, 0, 0, 0); -#endif - /* Override the status which might be USBD_TIMEOUT. */ - xfer->ux_status = status; DPRINTFN(4, "xfer %#jx waiting for abort to finish", (uintptr_t)xfer, 0, 0, 0); - xfer->ux_hcflags |= UXFER_ABORTWAIT; while (xfer->ux_hcflags & UXFER_ABORTING) cv_wait(&xfer->ux_hccv, &sc->sc_lock); - return; + goto done; } xfer->ux_hcflags |= UXFER_ABORTING; /* - * Step 1: Stop xfer timeout timer. + * 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. */ xfer->ux_status = status; - callout_stop(&xfer->ux_callout); + if (status == USBD_CANCELLED) { + callout_halt(&xfer->ux_callout, &sc->sc_lock); + usb_rem_task_wait(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, + USB_TASKQ_HC, &sc->sc_lock); + KASSERT(xfer->ux_status == status); + } + + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + DPRINTFN(4, "xfer %#jx dying %ju", (uintptr_t)xfer, + xfer->ux_status, 0, 0); + goto dying; + } /* * Step 2: Stop execution of TD on the ring. @@ -1770,12 +1770,11 @@ xhci_abort_xfer(struct usbd_xfer *xfer, /* * Step 4: Notify completion to waiting xfers. */ - int wake = xfer->ux_hcflags & UXFER_ABORTWAIT; - xfer->ux_hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT); +dying: usb_transfer_complete(xfer); - if (wake) { - cv_broadcast(&xfer->ux_hccv); - } + xfer->ux_hcflags &= ~UXFER_ABORTING; + cv_broadcast(&xfer->ux_hccv); +done: DPRINTFN(14, "end", 0, 0, 0, 0); KASSERT(mutex_owned(&sc->sc_lock)); @@ -2032,7 +2031,9 @@ xhci_event_transfer(struct xhci_softc * * UF_ENDPOINT_HALT). */ xfer->ux_status = err; - callout_stop(&xfer->ux_callout); + callout_halt(&xfer->ux_callout, &sc->sc_lock); + /* XXX wait */ + usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); xhci_clear_endpoint_stall_async(xfer); return; default: @@ -2042,12 +2043,8 @@ xhci_event_transfer(struct xhci_softc * } xfer->ux_status = err; - if ((trb_3 & XHCI_TRB_3_ED_BIT) != 0) { - if ((trb_0 & 0x3) == 0x0) { - callout_stop(&xfer->ux_callout); - usb_transfer_complete(xfer); - } - } else { + if ((trb_3 & XHCI_TRB_3_ED_BIT) == 0 || + (trb_0 & 0x3) == 0x0) { callout_stop(&xfer->ux_callout); usb_transfer_complete(xfer); } @@ -2213,6 +2210,8 @@ xhci_allocx(struct usbd_bus *bus, unsign xfer = pool_cache_get(sc->sc_xferpool, PR_WAITOK); if (xfer != NULL) { memset(xfer, 0, sizeof(struct xhci_xfer)); + usb_init_task(&xfer->ux_aborttask, xhci_timeout_task, xfer, + USB_TASKQ_MPSAFE); #ifdef DIAGNOSTIC xfer->ux_state = XFER_BUSY; #endif @@ -4093,30 +4092,31 @@ xhci_device_intr_close(struct usbd_pipe static void xhci_timeout(void *addr) { + XHCIHIST_FUNC(); XHCIHIST_CALLED(); struct xhci_xfer * const xx = addr; struct usbd_xfer * const xfer = &xx->xx_xfer; struct xhci_softc * const sc = XHCI_XFER2SC(xfer); - XHCIHIST_FUNC(); XHCIHIST_CALLED(); - - if (sc->sc_dying) { + mutex_enter(&sc->sc_lock); + if (sc->sc_dying || xfer->ux_status != USBD_CANCELLED) { + if (!sc->sc_dying) + xfer->ux_status = USBD_TIMEOUT; + mutex_exit(&sc->sc_lock); return; } + mutex_exit(&sc->sc_lock); - usb_init_task(&xx->xx_abort_task, xhci_timeout_task, addr, - USB_TASKQ_MPSAFE); - usb_add_task(xx->xx_xfer.ux_pipe->up_dev, &xx->xx_abort_task, - USB_TASKQ_HC); + /* Execute the abort in a process context. */ + usb_add_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, USB_TASKQ_HC); } static void xhci_timeout_task(void *addr) { + XHCIHIST_FUNC(); XHCIHIST_CALLED(); struct usbd_xfer * const xfer = addr; struct xhci_softc * const sc = XHCI_XFER2SC(xfer); - XHCIHIST_FUNC(); XHCIHIST_CALLED(); - mutex_enter(&sc->sc_lock); xhci_abort_xfer(xfer, USBD_TIMEOUT); mutex_exit(&sc->sc_lock); Index: dev/usb/xhcivar.h =================================================================== RCS file: /cvsroot/src/sys/dev/usb/xhcivar.h,v retrieving revision 1.9 diff -p -u -r1.9 xhcivar.h --- dev/usb/xhcivar.h 9 Apr 2018 16:21:11 -0000 1.9 +++ dev/usb/xhcivar.h 7 Aug 2018 21:19:48 -0000 @@ -35,7 +35,6 @@ struct xhci_xfer { struct usbd_xfer xx_xfer; - struct usb_task xx_abort_task; struct xhci_trb xx_trb[XHCI_XFER_NTRB]; }; @@ -85,7 +84,6 @@ struct xhci_softc { kmutex_t sc_lock; kmutex_t sc_intr_lock; - kcondvar_t sc_softwake_cv; pool_cache_t sc_xferpool; Index: external/bsd/dwc2/dwc2.c =================================================================== RCS file: /cvsroot/src/sys/external/bsd/dwc2/dwc2.c,v retrieving revision 1.49 diff -p -u -r1.49 dwc2.c --- external/bsd/dwc2/dwc2.c 9 Apr 2018 16:21:11 -0000 1.49 +++ external/bsd/dwc2/dwc2.c 7 Aug 2018 21:19:48 -0000 @@ -203,17 +203,22 @@ dwc2_allocx(struct usbd_bus *bus, unsign { struct dwc2_softc *sc = DWC2_BUS2SC(bus); struct dwc2_xfer *dxfer; + struct usbd_xfer *xfer; DPRINTFN(10, "\n"); DWC2_EVCNT_INCR(sc->sc_ev_xferpoolget); dxfer = pool_cache_get(sc->sc_xferpool, PR_WAITOK); + xfer = (struct usbd_xfer *)dxfer; if (dxfer != NULL) { memset(dxfer, 0, sizeof(*dxfer)); dxfer->urb = dwc2_hcd_urb_alloc(sc->sc_hsotg, nframes, GFP_KERNEL); + /* Initialise this always so we can call remove on it. */ + usb_init_task(&xfer->ux_aborttask, dwc2_timeout_task, addr, + USB_TASKQ_MPSAFE); #ifdef DIAGNOSTIC dxfer->xfer.ux_state = XFER_BUSY; #endif @@ -316,24 +321,21 @@ Static void dwc2_timeout(void *addr) { struct usbd_xfer *xfer = addr; - struct dwc2_xfer *dxfer = DWC2_XFER2DXFER(xfer); -// struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); struct dwc2_softc *sc = DWC2_XFER2SC(xfer); DPRINTF("dxfer=%p\n", dxfer); - if (sc->sc_dying) { - mutex_enter(&sc->sc_lock); - dwc2_abort_xfer(&dxfer->xfer, USBD_TIMEOUT); + mutex_enter(&sc->sc_lock); + if (sc->sc_dying || xfer->ux_status != USBD_CANCELLED) { + if (!sc->sc_dying) + xfer->ux_status = USBD_TIMEOUT; mutex_exit(&sc->sc_lock); return; } + mutex_exit(&sc->sc_lock); /* Execute the abort in a process context. */ - usb_init_task(&dxfer->abort_task, dwc2_timeout_task, addr, - USB_TASKQ_MPSAFE); - usb_add_task(dxfer->xfer.ux_pipe->up_dev, &dxfer->abort_task, - USB_TASKQ_HC); + usb_add_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, USB_TASKQ_HC); } Static void @@ -449,46 +451,57 @@ dwc2_abort_xfer(struct usbd_xfer *xfer, struct dwc2_softc *sc = DWC2_XFER2SC(xfer); struct dwc2_hsotg *hsotg = sc->sc_hsotg; struct dwc2_xfer *d, *tmp; - bool wake; int err; - DPRINTF("xfer=%p\n", xfer); + KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT), + "invalid status for abort: %d", (int)status); - KASSERT(mutex_owned(&sc->sc_lock)); - KASSERT(!cpu_intr_p() && !cpu_softintr_p()); + DPRINTF("xfer %pjx pipe %pjx status %jd", xfer, xfer->ux_pipe, status); - if (sc->sc_dying) { - xfer->ux_status = status; - callout_stop(&xfer->ux_callout); - usb_transfer_complete(xfer); - return; - } + KASSERT(mutex_owned(&sc->sc_lock)); + ASSERT_SLEEPABLE(); /* * If an abort is already in progress then just wait for it to * complete and return. */ if (xfer->ux_hcflags & UXFER_ABORTING) { - xfer->ux_status = status; - xfer->ux_hcflags |= UXFER_ABORTWAIT; + DPRINTF("xfer %p waiting for abort to finish", xfer); while (xfer->ux_hcflags & UXFER_ABORTING) cv_wait(&xfer->ux_hccv, &sc->sc_lock); - return; + goto done; } + xfer->ux_hcflags |= UXFER_ABORTING; /* - * Step 1: Make the stack ignore it and stop the callout. + * 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. */ - mutex_spin_enter(&hsotg->lock); - xfer->ux_hcflags |= UXFER_ABORTING; + xfer->ux_status = status; + if (status == USBD_CANCELLED) { + callout_halt(&xfer->ux_callout, &sc->sc_lock); + usb_rem_task_wait(xfer->ux_pipe->up_dev, &xfer->ux_aborttask, + USB_TASKQ_HC, &sc->sc_lock); + KASSERT(xfer->ux_status == status); + } - xfer->ux_status = status; /* make software ignore it */ - callout_stop(&xfer->ux_callout); + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + DPRINTFN(4, "xfer %#jx dying %ju", (uintptr_t)xfer, + xfer->ux_status, 0, 0); + goto dying; + } + /* + * Step 2: Handle the hardware. + */ + mutex_spin_enter(&hsotg->lock); /* XXXNH suboptimal */ TAILQ_FOREACH_SAFE(d, &sc->sc_complete, xnext, tmp) { if (d == dxfer) { TAILQ_REMOVE(&sc->sc_complete, dxfer, xnext); + break; } } @@ -500,15 +513,15 @@ dwc2_abort_xfer(struct usbd_xfer *xfer, mutex_spin_exit(&hsotg->lock); /* - * Step 2: Execute callback. + * Step 3: Notify completion to waiting xfers. */ - wake = xfer->ux_hcflags & UXFER_ABORTWAIT; - xfer->ux_hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT); - +dying: usb_transfer_complete(xfer); - if (wake) { - cv_broadcast(&xfer->ux_hccv); - } + xfer->ux_hcflags &= ~UXFER_ABORTING; + cv_broadcast(&xfer->ux_hccv); +done: + + KASSERT(mutex_owned(&sc->sc_lock)); } Static void @@ -1112,7 +1125,7 @@ dwc2_device_start(struct usbd_xfer *xfer return USBD_IN_PROGRESS; fail2: - callout_stop(&xfer->ux_callout); + callout_halt(&xfer->ux_callout, &hsotg->lock); dwc2_urb->priv = NULL; mutex_spin_exit(&hsotg->lock); pool_cache_put(sc->sc_qtdpool, qtd); @@ -1493,7 +1506,9 @@ void dwc2_host_complete(struct dwc2_hsot } qtd->urb = NULL; - callout_stop(&xfer->ux_callout); + callout_halt(&xfer->ux_callout, &hsotg->lock); + /* XXX wait */ + usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); KASSERT(mutex_owned(&hsotg->lock));