From: Atanas Vladimirov Subject: Re: xhci: recover halted endpoints on USB Transaction Errors To: Tech Date: Wed, 19 Aug 2026 12:36:29 +0300 Hello, Here is an updated patch: xhci(4): recover halted endpoints on USB Transaction Errors. XHCI_CODE_TXERR / XHCI_CODE_SPLITERR leave the endpoint in the Halted state (xHCI r1.1 4.10.2.6). The old code just flagged USBD_IOERROR and broke, so every subsequent xfer queued on the pipe was silently dropped by the halted endpoint and the device stopped responding. Treat these errors like XHCI_CODE_STALL: share its recovery path so the endpoint is always reset (async reset-ep + set-tr-deq) before the xfer completes with USBD_IOERROR, and the stack can restart the pipe on a clean endpoint. Keep a per-pipe counter of consecutive errors; past XHCI_TXERR_RETRIES additionally call usb_needs_reattach() so the hub explore task re-enumerates the device instead of the pipe retrying forever. The counter is cleared on any successful completion and on pipe (re-)init. Fixes a stuck USB keyboard/mouse after a BMC/iKVM reset on Supermicro X10/X11 boards, without needing a host reboot. Index: sys/dev/usb/xhci.c =================================================================== --- sys/dev/usb/xhci.c +++ sys/dev/usb/xhci.c @@ -70,6 +70,7 @@ struct xhci_pipe { struct usbd_xfer *pending_xfers[XHCI_MAX_XFER]; struct usbd_xfer *aborted_xfer; int halted; + u_int txerr_count; size_t free_trbs; int skip; #define TRB_PROCESSED_NO 0 @@ -78,6 +79,8 @@ struct xhci_pipe { uint8_t trb_processed[XHCI_MAX_XFER]; }; +#define XHCI_TXERR_RETRIES 3 + int xhci_reset(struct xhci_softc *); void xhci_suspend(struct xhci_softc *); int xhci_intr1(struct xhci_softc *); @@ -953,6 +956,7 @@ xhci_event_xfer_generic(struct xhci_softc *sc, struct usbd_xfer *xfer, usbd_xfer_isread(xfer) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); xfer->status = USBD_NORMAL_COMPLETION; + xp->txerr_count = 0; break; case XHCI_CODE_SHORT_XFER: /* @@ -977,12 +981,22 @@ xhci_event_xfer_generic(struct xhci_softc *sc, struct usbd_xfer *xfer, usbd_xfer_isread(xfer) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); xfer->status = USBD_NORMAL_COMPLETION; + xp->txerr_count = 0; break; case XHCI_CODE_TXERR: case XHCI_CODE_SPLITERR: DPRINTF(("%s: txerr? code %d\n", DEVNAME(sc), code)); - xfer->status = USBD_IOERROR; - break; + /* + * A USB Transaction Error leaves the endpoint Halted + * (xHCI r1.1 4.10.2.6), so reset it like a STALL to + * let the stack restart the pipe on a clean endpoint. + * If the endpoint keeps failing, additionally ask the + * hub to re-enumerate the device instead of retrying + * forever. + */ + if (++xp->txerr_count > XHCI_TXERR_RETRIES) + usb_needs_reattach(xfer->device); + /* FALLTHROUGH */ case XHCI_CODE_STALL: case XHCI_CODE_BABBLE: DPRINTF(("%s: babble code %d\n", DEVNAME(sc), code)); @@ -1623,6 +1637,7 @@ xhci_pipe_init(struct xhci_softc *sc, struct usbd_pipe *pipe) xp->free_trbs = xp->ring.ntrb; xp->halted = 0; + xp->txerr_count = 0; sdev->pipes[xp->dci - 1] = xp;