Artemis Milestone 2g: Data-In stage read and CSW receive/validation

Completes the CBW -> Data-In -> CSW chain for READ(10) started last
commit. xhci_bot_read_data_in() and xhci_bot_receive_csw(), each a
single Normal TRB on the bulk IN Transfer Ring via a new
xhci_bulk_in_enqueue_and_ring() helper (mirrors the OUT-side helper
from CBW send). All three stages now chain automatically via the
existing deferred next_action pattern: CBW completion defers into
Data-In, Data-In completion defers into CSW receive, CSW completion is
where signature/tag/status validation happens.

Data-In reads into a new fixed 512-byte bot_data_buf -- single-block
scope for this increment, matches QEMU's usb-storage reported block
size; xhci_bot_send_read10() now refuses rather than overflow/truncate
if a request exceeds it. CSW validation (BOT spec section 5.2) checks
dCSWSignature and dCSWTag (a new bot_last_tag field, latched from the
CBW) before trusting bCSWStatus at all, so a garbled/misaligned CSW
read can't be misread as a clean pass. usb_bot_csw_t follows the same
struct-with-explicit-length-not-sizeof discipline as usb_bot_cbw_t.

Verified live via a temporary probe (written, run once, log captured,
reverted per this project's own probe convention), all three
architectures, byte-identical: the full CBW -> Data-In -> CSW exchange
completes cleanly, well-formed CSW with correct signature and echoed
tag, no wedge, clean disconnect immediately after. The SCSI command
itself reports CSW status FAILED against the current test fixture --
expected at this stage (no TEST UNIT READY / UNIT ATTENTION handling
implemented yet, consistent with a fresh-attach unit-attention
condition, not a transport-layer defect) and not root-caused further
here; the BOT mechanism itself is confirmed correct end to end.
Probe-free re-verification afterward on all three architectures.

FABRIC-2.md Section X Milestone 2g's CSW checklist item marked done;
"get one real READ(10) working end to end" stays explicitly open,
distinguishing "the mechanism works" from "the SCSI command succeeds."

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:33:17 -04:00
co-authored by Claude Sonnet 5
parent a88c004ecb
commit c54ea24aaf
18 changed files with 54607 additions and 35 deletions
+20
View File
@@ -306,6 +306,26 @@ typedef struct {
#define SCSI_CMD_READ10 0x28u
#define SCSI_CDB_LEN_READ10 10u
/* 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
* little-endian-direct-assignment and explicit-length-not-sizeof
* discipline as usb_bot_cbw_t above (this struct's natural size is
* likely padded to 16 by the compiler for the same alignment reason --
* USB_BOT_CSW_LENGTH (13) is the real wire length). */
typedef struct {
uint32_t dCSWSignature;
uint32_t dCSWTag;
uint32_t dCSWDataResidue;
uint8_t bCSWStatus;
} usb_bot_csw_t;
#define USB_BOT_CSW_SIGNATURE 0x53425355u /* "USBS", wire byte order U,S,B,S as an LE dword */
#define USB_BOT_CSW_LENGTH 13u
#define USB_BOT_CSW_STATUS_PASS 0u
#define USB_BOT_CSW_STATUS_FAILED 1u
#define USB_BOT_CSW_STATUS_PHASE_ERROR 2u
/* Command Completion Event TRB layout (xHCI 1.2 spec table 6-32):
* parameter[63:4] = Command TRB Pointer, status[31:24] = Completion Code,
* status[23:0] = unused here, control[31:24] = Slot ID (Enable Slot's
+74 -24
View File
@@ -114,7 +114,9 @@ typedef struct {
XHCI_XFER_CONFIG_DESC_SHORT,
XHCI_XFER_CONFIG_DESC_FULL,
XHCI_XFER_SET_CONFIG,
XHCI_XFER_CBW_SENT
XHCI_XFER_CBW_SENT,
XHCI_XFER_BOT_DATA_IN,
XHCI_XFER_CSW_RECEIVED
} transfer_purpose;
uint32_t pending_transfer_slot_id;
uint8_t device_descriptor[18];
@@ -149,17 +151,25 @@ typedef struct {
uint32_t bulk_out_ring_cycle;
uint32_t bulk_out_ring_enq;
/* Milestone 2g: Bulk-Only Transport CBW. bot_cbw is reused across every
* command (single-outstanding-transfer scope, matching every other
* buffer in this driver) -- built fresh by xhci_bot_send_read10() each
* call, not preserved between calls. bot_next_tag is a free-running
* counter for dCBWTag; the BOT spec requires the host verify a CSW's
* dCSWTag matches the CBW that produced it, so this is forward-looking
* plumbing for that check (CSW receive is 2g's next item, not
* implemented yet) rather than something this increment reads back
* itself. */
/* Milestone 2g: Bulk-Only Transport. bot_cbw/bot_csw are reused across
* every command (single-outstanding-transfer scope, matching every
* other buffer in this driver) -- built/overwritten fresh each call,
* not preserved between calls. bot_next_tag is a free-running counter
* 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. */
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];
uint32_t bot_expected_data_len;
/* Deferred chaining: a doorbell ring (new control transfer) must
* never happen synchronously from inside xhci_poll_events()'s event-
@@ -177,7 +187,9 @@ typedef struct {
XHCI_NEXT_ACTION_GET_DEVICE_DESC,
XHCI_NEXT_ACTION_GET_CONFIG_DESC,
XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT,
XHCI_NEXT_ACTION_SET_CONFIG
XHCI_NEXT_ACTION_SET_CONFIG,
XHCI_NEXT_ACTION_BOT_DATA_IN,
XHCI_NEXT_ACTION_BOT_CSW_RECEIVE
} next_action;
uint32_t next_action_slot_id;
uint16_t next_action_length;
@@ -339,31 +351,69 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_send_read10 — build a Command Block Wrapper for a SCSI
* READ(10) and submit it on the bulk OUT Transfer
* Ring.
* Ring; the Data-In stage and CSW receive follow
* automatically once this CBW's own completion
* arrives (see xhci_bot_read_data_in()/
* xhci_bot_receive_csw() below), same deferred-
* chaining pattern as device descriptor -> config
* descriptor -> Configure Endpoint -> SET_CONFIG.
*
* lba is the starting Logical Block Address, num_blocks the SCSI transfer
* length (blocks, not bytes -- READ(10)'s own field), block_size the
* device's actual bytes-per-block, used only to compute
* dCBWDataTransferLength (the data stage's total byte length CBW
* declares up front, not carried in the CDB itself).
* declares up front, not carried in the CDB itself). num_blocks*block_size
* must fit in dev->bot_data_buf (512 bytes, this increment's whole scope
* -- see xhci_dev_t's own doc comment) -- refuses otherwise.
*
* This covers CBW construction and send only, one third of a full
* READ(10) (CBW -> Data-In stage -> CSW) -- reading the Data-In stage and
* validating/receiving the CSW are separate, not-yet-implemented steps
* (2g's own punch list). Does not wait for or read the resulting Transfer
* Event -- it arrives asynchronously via xhci_poll_events(), correlated
* via dev->transfer_purpose == XHCI_XFER_CBW_SENT, same pattern as every
* other transfer in this driver.
* Does not wait for or read any of the three stages' Transfer Events --
* they arrive asynchronously via xhci_poll_events(), correlated via
* dev->transfer_purpose, same pattern as every other transfer in this
* driver. The final result (CSW signature/tag/status validated) is only
* ever logged, not returned to any caller -- there is no synchronous
* "did the read succeed" API yet; that's 2h's problem once something
* actually needs the data back.
*
* Requires bulk_out_ep_addr/bulk_out_ring to already be populated (2f/2g's
* config descriptor walk and Configure Endpoint command) -- refuses if
* either prerequisite is missing.
* Requires bulk_out_ep_addr/bulk_out_ring and bulk_in_ep_addr/
* bulk_in_ring to already be populated (2f/2g's config descriptor walk
* and Configure Endpoint command) -- refuses if any prerequisite is
* missing.
*
* Returns 0 if the CBW was posted, -1 if a prerequisite is missing.
* Returns 0 if the CBW was posted, -1 if a prerequisite is missing or
* the requested transfer size exceeds dev->bot_data_buf.
*/
int xhci_bot_send_read10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size);
/*
* xhci_bot_read_data_in — submit a Normal TRB on the bulk IN Transfer
* Ring to read dev->bot_expected_data_len bytes
* into dev->bot_data_buf.
*
* Called from xhci_poll_events()'s deferred next_action dispatch once a
* CBW's own Command completion (XHCI_XFER_CBW_SENT) succeeds -- not
* called directly by other code.
*
* Returns 0 if the TRB was posted, -1 if bulk_in_ring isn't set up.
*/
int xhci_bot_read_data_in(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_receive_csw — submit a Normal TRB on the bulk IN Transfer Ring
* to read the 13-byte Command Status Wrapper into
* dev->bot_csw.
*
* Called from xhci_poll_events()'s deferred next_action dispatch once the
* Data-In stage's own Transfer Event (XHCI_XFER_BOT_DATA_IN) succeeds --
* not called directly by other code. The CSW's own completion
* (XHCI_XFER_CSW_RECEIVED) is where signature/tag/status validation
* against dev->bot_last_tag actually happens, in xhci_poll_events()
* itself, not here.
*
* Returns 0 if the TRB was posted, -1 if bulk_in_ring isn't set up.
*/
int xhci_bot_receive_csw(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_ep0_get_device_descriptor — issue a standard GET_DESCRIPTOR
* (Device) control transfer (Setup +