xhci: fix Configure Endpoint completion drop under concurrent multi-device enumeration
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

Root cause of the FABRIC-3.md §IX.5 follow-on: with 9 devices attached
concurrently at boot (Zuse + 8 identities), only 1 of 9 ever completed
enumeration and reached blkio_usb: MSC device ready -- the other 8 produced
no error and no success, just silence.

xhci_poll_events()'s deferred per-slot dispatch loop submitted a Configure
Endpoint command (a Command Ring op) unconditionally for every slot with
that action pending in a single pass -- unlike every other Command Ring op
in this driver (Enable Slot, Address Device, Disable Slot), which is
correctly gated behind dev->connect_state == XHCI_CONN_IDLE before ever
submitting. With 2+ devices enumerating concurrently, this let multiple
Configure Endpoint commands sit outstanding on the Command Ring at once.
Their completion is correlated purely via the single shared
dev->connect_state field (== XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT), not the
completion event's own Slot ID -- so whichever slot's completion happened
to land while connect_state still read AWAIT_CONFIGURE_ENDPOINT got
correctly chained into SET_CONFIG, and every other slot's completion
arrived after connect_state had already moved on, silently swallowed by
the handler's generic "unrelated command completion" catch-all. No error
path exists for this, which is why it produced total silence rather than
a diagnosable failure.

Root-caused live via temporary WARN-level diagnostic probes (written,
captured, and fully reverted per the project's own probe convention --
this commit contains only the functional fix and its explanatory comment,
no probe code) added at four points: the initial port scan, the connect
handler, the Command Completion Event handler, and the deferred dispatch
loop itself. The probes showed all 9 devices correctly completing Enable
Slot + Address Device (ruling out the connect-state queue as the cause,
the original hypothesis), then all 9 correctly submitting Configure
Endpoint and all 9 commands completing successfully in hardware (code=
SUCCESS, no errors logged) -- but only 1 of 9 ever got its next_action
chained to SET_CONFIG.

Fix: apply the same single-in-flight discipline this driver already uses
for every other Command Ring op. If the Ring isn't free when a slot's
Configure Endpoint action is due, put the action back on that slot instead
of submitting a second command onto a busy Ring -- the next tick's
dispatch pass retries it once the Ring frees up.

Verified live: booting Zuse + all 8 identity drives concurrently (9
devices, one per real xHCI port via the XHCI_PORTS fix from the previous
commit) now produces 9 "MSC device ready" lines and zero xHCI errors,
where it previously produced exactly 1. Three-arch clean qemu acceptance
(single Zuse device, the standard regression case) passed on amd64,
aarch64, and riscv64 -- no change in that baseline behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78
This commit is contained in:
Robert Allan James
2026-09-07 09:02:45 -04:00
co-authored by Claude Sonnet 5
parent 30c26ade3d
commit e10fb76fb2
11 changed files with 209379 additions and 1 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-07T00:13:26Z -->
<!-- Generated by mkcapsule --manifest 2026-09-07T12:52:21Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+37
View File
@@ -2213,6 +2213,43 @@ void xhci_poll_events(void)
}
break;
case XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT:
/* Configure Endpoint is a Command Ring op, same as Enable
* Slot/Address Device/Disable Slot -- but unlike those
* (gated by xhci_handle_port_connected()'s own
* connect_state == XHCI_CONN_IDLE check before ever
* submitting), this deferred dispatch loop used to submit
* it unconditionally for every slot with the action
* pending in a single pass. With 2+ devices enumerating
* concurrently, that let multiple Configure Endpoint
* commands sit outstanding on the Command Ring at once --
* and their completion is correlated purely by
* dev->connect_state == XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT
* (see that branch in the Command Completion Event
* handler above), a single global flag, not the event's
* own Slot ID. Whichever slot's completion actually
* lands while connect_state still reads
* AWAIT_CONFIGURE_ENDPOINT gets correctly chained into
* SET_CONFIG; every other slot's completion arrives after
* connect_state has already moved on (to another slot's
* own Configure Endpoint, or unrelated activity) and is
* silently swallowed by the handler's generic "unrelated
* command completion" catch-all -- permanently stalling
* that slot's enumeration with no error ever logged.
* Confirmed live 2026-09-07 (FABRIC-3.md §IX.5 follow-on):
* 9 devices attached concurrently, only 1 of 9 ever
* reached SET_CONFIG/attach.
*
* Fix: apply the same single-in-flight discipline this
* driver already uses for every other Command Ring op --
* only submit if the Ring is actually free
* (connect_state == XHCI_CONN_IDLE); otherwise put this
* slot's action back so the very next tick's dispatch
* pass retries it once the Ring frees up, rather than
* racing a second command onto it. */
if (dev->connect_state != XHCI_CONN_IDLE) {
nms->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT;
break;
}
dev->connect_state = XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT;
if (xhci_cmd_configure_endpoint(dev, next_slot_id) != 0) {
log_message(LOG_ERROR, "xhci: deferred configure endpoint request setup failed");