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
+31
View File
@@ -190,6 +190,7 @@ typedef struct {
#define XHCI_TRB_TYPE(ctrl) (((ctrl) & XHCI_TRB_CONTROL_TYPE_MASK) >> XHCI_TRB_CONTROL_TYPE_SHIFT)
/* TRB types used by this driver (subset — xHCI defines many more) */
#define XHCI_TRB_TYPE_NORMAL 1 /* Transfer Ring, bulk/interrupt/isoch -- not EP0 */
#define XHCI_TRB_TYPE_LINK 6 /* ring-wraparound marker, Command/Transfer Rings only */
#define XHCI_TRB_TYPE_ENABLE_SLOT_CMD 9
#define XHCI_TRB_TYPE_DISABLE_SLOT_CMD 10
@@ -275,6 +276,36 @@ typedef struct {
#define USB_EP_ATTR_TYPE_MASK 0x03u
#define USB_EP_TYPE_BULK 0x02u
/* Bulk-Only Transport Command Block Wrapper (USB Mass Storage Class Bulk-
* Only Transport spec, section 5.1) -- sent host-to-device on the bulk OUT
* endpoint ahead of every SCSI command's data phase. Fixed 31-byte wire
* layout; every multi-byte field is little-endian, which this driver's
* targets (amd64/aarch64/riscv64, all little-endian) already assume
* throughout (no htole32-style conversions anywhere in this codebase) --
* direct field assignment is wire-correct as-is. sizeof() is NOT used as
* this struct's DMA length anywhere (may be padded to 32 by the compiler
* to satisfy the uint32_t members' alignment) -- USB_BOT_CBW_LENGTH (31)
* is the correct, explicit wire length, matching the same
* offset-constant-not-sizeof discipline already used for the USB
* descriptor field offsets above. */
typedef struct {
uint32_t dCBWSignature;
uint32_t dCBWTag;
uint32_t dCBWDataTransferLength;
uint8_t bmCBWFlags;
uint8_t bCBWLUN;
uint8_t bCBWCBLength;
uint8_t CBWCB[16];
} usb_bot_cbw_t;
#define USB_BOT_CBW_SIGNATURE 0x43425355u /* "USBC", wire byte order U,S,B,C as an LE dword */
#define USB_BOT_CBW_LENGTH 31u
#define USB_BOT_CBW_FLAG_DATA_IN 0x80u /* bmCBWFlags: device-to-host data stage */
#define USB_BOT_CBW_LUN_DEFAULT 0u /* no multi-LUN support -- single-LUN devices only */
#define SCSI_CMD_READ10 0x28u
#define SCSI_CDB_LEN_READ10 10u
/* 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
+42 -1
View File
@@ -113,7 +113,8 @@ typedef struct {
XHCI_XFER_DEVICE_DESC,
XHCI_XFER_CONFIG_DESC_SHORT,
XHCI_XFER_CONFIG_DESC_FULL,
XHCI_XFER_SET_CONFIG
XHCI_XFER_SET_CONFIG,
XHCI_XFER_CBW_SENT
} transfer_purpose;
uint32_t pending_transfer_slot_id;
uint8_t device_descriptor[18];
@@ -148,6 +149,18 @@ 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. */
usb_bot_cbw_t bot_cbw;
uint32_t bot_next_tag;
/* Deferred chaining: a doorbell ring (new control transfer) must
* never happen synchronously from inside xhci_poll_events()'s event-
* processing loop, before ERDP has been updated for the event
@@ -323,6 +336,34 @@ 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_bot_send_read10 — build a Command Block Wrapper for a SCSI
* READ(10) and submit it on the bulk OUT Transfer
* Ring.
*
* 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).
*
* 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.
*
* 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.
*
* Returns 0 if the CBW was posted, -1 if a prerequisite is missing.
*/
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_ep0_get_device_descriptor — issue a standard GET_DESCRIPTOR
* (Device) control transfer (Setup +