Artemis Milestone 2h (foundational): sync wait bridge + SCSI READ CAPACITY(10)

Closes the gap block_subsystem.c needs before any of 2h's real work
(blkio_usb.c, attach wiring, hot-detach) can start: this driver is fully
async/polled with no way for a synchronous caller (blkio_read()/
blkio_info() etc.) to get a result back. xhci_bot_wait_for_idle() is a
bounded busy-wait over xhci_poll_events() -- MUST be called only from
outside xhci_poll_events()'s own call frame, never from within it or a
next_action dispatch (recursion into live Event Ring/ERDP processing,
same class of hazard already documented for doorbell rings in this
driver). xhci_get_dev() exposes the module-static device handle to
outside callers that didn't observe the original hotplug event.

SCSI READ CAPACITY(10) (opcode 0x25) is the other half -- nothing could
learn a device's block size/capacity before this. First attempt sent it
bare and hit the classic first-command UNIT ATTENTION (CSW FAILED); fixed
with the same TUR-guard pattern READ(10) already used, generalized via a
new bot_tur_chain_target field so TEST UNIT READY's PASS handling can
chain into either command. bot_data_buf grown 512->1024 bytes (one Forth
block = two 512-byte SCSI blocks, per block_subsystem.c's own 1KiB-unit
convention).

