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:
+162
@@ -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.
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user