FABRIC-3.md: scope STALL -- full BOT-spec recovery designed, closing Milestone 2

Traced the completion-code handling before designing anything: every transfer completion (control or bulk) shares one gate that logs and bails on any non-success code -- STALL isn't distinguished from any other failure, and no recovery exists (no xHCI Reset Endpoint, no CLEAR_FEATURE(ENDPOINT_HALT)). A bounded-timeout safety net in xhci_bot_wait_for_idle() prevents a hang, but the endpoint stays wedged for everything after it. Per direct instruction, designed full recovery now rather than deferring to Milestone 8: new STALL_ERROR completion code, new xHCI Reset Endpoint + Set TR Dequeue Pointer command TRB types (neither exists today), CLEAR_FEATURE(ENDPOINT_HALT) reusing the existing control-transfer plumbing, escalating to a full Bulk-Only Mass Storage Reset on a second stall, bounded via a new retry counter mirroring the existing bot_tur_retries/XHCI_BOT_TUR_MAX_RETRIES precedent exactly. This closes the last open Milestone 2 item.
This commit is contained in:
Robert Allan James
2026-08-27 14:56:11 -04:00
parent 6a3eb5997f
commit f84e679a88
+72 -2
View File
@@ -121,7 +121,13 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR
read-only today specifically because of this). read-only today specifically because of this).
- [ ] Implement basic error/stall recovery (CSW failure status, endpoint stall clear) — at - [ ] Implement basic error/stall recovery (CSW failure status, endpoint stall clear) — at
minimum enough to not wedge the controller on a single bad transfer. minimum enough to not wedge the controller on a single bad transfer. **SCOPED 2026-08-27
(`FABRIC-3.md` §F.14)**: full BOT-spec recovery designed — new `STALL_ERROR` completion code,
new xHCI Reset Endpoint + Set TR Dequeue Pointer commands, `CLEAR_FEATURE(ENDPOINT_HALT)` via
existing control-transfer plumbing, escalating to a full Bulk-Only Mass Storage Reset on a
second stall, bounded via a new retry counter mirroring `bot_tur_retries`'s own precedent.
Real gap closed today: CSW status handling (PASS/FAILED/PHASE ERROR) already existed; nothing
distinguished a STALL specifically, and no recovery of any kind existed before this pass.
### From FABRIC-2.md §X, Milestone 3 — Block subsystem extensions ### From FABRIC-2.md §X, Milestone 3 — Block subsystem extensions
@@ -1435,7 +1441,7 @@ punch list hides that; a graph doesn't.
graph TD graph TD
W10["❌ WRITE(10) SCSI support<br/>(Milestone 2 — biggest single blocker)"] W10["❌ WRITE(10) SCSI support<br/>(Milestone 2 — biggest single blocker)"]
HOTPLUG["✅ Hotplug event surfacing<br/>CLOSED 2026-08-27, M2 (§F.3)"] HOTPLUG["✅ Hotplug event surfacing<br/>CLOSED 2026-08-27, M2 (§F.3)"]
STALL["❌ USB error/stall recovery (M2)"] STALL["❌ USB error/stall recovery (M2)<br/>SCOPED 2026-08-27 (§F.14) — full BOT-spec recovery designed"]
M6["✅ Milestone 6 — capsule PKI<br/>DONE 2026-08-26"] M6["✅ Milestone 6 — capsule PKI<br/>DONE 2026-08-26"]
PH8["✅ Phase 8 — Zuse identity<br/>+ block-fence, DONE 2026-08-26"] PH8["✅ Phase 8 — Zuse identity<br/>+ block-fence, DONE 2026-08-26"]
@@ -1573,6 +1579,10 @@ finished). Dashed arrows = softer "gates/informs" relationships.
noticing, because each only traced `homeblocks_sig_check()` itself, never the ordinary attach noticing, because each only traced `homeblocks_sig_check()` itself, never the ordinary attach
path running alongside it. Fixed now (relocate to devblock 1) rather than left to surface path running alongside it. Fixed now (relocate to devblock 1) rather than left to surface
later as a real, confusing runtime failure. later as a real, confusing runtime failure.
- **`STALL` closes out Milestone 2 entirely** (§F.14) — the last item on that punch list.
Unlike most nodes this session, this one designs genuinely new protocol machinery rather than
finding existing infrastructure already covers it: no completion-code distinction and no
recovery of any kind existed before this pass, only a bounded-timeout safety net.
**Not yet done:** an ordered plan (which node to attack first, given the graph). Per Captain **Not yet done:** an ordered plan (which node to attack first, given the graph). Per Captain
Bob's own framing, that's the next pass — "start asking and answering questions iteratively Bob's own framing, that's the next pass — "start asking and answering questions iteratively
@@ -2153,6 +2163,66 @@ power is lost between the two writes) — real question, not addressed here; the
updating from their currently-recorded devblock-0-relative assumption to devblock-1, a updating from their currently-recorded devblock-0-relative assumption to devblock-1, a
mechanical follow-up once any of this is actually coded, not re-litigated here. mechanical follow-up once any of this is actually coded, not re-litigated here.
### F.14 — `STALL` (the last M2 item) — full BOT-spec recovery designed
Traced the actual completion-code handling before designing anything. **Real finding: every
transfer completion, control or bulk, shares one gate** — `if (code != XHCI_COMPLETION_CODE_SUCCESS)
{ console_println("xhci: control transfer failed"); break; }` (`xhci.c:1267-1270`). A STALL
isn't distinguished from any other failure today, and there is no recovery of any kind — no
xHCI Reset Endpoint command, no USB `CLEAR_FEATURE(ENDPOINT_HALT)`. There is a real safety net
though: `xhci_bot_wait_for_idle()` (`xhci.c:831-839`) has a bounded iteration count and returns
`BOT_STATUS_TIMEOUT` rather than hanging forever, since the generic bail-out never resets
`bot_cmd_kind`. So today's failure mode is "clean timeout, endpoint left permanently wedged for
everything after it" — not a crash, but not recoverable either.
Also confirmed by inspection: none of the pieces real recovery needs exist yet —
`XHCI_COMPLETION_CODE_STALL_ERROR` isn't defined (only `_SUCCESS`), nor are the `RESET_ENDPOINT`
(xHCI spec value 14) or `SET_TR_DEQUEUE_POINTER` (value 16) command TRB types. Per direct
instruction, full recovery is designed now rather than deferred to Milestone 8.
**Decisions made 2026-08-27 — the recovery sequence, mirroring the existing bounded-retry
precedent `bot_tur_retries`/`XHCI_BOT_TUR_MAX_RETRIES` already establishes for TUR:**
1. **Distinguish the completion code.** Add `XHCI_COMPLETION_CODE_STALL_ERROR` (6, per xHCI
spec) alongside the existing `_SUCCESS` (1). The shared completion-code gate branches: a
plain non-success/non-stall code keeps today's behavior (log and bail); a stall specifically
enters the new recovery path below instead of falling straight to the generic message.
2. **xHCI-level reset (two new command TRB types + two new command functions, mirroring the
existing `xhci_cmd_disable_slot()`/`xhci_cmd_configure_endpoint()` shape exactly):**
- `xhci_cmd_reset_endpoint(dev, slot_id, ep_id)` — new `XHCI_TRB_TYPE_RESET_ENDPOINT_CMD`
(14), transitions the stalled endpoint from Halted back to Stopped in the xHC's internal
context. Which endpoint is "the stalled one" is already known — it's whichever of
`bulk_in_ep_addr`/`bulk_out_ep_addr` the in-flight transfer purpose
(`BOT_DATA_IN`/`BOT_DATA_OUT`/`CBW_SENT`/`CSW_RECEIVE`) was using, both already tracked
fields.
- `xhci_cmd_set_tr_dequeue_pointer(dev, slot_id, ep_id, new_dequeue, dcs)` — new
`XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD` (16), repositions the ring's dequeue pointer
past the failed TRB so the next enqueued transfer resumes cleanly.
3. **USB-level clear (reuses the existing control-transfer infrastructure already built for
`GET_DESCRIPTOR`/`SET_CONFIGURATION` — no new transfer-stage machinery, just a new request
payload):** send `CLEAR_FEATURE(ENDPOINT_HALT)` (standard request, `wValue=0`, `wIndex=`
the stalled endpoint address) to clear the device's own halt condition and reset its data
toggle.
4. **Escalation on a second stall (or if step 3 itself stalls):** issue Bulk-Only Mass Storage
Reset (BOT class request, `bmRequestType=0x21`, `bRequest=0xFF`, no data stage — again the
existing control-transfer machinery, a different request payload) followed by
`CLEAR_FEATURE(ENDPOINT_HALT)` on *both* bulk endpoints (BOT spec 5.3.4's full procedure),
then retry the original command from scratch — the same "fresh target" restart shape the
existing TUR-chain retry already uses.
5. **Bounded, not infinite:** a new `bot_stall_recoveries` counter capped at a new
`XHCI_BOT_STALL_MAX_RECOVERIES`, exact value TBD but same shape as `XHCI_BOT_TUR_MAX_RETRIES`.
Exhausting it sets `bot_last_status = BOT_STATUS_FAILED` and `bot_cmd_kind = BOT_CMD_NONE`
explicitly — a clean terminal failure signaled to `xhci_bot_wait_for_idle()`'s caller,
rather than relying purely on the outer timeout the way today's un-recovered stall does.
**Not yet scoped (deferred within this node):** the exact `XHCI_BOT_STALL_MAX_RECOVERIES` value;
whether a `WRITE(10)`-specific residual-data concern exists on a stalled Data-Out stage (a
partially-written SCSI command's recovery semantics may differ subtly from a stalled Data-In
read — not analyzed here, `WRITE(10)` itself still doesn't exist); real-hardware validation is
explicitly out of reach until Milestone 8, same caveat `WRITE(10)`'s own CSW-residue question
carries (§F.1) — this design is believed spec-correct but has only QEMU (which rarely if ever
issues real stalls) to test against for now.
### D.5 — Scope expansion (2026-08-27): identity is common to every VM, not just users ### D.5 — Scope expansion (2026-08-27): identity is common to every VM, not just users
Surfaced while scoping `ACLKEY`, stated directly: *"the whole object is to deliver a Surfaced while scoping `ACLKEY`, stated directly: *"the whole object is to deliver a