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 40d7d45720c0..7901aff14679 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 3e24e7d6bee4..2c249fa64c30 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 865780273312..7c68a879254e 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..cdea5808998f 100644
--- a/sys/dev/usb/usb_subr.c
+++ b/sys/dev/usb/usb_subr.c
@@ -948,6 +948,7 @@ 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;
 
 	err = dev->ud_bus->ub_methods->ubm_open(p);
 	if (err) {
@@ -967,6 +968,8 @@ 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_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..a49482fb863d 100644
--- a/sys/dev/usb/usbdi.c
+++ b/sys/dev/usb/usbdi.c
@@ -1017,8 +1017,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.
+	 */
+	KASSERTMSG(pipe->up_abortlwp == NULL, "pipe->up_abortlwp=%p",
+	    pipe->up_abortlwp);
+	pipe->up_abortlwp = curlwp;
+
 #ifdef USB_DEBUG
 	if (usbdebug > 5)
 		usbd_dump_queue(pipe);
@@ -1050,6 +1061,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));
+	KASSERTMSG(pipe->up_abortlwp == NULL, "pipe->up_abortlwp=%p",
+	    pipe->up_abortlwp);
+	pipe->up_abortlwp = NULL;
+
 	SDT_PROBE1(usb, device, pipe, abort__done,  pipe);
 }
 
@@ -1489,7 +1506,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 3315825d0ae2..b338b8e37902 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,8 @@ struct usbd_pipe {
 	struct usbd_xfer       *up_callingxfer; /* currently in callback */
 	kcondvar_t		up_callingcv;
 
+	struct lwp	       *up_abortlwp;	/* lwp currently aborting */
+
 	/* 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 ff396b33a106..2c21390f19e0 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 0605cd929115..8897ad354a26 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;