Module Name: src Committed By: riastradh Date: Wed Feb 12 16:01:00 UTC 2020
Modified Files: src/sys/arch/mips/adm5120/dev: ahci.c src/sys/dev/usb: ehci.c motg.c ohci.c uhci.c usbdi.c usbdi.h usbdivar.h xhci.c src/sys/external/bsd/dwc2: dwc2.c dwc2var.h Log Message: Factor out HCI-independent xfer completion logic. New API for HCI drivers to synchronize hardware completion interrupts, synchronous aborts, and asynchronous timeouts: - When submitting an xfer to hardware, call usbd_xfer_schedule_timeout(xfer). - On HCI completion interrupt for xfer completion: if (!usbd_xfer_trycomplete(xfer)) return; /* timed out or aborted, ignore it */ - In upm_abort methods, call usbd_xfer_abort(xfer). For HCI drivers that use this API (not needed in drivers that don't, or for xfers like root intr xfers that don't use it): - New ubm_abortx method serves role of former *hci_abort_xfer, but without any logic for wrangling timeouts/callouts/tasks -- caller in usbd_xfer_abort has already handled them. - New ubm_dying method, returns true if the device is in the process of detaching, used by the timeout logic. Converted and tested: - ehci - ohci Converted and compile-tested: - ahci (XXX did this ever work?) - dwc2 - motg (XXX missing usbd_xfer_schedule_timeout in motg_*_start?) - uhci - xhci Not changed: - slhci (sys/dev/ic/sl811hs.c) -- doesn't use a separate per-xfer callout for timeouts (XXX but maybe should?) - ugenhc (sys/rump/dev/lib/libugenhc/ugenhc.c) -- doesn't manage its own transfer timeouts - vhci -- times transfers out only on detach; could be adapted easily if we wanted to use the xfer->ux_callout To generate a diff of this commit: cvs rdiff -u -r1.17 -r1.18 src/sys/arch/mips/adm5120/dev/ahci.c cvs rdiff -u -r1.270 -r1.271 src/sys/dev/usb/ehci.c cvs rdiff -u -r1.25 -r1.26 src/sys/dev/usb/motg.c cvs rdiff -u -r1.292 -r1.293 src/sys/dev/usb/ohci.c cvs rdiff -u -r1.288 -r1.289 src/sys/dev/usb/uhci.c cvs rdiff -u -r1.191 -r1.192 src/sys/dev/usb/usbdi.c cvs rdiff -u -r1.100 -r1.101 src/sys/dev/usb/usbdi.h cvs rdiff -u -r1.121 -r1.122 src/sys/dev/usb/usbdivar.h cvs rdiff -u -r1.115 -r1.116 src/sys/dev/usb/xhci.c cvs rdiff -u -r1.66 -r1.67 src/sys/external/bsd/dwc2/dwc2.c cvs rdiff -u -r1.6 -r1.7 src/sys/external/bsd/dwc2/dwc2var.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/arch/mips/adm5120/dev/ahci.c diff -u src/sys/arch/mips/adm5120/dev/ahci.c:1.17 src/sys/arch/mips/adm5120/dev/ahci.c:1.18 --- src/sys/arch/mips/adm5120/dev/ahci.c:1.17 Sun Feb 17 04:17:52 2019 +++ src/sys/arch/mips/adm5120/dev/ahci.c Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: ahci.c,v 1.17 2019/02/17 04:17:52 rin Exp $ */ +/* $NetBSD: ahci.c,v 1.18 2020/02/12 16:01:00 riastradh Exp $ */ /*- * Copyright (c) 2007 Ruslan Ermilov and Vsevolod Lobko. @@ -64,7 +64,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ahci.c,v 1.17 2019/02/17 04:17:52 rin Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ahci.c,v 1.18 2020/02/12 16:01:00 riastradh Exp $"); #include <sys/param.h> #include <sys/systm.h> @@ -98,6 +98,7 @@ static void ahci_poll_device(void *arg) static struct usbd_xfer * ahci_allocx(struct usbd_bus *, unsigned int); static void ahci_freex(struct usbd_bus *, struct usbd_xfer *); +static void ahci_abortx(struct usbd_xfer *); static void ahci_get_lock(struct usbd_bus *, kmutex_t **); static int ahci_roothub_ctrl(struct usbd_bus *, usb_device_request_t *, @@ -136,7 +137,6 @@ static void ahci_device_bulk_done(struc static int ahci_transaction(struct ahci_softc *, struct usbd_pipe *, uint8_t, int, u_char *, uint8_t); static void ahci_noop(struct usbd_pipe *); -static void ahci_abort_xfer(struct usbd_xfer *, usbd_status); static void ahci_device_clear_toggle(struct usbd_pipe *); extern int usbdebug; @@ -169,6 +169,7 @@ struct usbd_bus_methods ahci_bus_methods .ubm_dopoll = ahci_poll, .ubm_allocx = ahci_allocx, .ubm_freex = ahci_freex, + .ubm_abortx = ahci_abortx, .ubm_getlock = ahci_get_lock, .ubm_rhctrl = ahci_roothub_ctrl, }; @@ -919,7 +920,7 @@ static void ahci_device_ctrl_abort(struct usbd_xfer *xfer) { DPRINTF(D_TRACE, ("Cab ")); - ahci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } static void @@ -1031,7 +1032,7 @@ ahci_device_intr_abort(struct usbd_xfer } else { printf("%s: sx == NULL!\n", __func__); } - ahci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } static void @@ -1247,7 +1248,7 @@ static void ahci_device_bulk_abort(struct usbd_xfer *xfer) { DPRINTF(D_TRACE, ("Bab ")); - ahci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } static void @@ -1377,11 +1378,15 @@ ahci_transaction(struct ahci_softc *sc, #endif } -void -ahci_abort_xfer(struct usbd_xfer *xfer, usbd_status status) +static void +ahci_abortx(struct usbd_xfer *xfer) { - xfer->ux_status = status; - usb_transfer_complete(xfer); + /* + * XXX This is totally busted; there's no way it can possibly + * work! All transfers are busy-waited, it seems, so there is + * no opportunity to abort. + */ + KASSERT(xfer->ux_status != USBD_IN_PROGRESS); } void Index: src/sys/dev/usb/ehci.c diff -u src/sys/dev/usb/ehci.c:1.270 src/sys/dev/usb/ehci.c:1.271 --- src/sys/dev/usb/ehci.c:1.270 Wed Feb 12 16:00:34 2020 +++ src/sys/dev/usb/ehci.c Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: ehci.c,v 1.270 2020/02/12 16:00:34 riastradh Exp $ */ +/* $NetBSD: ehci.c,v 1.271 2020/02/12 16:01:00 riastradh Exp $ */ /* * Copyright (c) 2004-2012 The NetBSD Foundation, Inc. @@ -53,7 +53,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.270 2020/02/12 16:00:34 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.271 2020/02/12 16:01:00 riastradh Exp $"); #include "ohci.h" #include "uhci.h" @@ -166,11 +166,6 @@ Static void ehci_check_itd_intr(ehci_so Static void ehci_check_sitd_intr(ehci_softc_t *, struct ehci_xfer *, ex_completeq_t *); Static void ehci_idone(struct ehci_xfer *, ex_completeq_t *); -Static void ehci_timeout(void *); -Static void ehci_timeout_task(void *); -Static bool ehci_probe_timeout(struct usbd_xfer *); -Static void ehci_schedule_timeout(struct usbd_xfer *); -Static void ehci_cancel_timeout_async(struct usbd_xfer *); Static void ehci_intrlist_timeout(void *); Static void ehci_doorbell(void *); Static void ehci_pcd(void *); @@ -180,6 +175,7 @@ Static struct usbd_xfer * Static void ehci_freex(struct usbd_bus *, struct usbd_xfer *); Static void ehci_get_lock(struct usbd_bus *, kmutex_t **); +Static bool ehci_dying(struct usbd_bus *); Static int ehci_roothub_ctrl(struct usbd_bus *, usb_device_request_t *, void *, int); @@ -281,7 +277,7 @@ Static void ehci_set_qh_qtd(ehci_soft_q Static void ehci_sync_hc(ehci_softc_t *); Static void ehci_close_pipe(struct usbd_pipe *, ehci_soft_qh_t *); -Static void ehci_abort_xfer(struct usbd_xfer *, usbd_status); +Static void ehci_abortx(struct usbd_xfer *); #ifdef EHCI_DEBUG Static ehci_softc_t *theehci; @@ -322,6 +318,8 @@ Static const struct usbd_bus_methods ehc .ubm_dopoll = ehci_poll, .ubm_allocx = ehci_allocx, .ubm_freex = ehci_freex, + .ubm_abortx = ehci_abortx, + .ubm_dying = ehci_dying, .ubm_getlock = ehci_get_lock, .ubm_rhctrl = ehci_roothub_ctrl, }; @@ -1049,22 +1047,11 @@ ehci_idone(struct ehci_xfer *ex, ex_comp DPRINTF("ex=%#jx", (uintptr_t)ex, 0, 0, 0); /* - * If software has completed it, either by cancellation - * or timeout, drop it on the floor. + * Try to claim this xfer for completion. If it has already + * completed or aborted, drop it on the floor. */ - if (xfer->ux_status != USBD_IN_PROGRESS) { - KASSERT(xfer->ux_status == USBD_CANCELLED || - xfer->ux_status == USBD_TIMEOUT); - DPRINTF("aborted xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); + if (!usbd_xfer_trycomplete(xfer)) return; - } - - /* - * We are completing the xfer. Cancel the timeout if we can, - * but only asynchronously. See ehci_cancel_timeout_async for - * why we need not wait for the callout or task here. - */ - ehci_cancel_timeout_async(xfer); #ifdef DIAGNOSTIC #ifdef EHCI_DEBUG @@ -1539,9 +1526,6 @@ ehci_allocx(struct usbd_bus *bus, unsign 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; @@ -1569,6 +1553,14 @@ ehci_freex(struct usbd_bus *bus, struct pool_cache_put(sc->sc_xferpool, xfer); } +Static bool +ehci_dying(struct usbd_bus *bus) +{ + struct ehci_softc *sc = EHCI_BUS2SC(bus); + + return sc->sc_dying; +} + Static void ehci_get_lock(struct usbd_bus *bus, kmutex_t **lock) { @@ -3201,22 +3193,12 @@ ehci_close_pipe(struct usbd_pipe *pipe, } /* - * 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 + * Arrrange 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) +ehci_abortx(struct usbd_xfer *xfer) { EHCIHIST_FUNC(); EHCIHIST_CALLED(); struct ehci_pipe *epipe = EHCI_XFER2EPIPE(xfer); @@ -3228,48 +3210,14 @@ ehci_abort_xfer(struct usbd_xfer *xfer, uint32_t qhstatus; int hit; - 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(); - /* - * Nobody else can set this status: only one caller can time - * out, and only one caller can synchronously abort. So the - * status can't be the status we're trying to set this to. - */ - KASSERT(xfer->ux_status != status); - - /* - * If host controller or timer interrupt has completed it, too - * late to abort. Forget about it. - */ - if (xfer->ux_status != USBD_IN_PROGRESS) - return; - - if (status == USBD_CANCELLED) { - /* - * We are synchronously aborting. Cancel the timeout - * if we can, but only asynchronously. See - * ehci_cancel_timeout_async for why we need not wait - * for the callout or task here. - */ - ehci_cancel_timeout_async(xfer); - } else { - /* - * Otherwise, we are timing out. No action needed to - * cancel the timeout because we _are_ the timeout. - * This case should happen only via the timeout task, - * invoked via the callout. - */ - KASSERT(status == USBD_TIMEOUT); - } - - /* We beat everyone else. Claim the status. */ - xfer->ux_status = status; + KASSERTMSG((xfer->ux_status == USBD_CANCELLED || + xfer->ux_status == USBD_TIMEOUT), + "bad abort status: %d", xfer->ux_status); /* * If we're dying, skip the hardware action and just notify the @@ -3477,291 +3425,6 @@ dying: KASSERT(mutex_owned(&sc->sc_lock)); } -/* - * ehci_timeout(xfer) - * - * Called at IPL_SOFTCLOCK when too much time has elapsed waiting - * for xfer to complete. Since we can't abort the xfer at - * IPL_SOFTCLOCK, defer to a usb_task to run it in thread context, - * unless the xfer has completed or aborted concurrently -- and if - * the xfer has also been resubmitted, take care of rescheduling - * the callout. - */ -Static void -ehci_timeout(void *addr) -{ - EHCIHIST_FUNC(); EHCIHIST_CALLED(); - struct usbd_xfer *xfer = addr; - ehci_softc_t *sc = EHCI_XFER2SC(xfer); - struct usbd_device *dev = xfer->ux_pipe->up_dev; - - DPRINTF("xfer %#jx", (uintptr_t)xfer, 0, 0, 0); -#ifdef EHCI_DEBUG - if (ehcidebug >= 2) { - struct usbd_pipe *pipe = xfer->ux_pipe; - usbd_dump_pipe(pipe); - } -#endif - - /* Acquire the lock so we can transition the timeout state. */ - mutex_enter(&sc->sc_lock); - - /* - * Use ehci_probe_timeout to check whether the timeout is still - * valid, or to reschedule the callout if necessary. If it is - * still valid, schedule the task. - */ - if (ehci_probe_timeout(xfer)) - usb_add_task(dev, &xfer->ux_aborttask, USB_TASKQ_HC); - - /* - * Notify ehci_cancel_timeout_async that we may have scheduled - * the task. This causes callout_invoking to return false in - * ehci_cancel_timeout_async so that it can tell which stage in - * the callout->task->abort process we're at. - */ - callout_ack(&xfer->ux_callout); - - /* All done -- release the lock. */ - mutex_exit(&sc->sc_lock); -} - -/* - * ehci_timeout_task(xfer) - * - * Called in thread context when too much time has elapsed waiting - * for xfer to complete. Issue ehci_abort_xfer(USBD_TIMEOUT), - * unless the xfer has completed or aborted concurrently -- and if - * the xfer has also been resubmitted, take care of rescheduling - * the callout. - */ -Static void -ehci_timeout_task(void *addr) -{ - struct usbd_xfer *xfer = addr; - ehci_softc_t *sc = EHCI_XFER2SC(xfer); - - EHCIHIST_FUNC(); EHCIHIST_CALLED(); - - DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - - /* Acquire the lock so we can transition the timeout state. */ - mutex_enter(&sc->sc_lock); - - /* - * Use ehci_probe_timeout to check whether the timeout is still - * valid, or to reschedule the callout if necessary. If it is - * still valid, schedule the task. - */ - if (ehci_probe_timeout(xfer)) - ehci_abort_xfer(xfer, USBD_TIMEOUT); - - /* All done -- release the lock. */ - mutex_exit(&sc->sc_lock); -} - -/* - * ehci_probe_timeout(xfer) - * - * Probe the status of xfer's timeout. Acknowledge and process a - * request to reschedule. Return true if the timeout is still - * valid and the caller should take further action (queueing a - * task or aborting the xfer), false if it must stop here. - */ -Static bool -ehci_probe_timeout(struct usbd_xfer *xfer) -{ - EHCIHIST_FUNC(); EHCIHIST_CALLED(); - struct ehci_softc *sc = EHCI_XFER2SC(xfer); - bool valid; - - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); - - /* The timeout must be set. */ - KASSERT(xfer->ux_timeout_set); - - /* - * Neither callout nor task may be pending; they execute - * alternately in lock step. - */ - KASSERT(!callout_pending(&xfer->ux_callout)); - KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); - - /* There are a few cases... */ - if (sc->sc_dying) { - /* Host controller dying. Drop it all on the floor. */ - xfer->ux_timeout_set = false; - xfer->ux_timeout_reset = false; - valid = false; - } else if (xfer->ux_timeout_reset) { - /* - * The xfer completed _and_ got resubmitted while we - * waited for the lock. Acknowledge the request to - * reschedule, and reschedule it if there is a timeout - * and the bus is not polling. - */ - xfer->ux_timeout_reset = false; - if (xfer->ux_timeout && !sc->sc_bus.ub_usepolling) { - KASSERT(xfer->ux_timeout_set); - callout_schedule(&xfer->ux_callout, - mstohz(xfer->ux_timeout)); - } else { - /* No more callout or task scheduled. */ - xfer->ux_timeout_set = false; - } - valid = false; - } else if (xfer->ux_status != USBD_IN_PROGRESS) { - /* - * The xfer has completed by hardware completion or by - * software abort, and has not been resubmitted, so the - * timeout must be unset, and is no longer valid for - * the caller. - */ - xfer->ux_timeout_set = false; - valid = false; - } else { - /* - * The xfer has not yet completed, so the timeout is - * valid. - */ - valid = true; - } - - /* Any reset must have been processed. */ - KASSERT(!xfer->ux_timeout_reset); - - /* - * Either we claim the timeout is set, or the callout is idle. - * If the timeout is still set, we may be handing off to the - * task instead, so this is an if but not an iff. - */ - KASSERT(xfer->ux_timeout_set || !callout_pending(&xfer->ux_callout)); - - /* - * The task must be idle now. - * - * - If the caller is the callout, _and_ the timeout is still - * valid, the caller will schedule it, but it hasn't been - * scheduled yet. (If the timeout is not valid, the task - * should not be scheduled.) - * - * - If the caller is the task, it cannot be scheduled again - * until the callout runs again, which won't happen until we - * next release the lock. - */ - KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); - - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); - - return valid; -} - -/* - * ehci_schedule_timeout(xfer) - * - * Ensure that xfer has a timeout. If the callout is already - * queued or the task is already running, request that they - * reschedule the callout. If not, and if we're not polling, - * schedule the callout anew. - */ -Static void -ehci_schedule_timeout(struct usbd_xfer *xfer) -{ - EHCIHIST_FUNC(); EHCIHIST_CALLED(); - ehci_softc_t *sc = EHCI_XFER2SC(xfer); - - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); - - if (xfer->ux_timeout_set) { - /* - * Callout or task has fired from a prior completed - * xfer but has not yet noticed that the xfer is done. - * Ask it to reschedule itself to ux_timeout. - */ - xfer->ux_timeout_reset = true; - } else if (xfer->ux_timeout && !sc->sc_bus.ub_usepolling) { - /* Callout is not scheduled. Schedule it. */ - KASSERT(!callout_pending(&xfer->ux_callout)); - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - ehci_timeout, xfer); - xfer->ux_timeout_set = true; - } - - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); -} - -/* - * ehci_cancel_timeout_async(xfer) - * - * Cancel the callout and the task of xfer, which have not yet run - * to completion, but don't wait for the callout or task to finish - * running. - * - * If they have already fired, at worst they are waiting for the - * bus lock. They will see that the xfer is no longer in progress - * and give up, or they will see that the xfer has been - * resubmitted with a new timeout and reschedule the callout. - * - * If a resubmitted request completed so fast that the callout - * didn't have time to process a timer reset, just cancel the - * timer reset. - */ -Static void -ehci_cancel_timeout_async(struct usbd_xfer *xfer) -{ - EHCIHIST_FUNC(); EHCIHIST_CALLED(); - struct ehci_softc *sc = EHCI_XFER2SC(xfer); - - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); - - KASSERT(xfer->ux_timeout_set); - xfer->ux_timeout_reset = false; - if (!callout_stop(&xfer->ux_callout)) { - /* - * We stopped the callout before it ran. The timeout - * is no longer set. - */ - xfer->ux_timeout_set = false; - } else if (callout_invoking(&xfer->ux_callout)) { - /* - * The callout has begun to run but it has not yet - * acquired the lock and called callout_ack. The task - * cannot be queued yet, and the callout cannot have - * been rescheduled yet. - * - * By the time the callout acquires the lock, we will - * have transitioned from USBD_IN_PROGRESS to a - * completed status, and possibly also resubmitted the - * xfer and set xfer->ux_timeout_reset = true. In both - * cases, the callout will DTRT, so no further action - * is needed here. - */ - } else if (usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)) { - /* - * The callout had fired and scheduled the task, but we - * stopped the task before it could run. The timeout - * is therefore no longer set -- the next resubmission - * of the xfer must schedule a new timeout. - * - * The callout should not be be pending at this point: - * it is scheduled only under the lock, and only when - * xfer->ux_timeout_set is false, or by the callout or - * task itself when xfer->ux_timeout_reset is true. - */ - xfer->ux_timeout_set = false; - } - - /* - * The callout cannot be scheduled and the task cannot be - * queued at this point. Either we cancelled them, or they are - * already running and waiting for the bus lock. - */ - KASSERT(!callout_pending(&xfer->ux_callout)); - KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); - - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); -} - /************************/ Static int @@ -4003,7 +3666,7 @@ ehci_device_ctrl_start(struct usbd_xfer /* Insert qTD in QH list - also does usb_syncmem(sqh) */ ehci_set_qh_qtd(sqh, setup); - ehci_schedule_timeout(xfer); + usbd_xfer_schedule_timeout(xfer); ehci_add_intr_list(sc, exfer); xfer->ux_status = USBD_IN_PROGRESS; if (!polling) @@ -4054,7 +3717,7 @@ ehci_device_ctrl_abort(struct usbd_xfer EHCIHIST_FUNC(); EHCIHIST_CALLED(); DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - ehci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } /* Close a device control pipe. */ @@ -4201,7 +3864,7 @@ ehci_device_bulk_start(struct usbd_xfer /* also does usb_syncmem(sqh) */ ehci_set_qh_qtd(sqh, exfer->ex_sqtdstart); - ehci_schedule_timeout(xfer); + usbd_xfer_schedule_timeout(xfer); ehci_add_intr_list(sc, exfer); xfer->ux_status = USBD_IN_PROGRESS; if (!polling) @@ -4232,7 +3895,7 @@ ehci_device_bulk_abort(struct usbd_xfer EHCIHIST_FUNC(); EHCIHIST_CALLED(); DPRINTF("xfer %#jx", (uintptr_t)xfer, 0, 0, 0); - ehci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } /* @@ -4417,7 +4080,7 @@ ehci_device_intr_start(struct usbd_xfer /* also does usb_syncmem(sqh) */ ehci_set_qh_qtd(sqh, exfer->ex_sqtdstart); - ehci_schedule_timeout(xfer); + usbd_xfer_schedule_timeout(xfer); ehci_add_intr_list(sc, exfer); xfer->ux_status = USBD_IN_PROGRESS; if (!polling) @@ -4451,7 +4114,7 @@ ehci_device_intr_abort(struct usbd_xfer * async doorbell. That's dependent on the async list, wheras * intr xfers are periodic, should not use this? */ - ehci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } Static void Index: src/sys/dev/usb/motg.c diff -u src/sys/dev/usb/motg.c:1.25 src/sys/dev/usb/motg.c:1.26 --- src/sys/dev/usb/motg.c:1.25 Sun Feb 17 04:17:52 2019 +++ src/sys/dev/usb/motg.c Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: motg.c,v 1.25 2019/02/17 04:17:52 rin Exp $ */ +/* $NetBSD: motg.c,v 1.26 2020/02/12 16:01:00 riastradh Exp $ */ /* * Copyright (c) 1998, 2004, 2011, 2012, 2014 The NetBSD Foundation, Inc. @@ -40,7 +40,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: motg.c,v 1.25 2019/02/17 04:17:52 rin Exp $"); +__KERNEL_RCSID(0, "$NetBSD: motg.c,v 1.26 2020/02/12 16:01:00 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" @@ -144,6 +144,7 @@ static void motg_softintr(void *); static struct usbd_xfer * motg_allocx(struct usbd_bus *, unsigned int); static void motg_freex(struct usbd_bus *, struct usbd_xfer *); +static bool motg_dying(struct usbd_bus *); static void motg_get_lock(struct usbd_bus *, kmutex_t **); static int motg_roothub_ctrl(struct usbd_bus *, usb_device_request_t *, void *, int); @@ -174,7 +175,7 @@ static void motg_device_data_read(struc static void motg_device_data_write(struct usbd_xfer *); static void motg_device_clear_toggle(struct usbd_pipe *); -static void motg_device_xfer_abort(struct usbd_xfer *); +static void motg_abortx(struct usbd_xfer *); #define UBARR(sc) bus_space_barrier((sc)->sc_iot, (sc)->sc_ioh, 0, (sc)->sc_size, \ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) @@ -233,6 +234,8 @@ const struct usbd_bus_methods motg_bus_m .ubm_dopoll = motg_poll, .ubm_allocx = motg_allocx, .ubm_freex = motg_freex, + .ubm_abortx = motg_abortx, + .ubm_dying = motg_dying, .ubm_getlock = motg_get_lock, .ubm_rhctrl = motg_roothub_ctrl, }; @@ -770,6 +773,14 @@ motg_freex(struct usbd_bus *bus, struct pool_cache_put(sc->sc_xferpool, xfer); } +static bool +motg_dying(struct usbd_bus *bus) +{ + struct motg_softc *sc = MOTG_BUS2SC(bus); + + return sc->sc_dying; +} + static void motg_get_lock(struct usbd_bus *bus, kmutex_t **lock) { @@ -1387,6 +1398,13 @@ motg_device_ctrl_intr_rx(struct motg_sof KASSERT(mutex_owned(&sc->sc_lock)); + /* + * Try to claim this xfer for completion. If it has already + * completed or aborted, drop it on the floor. + */ + if (!usbd_xfer_trycomplete(xfer)) + return; + KASSERT(ep->phase == DATA_IN || ep->phase == STATUS_IN); /* select endpoint 0 */ UWRITE1(sc, MUSB2_REG_EPINDEX, 0); @@ -1501,6 +1519,14 @@ motg_device_ctrl_intr_tx(struct motg_sof MOTGHIST_FUNC(); MOTGHIST_CALLED(); KASSERT(mutex_owned(&sc->sc_lock)); + + /* + * Try to claim this xfer for completion. If it has already + * completed or aborted, drop it on the floor. + */ + if (!usbd_xfer_trycomplete(xfer)) + return; + if (ep->phase == DATA_IN || ep->phase == STATUS_IN) { motg_device_ctrl_intr_rx(sc); return; @@ -1633,7 +1659,7 @@ motg_device_ctrl_abort(struct usbd_xfer { MOTGHIST_FUNC(); MOTGHIST_CALLED(); - motg_device_xfer_abort(xfer); + usbd_xfer_abort(xfer); } /* Close a device control pipe */ @@ -2019,6 +2045,14 @@ motg_device_intr_tx(struct motg_softc *s MOTGHIST_FUNC(); MOTGHIST_CALLED(); KASSERT(mutex_owned(&sc->sc_lock)); + + /* + * Try to claim this xfer for completion. If it has already + * completed or aborted, drop it on the floor. + */ + if (!usbd_xfer_trycomplete(xfer)) + return; + KASSERT(ep->ep_number == epnumber); DPRINTFN(MD_BULK, " on ep %jd", epnumber, 0, 0, 0); @@ -2102,7 +2136,7 @@ motg_device_data_abort(struct usbd_xfer MOTGHIST_FUNC(); MOTGHIST_CALLED(); - motg_device_xfer_abort(xfer); + usbd_xfer_abort(xfer); } /* Close a device control pipe */ @@ -2151,7 +2185,7 @@ motg_device_clear_toggle(struct usbd_pip /* Abort a device control request. */ static void -motg_device_xfer_abort(struct usbd_xfer *xfer) +motg_abortx(struct usbd_xfer *xfer) { MOTGHIST_FUNC(); MOTGHIST_CALLED(); uint8_t csr; @@ -2162,30 +2196,6 @@ motg_device_xfer_abort(struct usbd_xfer ASSERT_SLEEPABLE(); /* - * We are synchronously aborting. Try to stop the - * callout and task, but if we can't, wait for them to - * complete. - */ - 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); - - /* - * The xfer cannot have been cancelled already. It is the - * responsibility of the caller of usbd_abort_pipe not to try - * to abort a pipe multiple times, whether concurrently or - * sequentially. - */ - KASSERT(xfer->ux_status != USBD_CANCELLED); - - /* If anyone else beat us, we're done. */ - if (xfer->ux_status != USBD_IN_PROGRESS) - return; - - /* We beat everyone else. Claim the status. */ - xfer->ux_status = USBD_CANCELLED; - - /* * If we're dying, skip the hardware action and just notify the * software that we're done. */ Index: src/sys/dev/usb/ohci.c diff -u src/sys/dev/usb/ohci.c:1.292 src/sys/dev/usb/ohci.c:1.293 --- src/sys/dev/usb/ohci.c:1.292 Fri Nov 29 14:15:41 2019 +++ src/sys/dev/usb/ohci.c Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: ohci.c,v 1.292 2019/11/29 14:15:41 gson Exp $ */ +/* $NetBSD: ohci.c,v 1.293 2020/02/12 16:01:00 riastradh 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.292 2019/11/29 14:15:41 gson Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ohci.c,v 1.293 2020/02/12 16:01:00 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" @@ -169,6 +169,7 @@ Static void ohci_device_isoc_enter(stru Static struct usbd_xfer * ohci_allocx(struct usbd_bus *, unsigned int); Static void ohci_freex(struct usbd_bus *, struct usbd_xfer *); +Static bool ohci_dying(struct usbd_bus *); Static void ohci_get_lock(struct usbd_bus *, kmutex_t **); Static int ohci_roothub_ctrl(struct usbd_bus *, usb_device_request_t *, void *, int); @@ -213,12 +214,10 @@ Static void ohci_device_isoc_done(struc Static usbd_status ohci_device_setintr(ohci_softc_t *, struct ohci_pipe *, int); -Static void ohci_timeout(void *); -Static void ohci_timeout_task(void *); Static void ohci_rhsc_enable(void *); Static void ohci_close_pipe(struct usbd_pipe *, ohci_soft_ed_t *); -Static void ohci_abort_xfer(struct usbd_xfer *, usbd_status); +Static void ohci_abortx(struct usbd_xfer *); Static void ohci_device_clear_toggle(struct usbd_pipe *); Static void ohci_noop(struct usbd_pipe *); @@ -287,6 +286,8 @@ Static const struct usbd_bus_methods ohc .ubm_dopoll = ohci_poll, .ubm_allocx = ohci_allocx, .ubm_freex = ohci_freex, + .ubm_abortx = ohci_abortx, + .ubm_dying = ohci_dying, .ubm_getlock = ohci_get_lock, .ubm_rhctrl = ohci_roothub_ctrl, }; @@ -1068,9 +1069,6 @@ ohci_allocx(struct usbd_bus *bus, unsign 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 @@ -1092,6 +1090,14 @@ ohci_freex(struct usbd_bus *bus, struct pool_cache_put(sc->sc_xferpool, xfer); } +Static bool +ohci_dying(struct usbd_bus *bus) +{ + ohci_softc_t *sc = OHCI_BUS2SC(bus); + + return sc->sc_dying; +} + Static void ohci_get_lock(struct usbd_bus *bus, kmutex_t **lock) { @@ -1463,23 +1469,11 @@ ohci_softintr(void *v) } /* - * If software has completed it, either by cancellation - * or timeout, drop it on the floor. + * Try to claim this xfer for completion. If it has + * already completed or aborted, drop it on the floor. */ - if (xfer->ux_status != USBD_IN_PROGRESS) { - KASSERT(xfer->ux_status == USBD_CANCELLED || - xfer->ux_status == USBD_TIMEOUT); + if (!usbd_xfer_trycomplete(xfer)) continue; - } - - /* - * Cancel the timeout and the task, which have not yet - * run. If they have already fired, at worst they are - * waiting for the lock. They will see that the xfer - * is no longer in progress and give up. - */ - callout_stop(&xfer->ux_callout); - usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); len = std->len; if (std->td.td_cbp != 0) @@ -1553,23 +1547,11 @@ ohci_softintr(void *v) continue; /* - * If software has completed it, either by cancellation - * or timeout, drop it on the floor. + * Try to claim this xfer for completion. If it has + * already completed or aborted, drop it on the floor. */ - if (xfer->ux_status != USBD_IN_PROGRESS) { - KASSERT(xfer->ux_status == USBD_CANCELLED || - xfer->ux_status == USBD_TIMEOUT); + if (!usbd_xfer_trycomplete(xfer)) continue; - } - - /* - * Cancel the timeout and the task, which have not yet - * run. If they have already fired, at worst they are - * waiting for the lock. They will see that the xfer - * is no longer in progress and give up. - */ - callout_stop(&xfer->ux_callout); - usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); KASSERT(!sitd->isdone); #ifdef DIAGNOSTIC @@ -1909,37 +1891,6 @@ ohci_hash_find_itd(ohci_softc_t *sc, ohc return NULL; } -void -ohci_timeout(void *addr) -{ - OHCIHIST_FUNC(); OHCIHIST_CALLED(); - struct usbd_xfer *xfer = addr; - ohci_softc_t *sc = OHCI_XFER2SC(xfer); - struct usbd_device *dev = xfer->ux_pipe->up_dev; - - DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - - mutex_enter(&sc->sc_lock); - if (!sc->sc_dying && xfer->ux_status == USBD_IN_PROGRESS) - usb_add_task(dev, &xfer->ux_aborttask, USB_TASKQ_HC); - mutex_exit(&sc->sc_lock); -} - -void -ohci_timeout_task(void *addr) -{ - struct usbd_xfer *xfer = addr; - ohci_softc_t *sc = OHCI_XFER2SC(xfer); - - OHCIHIST_FUNC(); OHCIHIST_CALLED(); - - DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - - mutex_enter(&sc->sc_lock); - ohci_abort_xfer(xfer, USBD_TIMEOUT); - mutex_exit(&sc->sc_lock); -} - #ifdef OHCI_DEBUG void ohci_dump_tds(ohci_softc_t *sc, ohci_soft_td_t *std) @@ -2212,19 +2163,8 @@ ohci_close_pipe(struct usbd_pipe *pipe, } /* - * 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 (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 + * 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 @@ -2233,7 +2173,7 @@ ohci_close_pipe(struct usbd_pipe *pipe, * used. The softint handler will free the old ones. */ void -ohci_abort_xfer(struct usbd_xfer *xfer, usbd_status status) +ohci_abortx(struct usbd_xfer *xfer) { OHCIHIST_FUNC(); OHCIHIST_CALLED(); struct ohci_pipe *opipe = OHCI_PIPE2OPIPE(xfer->ux_pipe); @@ -2243,46 +2183,15 @@ ohci_abort_xfer(struct usbd_xfer *xfer, ohci_physaddr_t headp; int hit; - 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 (status == USBD_CANCELLED) { - /* - * We are synchronously aborting. Try to stop the - * callout and task, but if we can't, wait for them to - * complete. - */ - 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); - } else { - /* Otherwise, we are timing out. */ - KASSERT(status == USBD_TIMEOUT); - } - - /* - * The xfer cannot have been cancelled already. It is the - * responsibility of the caller of usbd_abort_pipe not to try - * to abort a pipe multiple times, whether concurrently or - * sequentially. - */ - KASSERT(xfer->ux_status != USBD_CANCELLED); - - /* Only the timeout, which runs only once, can time it out. */ - KASSERT(xfer->ux_status != USBD_TIMEOUT); - - /* If anyone else beat us, we're done. */ - if (xfer->ux_status != USBD_IN_PROGRESS) - return; - - /* We beat everyone else. Claim the status. */ - xfer->ux_status = status; + KASSERTMSG((xfer->ux_status == USBD_CANCELLED || + xfer->ux_status == USBD_TIMEOUT), + "bad abort status: %d", xfer->ux_status); /* * If we're dying, skip the hardware action and just notify the @@ -2875,10 +2784,7 @@ ohci_device_ctrl_start(struct usbd_xfer sizeof(sed->ed.ed_tailp), BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); - if (xfer->ux_timeout && !polling) { - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - ohci_timeout, xfer); - } + usbd_xfer_schedule_timeout(xfer); DPRINTF("done", 0, 0, 0, 0); @@ -2899,7 +2805,7 @@ ohci_device_ctrl_abort(struct usbd_xfer OHCIHIST_FUNC(); OHCIHIST_CALLED(); DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - ohci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } /* Close a device control pipe. */ @@ -3090,11 +2996,7 @@ ohci_device_bulk_start(struct usbd_xfer usb_syncmem(&sed->dma, sed->offs, sizeof(sed->ed), BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); - if (xfer->ux_timeout && !sc->sc_bus.ub_usepolling) { - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - ohci_timeout, xfer); - } - + usbd_xfer_schedule_timeout(xfer); xfer->ux_status = USBD_IN_PROGRESS; if (!polling) mutex_exit(&sc->sc_lock); @@ -3112,7 +3014,7 @@ ohci_device_bulk_abort(struct usbd_xfer KASSERT(mutex_owned(&sc->sc_lock)); DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - ohci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } /* @@ -3300,7 +3202,7 @@ ohci_device_intr_abort(struct usbd_xfer KASSERT(mutex_owned(&sc->sc_lock)); KASSERT(xfer->ux_pipe->up_intrxfer == xfer); - ohci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } /* Close a device interrupt pipe. */ Index: src/sys/dev/usb/uhci.c diff -u src/sys/dev/usb/uhci.c:1.288 src/sys/dev/usb/uhci.c:1.289 --- src/sys/dev/usb/uhci.c:1.288 Sun Feb 17 04:17:52 2019 +++ src/sys/dev/usb/uhci.c Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: uhci.c,v 1.288 2019/02/17 04:17:52 rin Exp $ */ +/* $NetBSD: uhci.c,v 1.289 2020/02/12 16:01:00 riastradh Exp $ */ /* * Copyright (c) 1998, 2004, 2011, 2012 The NetBSD Foundation, Inc. @@ -42,7 +42,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: uhci.c,v 1.288 2019/02/17 04:17:52 rin Exp $"); +__KERNEL_RCSID(0, "$NetBSD: uhci.c,v 1.289 2020/02/12 16:01:00 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" @@ -194,10 +194,8 @@ Static void uhci_check_intr(uhci_softc_ ux_completeq_t *); Static void uhci_idone(struct uhci_xfer *, ux_completeq_t *); -Static void uhci_abort_xfer(struct usbd_xfer *, usbd_status); +Static void uhci_abortx(struct usbd_xfer *); -Static void uhci_timeout(void *); -Static void uhci_timeout_task(void *); Static void uhci_add_ls_ctrl(uhci_softc_t *, uhci_soft_qh_t *); Static void uhci_add_hs_ctrl(uhci_softc_t *, uhci_soft_qh_t *); Static void uhci_add_bulk(uhci_softc_t *, uhci_soft_qh_t *); @@ -212,6 +210,7 @@ Static usbd_status uhci_setup_isoc(struc Static struct usbd_xfer * uhci_allocx(struct usbd_bus *, unsigned int); Static void uhci_freex(struct usbd_bus *, struct usbd_xfer *); +Static bool uhci_dying(struct usbd_bus *); Static void uhci_get_lock(struct usbd_bus *, kmutex_t **); Static int uhci_roothub_ctrl(struct usbd_bus *, usb_device_request_t *, void *, int); @@ -330,6 +329,8 @@ const struct usbd_bus_methods uhci_bus_m .ubm_dopoll = uhci_poll, .ubm_allocx = uhci_allocx, .ubm_freex = uhci_freex, + .ubm_abortx = uhci_abortx, + .ubm_dying = uhci_dying, .ubm_getlock = uhci_get_lock, .ubm_rhctrl = uhci_roothub_ctrl, }; @@ -657,9 +658,6 @@ 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; @@ -686,6 +684,14 @@ uhci_freex(struct usbd_bus *bus, struct pool_cache_put(sc->sc_xferpool, xfer); } +Static bool +uhci_dying(struct usbd_bus *bus) +{ + struct uhci_softc *sc = UHCI_BUS2SC(bus); + + return sc->sc_dying; +} + Static void uhci_get_lock(struct usbd_bus *bus, kmutex_t **lock) { @@ -1565,24 +1571,11 @@ uhci_idone(struct uhci_xfer *ux, ux_comp DPRINTFN(12, "ux=%#jx", (uintptr_t)ux, 0, 0, 0); /* - * If software has completed it, either by cancellation - * or timeout, drop it on the floor. + * Try to claim this xfer for completion. If it has already + * completed or aborted, drop it on the floor. */ - if (xfer->ux_status != USBD_IN_PROGRESS) { - KASSERT(xfer->ux_status == USBD_CANCELLED || - xfer->ux_status == USBD_TIMEOUT); - DPRINTF("aborted xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); + if (!usbd_xfer_trycomplete(xfer)) return; - } - - /* - * Cancel the timeout and the task, which have not yet - * run. If they have already fired, at worst they are - * waiting for the lock. They will see that the xfer - * is no longer in progress and give up. - */ - callout_stop(&xfer->ux_callout); - usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); #ifdef DIAGNOSTIC #ifdef UHCI_DEBUG @@ -1712,40 +1705,6 @@ uhci_idone(struct uhci_xfer *ux, ux_comp DPRINTFN(12, "ux=%#jx done", (uintptr_t)ux, 0, 0, 0); } -/* - * Called when a request does not complete. - */ -void -uhci_timeout(void *addr) -{ - UHCIHIST_FUNC(); UHCIHIST_CALLED(); - struct usbd_xfer *xfer = addr; - uhci_softc_t *sc = UHCI_XFER2SC(xfer); - struct usbd_device *dev = xfer->ux_pipe->up_dev; - - DPRINTF("xfer %#jx", (uintptr_t)xfer, 0, 0, 0); - - mutex_enter(&sc->sc_lock); - if (!sc->sc_dying && xfer->ux_status == USBD_IN_PROGRESS) - usb_add_task(dev, &xfer->ux_aborttask, USB_TASKQ_HC); - mutex_exit(&sc->sc_lock); -} - -void -uhci_timeout_task(void *addr) -{ - struct usbd_xfer *xfer = addr; - uhci_softc_t *sc = UHCI_XFER2SC(xfer); - - UHCIHIST_FUNC(); UHCIHIST_CALLED(); - - DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - - mutex_enter(&sc->sc_lock); - uhci_abort_xfer(xfer, USBD_TIMEOUT); - mutex_exit(&sc->sc_lock); -} - void uhci_poll(struct usbd_bus *bus) { @@ -2314,11 +2273,7 @@ uhci_device_bulk_start(struct usbd_xfer uhci_add_bulk(sc, sqh); uhci_add_intr_list(sc, ux); - - if (xfer->ux_timeout && !polling) { - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - uhci_timeout, xfer); - } + usbd_xfer_schedule_timeout(xfer); xfer->ux_status = USBD_IN_PROGRESS; if (!polling) mutex_exit(&sc->sc_lock); @@ -2336,25 +2291,14 @@ uhci_device_bulk_abort(struct usbd_xfer UHCIHIST_FUNC(); UHCIHIST_CALLED(); - uhci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } /* - * 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 (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) +Static void +uhci_abortx(struct usbd_xfer *xfer) { UHCIHIST_FUNC(); UHCIHIST_CALLED(); struct uhci_xfer *ux = UHCI_XFER2UXFER(xfer); @@ -2362,45 +2306,14 @@ uhci_abort_xfer(struct usbd_xfer *xfer, uhci_softc_t *sc = UHCI_XFER2SC(xfer); uhci_soft_td_t *std; - 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); + DPRINTFN(1,"xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); KASSERT(mutex_owned(&sc->sc_lock)); ASSERT_SLEEPABLE(); - if (status == USBD_CANCELLED) { - /* - * We are synchronously aborting. Try to stop the - * callout and task, but if we can't, wait for them to - * complete. - */ - 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); - } else { - /* Otherwise, we are timing out. */ - KASSERT(status == USBD_TIMEOUT); - } - - /* - * The xfer cannot have been cancelled already. It is the - * responsibility of the caller of usbd_abort_pipe not to try - * to abort a pipe multiple times, whether concurrently or - * sequentially. - */ - KASSERT(xfer->ux_status != USBD_CANCELLED); - - /* Only the timeout, which runs only once, can time it out. */ - KASSERT(xfer->ux_status != USBD_TIMEOUT); - - /* If anyone else beat us, we're done. */ - if (xfer->ux_status != USBD_IN_PROGRESS) - return; - - /* We beat everyone else. Claim the status. */ - xfer->ux_status = status; + KASSERTMSG((xfer->ux_status == USBD_CANCELLED || + xfer->ux_status == USBD_TIMEOUT), + "bad abort status: %d", xfer->ux_status); /* * If we're dying, skip the hardware action and just notify the @@ -2672,10 +2585,7 @@ uhci_device_ctrl_start(struct usbd_xfer DPRINTF("--- dump end ---", 0, 0, 0, 0); } #endif - if (xfer->ux_timeout && !polling) { - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - uhci_timeout, xfer); - } + usbd_xfer_schedule_timeout(xfer); xfer->ux_status = USBD_IN_PROGRESS; if (!polling) mutex_exit(&sc->sc_lock); @@ -2834,7 +2744,7 @@ uhci_device_ctrl_abort(struct usbd_xfer KASSERT(mutex_owned(&sc->sc_lock)); UHCIHIST_FUNC(); UHCIHIST_CALLED(); - uhci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } /* Close a device control pipe. */ @@ -2862,7 +2772,7 @@ uhci_device_intr_abort(struct usbd_xfer UHCIHIST_FUNC(); UHCIHIST_CALLED(); DPRINTF("xfer=%#jx", (uintptr_t)xfer, 0, 0, 0); - uhci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } /* Close a device interrupt pipe. */ Index: src/sys/dev/usb/usbdi.c diff -u src/sys/dev/usb/usbdi.c:1.191 src/sys/dev/usb/usbdi.c:1.192 --- src/sys/dev/usb/usbdi.c:1.191 Wed Feb 12 16:00:17 2020 +++ src/sys/dev/usb/usbdi.c Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: usbdi.c,v 1.191 2020/02/12 16:00:17 riastradh Exp $ */ +/* $NetBSD: usbdi.c,v 1.192 2020/02/12 16:01:00 riastradh Exp $ */ /* * Copyright (c) 1998, 2012, 2015 The NetBSD Foundation, Inc. @@ -32,7 +32,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: usbdi.c,v 1.191 2020/02/12 16:00:17 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: usbdi.c,v 1.192 2020/02/12 16:01:00 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" @@ -71,6 +71,10 @@ static void usbd_free_buffer(struct usbd static struct usbd_xfer *usbd_alloc_xfer(struct usbd_device *, unsigned int); static usbd_status usbd_free_xfer(struct usbd_xfer *); static void usbd_request_async_cb(struct usbd_xfer *, void *, usbd_status); +static void usbd_xfer_timeout(void *); +static void usbd_xfer_timeout_task(void *); +static bool usbd_xfer_probe_timeout(struct usbd_xfer *); +static void usbd_xfer_cancel_timeout_async(struct usbd_xfer *); #if defined(USB_DEBUG) void @@ -474,7 +478,10 @@ usbd_alloc_xfer(struct usbd_device *dev, goto out; xfer->ux_bus = dev->ud_bus; callout_init(&xfer->ux_callout, CALLOUT_MPSAFE); + callout_setfunc(&xfer->ux_callout, usbd_xfer_timeout, xfer); cv_init(&xfer->ux_cv, "usbxfer"); + usb_init_task(&xfer->ux_aborttask, usbd_xfer_timeout_task, xfer, + USB_TASKQ_MPSAFE); out: USBHIST_CALLARGS(usbdebug, "returns %#jx", (uintptr_t)xfer, 0, 0, 0); @@ -1402,3 +1409,381 @@ usbd_get_string0(struct usbd_device *dev #endif return USBD_NORMAL_COMPLETION; } + +/* + * usbd_xfer_trycomplete(xfer) + * + * Try to claim xfer for completion. Return true if successful, + * false if the xfer has been synchronously aborted or has timed + * out. + * + * If this returns true, caller is responsible for setting + * xfer->ux_status and calling usb_transfer_complete. To be used + * in a host controller interrupt handler. + * + * Caller must either hold the bus lock or have the bus in polling + * mode. + */ +bool +usbd_xfer_trycomplete(struct usbd_xfer *xfer) +{ + struct usbd_bus *bus __diagused = xfer->ux_bus; + + KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); + + /* + * If software has completed it, either by synchronous abort or + * by timeout, too late. + */ + if (xfer->ux_status != USBD_IN_PROGRESS) + return false; + + /* + * We are completing the xfer. Cancel the timeout if we can, + * but only asynchronously. See usbd_xfer_cancel_timeout_async + * for why we need not wait for the callout or task here. + */ + usbd_xfer_cancel_timeout_async(xfer); + + /* Success! Note: Caller must set xfer->ux_status afterwar. */ + return true; +} + +/* + * usbd_xfer_abort(xfer) + * + * Try to claim xfer to abort. If successful, mark it completed + * with USBD_CANCELLED and call the bus-specific method to abort + * at the hardware level. + * + * To be called in thread context from struct + * usbd_pipe_methods::upm_abort. + * + * Caller must hold the bus lock. + */ +void +usbd_xfer_abort(struct usbd_xfer *xfer) +{ + struct usbd_bus *bus = xfer->ux_bus; + + KASSERT(mutex_owned(bus->ub_lock)); + + /* + * If host controller interrupt or timer interrupt has + * completed it, too late. But the xfer cannot be + * cancelled already -- only one caller can synchronously + * abort. + */ + KASSERT(xfer->ux_status != USBD_CANCELLED); + if (xfer->ux_status != USBD_IN_PROGRESS) + return; + + /* + * Cancel the timeout if we can, but only asynchronously; see + * usbd_xfer_cancel_timeout_async for why we need not wait for + * the callout or task here. + */ + usbd_xfer_cancel_timeout_async(xfer); + + /* + * We beat everyone else. Claim the status as cancelled and do + * the bus-specific dance to abort the hardware. + */ + xfer->ux_status = USBD_CANCELLED; + bus->ub_methods->ubm_abortx(xfer); +} + +/* + * usbd_xfer_timeout(xfer) + * + * Called at IPL_SOFTCLOCK when too much time has elapsed waiting + * for xfer to complete. Since we can't abort the xfer at + * IPL_SOFTCLOCK, defer to a usb_task to run it in thread context, + * unless the xfer has completed or aborted concurrently -- and if + * the xfer has also been resubmitted, take care of rescheduling + * the callout. + */ +static void +usbd_xfer_timeout(void *cookie) +{ + struct usbd_xfer *xfer = cookie; + struct usbd_bus *bus = xfer->ux_bus; + struct usbd_device *dev = xfer->ux_pipe->up_dev; + + /* Acquire the lock so we can transition the timeout state. */ + mutex_enter(bus->ub_lock); + + /* + * Use usbd_xfer_probe_timeout to check whether the timeout is + * still valid, or to reschedule the callout if necessary. If + * it is still valid, schedule the task. + */ + if (usbd_xfer_probe_timeout(xfer)) + usb_add_task(dev, &xfer->ux_aborttask, USB_TASKQ_HC); + + /* + * Notify usbd_xfer_cancel_timeout_async that we may have + * scheduled the task. This causes callout_invoking to return + * false in usbd_xfer_cancel_timeout_async so that it can tell + * which stage in the callout->task->abort process we're at. + */ + callout_ack(&xfer->ux_callout); + + /* All done -- release the lock. */ + mutex_exit(bus->ub_lock); +} + +/* + * usbd_xfer_timeout_task(xfer) + * + * Called in thread context when too much time has elapsed waiting + * for xfer to complete. Abort the xfer with USBD_TIMEOUT, unless + * it has completed or aborted concurrently -- and if the xfer has + * also been resubmitted, take care of rescheduling the callout. + */ +static void +usbd_xfer_timeout_task(void *cookie) +{ + struct usbd_xfer *xfer = cookie; + struct usbd_bus *bus = xfer->ux_bus; + + /* Acquire the lock so we can transition the timeout state. */ + mutex_enter(bus->ub_lock); + + /* + * Use usbd_xfer_probe_timeout to check whether the timeout is + * still valid, or to reschedule the callout if necessary. If + * it is not valid -- the timeout has been asynchronously + * cancelled, or the xfer has already been resubmitted -- then + * we're done here. + */ + if (!usbd_xfer_probe_timeout(xfer)) + goto out; + + /* + * May have completed or been aborted, but we're the only one + * who can time it out. If it has completed or been aborted, + * no need to timeout. + */ + KASSERT(xfer->ux_status != USBD_TIMEOUT); + if (xfer->ux_status != USBD_IN_PROGRESS) + goto out; + + /* + * We beat everyone else. Claim the status as timed out and do + * the bus-specific dance to abort the hardware. + */ + xfer->ux_status = USBD_TIMEOUT; + bus->ub_methods->ubm_abortx(xfer); + +out: /* All done -- release the lock. */ + mutex_exit(bus->ub_lock); +} + +/* + * usbd_xfer_probe_timeout(xfer) + * + * Probe the status of xfer's timeout. Acknowledge and process a + * request to reschedule. Return true if the timeout is still + * valid and the caller should take further action (queueing a + * task or aborting the xfer), false if it must stop here. + */ +static bool +usbd_xfer_probe_timeout(struct usbd_xfer *xfer) +{ + struct usbd_bus *bus = xfer->ux_bus; + bool valid; + + KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); + + /* The timeout must be set. */ + KASSERT(xfer->ux_timeout_set); + + /* + * Neither callout nor task may be pending; they execute + * alternately in lock step. + */ + KASSERT(!callout_pending(&xfer->ux_callout)); + KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); + + /* There are a few cases... */ + if (bus->ub_methods->ubm_dying(bus)) { + /* Host controller dying. Drop it all on the floor. */ + xfer->ux_timeout_set = false; + xfer->ux_timeout_reset = false; + valid = false; + } else if (xfer->ux_timeout_reset) { + /* + * The xfer completed _and_ got resubmitted while we + * waited for the lock. Acknowledge the request to + * reschedule, and reschedule it if there is a timeout + * and the bus is not polling. + */ + xfer->ux_timeout_reset = false; + if (xfer->ux_timeout && !bus->ub_usepolling) { + KASSERT(xfer->ux_timeout_set); + callout_schedule(&xfer->ux_callout, + mstohz(xfer->ux_timeout)); + } else { + /* No more callout or task scheduled. */ + xfer->ux_timeout_set = false; + } + valid = false; + } else if (xfer->ux_status != USBD_IN_PROGRESS) { + /* + * The xfer has completed by hardware completion or by + * software abort, and has not been resubmitted, so the + * timeout must be unset, and is no longer valid for + * the caller. + */ + xfer->ux_timeout_set = false; + valid = false; + } else { + /* + * The xfer has not yet completed, so the timeout is + * valid. + */ + valid = true; + } + + /* Any reset must have been processed. */ + KASSERT(!xfer->ux_timeout_reset); + + /* + * Either we claim the timeout is set, or the callout is idle. + * If the timeout is still set, we may be handing off to the + * task instead, so this is an if but not an iff. + */ + KASSERT(xfer->ux_timeout_set || !callout_pending(&xfer->ux_callout)); + + /* + * The task must be idle now. + * + * - If the caller is the callout, _and_ the timeout is still + * valid, the caller will schedule it, but it hasn't been + * scheduled yet. (If the timeout is not valid, the task + * should not be scheduled.) + * + * - If the caller is the task, it cannot be scheduled again + * until the callout runs again, which won't happen until we + * next release the lock. + */ + KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); + + KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); + + return valid; +} + +/* + * usbd_xfer_schedule_timeout(xfer) + * + * Ensure that xfer has a timeout. If the callout is already + * queued or the task is already running, request that they + * reschedule the callout. If not, and if we're not polling, + * schedule the callout anew. + * + * To be called in thread context from struct + * usbd_pipe_methods::upm_start. + */ +void +usbd_xfer_schedule_timeout(struct usbd_xfer *xfer) +{ + struct usbd_bus *bus = xfer->ux_bus; + + KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); + + if (xfer->ux_timeout_set) { + /* + * Callout or task has fired from a prior completed + * xfer but has not yet noticed that the xfer is done. + * Ask it to reschedule itself to ux_timeout. + */ + xfer->ux_timeout_reset = true; + } else if (xfer->ux_timeout && !bus->ub_usepolling) { + /* Callout is not scheduled. Schedule it. */ + KASSERT(!callout_pending(&xfer->ux_callout)); + callout_schedule(&xfer->ux_callout, mstohz(xfer->ux_timeout)); + xfer->ux_timeout_set = true; + } + + KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); +} + +/* + * usbd_xfer_cancel_timeout_async(xfer) + * + * Cancel the callout and the task of xfer, which have not yet run + * to completion, but don't wait for the callout or task to finish + * running. + * + * If they have already fired, at worst they are waiting for the + * bus lock. They will see that the xfer is no longer in progress + * and give up, or they will see that the xfer has been + * resubmitted with a new timeout and reschedule the callout. + * + * If a resubmitted request completed so fast that the callout + * didn't have time to process a timer reset, just cancel the + * timer reset. + */ +static void +usbd_xfer_cancel_timeout_async(struct usbd_xfer *xfer) +{ + struct usbd_bus *bus __diagused = xfer->ux_bus; + + KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); + + /* + * If the timer wasn't running anyway, forget about it. This + * can happen if we are completing an isochronous transfer + * which doesn't use the same timeout logic. + */ + if (!xfer->ux_timeout_set) + return; + + xfer->ux_timeout_reset = false; + if (!callout_stop(&xfer->ux_callout)) { + /* + * We stopped the callout before it ran. The timeout + * is no longer set. + */ + xfer->ux_timeout_set = false; + } else if (callout_invoking(&xfer->ux_callout)) { + /* + * The callout has begun to run but it has not yet + * acquired the lock and called callout_ack. The task + * cannot be queued yet, and the callout cannot have + * been rescheduled yet. + * + * By the time the callout acquires the lock, we will + * have transitioned from USBD_IN_PROGRESS to a + * completed status, and possibly also resubmitted the + * xfer and set xfer->ux_timeout_reset = true. In both + * cases, the callout will DTRT, so no further action + * is needed here. + */ + } else if (usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)) { + /* + * The callout had fired and scheduled the task, but we + * stopped the task before it could run. The timeout + * is therefore no longer set -- the next resubmission + * of the xfer must schedule a new timeout. + * + * The callout should not be be pending at this point: + * it is scheduled only under the lock, and only when + * xfer->ux_timeout_set is false, or by the callout or + * task itself when xfer->ux_timeout_reset is true. + */ + xfer->ux_timeout_set = false; + } + + /* + * The callout cannot be scheduled and the task cannot be + * queued at this point. Either we cancelled them, or they are + * already running and waiting for the bus lock. + */ + KASSERT(!callout_pending(&xfer->ux_callout)); + KASSERT(!usb_task_pending(xfer->ux_pipe->up_dev, &xfer->ux_aborttask)); + + KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); +} Index: src/sys/dev/usb/usbdi.h diff -u src/sys/dev/usb/usbdi.h:1.100 src/sys/dev/usb/usbdi.h:1.101 --- src/sys/dev/usb/usbdi.h:1.100 Wed Feb 12 15:59:44 2020 +++ src/sys/dev/usb/usbdi.h Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: usbdi.h,v 1.100 2020/02/12 15:59:44 riastradh Exp $ */ +/* $NetBSD: usbdi.h,v 1.101 2020/02/12 16:01:00 riastradh Exp $ */ /* $FreeBSD: src/sys/dev/usb/usbdi.h,v 1.18 1999/11/17 22:33:49 n_hibma Exp $ */ /* @@ -188,6 +188,11 @@ int usbd_ratecheck(struct timeval *); usbd_status usbd_get_string(struct usbd_device *, int, char *); usbd_status usbd_get_string0(struct usbd_device *, int, char *, int); +/* For use by HCI drivers, not USB device drivers */ +void usbd_xfer_schedule_timeout(struct usbd_xfer *); +bool usbd_xfer_trycomplete(struct usbd_xfer *); +void usbd_xfer_abort(struct usbd_xfer *); + /* An iterator for descriptors. */ typedef struct { const uByte *cur; Index: src/sys/dev/usb/usbdivar.h diff -u src/sys/dev/usb/usbdivar.h:1.121 src/sys/dev/usb/usbdivar.h:1.122 --- src/sys/dev/usb/usbdivar.h:1.121 Wed Feb 12 16:00:17 2020 +++ src/sys/dev/usb/usbdivar.h Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: usbdivar.h,v 1.121 2020/02/12 16:00:17 riastradh Exp $ */ +/* $NetBSD: usbdivar.h,v 1.122 2020/02/12 16:01:00 riastradh Exp $ */ /* * Copyright (c) 1998, 2012 The NetBSD Foundation, Inc. @@ -96,6 +96,8 @@ struct usbd_bus_methods { void (*ubm_dopoll)(struct usbd_bus *); struct usbd_xfer *(*ubm_allocx)(struct usbd_bus *, unsigned int); void (*ubm_freex)(struct usbd_bus *, struct usbd_xfer *); + void (*ubm_abortx)(struct usbd_xfer *); + bool (*ubm_dying)(struct usbd_bus *); void (*ubm_getlock)(struct usbd_bus *, kmutex_t **); usbd_status (*ubm_newdev)(device_t, struct usbd_bus *, int, int, int, struct usbd_port *); Index: src/sys/dev/usb/xhci.c diff -u src/sys/dev/usb/xhci.c:1.115 src/sys/dev/usb/xhci.c:1.116 --- src/sys/dev/usb/xhci.c:1.115 Sun Dec 29 09:17:51 2019 +++ src/sys/dev/usb/xhci.c Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: xhci.c,v 1.115 2019/12/29 09:17:51 skrll Exp $ */ +/* $NetBSD: xhci.c,v 1.116 2020/02/12 16:01:00 riastradh Exp $ */ /* * Copyright (c) 2013 Jonathan A. Kollasch @@ -34,7 +34,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: xhci.c,v 1.115 2019/12/29 09:17:51 skrll Exp $"); +__KERNEL_RCSID(0, "$NetBSD: xhci.c,v 1.116 2020/02/12 16:01:00 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" @@ -143,6 +143,8 @@ static void xhci_softintr(void *); static void xhci_poll(struct usbd_bus *); static struct usbd_xfer *xhci_allocx(struct usbd_bus *, unsigned int); static void xhci_freex(struct usbd_bus *, struct usbd_xfer *); +static void xhci_abortx(struct usbd_xfer *); +static bool xhci_dying(struct usbd_bus *); static void xhci_get_lock(struct usbd_bus *, kmutex_t **); static usbd_status xhci_new_device(device_t, struct usbd_bus *, int, int, int, struct usbd_port *); @@ -208,15 +210,14 @@ static void xhci_device_bulk_abort(struc static void xhci_device_bulk_close(struct usbd_pipe *); static void xhci_device_bulk_done(struct usbd_xfer *); -static void xhci_timeout(void *); -static void xhci_timeout_task(void *); - static const struct usbd_bus_methods xhci_bus_methods = { .ubm_open = xhci_open, .ubm_softint = xhci_softintr, .ubm_dopoll = xhci_poll, .ubm_allocx = xhci_allocx, .ubm_freex = xhci_freex, + .ubm_abortx = xhci_abortx, + .ubm_dying = xhci_dying, .ubm_getlock = xhci_get_lock, .ubm_newdev = xhci_new_device, .ubm_rhctrl = xhci_roothub_ctrl, @@ -1720,53 +1721,22 @@ xhci_close_pipe(struct usbd_pipe *pipe) * Should be called with sc_lock held. */ static void -xhci_abort_xfer(struct usbd_xfer *xfer, usbd_status status) +xhci_abortx(struct usbd_xfer *xfer) { XHCIHIST_FUNC(); 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); - KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT), - "invalid status for abort: %d", (int)status); - - XHCIHIST_CALLARGS("xfer %#jx pipe %#jx status %jd", - (uintptr_t)xfer, (uintptr_t)xfer->ux_pipe, status, 0); + XHCIHIST_CALLARGS("xfer %#jx pipe %#jx", + (uintptr_t)xfer, (uintptr_t)xfer->ux_pipe, 0, 0); KASSERT(mutex_owned(&sc->sc_lock)); ASSERT_SLEEPABLE(); - if (status == USBD_CANCELLED) { - /* - * We are synchronously aborting. Try to stop the - * callout and task, but if we can't, wait for them to - * complete. - */ - 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); - } else { - /* Otherwise, we are timing out. */ - KASSERT(status == USBD_TIMEOUT); - } - - /* - * The xfer cannot have been cancelled already. It is the - * responsibility of the caller of usbd_abort_pipe not to try - * to abort a pipe multiple times, whether concurrently or - * sequentially. - */ - KASSERT(xfer->ux_status != USBD_CANCELLED); - - /* Only the timeout, which runs only once, can time it out. */ - KASSERT(xfer->ux_status != USBD_TIMEOUT); - - /* If anyone else beat us, we're done. */ - if (xfer->ux_status != USBD_IN_PROGRESS) - return; - - /* We beat everyone else. Claim the status. */ - xfer->ux_status = status; + KASSERTMSG((xfer->ux_status == USBD_CANCELLED || + xfer->ux_status == USBD_TIMEOUT), + "bad abort status: %d", xfer->ux_status); /* * If we're dying, skip the hardware action and just notify the @@ -1998,6 +1968,13 @@ xhci_event_transfer(struct xhci_softc * return; } + /* + * Try to claim this xfer for completion. If it has already + * completed or aborted, drop it on the floor. + */ + if (!usbd_xfer_trycomplete(xfer)) + return; + /* 4.11.5.2 Event Data TRB */ if ((trb_3 & XHCI_TRB_3_ED_BIT) != 0) { DPRINTFN(14, "transfer Event Data: 0x%016jx 0x%08jx" @@ -2046,17 +2023,7 @@ xhci_event_transfer(struct xhci_softc * case XHCI_TRB_ERROR_STOPPED: case XHCI_TRB_ERROR_LENGTH: case XHCI_TRB_ERROR_STOPPED_SHORT: - /* - * don't complete the transfer being aborted - * as abort_xfer does instead. - */ - if (xfer->ux_status == USBD_CANCELLED || - xfer->ux_status == USBD_TIMEOUT) { - DPRINTFN(14, "ignore aborting xfer %#jx", - (uintptr_t)xfer, 0, 0, 0); - return; - } - err = USBD_CANCELLED; + err = USBD_IOERROR; break; case XHCI_TRB_ERROR_STALL: case XHCI_TRB_ERROR_BABBLE: @@ -2079,15 +2046,6 @@ xhci_event_transfer(struct xhci_softc * /* Override the status. */ xfer->ux_status = USBD_STALLED; - /* - * Cancel the timeout and the task, which have not yet - * run. If they have already fired, at worst they are - * waiting for the lock. They will see that the xfer - * is no longer in progress and give up. - */ - callout_stop(&xfer->ux_callout); - usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); - xhci_clear_endpoint_stall_async(xfer); return; default: @@ -2096,29 +2054,9 @@ xhci_event_transfer(struct xhci_softc * break; } - /* - * If software has completed it, either by cancellation - * or timeout, drop it on the floor. - */ - if (xfer->ux_status != USBD_IN_PROGRESS) { - KASSERTMSG((xfer->ux_status == USBD_CANCELLED || - xfer->ux_status == USBD_TIMEOUT), - "xfer %p status %x", xfer, xfer->ux_status); - return; - } - - /* Otherwise, set the status. */ + /* Set the status. */ xfer->ux_status = err; - /* - * Cancel the timeout and the task, which have not yet - * run. If they have already fired, at worst they are - * waiting for the lock. They will see that the xfer - * is no longer in progress and give up. - */ - callout_stop(&xfer->ux_callout); - usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); - if ((trb_3 & XHCI_TRB_3_ED_BIT) == 0 || (trb_0 & 0x3) == 0x0) { usb_transfer_complete(xfer); @@ -2285,8 +2223,6 @@ 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 @@ -2313,6 +2249,14 @@ xhci_freex(struct usbd_bus *bus, struct pool_cache_put(sc->sc_xferpool, xfer); } +static bool +xhci_dying(struct usbd_bus *bus) +{ + struct xhci_softc * const sc = XHCI_BUS2SC(bus); + + return sc->sc_dying; +} + static void xhci_get_lock(struct usbd_bus *bus, kmutex_t **lock) { @@ -3908,11 +3852,7 @@ xhci_device_ctrl_start(struct usbd_xfer mutex_enter(&sc->sc_lock); xfer->ux_status = USBD_IN_PROGRESS; xhci_db_write_4(sc, XHCI_DOORBELL(xs->xs_idx), dci); - - if (xfer->ux_timeout && !xhci_polling_p(sc)) { - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - xhci_timeout, xfer); - } + usbd_xfer_schedule_timeout(xfer); if (!polling) mutex_exit(&sc->sc_lock); @@ -3937,7 +3877,7 @@ xhci_device_ctrl_abort(struct usbd_xfer { XHCIHIST_FUNC(); XHCIHIST_CALLED(); - xhci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } static void @@ -4032,11 +3972,7 @@ xhci_device_bulk_start(struct usbd_xfer mutex_enter(&sc->sc_lock); xfer->ux_status = USBD_IN_PROGRESS; xhci_db_write_4(sc, XHCI_DOORBELL(xs->xs_idx), dci); - - if (xfer->ux_timeout && !xhci_polling_p(sc)) { - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - xhci_timeout, xfer); - } + usbd_xfer_schedule_timeout(xfer); if (!polling) mutex_exit(&sc->sc_lock); @@ -4065,7 +4001,7 @@ xhci_device_bulk_abort(struct usbd_xfer { XHCIHIST_FUNC(); XHCIHIST_CALLED(); - xhci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } static void @@ -4146,11 +4082,7 @@ xhci_device_intr_start(struct usbd_xfer mutex_enter(&sc->sc_lock); xfer->ux_status = USBD_IN_PROGRESS; xhci_db_write_4(sc, XHCI_DOORBELL(xs->xs_idx), dci); - - if (xfer->ux_timeout && !polling) { - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - xhci_timeout, xfer); - } + usbd_xfer_schedule_timeout(xfer); if (!polling) mutex_exit(&sc->sc_lock); @@ -4187,7 +4119,7 @@ xhci_device_intr_abort(struct usbd_xfer KASSERT(mutex_owned(&sc->sc_lock)); KASSERT(xfer->ux_pipe->up_intrxfer == xfer); - xhci_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } static void @@ -4200,32 +4132,3 @@ xhci_device_intr_close(struct usbd_pipe xhci_close_pipe(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); - struct usbd_device *dev = xfer->ux_pipe->up_dev; - - mutex_enter(&sc->sc_lock); - if (!sc->sc_dying && xfer->ux_status == USBD_IN_PROGRESS) - usb_add_task(dev, &xfer->ux_aborttask, USB_TASKQ_HC); - mutex_exit(&sc->sc_lock); -} - -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); - - mutex_enter(&sc->sc_lock); - xhci_abort_xfer(xfer, USBD_TIMEOUT); - mutex_exit(&sc->sc_lock); -} Index: src/sys/external/bsd/dwc2/dwc2.c diff -u src/sys/external/bsd/dwc2/dwc2.c:1.66 src/sys/external/bsd/dwc2/dwc2.c:1.67 --- src/sys/external/bsd/dwc2/dwc2.c:1.66 Wed Dec 4 06:28:35 2019 +++ src/sys/external/bsd/dwc2/dwc2.c Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: dwc2.c,v 1.66 2019/12/04 06:28:35 skrll Exp $ */ +/* $NetBSD: dwc2.c,v 1.67 2020/02/12 16:01:00 riastradh Exp $ */ /*- * Copyright (c) 2013 The NetBSD Foundation, Inc. @@ -30,7 +30,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: dwc2.c,v 1.66 2019/12/04 06:28:35 skrll Exp $"); +__KERNEL_RCSID(0, "$NetBSD: dwc2.c,v 1.67 2020/02/12 16:01:00 riastradh Exp $"); #include "opt_usb.h" @@ -116,6 +116,7 @@ Static struct usbd_xfer * dwc2_allocx(struct usbd_bus *, unsigned int); Static void dwc2_freex(struct usbd_bus *, struct usbd_xfer *); Static void dwc2_get_lock(struct usbd_bus *, kmutex_t **); +Static bool dwc2_dying(struct usbd_bus *); Static int dwc2_roothub_ctrl(struct usbd_bus *, usb_device_request_t *, void *, int); @@ -150,7 +151,7 @@ Static void dwc2_device_isoc_done(struc Static usbd_status dwc2_device_start(struct usbd_xfer *); Static void dwc2_close_pipe(struct usbd_pipe *); -Static void dwc2_abort_xfer(struct usbd_xfer *, usbd_status); +Static void dwc2_abortx(struct usbd_xfer *); Static void dwc2_device_clear_toggle(struct usbd_pipe *); Static void dwc2_noop(struct usbd_pipe *pipe); @@ -158,9 +159,6 @@ Static void dwc2_noop(struct usbd_pipe Static int dwc2_interrupt(struct dwc2_softc *); Static void dwc2_rhc(void *); -Static void dwc2_timeout(void *); -Static void dwc2_timeout_task(void *); - static inline void dwc2_allocate_bus_bandwidth(struct dwc2_hsotg *hsotg, u16 bw, @@ -180,6 +178,8 @@ Static const struct usbd_bus_methods dwc .ubm_dopoll = dwc2_poll, .ubm_allocx = dwc2_allocx, .ubm_freex = dwc2_freex, + .ubm_abortx = dwc2_abortx, + .ubm_dying = dwc2_dying, .ubm_getlock = dwc2_get_lock, .ubm_rhctrl = dwc2_roothub_ctrl, }; @@ -232,22 +232,15 @@ 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, xfer, - USB_TASKQ_MPSAFE); #ifdef DIAGNOSTIC dxfer->xfer.ux_state = XFER_BUSY; #endif @@ -275,6 +268,14 @@ dwc2_freex(struct usbd_bus *bus, struct pool_cache_put(sc->sc_xferpool, xfer); } +Static bool +dwc2_dying(struct usbd_bus *bus) +{ + struct dwc2_softc *sc = DWC2_BUS2SC(bus); + + return sc->sc_dying; +} + Static void dwc2_get_lock(struct usbd_bus *bus, kmutex_t **lock) { @@ -318,61 +319,49 @@ dwc2_softintr(void *v) struct usbd_bus *bus = v; struct dwc2_softc *sc = DWC2_BUS2SC(bus); struct dwc2_hsotg *hsotg = sc->sc_hsotg; - struct dwc2_xfer *dxfer; + struct dwc2_xfer *dxfer, *next; + TAILQ_HEAD(, dwc2_xfer) claimed = TAILQ_HEAD_INITIALIZER(claimed); KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); + /* + * Grab all the xfers that have not been aborted or timed out. + * Do so under a single lock -- without dropping it to run + * usb_transfer_complete as we go -- so that dwc2_abortx won't + * remove next out from under us during iteration when we've + * dropped the lock. + */ mutex_spin_enter(&hsotg->lock); - while ((dxfer = TAILQ_FIRST(&sc->sc_complete)) != NULL) { - - KASSERTMSG(!callout_pending(&dxfer->xfer.ux_callout), - "xfer %p pipe %p\n", dxfer, dxfer->xfer.ux_pipe); - - /* - * dwc2_abort_xfer will remove this transfer from the - * sc_complete queue - */ - /*XXXNH not tested */ - if (dxfer->xfer.ux_status == USBD_CANCELLED || - dxfer->xfer.ux_status == USBD_TIMEOUT) { + TAILQ_FOREACH_SAFE(dxfer, &sc->sc_complete, xnext, next) { + if (!usbd_xfer_trycomplete(&dxfer->xfer)) + /* + * The hard interrput handler decided to + * complete the xfer, and put it on sc_complete + * to pass it to us in the soft interrupt + * handler, but in the time between hard + * interrupt and soft interrupt, the xfer was + * aborted or timed out and we lost the race. + */ continue; - } - + KASSERT(dxfer->xfer.ux_status == USBD_IN_PROGRESS); + KASSERT(dxfer->intr_status != USBD_CANCELLED); + KASSERT(dxfer->intr_status != USBD_TIMEOUT); TAILQ_REMOVE(&sc->sc_complete, dxfer, xnext); - - mutex_spin_exit(&hsotg->lock); - usb_transfer_complete(&dxfer->xfer); - mutex_spin_enter(&hsotg->lock); + TAILQ_INSERT_TAIL(&claimed, dxfer, xnext); } mutex_spin_exit(&hsotg->lock); -} - -Static void -dwc2_timeout(void *addr) -{ - struct usbd_xfer *xfer = addr; - struct dwc2_softc *sc = DWC2_XFER2SC(xfer); - struct usbd_device *dev = xfer->ux_pipe->up_dev; - - DPRINTF("xfer=%p\n", xfer); - - mutex_enter(&sc->sc_lock); - if (!sc->sc_dying && xfer->ux_status == USBD_IN_PROGRESS) - usb_add_task(dev, &xfer->ux_aborttask, USB_TASKQ_HC); - mutex_exit(&sc->sc_lock); -} - -Static void -dwc2_timeout_task(void *addr) -{ - struct usbd_xfer *xfer = addr; - struct dwc2_softc *sc = DWC2_XFER2SC(xfer); - DPRINTF("xfer=%p\n", xfer); + /* Now complete them. */ + while (!TAILQ_EMPTY(&claimed)) { + dxfer = TAILQ_FIRST(&claimed); + KASSERT(dxfer->xfer.ux_status == USBD_IN_PROGRESS); + KASSERT(dxfer->intr_status != USBD_CANCELLED); + KASSERT(dxfer->intr_status != USBD_TIMEOUT); + TAILQ_REMOVE(&claimed, dxfer, xnext); - mutex_enter(&sc->sc_lock); - dwc2_abort_xfer(xfer, USBD_TIMEOUT); - mutex_exit(&sc->sc_lock); + dxfer->xfer.ux_status = dxfer->intr_status; + usb_transfer_complete(&dxfer->xfer); + } } usbd_status @@ -469,53 +458,53 @@ dwc2_close_pipe(struct usbd_pipe *pipe) * Abort a device request. */ Static void -dwc2_abort_xfer(struct usbd_xfer *xfer, usbd_status status) +dwc2_abortx(struct usbd_xfer *xfer) { struct dwc2_xfer *dxfer = DWC2_XFER2DXFER(xfer); struct dwc2_softc *sc = DWC2_XFER2SC(xfer); struct dwc2_hsotg *hsotg = sc->sc_hsotg; - struct dwc2_xfer *d, *tmp; + struct dwc2_xfer *d; int err; - KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT), - "invalid status for abort: %d", (int)status); - - DPRINTF("xfer %p pipe %p status 0x%08x", xfer, xfer->ux_pipe, status); + DPRINTF("xfer %p pipe %p status 0x%08x", xfer, xfer->ux_pipe, + xfer->ux_status); KASSERT(mutex_owned(&sc->sc_lock)); ASSERT_SLEEPABLE(); - if (status == USBD_CANCELLED) { - /* - * We are synchronously aborting. Try to stop the - * callout and task, but if we can't, wait for them to - * complete. - */ - 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); - } else { - /* Otherwise, we are timing out. */ - KASSERT(status == USBD_TIMEOUT); - } + KASSERTMSG((xfer->ux_status == USBD_CANCELLED || + xfer->ux_status == USBD_TIMEOUT), + "bad abort status: %d", xfer->ux_status); + + mutex_spin_enter(&hsotg->lock); /* - * The xfer cannot have been cancelled already. It is the - * responsibility of the caller of usbd_abort_pipe not to try - * to abort a pipe multiple times, whether concurrently or - * sequentially. + * Check whether we aborted or timed out after the hardware + * completion interrupt determined that it's done but before + * the soft interrupt could actually complete it. If so, it's + * too late for the soft interrupt -- at this point we've + * already committed to abort it or time it out, so we need to + * take it off the softint's list of work in case the caller, + * say, frees the xfer before the softint runs. + * + * This logic is unusual among host controller drivers, and + * happens because dwc2 decides to complete xfers in the hard + * interrupt handler rather than in the soft interrupt handler, + * but usb_transfer_complete must be deferred to softint -- and + * we happened to swoop in between the hard interrupt and the + * soft interrupt. Other host controller drivers do almost all + * processing in the softint so there's no intermediate stage. + * + * Fortunately, this linear search to discern the intermediate + * stage is not likely to be a serious performance impact + * because it happens only on abort or timeout. */ - KASSERT(xfer->ux_status != USBD_CANCELLED); - - /* Only the timeout, which runs only once, can time it out. */ - KASSERT(xfer->ux_status != USBD_TIMEOUT); - - /* If anyone else beat us, we're done. */ - if (xfer->ux_status != USBD_IN_PROGRESS) - return; - - /* We beat everyone else. Claim the status. */ - xfer->ux_status = status; + TAILQ_FOREACH(d, &sc->sc_complete, xnext) { + if (d == dxfer) { + TAILQ_REMOVE(&sc->sc_complete, dxfer, xnext); + break; + } + } /* * If we're dying, skip the hardware action and just notify the @@ -529,26 +518,17 @@ dwc2_abort_xfer(struct usbd_xfer *xfer, /* * HC Step 1: 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; - } - } - err = dwc2_hcd_urb_dequeue(hsotg, dxfer->urb); if (err) { DPRINTF("dwc2_hcd_urb_dequeue failed\n"); } +dying: mutex_spin_exit(&hsotg->lock); /* * Final Step: Notify completion to waiting xfers. */ -dying: usb_transfer_complete(xfer); KASSERT(mutex_owned(&sc->sc_lock)); } @@ -761,7 +741,7 @@ dwc2_device_ctrl_abort(struct usbd_xfer KASSERT(mutex_owned(&sc->sc_lock)); DPRINTF("xfer=%p\n", xfer); - dwc2_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } Static void @@ -811,7 +791,7 @@ dwc2_device_bulk_abort(struct usbd_xfer KASSERT(mutex_owned(&sc->sc_lock)); DPRINTF("xfer=%p\n", xfer); - dwc2_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } Static void @@ -885,8 +865,7 @@ dwc2_device_intr_abort(struct usbd_xfer KASSERT(xfer->ux_pipe->up_intrxfer == xfer); DPRINTF("xfer=%p\n", xfer); - - dwc2_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } Static void @@ -935,7 +914,7 @@ dwc2_device_isoc_abort(struct usbd_xfer KASSERT(mutex_owned(&sc->sc_lock)); DPRINTF("xfer=%p\n", xfer); - dwc2_abort_xfer(xfer, USBD_CANCELLED); + usbd_xfer_abort(xfer); } void @@ -1150,14 +1129,11 @@ dwc2_device_start(struct usbd_xfer *xfer /* might need to check cpu_intr_p */ mutex_spin_enter(&hsotg->lock); - - if (xfer->ux_timeout && !sc->sc_bus.ub_usepolling) { - callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), - dwc2_timeout, xfer); - } retval = dwc2_hcd_urb_enqueue(hsotg, dwc2_urb, qh, qtd); if (retval) goto fail2; + usbd_xfer_schedule_timeout(xfer); + xfer->ux_status = USBD_IN_PROGRESS; if (alloc_bandwidth) { dwc2_allocate_bus_bandwidth(hsotg, @@ -1171,7 +1147,6 @@ dwc2_device_start(struct usbd_xfer *xfer return USBD_IN_PROGRESS; fail2: - callout_halt(&xfer->ux_callout, &hsotg->lock); dwc2_urb->priv = NULL; mutex_spin_exit(&hsotg->lock); pool_cache_put(sc->sc_qtdpool, qtd); @@ -1461,6 +1436,8 @@ void dwc2_host_complete(struct dwc2_hsot usb_endpoint_descriptor_t *ed; uint8_t xfertype; + KASSERT(mutex_owned(&hsotg->lock)); + if (!qtd) { dev_dbg(hsotg->dev, "## %s: qtd is NULL ##\n", __func__); return; @@ -1477,25 +1454,6 @@ void dwc2_host_complete(struct dwc2_hsot return; } - /* - * If software has completed it, either by cancellation - * or timeout, drop it on the floor. - */ - if (xfer->ux_status != USBD_IN_PROGRESS) { - KASSERT(xfer->ux_status == USBD_CANCELLED || - xfer->ux_status == USBD_TIMEOUT); - return; - } - - /* - * Cancel the timeout and the task, which have not yet - * run. If they have already fired, at worst they are - * waiting for the lock. They will see that the xfer - * is no longer in progress and give up. - */ - callout_stop(&xfer->ux_callout); - usb_rem_task(xfer->ux_pipe->up_dev, &xfer->ux_aborttask); - dxfer = DWC2_XFER2DXFER(xfer); sc = DWC2_XFER2SC(xfer); ed = xfer->ux_pipe->up_endpoint->ue_edesc; @@ -1533,29 +1491,26 @@ void dwc2_host_complete(struct dwc2_hsot switch (status) { case 0: - xfer->ux_status = USBD_NORMAL_COMPLETION; + dxfer->intr_status = USBD_NORMAL_COMPLETION; break; case -EPIPE: - xfer->ux_status = USBD_STALLED; - break; - case -ETIMEDOUT: - xfer->ux_status = USBD_TIMEOUT; + dxfer->intr_status = USBD_STALLED; break; case -EPROTO: - xfer->ux_status = USBD_INVAL; + dxfer->intr_status = USBD_INVAL; break; case -EIO: - xfer->ux_status = USBD_IOERROR; + dxfer->intr_status = USBD_IOERROR; break; case -EOVERFLOW: - xfer->ux_status = USBD_IOERROR; + dxfer->intr_status = USBD_IOERROR; break; default: - xfer->ux_status = USBD_IOERROR; + dxfer->intr_status = USBD_IOERROR; printf("%s: unknown error status %d\n", __func__, status); } - if (xfer->ux_status == USBD_NORMAL_COMPLETION) { + if (dxfer->intr_status == USBD_NORMAL_COMPLETION) { /* * control transfers with no data phase don't touch dmabuf, but * everything else does. Index: src/sys/external/bsd/dwc2/dwc2var.h diff -u src/sys/external/bsd/dwc2/dwc2var.h:1.6 src/sys/external/bsd/dwc2/dwc2var.h:1.7 --- src/sys/external/bsd/dwc2/dwc2var.h:1.6 Sun Nov 18 11:48:57 2018 +++ src/sys/external/bsd/dwc2/dwc2var.h Wed Feb 12 16:01:00 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: dwc2var.h,v 1.6 2018/11/18 11:48:57 skrll Exp $ */ +/* $NetBSD: dwc2var.h,v 1.7 2020/02/12 16:01:00 riastradh Exp $ */ /*- * Copyright (c) 2013 The NetBSD Foundation, Inc. @@ -43,6 +43,7 @@ struct dwc2_xfer { struct dwc2_hcd_urb *urb; TAILQ_ENTRY(dwc2_xfer) xnext; /* list of complete xfers */ + usbd_status intr_status; }; struct dwc2_pipe {