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:
co-authored by
Claude Sonnet 5
parent
92ce1f85dd
commit
a88c004ecb
@@ -130,6 +130,8 @@ int xhci_cmd_disable_slot(xhci_dev_t *dev, uint32_t slot_id);
|
||||
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
||||
uint32_t port_id, uint32_t speed);
|
||||
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_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id);
|
||||
int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t length);
|
||||
int xhci_ep0_set_configuration(xhci_dev_t *dev, uint32_t slot_id, uint8_t config_value);
|
||||
@@ -306,6 +308,7 @@ int xhci_bringup(xhci_dev_t *dev)
|
||||
dev->bulk_out_ring = NULL;
|
||||
dev->bulk_out_ring_cycle = 1;
|
||||
dev->bulk_out_ring_enq = 0;
|
||||
dev->bot_next_tag = 1;
|
||||
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
||||
dev->next_action_slot_id = 0;
|
||||
dev->next_action_length = 0;
|
||||
@@ -618,6 +621,90 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Enqueue one Normal TRB to the bulk OUT Transfer Ring -- same fixed-
|
||||
* ring-plus-Link-TRB wraparound pattern as xhci_ep0_enqueue_trb(),
|
||||
* operating on bulk_out_ring/bulk_out_ring_enq/bulk_out_ring_cycle
|
||||
* instead of the EP0 ring's fields. A CBW is always exactly one TRB
|
||||
* (no Setup/Data/Status split -- that's a control-transfer-only
|
||||
* concept), so unlike xhci_ep0_enqueue_trb() this rings the doorbell
|
||||
* itself rather than leaving that to a caller assembling a group. */
|
||||
static void xhci_bulk_out_enqueue_and_ring(xhci_dev_t *dev, uint32_t slot_id,
|
||||
uint64_t parameter, uint32_t status,
|
||||
uint32_t control_bits)
|
||||
{
|
||||
xhci_trb_t *trb = &dev->bulk_out_ring[dev->bulk_out_ring_enq];
|
||||
trb->parameter = parameter;
|
||||
trb->status = status;
|
||||
trb->control = control_bits | (dev->bulk_out_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
||||
|
||||
dev->bulk_out_ring_enq++;
|
||||
if (dev->bulk_out_ring_enq == XHCI_RING_TRB_COUNT - 1) {
|
||||
dev->bulk_out_ring[XHCI_RING_TRB_COUNT - 1].control =
|
||||
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
||||
XHCI_TRB_CONTROL_TC |
|
||||
(dev->bulk_out_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
||||
dev->bulk_out_ring_enq = 0;
|
||||
dev->bulk_out_ring_cycle ^= 1u;
|
||||
}
|
||||
|
||||
/* Doorbell target is the bulk OUT endpoint's own DCI, not target 1
|
||||
* (EP0) -- distinct rings need distinct doorbell targets so the
|
||||
* controller knows which Transfer Ring just gained a new TRB. */
|
||||
dev->doorbell[slot_id] = XHCI_DB_TARGET(XHCI_EP_ADDR_TO_DCI(dev->bulk_out_ep_addr));
|
||||
}
|
||||
|
||||
int xhci_bot_send_read10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
|
||||
uint16_t num_blocks, uint32_t block_size)
|
||||
{
|
||||
if (!dev || !dev->bulk_out_ring) return -1;
|
||||
if (dev->bulk_out_ep_addr == 0) return -1;
|
||||
|
||||
usb_bot_cbw_t *cbw = &dev->bot_cbw;
|
||||
cbw->dCBWSignature = USB_BOT_CBW_SIGNATURE;
|
||||
cbw->dCBWTag = dev->bot_next_tag++;
|
||||
cbw->dCBWDataTransferLength = (uint32_t)num_blocks * block_size;
|
||||
cbw->bmCBWFlags = USB_BOT_CBW_FLAG_DATA_IN; /* READ(10): device -> host data stage */
|
||||
cbw->bCBWLUN = USB_BOT_CBW_LUN_DEFAULT;
|
||||
cbw->bCBWCBLength = SCSI_CDB_LEN_READ10;
|
||||
for (uint32_t i = 0; i < sizeof(cbw->CBWCB); i++) cbw->CBWCB[i] = 0;
|
||||
|
||||
/* SCSI READ(10) CDB (SBC-3 section 5.13): opcode, then LBA and
|
||||
* Transfer Length as big-endian fields -- SCSI multi-byte fields are
|
||||
* big-endian on the wire regardless of host or USB byte order, unlike
|
||||
* every other multi-byte value in this driver (TRBs, contexts, CBW
|
||||
* itself), which are all little-endian. Written byte-by-byte rather
|
||||
* than via a struct + byte-swap helper, matching this codebase's
|
||||
* existing preference for explicit field layout over struct-based
|
||||
* binary formats wherever the layout isn't naturally what a C struct
|
||||
* would produce (see usb_bot_cbw_t's own doc comment, and the
|
||||
* Interface/Endpoint descriptor offset macros). */
|
||||
cbw->CBWCB[0] = SCSI_CMD_READ10;
|
||||
cbw->CBWCB[1] = 0; /* flags: no FUA/DPO/RDPROTECT for this increment */
|
||||
cbw->CBWCB[2] = (uint8_t)(lba >> 24);
|
||||
cbw->CBWCB[3] = (uint8_t)(lba >> 16);
|
||||
cbw->CBWCB[4] = (uint8_t)(lba >> 8);
|
||||
cbw->CBWCB[5] = (uint8_t)(lba);
|
||||
cbw->CBWCB[6] = 0; /* group number */
|
||||
cbw->CBWCB[7] = (uint8_t)(num_blocks >> 8);
|
||||
cbw->CBWCB[8] = (uint8_t)(num_blocks);
|
||||
cbw->CBWCB[9] = 0; /* control */
|
||||
|
||||
dev->transfer_purpose = XHCI_XFER_CBW_SENT;
|
||||
dev->pending_transfer_slot_id = slot_id;
|
||||
|
||||
/* IOC set -- the CBW is always exactly one TRB, so it alone signals
|
||||
* "this transfer is done" (matching the EP0 control-read pattern of
|
||||
* IOC on the one TRB whose completion means something). Length is
|
||||
* USB_BOT_CBW_LENGTH (31), not sizeof(*cbw) -- see usb_bot_cbw_t's
|
||||
* own doc comment on why. */
|
||||
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 (READ10) submitted");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Enqueue one TRB to the EP0 Transfer Ring without ringing the doorbell
|
||||
* -- Setup/Data/Status stage TRBs are enqueued as a group, then the
|
||||
* doorbell is rung once after all three are posted, matching how a real
|
||||
@@ -1131,6 +1218,15 @@ void xhci_poll_events(void)
|
||||
console_println("xhci: device configured");
|
||||
break;
|
||||
}
|
||||
case XHCI_XFER_CBW_SENT: {
|
||||
/* CBW send is confirmed done; the Data-In
|
||||
* stage and CSW receive/validation are 2g's
|
||||
* next items, not implemented yet -- nothing
|
||||
* further chains from here in this
|
||||
* increment. */
|
||||
console_println("xhci: CBW send completed");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console_println("xhci: transfer event");
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user