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
+109 -6
View File
@@ -133,6 +133,8 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id);
int xhci_bot_send_read10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size);
int xhci_bot_send_test_unit_ready(xhci_dev_t *dev, uint32_t slot_id);
int xhci_bot_send_read_capacity10(xhci_dev_t *dev, uint32_t slot_id);
int xhci_bot_get_capacity(xhci_dev_t *dev, uint32_t slot_id);
int xhci_bot_read_data_in(xhci_dev_t *dev, uint32_t slot_id);
int xhci_bot_receive_csw(xhci_dev_t *dev, uint32_t slot_id);
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id);
@@ -315,10 +317,14 @@ int xhci_bringup(xhci_dev_t *dev)
dev->bot_last_tag = 0;
dev->bot_expected_data_len = 0;
dev->bot_cmd_kind = BOT_CMD_NONE;
dev->bot_last_status = BOT_STATUS_IDLE;
dev->bot_tur_chain_target = BOT_TUR_CHAIN_NONE;
dev->bot_tur_retries = 0;
dev->bot_read10_lba = 0;
dev->bot_read10_num_blocks = 0;
dev->bot_read10_block_size = 0;
dev->bot_cap_last_lba = 0;
dev->bot_cap_block_size = 0;
dev->next_action = XHCI_NEXT_ACTION_NONE;
dev->next_action_slot_id = 0;
dev->next_action_length = 0;
@@ -337,6 +343,11 @@ int xhci_bringup(xhci_dev_t *dev)
return 0;
}
xhci_dev_t *xhci_get_dev(void)
{
return g_xhci_dev;
}
/* -------------------------------------------------------------------------
* Command Ring submission -- Milestone 2e. Shared by Enable Slot now and
* Address Device next; xhci_poll_events() above is the read side of this
@@ -752,6 +763,40 @@ int xhci_bot_send_test_unit_ready(xhci_dev_t *dev, uint32_t slot_id)
return 0;
}
int xhci_bot_send_read_capacity10(xhci_dev_t *dev, uint32_t slot_id)
{
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
dev->bot_cmd_kind = BOT_CMD_READ_CAPACITY10;
usb_bot_cbw_t *cbw = &dev->bot_cbw;
cbw->dCBWSignature = USB_BOT_CBW_SIGNATURE;
cbw->dCBWTag = dev->bot_next_tag++;
dev->bot_last_tag = cbw->dCBWTag;
dev->bot_expected_data_len = SCSI_READ_CAPACITY10_DATA_LEN;
cbw->dCBWDataTransferLength = SCSI_READ_CAPACITY10_DATA_LEN;
cbw->bmCBWFlags = USB_BOT_CBW_FLAG_DATA_IN; /* READ CAPACITY(10): device -> host data stage */
cbw->bCBWLUN = USB_BOT_CBW_LUN_DEFAULT;
cbw->bCBWCBLength = SCSI_CDB_LEN_READ_CAPACITY10;
for (uint32_t i = 0; i < sizeof(cbw->CBWCB); i++) cbw->CBWCB[i] = 0;
cbw->CBWCB[0] = SCSI_CMD_READ_CAPACITY10; /* opcode 0x25, standard "report
* capacity" form -- LBA field
* and PMI bit left zero, see
* this constant's own doc
* comment in xhci.h */
dev->transfer_purpose = XHCI_XFER_CBW_SENT;
dev->pending_transfer_slot_id = slot_id;
xhci_bulk_out_enqueue_and_ring(dev, slot_id, (uint64_t)(uintptr_t)cbw,
USB_BOT_CBW_LENGTH,
(XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IOC);
console_println("xhci: CBW (READ CAPACITY10) submitted");
return 0;
}
int xhci_bot_read_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size)
{
@@ -762,11 +807,33 @@ int xhci_bot_read_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
dev->bot_read10_lba = lba;
dev->bot_read10_num_blocks = num_blocks;
dev->bot_read10_block_size = block_size;
dev->bot_tur_chain_target = BOT_TUR_CHAIN_READ10;
dev->bot_tur_retries = 0;
return xhci_bot_send_test_unit_ready(dev, slot_id);
}
int xhci_bot_get_capacity(xhci_dev_t *dev, uint32_t slot_id)
{
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
dev->bot_tur_chain_target = BOT_TUR_CHAIN_READ_CAPACITY10;
dev->bot_tur_retries = 0;
return xhci_bot_send_test_unit_ready(dev, slot_id);
}
int xhci_bot_wait_for_idle(xhci_dev_t *dev, uint32_t max_iters)
{
if (!dev) return BOT_STATUS_FAILED;
for (uint32_t i = 0; i < max_iters; i++) {
if (dev->bot_cmd_kind == BOT_CMD_NONE) return (int)dev->bot_last_status;
xhci_poll_events();
}
return BOT_STATUS_TIMEOUT;
}
/* Enqueue one Normal TRB to the bulk IN Transfer Ring and ring its
* doorbell -- same shape as xhci_bulk_out_enqueue_and_ring() (a BOT
* Data-In or CSW read is, like a CBW send, always exactly one TRB), just
@@ -1371,9 +1438,13 @@ void xhci_poll_events(void)
* misread as a clean pass. */
if (dev->bot_csw.dCSWSignature != USB_BOT_CSW_SIGNATURE) {
console_println("xhci: CSW signature mismatch -- discarding");
dev->bot_last_status = BOT_STATUS_FAILED;
dev->bot_cmd_kind = BOT_CMD_NONE;
break;
} else if (dev->bot_csw.dCSWTag != dev->bot_last_tag) {
console_println("xhci: CSW tag mismatch -- discarding");
dev->bot_last_status = BOT_STATUS_FAILED;
dev->bot_cmd_kind = BOT_CMD_NONE;
break;
}
@@ -1393,13 +1464,20 @@ void xhci_poll_events(void)
* target's standard first-command UNIT
* ATTENTION behavior, not a driver defect (see
* SCSI_CMD_TEST_UNIT_READY's doc comment in
* xhci.h). READ10 completions are terminal --
* this driver has no synchronous caller to
* report back to yet (2h's problem). */
* xhci.h). Every other command kind is
* terminal here: bot_last_status/bot_cmd_kind
* reset so a synchronous caller waiting in
* xhci_bot_wait_for_idle() (Milestone 2h) sees
* the command as finished. */
if (dev->bot_cmd_kind == BOT_CMD_TEST_UNIT_READY) {
if (csw_pass) {
console_println("xhci: unit ready -- issuing READ10");
dev->next_action = XHCI_NEXT_ACTION_BOT_SEND_READ10;
if (dev->bot_tur_chain_target == BOT_TUR_CHAIN_READ_CAPACITY10) {
console_println("xhci: unit ready -- issuing READ CAPACITY10");
dev->next_action = XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10;
} else {
console_println("xhci: unit ready -- issuing READ10");
dev->next_action = XHCI_NEXT_ACTION_BOT_SEND_READ10;
}
dev->next_action_slot_id = xfer_slot_id;
} else if (dev->bot_tur_retries < XHCI_BOT_TUR_MAX_RETRIES) {
dev->bot_tur_retries++;
@@ -1407,8 +1485,27 @@ void xhci_poll_events(void)
dev->next_action = XHCI_NEXT_ACTION_BOT_SEND_TUR;
dev->next_action_slot_id = xfer_slot_id;
} else {
console_println("xhci: unit still not ready -- giving up on READ10");
console_println("xhci: unit still not ready -- giving up");
dev->bot_last_status = BOT_STATUS_FAILED;
dev->bot_cmd_kind = BOT_CMD_NONE;
}
} else if (dev->bot_cmd_kind == BOT_CMD_READ_CAPACITY10) {
if (csw_pass) {
const uint8_t *d = dev->bot_data_buf;
dev->bot_cap_last_lba = ((uint32_t)d[0] << 24) | ((uint32_t)d[1] << 16) |
((uint32_t)d[2] << 8) | (uint32_t)d[3];
dev->bot_cap_block_size = ((uint32_t)d[4] << 24) | ((uint32_t)d[5] << 16) |
((uint32_t)d[6] << 8) | (uint32_t)d[7];
xhci_log_hex32("xhci: READ CAPACITY last LBA=", dev->bot_cap_last_lba);
xhci_log_hex32("xhci: READ CAPACITY block size=", dev->bot_cap_block_size);
}
dev->bot_last_status = csw_pass ? BOT_STATUS_PASS : BOT_STATUS_FAILED;
dev->bot_cmd_kind = BOT_CMD_NONE;
} else {
/* BOT_CMD_READ10 (BOT_CMD_NONE shouldn't
* reach here) -- terminal either way. */
dev->bot_last_status = csw_pass ? BOT_STATUS_PASS : BOT_STATUS_FAILED;
dev->bot_cmd_kind = BOT_CMD_NONE;
}
break;
}
@@ -1502,5 +1599,11 @@ void xhci_poll_events(void)
dev->bot_read10_block_size) != 0) {
console_println("xhci: deferred READ10 setup failed");
}
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10) {
uint32_t next_slot_id = dev->next_action_slot_id;
dev->next_action = XHCI_NEXT_ACTION_NONE;
if (xhci_bot_send_read_capacity10(dev, next_slot_id) != 0) {
console_println("xhci: deferred READ CAPACITY10 setup failed");
}
}
}