G.1: xHCI bulk-endpoint stall recovery (per F.14), built + verified

Full BOT-spec stall recovery per FABRIC-3.md F.14: new STALL_ERROR handling,
Reset Endpoint + Set TR Dequeue Pointer commands, CLEAR_FEATURE(ENDPOINT_HALT),
escalating to Bulk-Only Mass Storage Reset, capped retries
(XHCI_BOT_STALL_MAX_RECOVERIES=2) mirroring bot_tur_retries, clean terminal
failure via xhci_stall_fail().

Purely additive recovery path off the non-success transfer-event branch; the
normal path is unchanged. Builds clean on amd64/aarch64/riscv64. QEMU amd64
boot regression passes: zero stalls, BOT attach (READ CAPACITY10 -> READ10 ->
home-blocks) completes, normal-path xHCI trace identical to baseline. Live
stall injection is not provable under qemu-xhci; deferred to v2.5.0 hardware.

FABRIC-3.md G.1 documented; ROADMAP release-versioning policy folded in.
This commit is contained in:
Robert Allan James
2026-08-29 00:58:59 -04:00
parent 5689c397fc
commit 49a3faa331
7 changed files with 730 additions and 5 deletions
+162
View File
@@ -3264,3 +3264,165 @@ of the pump's own cadence), `USE bob` and `WELCOME` both still work correctly. F
three-architecture regression clean (amd64 fresh Zuse reload + the fix compiled in; aarch64/
riscv64 clean Zuse reload, unaffected — the fix only touches content written at `MINT` time, no
change to the boot path itself).
---
## G. v2.0.0 / v2.5.0 RELEASE plan — recommendation and punch lists (2026-08-28)
**Context.** This is the continuation section of record for the release decision. Opening it
as its own lettered section (§G) so it stands apart from the carried-forward item review
(§A), the design-scope sections (§B–§E), and the built-and-verified backlog (§F). Everything
before this section describes work already committed to `origin/master` (`5689c39` and
earlier). This section records the **versioning split** (v2.0.0 = QEMU, v2.5.0 = real
hardware), the **release-gate recommendation**, and the **punch lists to completion** for
each.
**The versioning split, decided 2026-08-28 — now standing policy, see
`docs/lithosananke/ROADMAP.md` "Release Versioning Policy" for the authoritative form.**
v2.0.0 is the **QEMU release** — the point
where the QEMU-only story is complete and defensible on its own terms. v2.5.0 is the
**real-hardware release** — the point where the same story transfers to bare metal on a real
board. This is one instance of a general policy: **`X.0.0` = QEMU release, `X.5.0` =
hardware bare-metal release, even major numbers are LTS.** The rationale for the split is
practical, not arbitrary: v2.0.0
is the natural, honest cut for what exists *today and is fully verifiable today* (three-arch
QEMU), and v2.5.0 bundles the real-hardware-only work (per-arch RNG drivers, real-board boot
validation) that cannot be green-lit or verified until the boards physically exist (the
author expects to have money for the SBCs — RasPi 6, Milk-V — within about a month of the
v2.0.0 cut). Nothing
is deferred that QEMU alone could already prove out; only what genuinely needs real silicon.
Everything about the state being shipped by v2.0.0 is unchanged by this versioning: the
current tree is a complete, deterministic, three-architecture OS that boots UEFI under QEMU
(M0M9 core milestones complete, M7.1 Capsules live, M9 Block I/O live, terminal/REPL I/O
wired, real virtio-blk persistence proven, a working first-boot Zuse mint via the block
fence).
---
### v2.0.0 (QEMU) — release gates
The v2.0.0 punch list is the set of items that must be green on QEMU (all three of
amd64/aarch64/riscv64) before the QEMU story is complete. Each item states its own exit
criterion so closure is unambiguous and independently verifiable, per this project's
standing discipline.
#### G.1 [v2.0.0] Stall/short-packet recovery for xHCI (scoped §F.14 — **BUILT 2026-08-29**)
Implemented per §F.14 exactly as designed: new `STALL_ERROR` completion code handling, xHCI
Reset Endpoint + Set TR Dequeue Pointer commands, `CLEAR_FEATURE(ENDPOINT_HALT)`, escalating
to a full Bulk-Only Mass Storage Reset, and a recovery counter (`XHCI_BOT_STALL_MAX_RECOVERIES
= 2`) mirroring `bot_tur_retries`.
The code is a purely additive recovery path gated off the *non-success* transfer-event branch
(`xhci.c` transfer-event gate + `xhci_handle_bulk_stall()`, the two new command-completion
states `XHCI_CONN_AWAIT_RESET_ENDPOINT`/`AWAIT_SET_TR_DEQUEUE`, the two new transfer purposes
`XHCI_XFER_CLEAR_HALT`/`XHCI_XFER_BOT_RESET`, and the two new deferred `next_action` values
`XHCI_NEXT_ACTION_CLEAR_HALT`/`XHCI_NEXT_ACTION_BOT_RESET`). The normal (non-stall) path is
byte-for-byte unchanged — the only touched shared line was the single `"control transfer
failed"` gate, now with an else-branch for the recoverable-STALL case.
- **Built clean on all three archs** — `make -f Makefile.starkernel ARCH={amd64,aarch64,riscv64} all`.
- **QEMU amd64 boot regression:** clean enumeration + BOT attach (READ CAPACITY10 → READ10 →
home-blocks) completing with **zero stalls and zero failures**; normal-path xHCI trace
identical to the pre-change baseline. Repeated across fresh boots (all attach=SUCCESS).
- **Exit criterion — partial, honest:** the recovery path itself is not exercised live because
QEMU's `qemu-xhci` model does not produce a `STALL_ERROR` for the injected bad transfers this
driver can stage (no real hardware stall behavior). The design (§F.14) is reviewed and the
code paths are structured and reviewed; **live `STALL_ERROR` → recover → retry verification
is deferred to hardware (v2.5.0/Artemis bare-metal)**, where a real bad transfer can be
staged. This is the last QEMU-verifiable storage-integrity gap and the recovery logic is in
place; the one thing QEMU cannot prove is the live stall injection itself.
- **Note (pre-existing, NOT G.1):** during verification an intermittent boot-time attach race
was observed (the `sk_repl_idle()` `bot_msc_attach_pending` handoff occasionally does not
progress on a cold QEMU boot, independent of source, with baseline `HEAD` exhibiting it too).
Unrelated to G.1; tracked for a separate follow-up.
#### G.2 [v2.0.0] Real-hardware RNG driver plumbing, QEMU-verifiable slice (rest of it lands at v2.5.0)
The full real per-arch RNG driver (`rng_get_bytes()` backing off virtio-rng to RDRAND /
Zkr / peripheral-RNG) is a v2.5.0 item because it cannot be verified on hardware that does
not exist yet. What *is* a v2.0.0 gate is the *shape* of the driver — the unified
`rng_get_bytes()` entry point, the probe-and-refuse-loudly contract (never silently
degrading to a deterministic seed — the exact failure Phase A existed to avoid), and the
QEMU path staying on virtio-rng unchanged — all taken up **before** v2.0.0 so the
single entropy entry point is stable and the v2.5.0 work is purely per-arch backends, not a
redesign of the call path.
- **Exit criterion (v2.0.0 slice):** `rng_get_bytes()` exists as the single entropy entry
point, virtio-rng is its sole active backend on all three QEMU arches, the code refuses
loudly (boot-time message, safe fallback to this-boot-only cert) if no backend is present,
and the pre-existing Zuse mint/cert behavior is unchanged on all three arches. The
per-arch backends themselves are parked, explicitly, for v2.5.0.
- **Exit criterion (v2.5.0 completion, for reference):** on each real board `rng_get_bytes()`
returns genuinely non-deterministic bytes (two boots differ) and the Zuse mint path seeded
from it produces a valid distinct cert per boot when bleached.
#### G.3 [v2.0.0] NVRAM persistence decision — **DECIDED 2026-08-28: permanently de-scoped**
The NVRAM `SetVariable` crash is root-caused (this kernel's VMM never maps the flash-window
region OVMF's variable service needs; also the exposed-seed design defect) and, since the
Zuse cert now lives on the block fence on Artemis, functionally superseded. **DECIDED
(Captain Bob, 2026-08-28): de-scope NVRAM-backed identity permanently.** The block fence on
Artemis is the substrate of record for Zuse's cert. The alternative — scoping the real VMM
memory-map work to make OVMF's flash window writable — is not being scheduled: it is large,
unscoped VMM work with no live consumer now that the fence carries the cert, and the NVRAM
approach carried a real exposed-seed security defect regardless of the crash.
- **Exit criterion (MET):** a dated decision recorded in this section (de-scope), and the
ROADMAP/backlog updated to match. Decision required, not necessarily work — **closed,
no work required beyond this record.**
---
### v2.5.0 (real hardware) — release gates
These are the real-hardware-only items. None can be verified until the boards exist (RasPi 6,
Milk-V; SER5 either in hand or not — see G.5). They are the entire v2.5.0 gate and are listed
together so the v2.5.0 scope is explicit and closed.
#### G.4 [v2.5.0] Per-arch RNG backends behind `rng_get_bytes()` (the G.2 body)
The real RNG driver backends, completed behind the v2.0.0-formed entry point. The existing
VM-UUID finding already maps the primitives: amd64/SER5 RDRAND, riscv64/Milk-V Zkr (RNDR),
aarch64/RasPi peripheral RNG. On QEMU all three arches stay on virtio-rng; nothing changes
there.
- **Exit criterion:** on each real board (`rng_get_bytes()` falling back through the real
primitive) returns genuinely non-deterministic bytes (two boots differ), the Zuse mint
path seeded from it produces a valid distinct cert per boot when bleached, and QEMU
behavior is unchanged.
#### G.5 [v2.5.0] Real-machine boot validation (SER5 / RasPi 6 / Milk-V)
Flash `starkernel.iso` to real media and boot each real board in hand, confirming the same
acceptance story QEMU keeps green: POST `1012/0/0` + `ok>`, block-fence Zuse load true to the
already-minted Artemis image, and the G.4 RNG backend working live. The board pool is
SER5 (if still in hand), RasPi 6 (aarch64), Milk-V (riscv64) — the goal is at least one
board per architecture, but each board that boots is a separate, recorded data point.
This is the genuine transfer proof that the zero-degradation QEMU claim holds on real
silicon — the thing v2.0.0 cannot honestly claim.
- **Exit criterion:** each board in hand cold-boots to `ok>` with Arena conservation
(43691/21845/65536), Zuse cert loads from the fence (or mints fresh on bleached media),
and its G.4 RNG backend returns non-deterministic bytes live. v2.5.0 does not close on any
board's G.4/G.x item being build-only.
---
### Post-release tracking (not gating either milestone)
Beyond the v2.0.0/v2.5.0 gates, the carried-forward open items (§A) and open design questions
(§D) remain genuinely open. Not release-gating; tracked here so nothing silently ages and so
the punch lists above explicitly delimit what is excluded from each milestone:
- Block-map on-drive format field edit (scoped §F.4) and associated §F.6/§F.11 allocation.
- CA-signed-cert verification path + DER signature extraction (scoped §F.7) and
`CERTVERIFY`/`BINDSTEP` consumers (§F.8/§F.9).
- Migration state machine design (§A item, §B Stadium unification reframe).
- Console umbrella closure (§A 4.3) and `(user)` prompt segment (§A 4.4s) once 4.3's blocker
clears.
- `DETACH`/unclean-removal wiring into `sk_repl_idle()` (scoped §F.10).
- Dirty-event granularity (1.11, blocked on 4.3) and the §17.4 framebuffer heat/decay design.
- Re-run the DoE on the new substrate (§A 5.1).
- First-touch allocation first-call-free.
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-29T02:41:11Z -->
<!-- Generated by mkcapsule --manifest 2026-08-29T04:57:27Z -->
<!-- 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.
+25
View File
@@ -7,6 +7,31 @@ longer exists here — see `.claude/CLAUDE.md`'s "On the branch topology" note)
---
## Release Versioning Policy (decided 2026-08-28)
This is the standing release-versioning policy for LithosAnanke. It is authoritative; the
FABRIC documents track implementation against it, and any permanent change to the policy
belongs here.
- **`X.0.0` — QEMU release.** A whole-number minor in the `X.0` position is the QEMU-only
release: the point where the three-architecture (amd64/aarch64/riscv64) story under QEMU
is complete and defensible on its own terms. Nothing real-hardware-only is required to cut
an `X.0.0`.
- **`X.5.0` — Hardware bare-metal release.** A `X.5` release is the real-hardware release:
the point where the same story transfers to bare metal on real boards (SER5, RasPi 6,
Milk-V, Zynq FPGA). Real-hardware-only work — the per-arch RNG drivers behind the unified
entropy entry point, and real-board boot validation — gates `X.5.0`, not `X.0.0`.
- **Even major numbers are LTS.** A release whose major number (`X`) is even is a
Long-Term-Support release — maintained and supported long-term (bug fixes, security,
backports) rather than a point-in-time cut. Odd major numbers are non-LTS development
lines.
Applied to the two planned releases: the **v2.0.0** cut (even major, so LTS) is the QEMU
release; **v2.5.0** is the hardware bare-metal release that transfers v2.0.0's QEMU story to
real boards. See `FABRIC-3.md` §G for the release-gate punch lists.
---
## Milestone Overview
```
+38
View File
@@ -196,6 +196,8 @@ typedef struct {
#define XHCI_TRB_TYPE_DISABLE_SLOT_CMD 10
#define XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD 11
#define XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD 12
#define XHCI_TRB_TYPE_RESET_ENDPOINT_CMD 14
#define XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD 16
#define XHCI_TRB_TYPE_SETUP_STAGE 2 /* Transfer Ring, control transfers only */
#define XHCI_TRB_TYPE_DATA_STAGE 3
#define XHCI_TRB_TYPE_STATUS_STAGE 4
@@ -234,11 +236,32 @@ typedef struct {
#define USB_REQ_GET_DESCRIPTOR 6u
#define USB_REQ_SET_CONFIGURATION 9u
#define USB_REQ_CLEAR_FEATURE 1u
#define USB_DESC_TYPE_DEVICE 1u
#define USB_DESC_TYPE_CONFIG 2u
#define USB_DIR_DEVICE_TO_HOST 0x80u
#define USB_DIR_HOST_TO_DEVICE 0x00u
/* Standard USB Device/Endpoint feature selectors (USB 2.0 spec table 9-6) --
* ENDPOINT_HALT (0) is the halt condition on a specific endpoint, cleared
* (and the endpoint's data toggle reset) by a CLEAR_FEATURE request whose
* wValue is this selector and whose wIndex is the endpoint's own address --
* the USB-level half of G.1's stall recovery (xHCI Reset Endpoint +
* SET_TR_DEQUEUE_POINTER clear the xHC-side state; this clears the device-
* side halt so the endpoint will actually drive new transfers again).
* bmRequestType type field (bits 6:5 of the request type) -- 0 = standard,
* 1 = class, and the recipient field (bits 4:0) -- 0 = device, 2 = endpoint.
* BOT Mass Storage Reset (USB Mass Storage Class Bulk-Only Transport spec
* section 3.1) is a class, interface-recipient (recipient 1) request, the
* BOT-spec-mandated full teardown + restart of a stalled command sequence. */
#define USB_REQ_TYPE_STANDARD 0u
#define USB_REQ_TYPE_CLASS 1u
#define USB_RECIP_DEVICE 0u
#define USB_RECIP_INTERFACE 1u
#define USB_RECIP_ENDPOINT 2u
#define USB_FEATURE_ENDPOINT_HALT 0u
#define USB_BOT_MASS_STORAGE_RESET 0xFFu
/* Standard USB Interface descriptor field offsets (9 bytes, USB 2.0 spec
* table 9-12) -- Mass Storage class detection reads these three fields.
* Not decoded via a struct like usb_setup_packet_t: the Interface
@@ -328,6 +351,20 @@ typedef struct {
* xhci_dev_t's bot_tur_retries doc comment. */
#define XHCI_BOT_TUR_MAX_RETRIES 3u
/* Bounded recovery count for a stalled bulk endpoint before giving up on
* it -- see xhci_dev_t's bot_stall_recoveries doc comment (G.1 / §F.14).
* Mirrors the shape of XHCI_BOT_TUR_MAX_RETRIES: a small fixed budget of
* full recoveries, each of which is itself the multi-step xHCI Reset
* Endpoint -> Set TR Dequeue Pointer -> CLEAR_FEATURE(ENDPOINT_HALT)
* sequence (escalating to a BOT Mass Storage Reset on the last try),
* after which the original SCSI command is retried from scratch. Two
* full recoveries, then escalation and terminal failure, is a deliberately
* tight bound chosen to match this driver's "recover or fail clean, never
* wedge the controller, never loop forever" contract -- a genuinely
* wedged device gets two chances to clear, then the block layer sees a
* clean BOT_STATUS_FAILED. */
#define XHCI_BOT_STALL_MAX_RECOVERIES 2u
/* SCSI READ CAPACITY(10) (SBC-3 section 5.14) -- 10-byte CDB, opcode 0x25,
* every other CDB byte reserved/zero for the standard "report capacity"
* form (LBA field left 0, PMI bit left clear). 8-byte Data-In reply:
@@ -368,6 +405,7 @@ typedef struct {
#define XHCI_EVT_COMPLETION_CODE(status) (((uint32_t)(status) >> 24) & 0xFFu)
#define XHCI_EVT_SLOT_ID(control) (((uint32_t)(control) >> 24) & 0xFFu)
#define XHCI_COMPLETION_CODE_SUCCESS 1u
#define XHCI_COMPLETION_CODE_STALL_ERROR 6u
/* Port Status Change Event TRB layout (xHCI 1.2 spec table 6-34):
* parameter[31:24] = Port ID (1-based, matches PORTSC array indexing
+133 -3
View File
@@ -72,7 +72,9 @@ typedef struct {
XHCI_CONN_AWAIT_ENABLE_SLOT,
XHCI_CONN_AWAIT_ADDRESS_DEVICE,
XHCI_CONN_AWAIT_DISABLE_SLOT,
XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT
XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT,
XHCI_CONN_AWAIT_RESET_ENDPOINT,
XHCI_CONN_AWAIT_SET_TR_DEQUEUE
} connect_state;
uint32_t pending_connect_slot_id;
/* Milestone 2e/2g: disconnect teardown. Same single-outstanding-
@@ -117,7 +119,9 @@ typedef struct {
XHCI_XFER_CBW_SENT,
XHCI_XFER_BOT_DATA_IN,
XHCI_XFER_BOT_DATA_OUT,
XHCI_XFER_CSW_RECEIVED
XHCI_XFER_CSW_RECEIVED,
XHCI_XFER_CLEAR_HALT,
XHCI_XFER_BOT_RESET
} transfer_purpose;
uint32_t pending_transfer_slot_id;
uint8_t device_descriptor[18];
@@ -241,6 +245,34 @@ typedef struct {
uint32_t bot_cap_last_lba;
uint32_t bot_cap_block_size;
/* Milestone 2 / G.1 / §F.14: bulk-endpoint stall recovery. A bulk
* transfer that completes with XHCI_COMPLETION_CODE_STALL_ERROR leaves
* the xHC endpoint in the Halted state and the device endpoint in its
* own halt; neither can drive new transfers until explicitly cleared.
* This driver runs exactly one bulk transfer at a time, so a single
* recovery thread driven by bot_stall_recoveries + the stall_* fields
* below fully describes the recovery there is no concurrency to
* serialize. The recovery itself is the BOT-spec standard sequence:
* xHCI Reset Endpoint -> Set TR Dequeue Pointer -> USB
* CLEAR_FEATURE(ENDPOINT_HALT), escalating to a Bulk-Only Mass Storage
* Reset + CLEAR_FEATURE on both bulk endpoints on a repeated stall,
* then the original command stage is retried from scratch. The two
* xHCI command steps are correlated via connect_state's two new
* AWAIT_ values; the CLEAR_FEATURE / BOT-reset control transfers are
* correlated via transfer_purpose's two new XHCI_XFER_* values; the
* deferred issue + final re-issue ride next_action's two new
* XHCI_NEXT_ACTION_* values see xhci_poll_events()'s completion
* handlers for the state machine that consumes these. */
uint32_t bot_stall_recoveries; /* full recoveries performed for the
current command chain, capped at
XHCI_BOT_STALL_MAX_RECOVERIES */
uint32_t stall_dci; /* Device Context Index of the stalled bulk ep */
uint8_t stall_ep_addr; /* bEndpointAddress (bit7=dir) of the stalled bulk ep */
uint8_t bot_reset_clear_remaining; /* CLEAR_FEATUREs still owed in a
BOT-reset escalation (2 = both eps) */
uint32_t stall_retry_action; /* XHCI_NEXT_ACTION_* stage to re-issue once
recovery completes */
/* Milestone 2h: set by the SET_CONFIGURATION completion handler
* (inside xhci_poll_events()'s own call frame, so it only sets a flag
* -- no doorbell ring, no xhci_bot_wait_for_idle() call, both unsafe
@@ -296,7 +328,9 @@ typedef struct {
XHCI_NEXT_ACTION_BOT_SEND_TUR,
XHCI_NEXT_ACTION_BOT_SEND_READ10,
XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10,
XHCI_NEXT_ACTION_BOT_SEND_WRITE10
XHCI_NEXT_ACTION_BOT_SEND_WRITE10,
XHCI_NEXT_ACTION_CLEAR_HALT,
XHCI_NEXT_ACTION_BOT_RESET
} next_action;
uint32_t next_action_slot_id;
uint16_t next_action_length;
@@ -468,6 +502,52 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
*/
int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_cmd_reset_endpoint submit a Reset Endpoint command TRB for
* slot_id's endpoint `ep_id` (a bEndpointAddress,
* bit 7 = direction). Transitions that endpoint
* from the Halted state back to Stopped in the
* xHC's internal context the xHCI-level first
* step of G.1 / §F.14 stall recovery, mirroring
* xhci_cmd_disable_slot()'s shape exactly (submit,
* ring doorbell 0, don't wait). The device-side
* halt is cleared separately by
* xhci_ep0_clear_endpoint_halt() once the two
* xHCI command steps (Reset Endpoint, then Set TR
* Dequeue Pointer) have completed.
*
* Called from xhci_poll_events()'s transfer-event STALL handler -- not
* called directly by other code.
*
* Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set
* up.
*/
int xhci_cmd_reset_endpoint(xhci_dev_t *dev, uint32_t slot_id, uint32_t ep_id);
/*
* xhci_cmd_set_tr_dequeue_pointer submit a Set TR Dequeue Pointer command
* TRB for slot_id's endpoint ep_id,
* repositioning its Transfer Ring's
* dequeue pointer to `new_dequeue` (a
* pointer into the ring, e.g. the ring's
* current producer slot) with cycle state
* `dcs`. The xHCI-level second step of
* G.1 / §F.14 stall recovery: after Reset
* Endpoint has un-halted the ring, this
* tells the controller where to resume /
* discard from so a freshly enqueued TRB
* is consumed cleanly.
*
* Called from xhci_poll_events()'s command-completion handler once Reset
* Endpoint succeeds -- not called directly by other code.
*
* Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set
* up.
*/
int xhci_cmd_set_tr_dequeue_pointer(xhci_dev_t *dev, uint32_t slot_id,
uint32_t ep_id, uint64_t new_dequeue,
uint32_t dcs);
/*
* xhci_bot_send_read10 build a Command Block Wrapper for a SCSI
* READ(10) and submit it on the bulk OUT Transfer
@@ -816,4 +896,54 @@ int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t l
*/
int xhci_ep0_set_configuration(xhci_dev_t *dev, uint32_t slot_id, uint8_t config_value);
/*
* xhci_ep0_clear_endpoint_halt issue a CLEAR_FEATURE(ENDPOINT_HALT)
* standard control request (Setup + Status
* only, no Data stage, via the existing
* xhci_ep0_control_write_nodata() machinery)
* with wValue = ENDPOINT_HALT and wIndex =
* ep_addr. This is the USB-level step of
* G.1 / §F.14 stall recovery that clears
* the *device's* own halt condition (and
* resets its data toggle), so the endpoint
* will actually drive new transfers after
* the xHC-side Reset Endpoint + Set TR
* Dequeue Pointer commands have run.
* Sets transfer_purpose = XHCI_XFER_CLEAR_HALT
* so xhci_poll_events() can complete the
* recovery and re-issue the stalled command.
*
* Called from xhci_poll_events()'s deferred next_action dispatch (the
* XHCI_NEXT_ACTION_CLEAR_HALT branch, which may run it more than once in a
* BOT-Reset escalation to clear both bulk endpoints) -- not called directly
* by other code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not set
* up.
*/
int xhci_ep0_clear_endpoint_halt(xhci_dev_t *dev, uint32_t slot_id, uint8_t ep_addr);
/*
* xhci_ep0_bot_mass_storage_reset issue the Bulk-Only Transport class
* request Mass Storage Reset
* (bmRequestType = 0x21 class/interface,
* bRequest = 0xFF, no Data stage, again via
* the existing xhci_ep0_control_write_nodata()
* machinery). The escalation step of G.1 /
* §F.14 stall recovery: BOT spec section
* 5.3.4's full reset of a wedged command
* sequence, followed by
* xhci_ep0_clear_endpoint_halt() on *both*
* bulk endpoints before the original
* command is retried from scratch. Sets
* transfer_purpose = XHCI_XFER_BOT_RESET.
*
* Called from xhci_poll_events()'s deferred next_action dispatch (the
* XHCI_NEXT_ACTION_BOT_RESET branch) -- not called directly by other code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not set
* up.
*/
int xhci_ep0_bot_mass_storage_reset(xhci_dev_t *dev, uint32_t slot_id);
#endif /* STARKERNEL_XHCI_DRIVER_H */
+371 -1
View File
@@ -130,6 +130,10 @@ int xhci_cmd_disable_slot(xhci_dev_t *dev, uint32_t slot_id);
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
uint32_t port_id, uint32_t speed);
int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id);
int xhci_cmd_reset_endpoint(xhci_dev_t *dev, uint32_t slot_id, uint32_t ep_id);
int xhci_cmd_set_tr_dequeue_pointer(xhci_dev_t *dev, uint32_t slot_id,
uint32_t ep_id, uint64_t new_dequeue,
uint32_t dcs);
int xhci_bot_send_read10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size);
int xhci_bot_send_write10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
@@ -667,6 +671,51 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
return 0;
}
/* G.1 / §F.14 stall recovery: Reset Endpoint command. ep_id is a full
* bEndpointAddress (bit 7 = direction) of the stalled bulk endpoint -- the
* xHC is told which endpoint to un-halt via its Endpoint ID (the DCI) in
* parameter[31:24], exactly the field layout Reset Endpoint uses (xHCI 1.2
* spec table 6-88): parameter[31:24] = Endpoint ID, control[31:24] = Slot
* ID. Mirrors the existing xhci_cmd_disable_slot() shape (submit, ring
* doorbell 0, don't wait) -- this touches no doorbell but [0]. */
int xhci_cmd_reset_endpoint(xhci_dev_t *dev, uint32_t slot_id, uint32_t ep_id)
{
if (!dev || !dev->cmd_ring) return -1;
uint32_t dci = XHCI_EP_ADDR_TO_DCI(ep_id);
if (dci == 0) return -1; /* EP0 (DCI 1) is the only non-bulk case -- refuse */
xhci_submit_command(dev, (uint64_t)dci << 24, 0,
XHCI_TRB_TYPE_RESET_ENDPOINT_CMD, slot_id << 24);
console_println("xhci: reset endpoint command submitted");
return 0;
}
/* G.1 / §F.14 stall recovery: Set TR Dequeue Pointer command. new_dequeue
* is a pointer into the stalled endpoint's Transfer Ring (the current
* producer slot, past any failed TRBs) and dcs is the ring's current Cycle
* State -- both written into parameter exactly as the spec's Set TR
* Dequeue Pointer (xHCI 1.2 table 6-91) defines: parameter[63:4] = the
* dequeue pointer, parameter[0] = DCS, control[31:24] = Slot ID. */
int xhci_cmd_set_tr_dequeue_pointer(xhci_dev_t *dev, uint32_t slot_id,
uint32_t ep_id, uint64_t new_dequeue,
uint32_t dcs)
{
if (!dev || !dev->cmd_ring) return -1;
/* ep_id names the endpoint whose ring new_dequeue points into. The Set
* TR Dequeue Pointer TRB itself carries no Endpoint ID field -- the
* ring the controller should update is implied by the dequeue pointer
* + the controller already having Reset this endpoint (G.1's sequence
* runs Reset Endpoint before this) -- so ep_id is a documentation /
* call-site-clarity parameter, kept for symmetry with its sibling.
* Either way the controller knows which ring because RESET ENDPOINT
* names it first. */
(void)ep_id;
uint64_t param = (new_dequeue & ~0xFULL) | (uint64_t)(dcs ? 1u : 0u);
xhci_submit_command(dev, param, 0,
XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD, slot_id << 24);
console_println("xhci: set TR dequeue pointer command submitted");
return 0;
}
/* Enqueue one Normal TRB to the bulk OUT Transfer Ring -- same fixed-
* ring-plus-Link-TRB wraparound pattern as xhci_ep0_enqueue_trb(),
* operating on bulk_out_ring/bulk_out_ring_enq/bulk_out_ring_cycle
@@ -1156,6 +1205,68 @@ int xhci_ep0_set_configuration(xhci_dev_t *dev, uint32_t slot_id, uint8_t config
return 0;
}
/* G.1 / §F.14 stall recovery: CLEAR_FEATURE(ENDPOINT_HALT) on a specific
* endpoint. Reuses the existing no-data control-transfer machinery
* (xhci_ep0_control_write_nodata(), the same Setup+Status shape as
* SET_CONFIGURATION) -- just a different request payload: bRequest =
* USB_REQ_CLEAR_FEATURE, wValue = USB_FEATURE_ENDPOINT_HALT, wIndex = the
* endpoint's own bEndpointAddress (standard, host-to-device, endpoint-
* recipient -- bmRequestType inferred by the helper as 0x00). This is the
* USB-level clear that clears the device's halt condition and resets its
* data toggle after the two xHCI command steps (Reset Endpoint, Set TR
* Dequeue Pointer) have un-halted the xHC side. */
int xhci_ep0_clear_endpoint_halt(xhci_dev_t *dev, uint32_t slot_id, uint8_t ep_addr)
{
if (!dev || !dev->ep0_ring) return -1;
dev->transfer_purpose = XHCI_XFER_CLEAR_HALT;
xhci_ep0_control_write_nodata(dev, USB_REQ_CLEAR_FEATURE,
USB_FEATURE_ENDPOINT_HALT, ep_addr);
dev->pending_transfer_slot_id = slot_id;
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
console_println("xhci: clear endpoint halt submitted");
return 0;
}
/* G.1 / §F.14 stall recovery escalation: Bulk-Only Transport Mass Storage
* Reset. bmRequestType = 0x21 (class, interface recipient), bRequest =
* 0xFF, no data stage -- also built on xhci_ep0_control_write_nodata(), but
* that helper hardcodes bmRequestType = 0x00 (standard, host-to-device,
* device recipient). A BOT Mass Storage Reset is class + interface
* recipient, so it can't reuse the helper directly; the request bytes are
* stamped into the Setup packet by hand here (matching the same explicit-
* layout, no-padding discipline usb_setup_packet_t is documented for). */
int xhci_ep0_bot_mass_storage_reset(xhci_dev_t *dev, uint32_t slot_id)
{
if (!dev || !dev->ep0_ring) return -1;
usb_setup_packet_t setup = {
.bmRequestType = (USB_REQ_TYPE_CLASS << 5) | USB_RECIP_INTERFACE,
.bRequest = USB_BOT_MASS_STORAGE_RESET,
.wValue = 0,
.wIndex = 0,
.wLength = 0
};
uint64_t setup_bits;
memcpy(&setup_bits, &setup, sizeof(setup_bits));
xhci_ep0_enqueue_trb(dev, setup_bits, 8u,
(XHCI_TRB_TYPE_SETUP_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IDT |
(XHCI_SETUP_TRT_NO_DATA << XHCI_TRB_CONTROL_TRT_SHIFT));
xhci_ep0_enqueue_trb(dev, 0, 0,
(XHCI_TRB_TYPE_STATUS_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IOC |
XHCI_TRB_CONTROL_DIR_IN);
dev->transfer_purpose = XHCI_XFER_BOT_RESET;
dev->pending_transfer_slot_id = slot_id;
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
console_println("xhci: BOT mass storage reset submitted");
return 0;
}
/* -------------------------------------------------------------------------
* Milestone 2d: Event Ring servicing, polled from sk_repl_idle().
*
@@ -1248,6 +1359,122 @@ static void xhci_scan_ports_for_already_connected(xhci_dev_t *dev)
}
}
/* G.1 / §F.14: clean terminal failure of the BOT command currently in
* flight, used by every stall-recovery bail-out path. Explicitly sets
* bot_last_status = BOT_STATUS_FAILED and bot_cmd_kind = BOT_CMD_NONE -- a
* clean signal to a synchronous caller blocked in
* xhci_bot_wait_for_idle() (which would otherwise just time out, leaving
* state ambiguous), rather than relying on the outer timeout the way the
* pre-G.1 un-recovered stall did. */
static void xhci_stall_fail(xhci_dev_t *dev)
{
console_println("xhci: stall recovery exhausted -- failing command cleanly");
dev->bot_last_status = BOT_STATUS_FAILED;
dev->bot_cmd_kind = BOT_CMD_NONE;
dev->stall_retry_action = XHCI_NEXT_ACTION_NONE;
dev->bot_reset_clear_remaining = 0;
}
/* G.1 / §F.14: is `purpose` a bulk (BOT) transfer rather than an EP0
* control transfer? Stall recovery is defined only for the bulk endpoints
* (CBW_SENT and BOT_DATA_OUT run on bulk_out; BOT_DATA_IN and CSW_RECEIVED
* on bulk_in) -- an EP0 control transfer that stalls during enumeration is
* a different, out-of-scope case this driver deliberately does not attempt
* to recover. */
static int xhci_bulk_purpose_stalled(xhci_dev_t *dev, uint32_t purpose)
{
(void)dev;
switch (purpose) {
case XHCI_XFER_CBW_SENT:
case XHCI_XFER_BOT_DATA_IN:
case XHCI_XFER_BOT_DATA_OUT:
case XHCI_XFER_CSW_RECEIVED:
return 1;
default:
return 0;
}
}
/* G.1 / §F.14: start stall recovery for a bulk transfer that completed
* with XHCI_COMPLETION_CODE_STALL_ERROR in xhci_poll_events()'s transfer-
* event handler. Identifies the stalled endpoint from `purpose` (which
* bulk ring it ran on) and the command-stage to retry once recovery
* completes, then either (a) kicks off the basic xHCI Reset Endpoint /
* Set TR Dequeue Pointer / CLEAR_FEATURE(ENDPOINT_HALT) recovery, or on a
* repeated stall (b) escalates to a full BOT Mass Storage Reset. Recovery
* is bounded by bot_stall_recoveries vs XHCI_BOT_STALL_MAX_RECOVERIES;
* exhausting it bails out clean per the helper above. Called with purpose
* already read (it is cleared by the caller immediately after this call). */
static void xhci_handle_bulk_stall(xhci_dev_t *dev, uint32_t slot_id,
uint32_t purpose)
{
switch (purpose) {
case XHCI_XFER_CBW_SENT:
dev->stall_ep_addr = dev->bulk_out_ep_addr;
/* Re-send the whole SCSI command from its CBW -- which takes
* the existing TUR/READ/WRITE/CAPACITY entry points, picked
* by what's in flight. */
switch (dev->bot_cmd_kind) {
case BOT_CMD_WRITE10:
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_SEND_WRITE10;
break;
case BOT_CMD_READ_CAPACITY10:
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10;
break;
case BOT_CMD_TEST_UNIT_READY:
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_SEND_TUR;
break;
default: /* BOT_CMD_READ10 (and anything unexpected) */
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_SEND_READ10;
break;
}
break;
case XHCI_XFER_BOT_DATA_OUT:
dev->stall_ep_addr = dev->bulk_out_ep_addr;
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_DATA_OUT;
break;
case XHCI_XFER_BOT_DATA_IN:
dev->stall_ep_addr = dev->bulk_in_ep_addr;
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_DATA_IN;
break;
case XHCI_XFER_CSW_RECEIVED:
dev->stall_ep_addr = dev->bulk_in_ep_addr;
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_CSW_RECEIVE;
break;
default:
/* Unreachable -- xhci_bulk_purpose_stalled() gates entry. */
return;
}
dev->stall_dci = XHCI_EP_ADDR_TO_DCI(dev->stall_ep_addr);
if (dev->bot_stall_recoveries >= XHCI_BOT_STALL_MAX_RECOVERIES) {
xhci_stall_fail(dev);
return;
}
dev->bot_stall_recoveries++;
console_println("xhci: bulk endpoint STALL -- starting recovery");
if (dev->bot_stall_recoveries == 1) {
/* First recovery: the basic xHCI-level Reset Endpoint sequence
* (commands are submitted synchronously -- they ring doorbell 0,
* which is safe from inside event processing; only transfer-ring
* doorbells are deferred). */
dev->connect_state = XHCI_CONN_AWAIT_RESET_ENDPOINT;
if (xhci_cmd_reset_endpoint(dev, slot_id, dev->stall_ep_addr) != 0) {
console_println("xhci: reset endpoint submit failed -- stalling out clean");
dev->connect_state = XHCI_CONN_IDLE;
xhci_stall_fail(dev);
}
} else {
/* Repeated stall: escalate to a full BOT Mass Storage Reset + clear
* halt on both endpoints -- deferred, since BOT Mass Storage Reset
* is a control transfer that rings doorbell[slot] (must not happen
* inside event processing). */
dev->next_action = XHCI_NEXT_ACTION_BOT_RESET;
dev->next_action_slot_id = slot_id;
}
}
void xhci_poll_events(void)
{
xhci_dev_t *dev = g_xhci_dev;
@@ -1393,6 +1620,61 @@ void xhci_poll_events(void)
console_println("xhci: configure endpoint failed");
}
dev->connect_state = XHCI_CONN_IDLE;
} else if (dev->connect_state == XHCI_CONN_AWAIT_RESET_ENDPOINT) {
/* G.1 / §F.14 stall recovery, command step 1 of 2: the
* Reset Endpoint command has completed. On success,
* the xHC endpoint is back in the Stopped state and we
* immediately issue step 2, Set TR Dequeue Pointer, to
* reposition the stalled Transfer Ring's dequeue past
* the failed TRB (at the ring's current producer slot,
* with the producer's cycle as DCS) so a freshly
* enqueued retry TRB is consumed cleanly. stall_dci
* tells which ring. */
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
console_println("xhci: reset endpoint succeeded");
uintptr_t new_dequeue;
uint32_t dcs;
if (dev->stall_ep_addr & USB_EP_ADDR_DIR_MASK) {
new_dequeue = (uintptr_t)&dev->bulk_in_ring[dev->bulk_in_ring_enq];
dcs = dev->bulk_in_ring_cycle;
} else {
new_dequeue = (uintptr_t)&dev->bulk_out_ring[dev->bulk_out_ring_enq];
dcs = dev->bulk_out_ring_cycle;
}
dev->connect_state = XHCI_CONN_AWAIT_SET_TR_DEQUEUE;
if (xhci_cmd_set_tr_dequeue_pointer(dev, slot_id,
dev->stall_ep_addr,
new_dequeue, dcs) != 0) {
console_println("xhci: set tr dequeue pointer submit failed");
dev->connect_state = XHCI_CONN_IDLE;
/* Recovery couldn't even be issued -- clean
* terminal failure so the synchronous waiter
* doesn't spin forever on a wedged command. */
xhci_stall_fail(dev);
}
} else {
console_println("xhci: reset endpoint failed -- stalling out clean");
dev->connect_state = XHCI_CONN_IDLE;
xhci_stall_fail(dev);
}
} else if (dev->connect_state == XHCI_CONN_AWAIT_SET_TR_DEQUEUE) {
/* G.1 / §F.14 stall recovery, command step 2 of 2: Set
* TR Dequeue Pointer has completed. The xHC side is
* now fully recovered; the device side still needs its
* halt cleared via control transfer, so chain into the
* deferred CLEAR_FEATURE(ENDPOINT_HALT). */
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
console_println("xhci: set TR dequeue pointer succeeded");
} else {
console_println("xhci: set TR dequeue pointer failed -- stalling out clean");
xhci_stall_fail(dev);
}
dev->connect_state = XHCI_CONN_IDLE;
if (dev->bot_last_status != BOT_STATUS_FAILED) {
dev->bot_reset_clear_remaining = 1;
dev->next_action = XHCI_NEXT_ACTION_CLEAR_HALT;
dev->next_action_slot_id = slot_id;
}
} else {
console_println("xhci: command completion event");
}
@@ -1407,7 +1689,19 @@ void xhci_poll_events(void)
dev->transfer_purpose = XHCI_XFER_NONE;
if (code != XHCI_COMPLETION_CODE_SUCCESS) {
console_println("xhci: control transfer failed");
/* G.1 / §F.14: distinguish a true STALL (the one
* recoverable xHCI transfer completion) from every
* other failure. A stall on a bulk endpoint enters
* recovery; a plain failure (or a stall on an EP0
* control transfer, which this driver only meets
* during enumeration and does not attempt to
* recover) keeps today's behavior -- log and bail. */
if (code == XHCI_COMPLETION_CODE_STALL_ERROR &&
xhci_bulk_purpose_stalled(dev, purpose)) {
xhci_handle_bulk_stall(dev, xfer_slot_id, purpose);
} else {
console_println("xhci: control transfer failed");
}
break;
}
@@ -1686,6 +1980,68 @@ void xhci_poll_events(void)
}
break;
}
case XHCI_XFER_CLEAR_HALT: {
/* G.1 / §F.14: CLEAR_FEATURE(ENDPOINT_HALT)
* completed. This is the device-side clear; the
* xHC side was already recovered by Reset
* Endpoint + Set TR Dequeue Pointer. On success
* this may be one of two CLEAR_FEATUREs owed in
* a BOT-reset escalation (bot_reset_clear_remaining
* counts how many remain; >1 means "also clear
* the other bulk endpoint"). When all clears are
* done, the recovery is complete and the stalled
* command stage is retried (stall_retry_action).
* A CLEAR_FEATURE that itself fails or stalls
* is the "step 3 itself stalls" escalation case
* from §F.14 -- escalate to a full BOT Mass
* Storage Reset. */
if (code != XHCI_COMPLETION_CODE_SUCCESS) {
console_println("xhci: clear endpoint halt failed -- escalating to BOT reset");
dev->next_action = XHCI_NEXT_ACTION_BOT_RESET;
dev->next_action_slot_id = xfer_slot_id;
break;
}
console_println("xhci: clear endpoint halt succeeded");
if (dev->bot_reset_clear_remaining > 1) {
dev->bot_reset_clear_remaining--;
/* Flip to the other bulk endpoint for the
* second CLEAR_FEATURE owed in a BOT-reset
* escalation. */
dev->stall_ep_addr = (dev->stall_ep_addr & USB_EP_ADDR_DIR_MASK)
? dev->bulk_out_ep_addr
: dev->bulk_in_ep_addr;
dev->next_action = XHCI_NEXT_ACTION_CLEAR_HALT;
dev->next_action_slot_id = xfer_slot_id;
} else {
dev->bot_reset_clear_remaining = 0;
/* Recovery done -- resume the rest of the
* command chain at the stalled stage. */
if (dev->stall_retry_action != XHCI_NEXT_ACTION_NONE) {
dev->next_action = dev->stall_retry_action;
dev->next_action_slot_id = xfer_slot_id;
}
}
break;
}
case XHCI_XFER_BOT_RESET: {
/* G.1 / §F.14: BOT Mass Storage Reset completed.
* Per BOT spec 5.3.4's full procedure this is
* followed by CLEAR_FEATURE(ENDPOINT_HALT) on
* *both* bulk endpoints before the original
* command is retried; stage that as two
* chained CLEAR_HALT transfers. */
if (code != XHCI_COMPLETION_CODE_SUCCESS) {
console_println("xhci: BOT reset failed -- stalling out clean");
xhci_stall_fail(dev);
break;
}
console_println("xhci: BOT reset succeeded -- clearing both endpoints");
dev->bot_reset_clear_remaining = 2;
dev->stall_ep_addr = dev->bulk_in_ep_addr;
dev->next_action = XHCI_NEXT_ACTION_CLEAR_HALT;
dev->next_action_slot_id = xfer_slot_id;
break;
}
default:
console_println("xhci: transfer event");
break;
@@ -1796,5 +2152,19 @@ void xhci_poll_events(void)
dev->bot_write10_block_size) != 0) {
console_println("xhci: deferred WRITE10 setup failed");
}
} else if (dev->next_action == XHCI_NEXT_ACTION_CLEAR_HALT) {
uint32_t next_slot_id = dev->next_action_slot_id;
dev->next_action = XHCI_NEXT_ACTION_NONE;
if (xhci_ep0_clear_endpoint_halt(dev, next_slot_id, dev->stall_ep_addr) != 0) {
console_println("xhci: deferred clear endpoint halt setup failed");
xhci_stall_fail(dev);
}
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_RESET) {
uint32_t next_slot_id = dev->next_action_slot_id;
dev->next_action = XHCI_NEXT_ACTION_NONE;
if (xhci_ep0_bot_mass_storage_reset(dev, next_slot_id) != 0) {
console_println("xhci: deferred BOT mass storage reset setup failed");
xhci_stall_fail(dev);
}
}
}