Index | Thread | Search

From:
Atanas Vladimirov <vlado@bsdbg.net>
Subject:
Re: xhci: recover halted endpoints on USB Transaction Errors
To:
Tech <tech@openbsd.org>
Date:
Wed, 19 Aug 2026 12:36:29 +0300

Download raw body.

Thread
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;