diff --git a/sys/dev/pci/ehci_pci.c b/sys/dev/pci/ehci_pci.c index b69a93b70850..1b6244eebbb9 100644 --- a/sys/dev/pci/ehci_pci.c +++ b/sys/dev/pci/ehci_pci.c @@ -325,6 +325,7 @@ ehci_pci_detach(device_t self, int flags) #if 1 /* XXX created in ehci.c */ if (sc->sc_init_state >= EHCI_INIT_INITED) { + mutex_destroy(&sc->sc.sc_rhlock); mutex_destroy(&sc->sc.sc_lock); mutex_destroy(&sc->sc.sc_intr_lock); softint_disestablish(sc->sc.sc_doorbell_si); diff --git a/sys/dev/usb/ehci.c b/sys/dev/usb/ehci.c index 1ab17bf8c225..811b2461cdd1 100644 --- a/sys/dev/usb/ehci.c +++ b/sys/dev/usb/ehci.c @@ -412,6 +412,7 @@ ehci_init(ehci_softc_t *sc) theehci = sc; #endif + mutex_init(&sc->sc_rhlock, MUTEX_DEFAULT, IPL_NONE); mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB); mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_USB); cv_init(&sc->sc_doorbell, "ehcidb"); @@ -703,6 +704,7 @@ fail2: fail1: softint_disestablish(sc->sc_doorbell_si); softint_disestablish(sc->sc_pcd_si); + mutex_destroy(&sc->sc_rhlock); mutex_destroy(&sc->sc_lock); mutex_destroy(&sc->sc_intr_lock); @@ -816,7 +818,10 @@ ehci_doorbell(void *addr) EHCIHIST_FUNC(); EHCIHIST_CALLED(); mutex_enter(&sc->sc_lock); - cv_broadcast(&sc->sc_doorbell); + if (sc->sc_doorbelllwp == NULL) + DPRINTF("spurious doorbell interrupt", 0, 0, 0, 0); + sc->sc_doorbelllwp = NULL; + cv_signal(&sc->sc_doorbell); mutex_exit(&sc->sc_lock); } @@ -1408,6 +1413,7 @@ ehci_detach(struct ehci_softc *sc, int flags) /* XXX destroyed in ehci_pci.c as it controls ehci_intr access */ softint_disestablish(sc->sc_doorbell_si); softint_disestablish(sc->sc_pcd_si); + mutex_destroy(&sc->sc_rhlock); mutex_destroy(&sc->sc_lock); mutex_destroy(&sc->sc_intr_lock); #endif @@ -1438,9 +1444,6 @@ ehci_activate(device_t self, enum devact act) * * Note that this power handler isn't to be registered directly; the * bus glue needs to call out to it. - * - * XXX This should be serialized with ehci_roothub_ctrl's access to the - * portsc registers. */ bool ehci_suspend(device_t dv, const pmf_qual_t *qual) @@ -1451,6 +1454,8 @@ ehci_suspend(device_t dv, const pmf_qual_t *qual) EHCIHIST_FUNC(); EHCIHIST_CALLED(); + mutex_enter(&sc->sc_rhlock); + for (i = 1; i <= sc->sc_noport; i++) { cmd = EOREAD4(sc, EHCI_PORTSC(i)) & ~EHCI_PS_CLEAR; if ((cmd & EHCI_PS_PO) == 0 && (cmd & EHCI_PS_PE) == EHCI_PS_PE) @@ -1485,6 +1490,8 @@ ehci_suspend(device_t dv, const pmf_qual_t *qual) if (hcr != EHCI_STS_HCH) printf("%s: config timeout\n", device_xname(dv)); + mutex_exit(&sc->sc_rhlock); + return true; } @@ -1497,6 +1504,8 @@ ehci_resume(device_t dv, const pmf_qual_t *qual) EHCIHIST_FUNC(); EHCIHIST_CALLED(); + mutex_enter(&sc->sc_rhlock); + /* restore things in case the bios sucks */ EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); EOWRITE4(sc, EHCI_PERIODICLISTBASE, DMAADDR(&sc->sc_fldma, 0)); @@ -1542,6 +1551,8 @@ ehci_resume(device_t dv, const pmf_qual_t *qual) if (hcr == EHCI_STS_HCH) printf("%s: config timeout\n", device_xname(dv)); + mutex_exit(&sc->sc_rhlock); + return true; } @@ -2220,11 +2231,16 @@ ehci_set_qh_qtd(ehci_soft_qh_t *sqh, ehci_soft_qtd_t *sqtd) * by asking for a Async Advance Doorbell interrupt and then we wait for * the interrupt. * To make this easier we first obtain exclusive use of the doorbell. + * + * Releases the bus lock to sleep while waiting for interrupt. */ Static void ehci_sync_hc(ehci_softc_t *sc) { - int error __diagused; + unsigned delta = hz; + unsigned starttime = getticks(); + unsigned endtime = starttime + delta; + unsigned now; KASSERT(mutex_owned(&sc->sc_lock)); @@ -2235,22 +2251,39 @@ ehci_sync_hc(ehci_softc_t *sc) return; } + /* + * Wait until any concurrent ehci_sync_hc has completed so we + * have exclusive access to the doorbell. + */ + while (sc->sc_doorbelllwp) + cv_wait(&sc->sc_doorbell, &sc->sc_lock); + sc->sc_doorbelllwp = curlwp; + /* ask for doorbell */ EOWRITE4(sc, EHCI_USBCMD, EOREAD4(sc, EHCI_USBCMD) | EHCI_CMD_IAAD); DPRINTF("cmd = 0x%08jx sts = 0x%08jx", EOREAD4(sc, EHCI_USBCMD), EOREAD4(sc, EHCI_USBSTS), 0, 0); - error = cv_timedwait(&sc->sc_doorbell, &sc->sc_lock, hz); /* bell wait */ + /* + * Wait for the ehci to ring our doorbell. + */ + while (sc->sc_doorbelllwp == curlwp) { + now = getticks(); + if (endtime - now > delta) { + sc->sc_doorbelllwp = NULL; + cv_signal(&sc->sc_doorbell); + DPRINTF("doorbell timeout", 0, 0, 0, 0); +#ifdef DIAGNOSTIC /* XXX DIAGNOSTIC abuse, do this differently */ + printf("ehci_sync_hc: timed out\n"); +#endif + break; + } + (void)cv_timedwait(&sc->sc_doorbell, &sc->sc_lock, + endtime - now); + } DPRINTF("cmd = 0x%08jx sts = 0x%08jx ... done", EOREAD4(sc, EHCI_USBCMD), EOREAD4(sc, EHCI_USBSTS), 0, 0); -#ifdef DIAGNOSTIC - if (error == EWOULDBLOCK) { - printf("ehci_sync_hc: timed out\n"); - } else if (error) { - printf("ehci_sync_hc: cv_timedwait: error %d\n", error); - } -#endif } Static void @@ -2350,8 +2383,8 @@ ehci_free_sitd_chain(ehci_softc_t *sc, struct ehci_soft_sitd *sitd) /***********/ -Static int -ehci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req, +static int +ehci_roothub_ctrl_locked(struct usbd_bus *bus, usb_device_request_t *req, void *buf, int buflen) { ehci_softc_t *sc = EHCI_BUS2SC(bus); @@ -2364,10 +2397,7 @@ ehci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req, EHCIHIST_FUNC(); EHCIHIST_CALLED(); - /* - * XXX This should be serialized with ehci_suspend/resume's - * access to the portsc registers. - */ + KASSERT(mutex_owned(&sc->sc_rhlock)); if (sc->sc_dying) return -1; @@ -2642,6 +2672,20 @@ ehci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req, return totlen; } +Static int +ehci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req, + void *buf, int buflen) +{ + struct ehci_softc *sc = EHCI_BUS2SC(bus); + int actlen; + + mutex_enter(&sc->sc_rhlock); + actlen = ehci_roothub_ctrl_locked(bus, req, buf, buflen); + mutex_exit(&sc->sc_rhlock); + + return actlen; +} + /* * Handle ehci hand-off in early boot vs RB_ASKNAME/RB_SINGLE. * diff --git a/sys/dev/usb/ehcivar.h b/sys/dev/usb/ehcivar.h index eb7690ba63a0..19e04bc79fe8 100644 --- a/sys/dev/usb/ehcivar.h +++ b/sys/dev/usb/ehcivar.h @@ -162,10 +162,12 @@ struct ehci_soft_islot { typedef struct ehci_softc { device_t sc_dev; + kmutex_t sc_rhlock; kmutex_t sc_lock; kmutex_t sc_intr_lock; kcondvar_t sc_doorbell; void *sc_doorbell_si; + struct lwp *sc_doorbelllwp; void *sc_pcd_si; struct usbd_bus sc_bus; bus_space_tag_t iot; diff --git a/sys/dev/usb/uhci.c b/sys/dev/usb/uhci.c index fa80f49d6e6c..db16c2bc2519 100644 --- a/sys/dev/usb/uhci.c +++ b/sys/dev/usb/uhci.c @@ -166,7 +166,7 @@ typedef TAILQ_HEAD(ux_completeq, uhci_xfer) ux_completeq_t; Static void uhci_globalreset(uhci_softc_t *); Static usbd_status uhci_portreset(uhci_softc_t*, int); Static void uhci_reset(uhci_softc_t *); -Static usbd_status uhci_run(uhci_softc_t *, int, int); +Static usbd_status uhci_run(uhci_softc_t *, int); Static uhci_soft_td_t *uhci_alloc_std(uhci_softc_t *); Static void uhci_free_std(uhci_softc_t *, uhci_soft_td_t *); Static void uhci_free_std_locked(uhci_softc_t *, uhci_soft_td_t *); @@ -587,7 +587,7 @@ uhci_init(uhci_softc_t *sc) DPRINTF("Enabling...", 0, 0, 0, 0); - err = uhci_run(sc, 1, 0); /* and here we go... */ + err = uhci_run(sc, 1); /* and here we go... */ UWRITE2(sc, UHCI_INTR, UHCI_INTR_TOCRCIE | UHCI_INTR_RIE | UHCI_INTR_IOCE | UHCI_INTR_SPIE); /* enable interrupts */ return err; @@ -638,6 +638,7 @@ uhci_detach(struct uhci_softc *sc, int flags) if (rv != 0) return rv; + KASSERT(sc->sc_intr_xfer == NULL); callout_halt(&sc->sc_poll_handle, NULL); callout_destroy(&sc->sc_poll_handle); @@ -717,15 +718,12 @@ uhci_resume(device_t dv, const pmf_qual_t *qual) uhci_softc_t *sc = device_private(dv); int cmd; - mutex_spin_enter(&sc->sc_intr_lock); - cmd = UREAD2(sc, UHCI_CMD); - sc->sc_bus.ub_usepolling++; UWRITE2(sc, UHCI_INTR, 0); uhci_globalreset(sc); uhci_reset(sc); if (cmd & UHCI_CMD_RS) - uhci_run(sc, 0, 1); + uhci_run(sc, 0); /* restore saved state */ UWRITE4(sc, UHCI_FLBASEADDR, DMAADDR(&sc->sc_dma, 0)); @@ -733,23 +731,23 @@ uhci_resume(device_t dv, const pmf_qual_t *qual) UWRITE1(sc, UHCI_SOF, sc->sc_saved_sof); UHCICMD(sc, cmd | UHCI_CMD_FGR); /* force resume */ - usb_delay_ms_locked(&sc->sc_bus, USB_RESUME_DELAY, &sc->sc_intr_lock); + usb_delay_ms(&sc->sc_bus, USB_RESUME_DELAY); UHCICMD(sc, cmd & ~UHCI_CMD_EGSM); /* back to normal */ UWRITE2(sc, UHCI_INTR, UHCI_INTR_TOCRCIE | UHCI_INTR_RIE | UHCI_INTR_IOCE | UHCI_INTR_SPIE); UHCICMD(sc, UHCI_CMD_MAXP); - uhci_run(sc, 1, 1); /* and start traffic again */ - usb_delay_ms_locked(&sc->sc_bus, USB_RESUME_RECOVERY, &sc->sc_intr_lock); - sc->sc_bus.ub_usepolling--; - if (sc->sc_intr_xfer != NULL) - callout_schedule(&sc->sc_poll_handle, sc->sc_ival); + uhci_run(sc, 1); /* and start traffic again */ + usb_delay_ms(&sc->sc_bus, USB_RESUME_RECOVERY); #ifdef UHCI_DEBUG if (uhcidebug >= 2) uhci_dumpregs(sc); #endif + mutex_enter(&sc->sc_lock); sc->sc_suspend = PWR_RESUME; - mutex_spin_exit(&sc->sc_intr_lock); + if (sc->sc_intr_xfer != NULL) + callout_schedule(&sc->sc_poll_handle, sc->sc_ival); + mutex_exit(&sc->sc_lock); return true; } @@ -760,7 +758,11 @@ uhci_suspend(device_t dv, const pmf_qual_t *qual) uhci_softc_t *sc = device_private(dv); int cmd; - mutex_spin_enter(&sc->sc_intr_lock); + mutex_enter(&sc->sc_lock); + sc->sc_suspend = PWR_SUSPEND; + if (sc->sc_intr_xfer != NULL) + callout_halt(&sc->sc_poll_handle, &sc->sc_intr_lock); + mutex_exit(&sc->sc_lock); cmd = UREAD2(sc, UHCI_CMD); @@ -768,12 +770,8 @@ uhci_suspend(device_t dv, const pmf_qual_t *qual) if (uhcidebug >= 2) uhci_dumpregs(sc); #endif - sc->sc_suspend = PWR_SUSPEND; - if (sc->sc_intr_xfer != NULL) - callout_halt(&sc->sc_poll_handle, &sc->sc_intr_lock); - sc->sc_bus.ub_usepolling++; - uhci_run(sc, 0, 1); /* stop the controller */ + uhci_run(sc, 0); /* stop the controller */ cmd &= ~UHCI_CMD_RS; /* save some state if BIOS doesn't */ @@ -783,10 +781,7 @@ uhci_suspend(device_t dv, const pmf_qual_t *qual) UWRITE2(sc, UHCI_INTR, 0); /* disable intrs */ UHCICMD(sc, cmd | UHCI_CMD_EGSM); /* enter suspend */ - usb_delay_ms_locked(&sc->sc_bus, USB_RESUME_WAIT, &sc->sc_intr_lock); - sc->sc_bus.ub_usepolling--; - - mutex_spin_exit(&sc->sc_intr_lock); + usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); return true; } @@ -1783,7 +1778,7 @@ uhci_reset(uhci_softc_t *sc) } usbd_status -uhci_run(uhci_softc_t *sc, int run, int locked) +uhci_run(uhci_softc_t *sc, int run) { int n, running; uint16_t cmd; @@ -1791,8 +1786,6 @@ uhci_run(uhci_softc_t *sc, int run, int locked) UHCIHIST_FUNC(); UHCIHIST_CALLED(); run = run != 0; - if (!locked) - mutex_spin_enter(&sc->sc_intr_lock); DPRINTF("setting run=%jd", run, 0, 0, 0); cmd = UREAD2(sc, UHCI_CMD); @@ -1805,16 +1798,12 @@ uhci_run(uhci_softc_t *sc, int run, int locked) running = !(UREAD2(sc, UHCI_STS) & UHCI_STS_HCH); /* return when we've entered the state we want */ if (run == running) { - if (!locked) - mutex_spin_exit(&sc->sc_intr_lock); DPRINTF("done cmd=%#jx sts=%#jx", UREAD2(sc, UHCI_CMD), UREAD2(sc, UHCI_STS), 0, 0); return USBD_NORMAL_COMPLETION; } - usb_delay_ms_locked(&sc->sc_bus, 1, &sc->sc_intr_lock); + usb_delay_ms(&sc->sc_bus, 1); } - if (!locked) - mutex_spin_exit(&sc->sc_intr_lock); printf("%s: cannot %s\n", device_xname(sc->sc_dev), run ? "start" : "stop"); return USBD_IOERROR; @@ -3855,7 +3844,8 @@ uhci_root_intr_start(struct usbd_xfer *xfer) /* XXX temporary variable needed to avoid gcc3 warning */ ival = xfer->ux_pipe->up_endpoint->ue_edesc->bInterval; sc->sc_ival = mstohz(ival); - callout_schedule(&sc->sc_poll_handle, sc->sc_ival); + if (sc->sc_suspend == PWR_RESUME) + callout_schedule(&sc->sc_poll_handle, sc->sc_ival); sc->sc_intr_xfer = xfer; xfer->ux_status = USBD_IN_PROGRESS; diff --git a/sys/dev/usb/usb.c b/sys/dev/usb/usb.c index 9188636084dd..3d93b63c52e4 100644 --- a/sys/dev/usb/usb.c +++ b/sys/dev/usb/usb.c @@ -296,6 +296,7 @@ usb_attach(device_t parent, device_t self, void *aux) usbrev = sc->sc_bus->ub_revision; cv_init(&sc->sc_bus->ub_needsexplore_cv, "usbevt"); + cv_init(&sc->sc_bus->ub_rhxfercv, "usbrhxfer"); sc->sc_pmf_registered = false; aprint_naive("\n"); @@ -1430,6 +1431,7 @@ usb_detach(device_t self, int flags) usb_add_event(USB_EVENT_CTRLR_DETACH, ue); cv_destroy(&sc->sc_bus->ub_needsexplore_cv); + cv_destroy(&sc->sc_bus->ub_rhxfercv); return 0; } diff --git a/sys/dev/usb/usb_subr.c b/sys/dev/usb/usb_subr.c index e8e886abfced..815710e05a65 100644 --- a/sys/dev/usb/usb_subr.c +++ b/sys/dev/usb/usb_subr.c @@ -948,6 +948,8 @@ usbd_setup_pipe_flags(struct usbd_device *dev, struct usbd_interface *iface, SIMPLEQ_INIT(&p->up_queue); p->up_callingxfer = NULL; cv_init(&p->up_callingcv, "usbpipecb"); + p->up_abortlwp = NULL; + cv_init(&p->up_abortlwpcv, "usbabort"); err = dev->ud_bus->ub_methods->ubm_open(p); if (err) { @@ -967,6 +969,9 @@ usbd_setup_pipe_flags(struct usbd_device *dev, struct usbd_interface *iface, err = USBD_NORMAL_COMPLETION; out: if (p) { + KASSERT(p->up_abortlwp == NULL); + KASSERT(p->up_callingxfer == NULL); + cv_destroy(&p->up_abortlwpcv); cv_destroy(&p->up_callingcv); kmem_free(p, dev->ud_bus->ub_pipesize); } diff --git a/sys/dev/usb/usbdi.c b/sys/dev/usb/usbdi.c index cb7e2ec285f9..32b2dbb23a32 100644 --- a/sys/dev/usb/usbdi.c +++ b/sys/dev/usb/usbdi.c @@ -354,6 +354,7 @@ usbd_close_pipe(struct usbd_pipe *pipe) pipe->up_methods->upm_close(pipe); usbd_unlock_pipe(pipe); + cv_destroy(&pipe->up_abortlwpcv); cv_destroy(&pipe->up_callingcv); if (pipe->up_intrxfer) usbd_destroy_xfer(pipe->up_intrxfer); @@ -1017,8 +1018,19 @@ usbd_ar_pipe(struct usbd_pipe *pipe) USBHIST_CALLARGS(usbdebug, "pipe = %#jx", (uintptr_t)pipe, 0, 0, 0); SDT_PROBE1(usb, device, pipe, abort__start, pipe); + ASSERT_SLEEPABLE(); KASSERT(mutex_owned(pipe->up_dev->ud_bus->ub_lock)); + /* + * Allow only one thread at a time to abort the pipe, so we + * don't get confused if upm_abort drops the lock in the middle + * of the abort to wait for hardware completion softints to + * stop using the xfer before returning. + */ + while (pipe->up_abortlwp) + cv_wait(&pipe->up_abortlwpcv, pipe->up_dev->ud_bus->ub_lock); + pipe->up_abortlwp = curlwp; + #ifdef USB_DEBUG if (usbdebug > 5) usbd_dump_queue(pipe); @@ -1050,6 +1062,12 @@ usbd_ar_pipe(struct usbd_pipe *pipe) /* XXX only for non-0 usbd_clear_endpoint_stall(pipe); */ } } + + KASSERT(mutex_owned(pipe->up_dev->ud_bus->ub_lock)); + KASSERT(pipe->up_abortlwp == curlwp); + pipe->up_abortlwp = NULL; + cv_signal(&pipe->up_abortlwpcv); + SDT_PROBE1(usb, device, pipe, abort__done, pipe); } @@ -1489,7 +1507,11 @@ usbd_get_string0(struct usbd_device *dev, int si, char *buf, int unicode) * in a host controller interrupt handler. * * Caller must either hold the bus lock or have the bus in polling - * mode. + * mode. If this succeeds, caller must proceed to call + * usb_complete_transfer under the bus lock or with polling + * enabled -- must not release and reacquire the bus lock in the + * meantime. Failing to heed this rule may lead to catastrophe + * with abort or timeout. */ bool usbd_xfer_trycomplete(struct usbd_xfer *xfer) diff --git a/sys/dev/usb/usbdivar.h b/sys/dev/usb/usbdivar.h index 1be0236e49d2..a7ef6b2d53ab 100644 --- a/sys/dev/usb/usbdivar.h +++ b/sys/dev/usb/usbdivar.h @@ -181,6 +181,8 @@ struct usbd_bus { /* Filled by usb driver */ kmutex_t *ub_lock; struct usbd_device *ub_roothub; + struct usbd_xfer *ub_rhxfer; /* roothub xfer in progress */ + kcondvar_t ub_rhxfercv; uint8_t ub_rhaddr; /* roothub address */ uint8_t ub_rhconf; /* roothub configuration */ struct usbd_device *ub_devices[USB_TOTAL_DEVICES]; @@ -257,6 +259,9 @@ struct usbd_pipe { struct usbd_xfer *up_callingxfer; /* currently in callback */ kcondvar_t up_callingcv; + struct lwp *up_abortlwp; /* another lwp aborting */ + kcondvar_t up_abortlwpcv; + /* Filled by HC driver. */ const struct usbd_pipe_methods *up_methods; diff --git a/sys/dev/usb/usbroothub.c b/sys/dev/usb/usbroothub.c index 74d42266725e..adf00c6d2c80 100644 --- a/sys/dev/usb/usbroothub.c +++ b/sys/dev/usb/usbroothub.c @@ -368,6 +368,9 @@ roothub_ctrl_start(struct usbd_xfer *xfer) */ KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock)); + /* Roothub xfers are serialized through the pipe. */ + KASSERTMSG(bus->ub_rhxfer == NULL, "rhxfer=%p", bus->ub_rhxfer); + KASSERT(xfer->ux_rqflags & URQ_REQUEST); req = &xfer->ux_request; @@ -549,19 +552,19 @@ roothub_ctrl_start(struct usbd_xfer *xfer) break; } - /* - * XXX This needs some mechanism for concurrent - * roothub_ctrl_abort to wait for ubm_rhctrl to finish. We - * can't use the bus lock because many ubm_rhctrl methods do - * usb_delay_ms and many bus locks are taken in softint - * context, leading to deadlock in the softclock needed to wake - * up usb_delay_ms. - */ + KASSERTMSG(bus->ub_rhxfer == NULL, "rhxfer=%p", bus->ub_rhxfer); + bus->ub_rhxfer = xfer; if (!bus->ub_usepolling) mutex_exit(bus->ub_lock); + actlen = bus->ub_methods->ubm_rhctrl(bus, req, buf, buflen); + if (!bus->ub_usepolling) mutex_enter(bus->ub_lock); + KASSERTMSG(bus->ub_rhxfer == xfer, "rhxfer=%p", bus->ub_rhxfer); + bus->ub_rhxfer = NULL; + cv_signal(&bus->ub_rhxfercv); + if (actlen < 0) goto fail; @@ -582,8 +585,19 @@ roothub_ctrl_start(struct usbd_xfer *xfer) Static void roothub_ctrl_abort(struct usbd_xfer *xfer) { + struct usbd_bus *bus = xfer->ux_bus; + + KASSERT(mutex_owned(bus->ub_lock)); + KASSERTMSG(bus->ub_rhxfer == xfer, "rhxfer=%p", bus->ub_rhxfer); - /* Nothing to do, all transfers are synchronous. */ + /* + * No mechanism to abort the xfer (would have to coordinate + * with the bus's ubm_rhctrl to be useful, and usually at most + * there's some short bounded delays of a few tens of + * milliseconds), so just wait for it to complete. + */ + while (bus->ub_rhxfer == xfer) + cv_wait(&bus->ub_rhxfercv, bus->ub_lock); } /* Close the root pipe. */ diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c index 6f271ac18229..a3cdaa987397 100644 --- a/sys/dev/usb/xhci.c +++ b/sys/dev/usb/xhci.c @@ -154,8 +154,9 @@ static usbd_status xhci_new_device(device_t, struct usbd_bus *, int, int, int, static int xhci_roothub_ctrl(struct usbd_bus *, usb_device_request_t *, void *, int); -static void xhci_pipe_async_task(void *); static void xhci_pipe_restart(struct usbd_pipe *); +static void xhci_pipe_restart_async_task(void *); +static void xhci_pipe_restart_async(struct usbd_pipe *); static usbd_status xhci_configure_endpoint(struct usbd_pipe *); //static usbd_status xhci_unconfigure_endpoint(struct usbd_pipe *); @@ -707,6 +708,12 @@ xhci_suspend(device_t self, const pmf_qual_t *qual) cv_wait(&sc->sc_cmdbusy_cv, &sc->sc_lock); mutex_exit(&sc->sc_lock); + /* + * Block roothub xfers which might touch portsc registers until + * we're done suspending. + */ + mutex_enter(&sc->sc_rhlock); + /* * xHCI Requirements Specification 1.2, May 2019, Sec. 4.23.2: * xHCI Power Management, p. 342 @@ -888,7 +895,8 @@ xhci_suspend(device_t self, const pmf_qual_t *qual) /* Success! */ ok = true; -out: return ok; +out: mutex_exit(&sc->sc_rhlock); + return ok; } bool @@ -904,6 +912,12 @@ xhci_resume(device_t self, const pmf_qual_t *qual) KASSERT(sc->sc_suspender); + /* + * Block roothub xfers which might touch portsc registers until + * we're done resuming. + */ + mutex_enter(&sc->sc_rhlock); + /* * xHCI Requirements Specification 1.2, May 2019, Sec. 4.23.2: * xHCI Power Management, p. 343 @@ -1088,7 +1102,8 @@ xhci_resume(device_t self, const pmf_qual_t *qual) /* Success! */ ok = true; -out: return ok; +out: mutex_exit(&sc->sc_rhlock); + return ok; } bool @@ -1590,6 +1605,7 @@ xhci_init(struct xhci_softc *sc) cv_init(&sc->sc_command_cv, "xhcicmd"); cv_init(&sc->sc_cmdbusy_cv, "xhcicmdq"); + mutex_init(&sc->sc_rhlock, MUTEX_DEFAULT, IPL_NONE); mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB); mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_USB); @@ -2017,8 +2033,8 @@ xhci_open(struct usbd_pipe *pipe) return USBD_NORMAL_COMPLETION; } - usb_init_task(&xpipe->xp_async_task, xhci_pipe_async_task, xpipe, - USB_TASKQ_MPSAFE); + usb_init_task(&xpipe->xp_async_task, xhci_pipe_restart_async_task, + pipe, USB_TASKQ_MPSAFE); switch (xfertype) { case UE_CONTROL: @@ -2137,8 +2153,8 @@ xhci_close_pipe(struct usbd_pipe *pipe) } /* - * Abort transfer. - * Should be called with sc_lock held. Must not drop sc_lock. + * Abort transfer. Must be called with sc_lock held. Releases and + * reacquires sc_lock to sleep until hardware acknowledges abort. */ static void xhci_abortx(struct usbd_xfer *xfer) @@ -2177,23 +2193,19 @@ xhci_host_dequeue(struct xhci_ring * const xr) * xHCI 1.1 sect 4.10.2.1 * Issue RESET_EP to recover halt condition and SET_TR_DEQUEUE to remove * all transfers on transfer ring. - * These are done in thread context asynchronously. */ static void -xhci_pipe_async_task(void *cookie) +xhci_pipe_restart(struct usbd_pipe *pipe) { - struct xhci_pipe * const xp = cookie; - struct usbd_pipe * const pipe = &xp->xp_pipe; struct xhci_softc * const sc = XHCI_PIPE2SC(pipe); struct xhci_slot * const xs = pipe->up_dev->ud_hcpriv; const u_int dci = xhci_ep_get_dci(pipe->up_endpoint->ue_edesc); - struct xhci_ring * const tr = xs->xs_xr[dci]; XHCIHIST_FUNC(); XHCIHIST_CALLARGS("pipe %#jx slot %ju dci %ju", (uintptr_t)pipe, xs->xs_idx, dci, 0); - mutex_enter(&sc->sc_lock); + KASSERT(xhci_polling_p(sc) || mutex_owned(&sc->sc_lock)); /* * - If the endpoint is halted, indicating a stall, reset it. @@ -2213,6 +2225,7 @@ xhci_pipe_async_task(void *cookie) xhci_stop_endpoint(pipe); break; } + switch (xhci_get_epstate(sc, xs, dci)) { case XHCI_EPSTATE_STOPPED: break; @@ -2224,34 +2237,54 @@ xhci_pipe_async_task(void *cookie) device_printf(sc->sc_dev, "endpoint 0x%x failed to stop\n", pipe->up_endpoint->ue_edesc->bEndpointAddress); } + xhci_set_dequeue(pipe); + DPRINTFN(4, "ends", 0, 0, 0, 0); +} + +static void +xhci_pipe_restart_async_task(void *cookie) +{ + struct usbd_pipe * const pipe = cookie; + struct xhci_softc * const sc = XHCI_PIPE2SC(pipe); + struct xhci_slot * const xs = pipe->up_dev->ud_hcpriv; + const u_int dci = xhci_ep_get_dci(pipe->up_endpoint->ue_edesc); + struct xhci_ring * const tr = xs->xs_xr[dci]; + struct usbd_xfer *xfer; + + mutex_enter(&sc->sc_lock); + + xhci_pipe_restart(pipe); + /* - * If we halted our own queue because it stalled, mark it no + * We halted our own queue because it stalled. Mark it no * longer halted and start issuing queued transfers again. */ - if (tr->is_halted) { - struct usbd_xfer *xfer = SIMPLEQ_FIRST(&pipe->up_queue); - - tr->is_halted = false; - if (xfer) - (*pipe->up_methods->upm_start)(xfer); - } + tr->is_halted = false; + xfer = SIMPLEQ_FIRST(&pipe->up_queue); + if (xfer) + (*pipe->up_methods->upm_start)(xfer); mutex_exit(&sc->sc_lock); - - DPRINTFN(4, "ends", 0, 0, 0, 0); } static void -xhci_pipe_restart(struct usbd_pipe *pipe) +xhci_pipe_restart_async(struct usbd_pipe *pipe) { struct xhci_pipe * const xp = container_of(pipe, struct xhci_pipe, xp_pipe); + struct xhci_softc * const sc = XHCI_PIPE2SC(pipe); + struct xhci_slot * const xs = pipe->up_dev->ud_hcpriv; + const u_int dci = xhci_ep_get_dci(pipe->up_endpoint->ue_edesc); + struct xhci_ring * const tr = xs->xs_xr[dci]; XHCIHIST_FUNC(); XHCIHIST_CALLARGS("pipe %#jx", (uintptr_t)pipe, 0, 0, 0); + KASSERT(xhci_polling_p(sc) || mutex_owned(&sc->sc_lock)); + + tr->is_halted = true; usb_add_task(pipe->up_dev, &xp->xp_async_task, USB_TASKQ_HC); DPRINTFN(4, "ends", 0, 0, 0, 0); @@ -2431,8 +2464,7 @@ xhci_event_transfer(struct xhci_softc * const sc, case XHCI_TRB_ERROR_STALL: case XHCI_TRB_ERROR_BABBLE: DPRINTFN(1, "ERR %ju slot %ju dci %ju", trbcode, slot, dci, 0); - xr->is_halted = true; - xhci_pipe_restart(xfer->ux_pipe); + xhci_pipe_restart_async(xfer->ux_pipe); err = USBD_STALLED; break; default: @@ -3846,7 +3878,7 @@ xhci_noop(struct usbd_pipe *pipe) * Process root hub request. */ static int -xhci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req, +xhci_roothub_ctrl_locked(struct usbd_bus *bus, usb_device_request_t *req, void *buf, int buflen) { struct xhci_softc * const sc = XHCI_BUS2SC(bus); @@ -3858,6 +3890,8 @@ xhci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req, XHCIHIST_FUNC(); + KASSERT(mutex_owned(&sc->sc_rhlock)); + if (sc->sc_dying) return -1; @@ -4110,6 +4144,20 @@ xhci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req, return totlen; } +static int +xhci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req, + void *buf, int buflen) +{ + struct xhci_softc *sc = XHCI_BUS2SC(bus); + int actlen; + + mutex_enter(&sc->sc_rhlock); + actlen = xhci_roothub_ctrl_locked(bus, req, buf, buflen); + mutex_exit(&sc->sc_rhlock); + + return actlen; +} + /* root hub interrupt */ static usbd_status diff --git a/sys/dev/usb/xhcivar.h b/sys/dev/usb/xhcivar.h index e2191f071701..5c39614467a1 100644 --- a/sys/dev/usb/xhcivar.h +++ b/sys/dev/usb/xhcivar.h @@ -96,6 +96,7 @@ struct xhci_softc { struct usbd_bus sc_bus; /* USB 3 bus */ struct usbd_bus sc_bus2; /* USB 2 bus */ + kmutex_t sc_rhlock; kmutex_t sc_lock; kmutex_t sc_intr_lock;