Re: xhci: recover halted endpoints on USB Transaction Errors

Atanas Vladimirov <[email protected]>
Newsgroups gmane.os.openbsd.tech
Message-ID <[email protected]>
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;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.