Artemis Milestone 2g: CBW construction and send for SCSI READ(10)

First real use of the bulk Transfer Rings Configure Endpoint wired up.
xhci_bot_send_read10() builds a 31-byte Command Block Wrapper (USB Mass
Storage Class Bulk-Only Transport spec section 5.1) and submits it as a
single Normal TRB on the bulk OUT ring via a new
xhci_bulk_out_enqueue_and_ring() helper -- a CBW is always exactly one
TRB, so unlike the EP0 helper this one rings its own doorbell rather
than leaving that to a caller assembling a group.

usb_bot_cbw_t is a real struct (every field up to the CDB array is
naturally aligned, and this driver's targets are all little-endian
already assumed everywhere else), but its DMA length is the explicit
USB_BOT_CBW_LENGTH (31) constant, never sizeof(*cbw), since the
compiler may pad the struct to 32 bytes. The SCSI READ(10) CDB itself
is written byte-by-byte since its LBA/Transfer Length fields are
big-endian on the wire, unlike everything else in this driver -- the
one place two byte orders are both live in the same function.

Completion is correlated via the existing pending_transfer_slot_id/
transfer_purpose gate (new XHCI_XFER_CBW_SENT purpose) -- no
ring-specific dispatch needed, since this driver's single-outstanding-
transfer scope already implies which ring produced an event.

This covers construction and send only (one third of a full READ(10):
CBW -> Data-In stage -> CSW) -- reading the Data-In stage and CSW
receive/validation are separate, explicitly not-yet-implemented items.

