From 49a3faa331c1a62b8dc2971d2b0c6dac2e8a3947 Mon Sep 17 00:00:00 2001 From: Robert Allan James Date: Sat, 29 Aug 2026 00:58:59 -0400 Subject: [PATCH] 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. --- FABRIC-3.md | 162 ++++++++++++++ capsules/BLOCK_MAP.md | 2 +- disk/artemis.img | Bin 31457280 -> 31457280 bytes docs/lithosananke/ROADMAP.md | 25 +++ include/starkernel/xhci.h | 38 ++++ include/starkernel/xhci_driver.h | 136 ++++++++++- src/starkernel/usb/xhci.c | 372 ++++++++++++++++++++++++++++++- 7 files changed, 730 insertions(+), 5 deletions(-) diff --git a/FABRIC-3.md b/FABRIC-3.md index bbc1099..0686ffb 100644 --- a/FABRIC-3.md +++ b/FABRIC-3.md @@ -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 +(M0–M9 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. diff --git a/capsules/BLOCK_MAP.md b/capsules/BLOCK_MAP.md index bb69c44..5d56475 100644 --- a/capsules/BLOCK_MAP.md +++ b/capsules/BLOCK_MAP.md @@ -1,5 +1,5 @@ # Capsule Block Manifest — Auto-generated - + diff --git a/disk/artemis.img b/disk/artemis.img index ffcecb8890f652d2a16a10b5adffa2ee603280e3..142cc9d956191f30499b85588bff9198d371e10a 100644 GIT binary patch delta 6023 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 diff --git a/docs/lithosananke/ROADMAP.md b/docs/lithosananke/ROADMAP.md index 736b5f0..c71da07 100644 --- a/docs/lithosananke/ROADMAP.md +++ b/docs/lithosananke/ROADMAP.md @@ -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 ``` diff --git a/include/starkernel/xhci.h b/include/starkernel/xhci.h index 478793e..13e2e89 100644 --- a/include/starkernel/xhci.h +++ b/include/starkernel/xhci.h @@ -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 diff --git a/include/starkernel/xhci_driver.h b/include/starkernel/xhci_driver.h index 538d40d..2de3c5a 100644 --- a/include/starkernel/xhci_driver.h +++ b/include/starkernel/xhci_driver.h @@ -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 */ diff --git a/src/starkernel/usb/xhci.c b/src/starkernel/usb/xhci.c index bce9b52..3386278 100644 --- a/src/starkernel/usb/xhci.c +++ b/src/starkernel/usb/xhci.c @@ -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); + } } }