G.4 (2h): bounded xHCI event-ring drain fixes boot-attach livelock

Root cause of the G.1 follow-up boot-time attach race: on pathological
controller behavior the xhci_poll_events() drain loop had no hard ceiling.
ERDP is written back only when the loop exits, so the controller cannot
reclaim event TRBs mid-drain; if it keeps producing events the head can
chase the software dequeue pointer forever. xhci_poll_events() never returns,
sk_repl_idle() never reaches its bot_msc_attach_pending check, and a fresh
USB BOT device that finished SET_CONFIGURATION is left flagged-but-never-
attached while the guest appears hung.

Fix: bound the drain to a full ring (XHCI_EVT_RING_MAX_DRAIN = 256), so
xhci_poll_events() always terminates and always writes ERDP each call.
Unprocessed events keep their cycle bit and are re-read next poll; nothing
is dropped. On the healthy path one drain processes only the one-or-few
events the controller posts per chained command, so the bound never triggers
except in the pathological case it breaks.

Beyond the G.1 additions: a new macro in include/starkernel/xhci.h and a
bounded loop in src/starkernel/usb/xhci.c. Builds clean on amd64. Verified
across six consecutive fresh QEMU boots (previously intermittently hung).
This commit is contained in:
Robert Allan James
2026-08-29 09:48:48 -04:00
parent 49a3faa331
commit dc2f38a1e1
5 changed files with 31 additions and 7 deletions
+20
View File
@@ -486,6 +486,26 @@ typedef struct {
#define XHCI_RING_TRB_COUNT 256u
#define XHCI_RING_BYTES (XHCI_RING_TRB_COUNT * sizeof(xhci_trb_t))
/* Bounded per-call event-ring drain (Milestone 2h boot-attach hardening).
* xhci_poll_events()'s drain loop is otherwise terminated only by the
* ring's cycle-bit match, which is a fine early-exit on the normal,
* quiescent path (each beat drains the one-or-few events the controller
* posts per chained command) but has no hard ceiling. If the controller
* keeps producing events across the whole drain -- ERDP is not written
* back until the loop exits, so the controller cannot reclaim event TRBs
* mid-drain, and on pathological controller behavior the head can chase
* the software dequeue pointer indefinitely -- the loop can livelock:
* xhci_poll_events() never returns, sk_repl_idle() never reaches its
* bot_msc_attach_pending check, and a fresh USB BOT device that finished
* SET_CONFIGURATION is left flagged-but-never-attached while the guest,
* though alive, appears hung. Bounding the drain makes xhci_poll_events()
* always terminate and always write ERDP each call; any events not yet
* processed keep their cycle bit and are simply re-read on the next poll,
* so nothing is dropped. Equal to a full ring: on the healthy path one
* drain never processes anywhere near this many events, so this bound
* only ever triggers in the pathological case it exists to break. */
#define XHCI_EVT_RING_MAX_DRAIN XHCI_RING_TRB_COUNT
/* Milestone 2e: upper bound on ports tracked for connect/disconnect ->
* Enable Slot correlation (xhci_dev_t.port_slot_id). PORTSC's own field
* width allows up to 255 ports (XHCI_HCSPARAMS1_MAX_PORTS is 8 bits), but