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
+13
View File
@@ -320,6 +320,19 @@ typedef struct {
* xhci_dev_t's bot_tur_retries doc comment. */
#define XHCI_BOT_TUR_MAX_RETRIES 3u
/* 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:
* bytes 0-3 = Returned Logical Block Address (the *last* valid LBA, not a
* block count) big-endian, bytes 4-7 = Block Length in Bytes big-endian.
* This is Milestone 2h's prerequisite for everything else -- there is no
* other way for this driver to learn a device's block size or capacity,
* and block_subsystem.c's blk_subsys_attach_device() needs exactly that
* (via blkio_info()) before it can do anything with a device. */
#define SCSI_CMD_READ_CAPACITY10 0x25u
#define SCSI_CDB_LEN_READ_CAPACITY10 10u
#define SCSI_READ_CAPACITY10_DATA_LEN 8u
/* Bulk-Only Transport Command Status Wrapper (same spec, section 5.2) --
* received device-to-host on the bulk IN endpoint after the Data-In
* stage, closing out every SCSI command. Fixed 13-byte wire layout; same
+148 -10
View File
@@ -158,17 +158,20 @@ typedef struct {
* for dCBWTag; bot_last_tag latches the tag of the CBW currently in
* flight, so the CSW stage can verify dCSWTag matches (BOT spec
* requirement) without needing to re-derive it. bot_data_buf is a
* fixed 512-byte Data-In destination -- covers exactly one 512-byte
* block, this increment's whole scope (a real multi-block/variable-
* block-size transfer is 2h's problem once the block subsystem
* actually calls this path with real sizes). bot_expected_data_len is
* the byte count the Data-In stage was told to read, staged at CBW
* build time and consumed once the Data-In TRB is actually enqueued. */
* fixed 1024-byte Data-In destination -- sized to cover exactly one
* Forth block (BLKIO_FORTH_BLOCK_SIZE, block_subsystem.c's own unit)
* as two consecutive 512-byte SCSI blocks, which is what
* xhci_bot_read_block() actually requests once Milestone 2h's blkio
* backend calls this path with real block-subsystem-driven sizes; grown
* from the single-512-byte-block buffer of the increment that first
* added it. bot_expected_data_len is the byte count the Data-In stage
* was told to read, staged at CBW build time and consumed once the
* Data-In TRB is actually enqueued. */
usb_bot_cbw_t bot_cbw;
usb_bot_csw_t bot_csw;
uint32_t bot_next_tag;
uint32_t bot_last_tag;
uint8_t bot_data_buf[512];
uint8_t bot_data_buf[1024];
uint32_t bot_expected_data_len;
/* Milestone 2g follow-up: TEST UNIT READY unit-init sequence, ahead of
@@ -183,16 +186,51 @@ typedef struct {
* pending bot_read10_* fields latch a caller's requested READ(10) so
* it can be issued once TUR reports PASS -- xhci_bot_read_block() is
* the entry point that stages these and kicks off TUR first, rather
* than callers driving xhci_bot_send_read10() directly. */
* than callers driving xhci_bot_send_read10() directly.
*
* Milestone 2h adds BOT_CMD_READ_CAPACITY10 (see
* xhci_bot_send_read_capacity10()) and bot_last_status: every command
* kind now resets bot_cmd_kind to BOT_CMD_NONE and sets
* bot_last_status once its own CSW is fully processed (TEST UNIT
* READY is the one exception -- a PASS or an in-progress retry both
* stay non-terminal, chaining into the next command instead). This is
* what lets xhci_bot_wait_for_idle() -- a synchronous busy-wait,
* called from OUTSIDE xhci_poll_events(), never from within it --
* detect "this command's whole chain is finished" without needing to
* know which specific command it was waiting on. */
enum {
BOT_CMD_NONE = 0,
BOT_CMD_TEST_UNIT_READY,
BOT_CMD_READ10
BOT_CMD_READ10,
BOT_CMD_READ_CAPACITY10
} bot_cmd_kind;
enum {
BOT_STATUS_IDLE = 0,
BOT_STATUS_PASS,
BOT_STATUS_FAILED,
BOT_STATUS_TIMEOUT
} bot_last_status;
/* Which command a TUR-PASS should chain into -- TEST UNIT READY's own
* completion handling can't tell READ10 and READ CAPACITY10 apart
* otherwise, since both now go through the identical TUR-first
* sequencing xhci_bot_read_block()/xhci_bot_get_capacity() both use.
* Set by whichever of those two entry points kicked off the TUR. */
enum {
BOT_TUR_CHAIN_NONE = 0,
BOT_TUR_CHAIN_READ10,
BOT_TUR_CHAIN_READ_CAPACITY10
} bot_tur_chain_target;
uint32_t bot_tur_retries;
uint32_t bot_read10_lba;
uint16_t bot_read10_num_blocks;
uint32_t bot_read10_block_size;
/* Latched from a successful READ CAPACITY(10) Data-In reply -- see
* SCSI_CMD_READ_CAPACITY10's own doc comment in xhci.h for field
* meaning. Untouched (stale) on a FAILED/TIMEOUT completion; callers
* must check xhci_bot_wait_for_idle()'s return value, not just read
* these blindly. */
uint32_t bot_cap_last_lba;
uint32_t bot_cap_block_size;
/* Deferred chaining: a doorbell ring (new control transfer) must
* never happen synchronously from inside xhci_poll_events()'s event-
@@ -214,7 +252,8 @@ typedef struct {
XHCI_NEXT_ACTION_BOT_DATA_IN,
XHCI_NEXT_ACTION_BOT_CSW_RECEIVE,
XHCI_NEXT_ACTION_BOT_SEND_TUR,
XHCI_NEXT_ACTION_BOT_SEND_READ10
XHCI_NEXT_ACTION_BOT_SEND_READ10,
XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10
} next_action;
uint32_t next_action_slot_id;
uint16_t next_action_length;
@@ -256,6 +295,19 @@ int xhci_find_and_map(xhci_dev_t *dev);
*/
int xhci_bringup(xhci_dev_t *dev);
/*
* xhci_get_dev — return the module-static xhci_dev_t* xhci_poll_events()
* itself reads (only one controller is supported), or NULL
* if xhci_bringup() has not completed successfully yet.
*
* Milestone 2h: callers outside this driver (sk_repl_idle(), eventually
* the block-subsystem attach glue) have no other way to reach the device
* handle -- every earlier caller of this driver's API already had one in
* hand (kernel_main.c's own local xhci_dev_t), which doesn't help code
* that only runs later, on a hotplug event it wasn't the one to observe.
*/
xhci_dev_t *xhci_get_dev(void);
/*
* xhci_poll_events — read Interrupter 0's Event Ring, dispatching each TRB
* by type: Port Status Change reads PORTSC to log
@@ -473,6 +525,92 @@ int xhci_bot_send_test_unit_ready(xhci_dev_t *dev, uint32_t slot_id);
int xhci_bot_read_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size);
/*
* xhci_bot_send_read_capacity10 — build a Command Block Wrapper for SCSI
* READ CAPACITY(10) (SBC-3 section 5.14,
* opcode 0x25, see xhci.h) and submit it
* on the bulk OUT Transfer Ring. Sets
* dev->bot_cmd_kind = BOT_CMD_READ_CAPACITY10
* and dev->bot_expected_data_len =
* SCSI_READ_CAPACITY10_DATA_LEN (8) so
* the existing CBW-completion handler
* runs the Data-In stage (unlike TEST
* UNIT READY, this command does have a
* short reply). On a PASS CSW,
* dev->bot_cap_last_lba/bot_cap_block_size
* are parsed from the 8-byte reply and
* bot_cmd_kind resets to BOT_CMD_NONE --
* this command is always terminal, it
* never chains into anything else.
*
* Requires the same bulk endpoint/ring prerequisites as
* xhci_bot_send_read10()/xhci_bot_send_test_unit_ready() -- refuses if any
* are missing.
*
* Low-level primitive -- does not run TEST UNIT READY first. Sent to a
* freshly attached device with no TUR ahead of it, this eats the same
* first-command UNIT ATTENTION READ(10) used to before xhci_bot_read_block()
* existed (confirmed live -- see this driver's own Milestone 2h capture
* log). Most callers want xhci_bot_get_capacity() below instead; this is
* called directly only by xhci_poll_events()'s own deferred dispatch
* (XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10, once a prior TUR has
* reported PASS).
*
* Returns 0 if the CBW was posted, -1 if a prerequisite is missing.
*/
int xhci_bot_send_read_capacity10(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_get_capacity — the real entry point for learning a device's
* block size/capacity. Sets
* dev->bot_tur_chain_target =
* BOT_TUR_CHAIN_READ_CAPACITY10, resets
* dev->bot_tur_retries, and issues a TEST UNIT
* READY first rather than a bare READ CAPACITY(10)
* -- same reasoning as xhci_bot_read_block(),
* and the same TUR retry budget
* (XHCI_BOT_TUR_MAX_RETRIES).
*
* Returns 0 if TEST UNIT READY was posted, -1 if a prerequisite is
* missing (same checks xhci_bot_send_read_capacity10() performs, done up
* front here so a bad request is rejected before spending a TUR
* round-trip on it).
*/
int xhci_bot_get_capacity(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_wait_for_idle — busy-wait for the BOT command currently in
* flight (bot_cmd_kind != BOT_CMD_NONE) to reach
* a terminal state, by calling xhci_poll_events()
* in a loop up to max_iters times.
*
* This is Milestone 2h's synchronous bridge over an otherwise fully
* asynchronous, polled driver -- block_subsystem.c's blkio_read()/
* blkio_info() etc. are ordinary synchronous function calls with no way
* to "come back later" for a result, so something has to spin until the
* driver's own event-driven state machine finishes.
*
* MUST NOT be called from inside xhci_poll_events() itself, or from any
* function xhci_poll_events() calls (a next_action dispatch, a Transfer
* Event handler) -- xhci_poll_events() is not reentrant, and this
* function's own busy-wait loop calls it again on every iteration; doing
* so from within an already-running call 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). Only call this from a
* context that is definitely outside that call frame -- a caller in
* sk_repl_idle() invoked strictly after its own xhci_poll_events() call
* has already returned, for example.
*
* Returns BOT_STATUS_PASS/BOT_STATUS_FAILED (dev->bot_last_status, as left
* by whichever command was in flight) once bot_cmd_kind returns to
* BOT_CMD_NONE, or BOT_STATUS_TIMEOUT if max_iters is exhausted first
* (the command may still complete later -- this driver has no cancel
* operation, the caller just stops waiting). Returns BOT_STATUS_FAILED
* immediately if dev is NULL.
*/
int xhci_bot_wait_for_idle(xhci_dev_t *dev, uint32_t max_iters);
/*
* xhci_bot_read_data_in — submit a Normal TRB on the bulk IN Transfer
* Ring to read dev->bot_expected_data_len bytes