Verified live via a temporary probe (written, run once, log captured,
reverted per this project's own probe convention) -- all three
architectures, byte-identical: CBW submitted -> CBW send completed,
then a clean disconnect even with the Data-In stage never drained
(confirms no wedge on a dangling BOT transaction). Probe-free
re-verification afterward on all three architectures.

FABRIC-2.md Section X Milestone 2g's CBW checklist item marked done.
Also records a monitoring gotcha hit three times this session: `ls -t`
over the logs/ tree can return a stale leftover log from an earlier
run in the same session -- fixed going forward by reading the log path
off the actual running QEMU process's own command line instead, and a
memory note added so it doesn't recur next session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4
This commit is contained in:
Robert Allan James
2026-08-25 09:07:33 -04:00
co-authored by Claude Sonnet 5
parent 92ce1f85dd
commit a88c004ecb
18 changed files with 54562 additions and 3 deletions
+64 -1
View File
@@ -3453,7 +3453,8 @@ arch afterward, no wedge. `logs/20260825-073235/amd64/`, `logs/20260825-073417/a
**2g. Bulk-Only Transport (BOT) — the actual read/write path**
- [x] Identify and configure the device's bulk IN and bulk OUT endpoints — **identification
done 2026-08-25, configuration done 2026-08-25**, see writeups below
- [ ] Implement CBW (Command Block Wrapper) construction and send, for a SCSI READ(10)
- [x] Implement CBW (Command Block Wrapper) construction and send, for a SCSI READ(10)
**done 2026-08-25**, see writeup below
- [ ] Implement CSW (Command Status Wrapper) receive and status check
- [ ] Get one real SCSI READ(10) working end to end — first proof the whole stack works,
before write
@@ -3535,6 +3536,68 @@ every other ring in this driver) are allocated and wired into the EP Contexts by
increment but not yet used for an actual transfer — CBW/CSW submission (2g's next items) is
what exercises them for the first time.
**CBW construction and send, done 2026-08-25.** First real use of the bulk Transfer Rings
Configure Endpoint wired up. `xhci_bot_send_read10()` builds a 31-byte Command Block Wrapper
(USB Mass Storage Class Bulk-Only Transport spec, section 5.1) into a new `usb_bot_cbw_t`
buffer, then submits it as a single Normal TRB (`XHCI_TRB_TYPE_NORMAL`, new in `xhci.h`) on
the bulk OUT ring via a new `xhci_bulk_out_enqueue_and_ring()` helper — a CBW is always
exactly one TRB (no Setup/Data/Status split, that's a control-transfer-only concept), so
unlike the EP0 helper this one rings the doorbell itself rather than leaving that to a caller
assembling a group. Doorbell target is the bulk OUT endpoint's own DCI (`XHCI_EP_ADDR_TO_DCI()`,
already built for Configure Endpoint), not target 1 (EP0) — distinct rings need distinct
doorbell targets.
`usb_bot_cbw_t` is a real C struct (unlike the Interface/Endpoint descriptor fields, which are
read via explicit offset macros rather than a struct) since every field up to the trailing
16-byte CDB array is already naturally aligned and this driver's targets are all little-endian
already assumed everywhere else (no `htole32`-style conversions anywhere in this codebase) —
but its DMA length is the explicit constant `USB_BOT_CBW_LENGTH` (31), never `sizeof(*cbw)`,
since the compiler may pad the struct to 32 bytes to satisfy its `uint32_t` members' alignment.
The SCSI READ(10) CDB itself (SBC-3 section 5.13) is written byte-by-byte instead, since its
multi-byte fields (LBA, Transfer Length) are big-endian on the wire regardless of USB's own
little-endian convention — the one place in this function two different byte orders are both
live at once, flagged in a comment so a future edit doesn't "fix" one to match the other.
Completion is correlated the same way every other transfer in this driver is: a new
`XHCI_XFER_CBW_SENT` `transfer_purpose`, read back via the existing `pending_transfer_slot_id`
gate in `xhci_poll_events()`'s Transfer Event handling — no ring-specific dispatch needed,
since this driver's single-outstanding-transfer scope already covers "which ring generated
this event" implicitly (there is only ever one transfer in flight, control or bulk, at a
time). `bot_next_tag` (a new `xhci_dev_t` field) is forward-looking plumbing for a future
dCSWTag match check, not read back by anything in this increment — CSW receive/validation is
2g's next item.
This covers construction and send only, one third of a full READ(10) (CBW → Data-In stage →
CSW) — the Data-In stage and CSW receive are explicitly separate, not-yet-implemented items on
this milestone's own punch list.
Verified live via a temporary probe (written, run once, log captured, then reverted per this
project's own probe convention — see `feedback_revert_probes_after_capture` in memory): a
`XHCI_NEXT_ACTION_BOT_TEST_READ` deferred action, triggered from the existing SET_CONFIGURATION
completion handler, issued a 1-block READ(10) at LBA 0 immediately once a device reached
`device configured`. All three architectures, byte-identical: `xhci: CBW (READ10) submitted`
`xhci: CBW send completed`, then a clean disconnect (`disable slot succeeded`) afterward even
with the READ10's Data-In stage never drained — confirms the driver doesn't wedge on a
dangling BOT transaction, a useful bonus check beyond this increment's own scope.
`logs/20260825-084942/amd64/`, `logs/20260825-085140/aarch64/`, `logs/20260825-085414/riscv64/`.
The probe itself (the `XHCI_NEXT_ACTION_BOT_TEST_READ` trigger and its enum value) was removed
after capture, per convention — only `xhci_bot_send_read10()` and its Transfer Event handling
remain as reusable substrate. Re-verified probe-free afterward, all three architectures, clean
boots with no BOT activity (nothing calls the function yet with the probe gone — expected):
`logs/20260825-090124/amd64/`, `logs/20260825-090238/aarch64/`, `logs/20260825-090426/riscv64/`.
**A monitoring mistake caught mid-session, same class as the one recorded in 2e's
disconnect-teardown writeup above.** While verifying the final probe-free boots, two separate
`ls -t logs/*/<arch>/*.log | head -1` globs each picked a *stale* leftover log from this same
session's own earlier hotplug-verification runs (their file mtimes apparently updated during
QEMU's own shutdown/log-flush, after the new run's log had already been created but before it
had accumulated enough content to look "newer" by content, only by directory-creation time) —
both times caught by cross-checking the glob's answer against the actual running QEMU
process's real command line (`ps aux | grep qemu-system-<arch>`, which names its own log path
directly on the command line) before trusting the result. `ls -t` on this log tree is now a
known-unreliable pattern for "find the log for the process I just launched" — the PID's own
command line is the ground truth, not directory mtime ordering.
**2h. Integration with the existing block subsystem**
- [ ] Wire a working USB MSC device into `blk_subsys_attach_device()` (or
`blk_subsys_add_raw_device()`, whichever fits — confirm which, since USB is