Verified live via a temporary probe (hot-attached disk/usb-thumbdrive-
test.img via QMP, reverted after capture): TUR-guarded READ CAPACITY10
correctly reported last LBA=0x1ffff, block size=0x200 -- exactly 64MiB,
matching the test image byte for byte -- followed by a TUR-guarded
1024-byte/2-block READ10, both PASS. All three architectures re-verified
clean, probe-free boot to ok> on the reverted tree. FABRIC-2.md Section X
2h updated with the writeup; the blkio_usb.c backend itself is next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
This commit is contained in:
Robert Allan James
2026-08-25 12:27:57 -04:00
co-authored by Claude Sonnet 5
parent 65effbd1ba
commit d686f28853
15 changed files with 45640 additions and 17 deletions
+70
View File
@@ -3724,6 +3724,76 @@ function. Detaching should be safe without renumbering other devices' LBNs as lo
the tail of the chain (true today; would need reconsidering if a second hot-pluggable device
class is ever added), but the function itself does not exist yet.
**2h foundational piece — synchronous bridge + READ CAPACITY(10), done 2026-08-25.** Before any
of the three items above can be built, something has to close the gap between this driver
(fully async, single-outstanding-transaction, polled from `sk_repl_idle()`) and
`block_subsystem.c` (ordinary synchronous function calls — `blkio_read()`/`blkio_info()` etc.
have no way to "come back later" for a result). `xhci_bot_wait_for_idle(dev, max_iters)` is
that bridge: a bounded busy-wait that calls `xhci_poll_events()` in a loop until
`dev->bot_cmd_kind` returns to `BOT_CMD_NONE`, returning `dev->bot_last_status`
(`BOT_STATUS_PASS`/`BOT_STATUS_FAILED`/`BOT_STATUS_TIMEOUT`). Every BOT command's CSW-received
handling now resets `bot_cmd_kind`/sets `bot_last_status` on every terminal path — including the
two CSW signature/tag-mismatch discard branches, which previously left `bot_cmd_kind` stuck
forever on a garbled CSW (harmless before, since nothing read it back; would have hung this new
wait loop for its full `max_iters` on the same condition).
**Load-bearing constraint, caught by advisor review before writing any code:** this wait
function must never be called from inside `xhci_poll_events()` itself, or from anything it
calls (a `next_action` dispatch, a Transfer Event handler) — the busy-wait's own repeated
`xhci_poll_events()` calls would recurse into live Event Ring/ERDP processing, the same class of
hazard this driver's `next_action` deferral mechanism exists to avoid for doorbell rings (see
`xhci_dev_t`'s own doc comment on `next_action`, and the confirmed-live amd64 hang recorded
there). A new `xhci_get_dev()` accessor (returns the module-static `xhci_dev_t*` this driver
already latches internally, or NULL if not brought up) lets an outside caller — this increment's
temp probe used `sk_repl_idle()`, immediately after its own `xhci_poll_events()` call had already
returned — reach the device handle safely; every earlier caller of this driver's public API
already had one in hand (`kernel_main.c`'s local `xhci_dev_t`), which doesn't help code that only
runs later, on a hotplug event it wasn't the one to observe.
SCSI READ CAPACITY(10) (SBC-3 section 5.14, opcode `0x25`) is the other half: `blk_subsys_attach_device()`
reads `info.total_blocks` (via `blkio_info()`) before it does anything else, and there was no way
for this driver to learn a device's block size/capacity at all until now.
`xhci_bot_send_read_capacity10()` mirrors `xhci_bot_send_read10()`'s CBW-build shape (opcode +
all-zero CDB otherwise, 8-byte Data-In reply); on a PASS CSW the reply's big-endian last-LBA and
block-length fields are parsed into `dev->bot_cap_last_lba`/`bot_cap_block_size`. First attempt
sent it bare (no TEST UNIT READY guard) and it failed — CSW status FAILED, residue 8 — the exact
first-command UNIT ATTENTION READ(10) used to eat before `xhci_bot_read_block()` existed, caught
immediately by the live capture rather than assumed away. Fixed the same way READ(10) was: a new
`bot_tur_chain_target` field (`BOT_TUR_CHAIN_READ10`/`BOT_TUR_CHAIN_READ_CAPACITY10`) lets TEST
UNIT READY's own PASS handling tell which command to chain into next — `xhci_bot_get_capacity()`
is the new TUR-guarded entry point (sets the chain target, resets the retry counter, issues TUR
first), matching `xhci_bot_read_block()`'s own shape exactly.
`bot_data_buf` grew from 512 to 1024 bytes — `block_subsystem.c`'s own header comment says
blkio backends operate on 1 KiB units, i.e. one Forth block is two consecutive 512-byte SCSI
blocks, and 512 bytes could never hold that.
Verified live via a temporary probe (written, run once, log captured, then reverted per this
project's own probe convention — hot-attached `disk/usb-thumbdrive-test.img` through the running
instance's QMP socket, same as every earlier BOT capture), amd64 only (the mechanism is
architecture-independent code already proven identical across all three arches by every earlier
BOT increment, same reasoning as the TEST UNIT READY increment's own capture scope): full chain
`CBW (TEST UNIT READY)``FAILED` → retry → `PASS``unit ready -- issuing READ CAPACITY10`
`CBW (READ CAPACITY10) submitted``CSW status = PASS``READ CAPACITY last LBA=0x0001ffff` /
`block size=0x00000200``probe READ CAPACITY10 PASS`, immediately followed by a second
TUR→READ(10) chain (`xhci_bot_read_block(dev, slot, 0, 2, 512)`, exercising the grown buffer)
also `PASS`. `0x1ffff` (131071) × `0x200` (512) = exactly 64 MiB, matching
`usb-thumbdrive-test.img`'s real size byte for byte — proof this isn't just a well-formed reply,
but the *correct* one. `logs/20260825-120043/amd64/` (first attempt, READ CAPACITY10 bare,
FAILED — kept as evidence of the bug this increment's own TUR-guard fix addresses, not deleted
as a failed run), `logs/20260825-121339/amd64/` (fixed, full PASS chain). The probe (a
`bot_probe_pending`/`bot_probe_slot_id` flag pair set by the SET_CONFIGURATION handler and
consumed in `sk_repl_idle()`) was removed after capture; `xhci_bot_wait_for_idle()`,
`xhci_get_dev()`, `xhci_bot_send_read_capacity10()`, and `xhci_bot_get_capacity()` remain as
permanent, reusable substrate for the next 2h increment (the actual `blkio_usb.c` backend).
Re-verified probe-free afterward, all three architectures, clean boots with no BOT activity:
`logs/20260825-122016/amd64/`, `logs/20260825-122318/aarch64/`, `logs/20260825-122527/riscv64/`.
Still ahead for 2h: the `blkio_usb.c` backend itself (per `blk_format_or_load_disk()`'s own
"NEVER writes to disk here" discipline at attach time, a read-only backend is sufficient to land
first — WRITE(10) doesn't exist in this driver yet and isn't a prerequisite), the connect-time
call site wiring a ready `blkio_dev*` into `blk_subsys_attach_device()`, and the hot-detach path.
### Milestone 3 — Block subsystem extensions (Section U items 3-6, Section V area A)
Depends on Milestone 2 existing (needs a real device to test against, though the