Artemis Milestone 2f: Configuration descriptor read + Mass Storage/BOT class confirmation
Chains off the device descriptor request via a new deferred-action mechanism on xhci_dev_t (next_action/next_action_slot_id/next_action_length): a short 9-byte Configuration descriptor read learns wTotalLength, then a full read retrieves Config+Interface+Endpoint descriptors, walked for the Interface descriptor to confirm bInterfaceClass/SubClass/Protocol == Mass Storage/ SCSI/Bulk-Only Transport. The deferral exists because ringing the next doorbell synchronously inside xhci_poll_events()'s event-processing loop -- before the current event's ERDP write -- hung the guest outright (confirmed live via checkpoint logging, amd64). Fixed by moving the actual control-transfer submission to a small dispatch at the end of xhci_poll_events(), after ERDP is updated. A debug hack that shipped mid-session (forcing a repeated 9-byte read instead of chaining into the real 44-byte length, to isolate whether the hang was doorbell-ordering or length-specific) has been reverted: restored the real length and re-verified live. The doorbell-ordering fix was the whole story -- the 44-byte read completes cleanly. Verified live via QMP hotplug, all three architectures, byte-identical results: wTotalLength=0x2c, bInterfaceClass=0x08, bInterfaceSubClass=0x06, bInterfaceProtocol=0x50 -- confirmed Mass Storage/SCSI/BOT. Disconnect confirmed clean on every arch, no wedge. FABRIC-2.md Section X Milestone 2f updated with the full writeup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPfdtaXs9ay1nbwuMnrscu
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2c34e45d05
commit
b9c540a78b
+176
-37
@@ -129,6 +129,7 @@ int xhci_cmd_enable_slot(xhci_dev_t *dev);
|
||||
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
||||
uint32_t port_id, uint32_t speed);
|
||||
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_bringup(xhci_dev_t *dev)
|
||||
{
|
||||
@@ -289,6 +290,11 @@ int xhci_bringup(xhci_dev_t *dev)
|
||||
dev->ep0_ring_cycle = 1;
|
||||
dev->ep0_ring_enq = 0;
|
||||
dev->pending_transfer_slot_id = 0;
|
||||
dev->transfer_purpose = XHCI_XFER_NONE;
|
||||
dev->config_total_length = 0;
|
||||
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
||||
dev->next_action_slot_id = 0;
|
||||
dev->next_action_length = 0;
|
||||
|
||||
console_println("xhci: controller running");
|
||||
/* Milestone 2e prep: HCCPARAMS1.CSZ decides 32- vs 64-byte Slot/
|
||||
@@ -476,21 +482,25 @@ static void xhci_ep0_enqueue_trb(xhci_dev_t *dev, uint64_t parameter,
|
||||
}
|
||||
}
|
||||
|
||||
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id)
|
||||
/* Shared submission for any "device-to-host, standard, device recipient,
|
||||
* IN data stage" control read -- both GET_DESCRIPTOR(Device) and
|
||||
* GET_DESCRIPTOR(Configuration) are this same shape, differing only in
|
||||
* wValue/wLength/destination buffer. Does not set dev->transfer_purpose
|
||||
* or dev->pending_transfer_slot_id -- callers do that themselves so the
|
||||
* purpose is set before the doorbell rings (avoids a window where a
|
||||
* stray Transfer Event could be misread against a not-yet-set purpose,
|
||||
* even though this driver is polled and that window can't actually be
|
||||
* hit by anything external in practice). */
|
||||
static void xhci_ep0_control_read(xhci_dev_t *dev, uint8_t bRequest,
|
||||
uint16_t wValue, uint16_t wIndex,
|
||||
uint8_t *buf, uint16_t len)
|
||||
{
|
||||
if (!dev || !dev->ep0_ring) return -1;
|
||||
|
||||
/* Standard GET_DESCRIPTOR(Device) request (USB 2.0 spec section
|
||||
* 9.4.3): device-to-host, standard, device recipient; wValue high
|
||||
* byte selects descriptor type, low byte the index (0 for the one
|
||||
* Device descriptor); wLength 18 = the full standard Device
|
||||
* descriptor size. */
|
||||
usb_setup_packet_t setup = {
|
||||
.bmRequestType = USB_DIR_DEVICE_TO_HOST,
|
||||
.bRequest = USB_REQ_GET_DESCRIPTOR,
|
||||
.wValue = (uint16_t)(USB_DESC_TYPE_DEVICE << 8),
|
||||
.wIndex = 0,
|
||||
.wLength = sizeof(dev->device_descriptor)
|
||||
.bRequest = bRequest,
|
||||
.wValue = wValue,
|
||||
.wIndex = wIndex,
|
||||
.wLength = len
|
||||
};
|
||||
uint64_t setup_bits;
|
||||
memcpy(&setup_bits, &setup, sizeof(setup_bits));
|
||||
@@ -503,10 +513,9 @@ int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id)
|
||||
(XHCI_SETUP_TRT_IN_DATA << XHCI_TRB_CONTROL_TRT_SHIFT));
|
||||
|
||||
/* Data Stage: parameter is a real pointer here (not immediate) --
|
||||
* points at the reused device_descriptor buffer. DIR=IN matches the
|
||||
* Setup Stage's TRT. */
|
||||
xhci_ep0_enqueue_trb(dev, (uint64_t)(uintptr_t)dev->device_descriptor,
|
||||
(uint32_t)sizeof(dev->device_descriptor),
|
||||
* points at the caller's buffer. DIR=IN matches the Setup Stage's
|
||||
* TRT. */
|
||||
xhci_ep0_enqueue_trb(dev, (uint64_t)(uintptr_t)buf, len,
|
||||
(XHCI_TRB_TYPE_DATA_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
||||
XHCI_TRB_CONTROL_DIR_IN);
|
||||
|
||||
@@ -517,6 +526,16 @@ int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id)
|
||||
xhci_ep0_enqueue_trb(dev, 0, 0,
|
||||
(XHCI_TRB_TYPE_STATUS_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
||||
XHCI_TRB_CONTROL_IOC);
|
||||
}
|
||||
|
||||
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id)
|
||||
{
|
||||
if (!dev || !dev->ep0_ring) return -1;
|
||||
|
||||
dev->transfer_purpose = XHCI_XFER_DEVICE_DESC;
|
||||
xhci_ep0_control_read(dev, USB_REQ_GET_DESCRIPTOR,
|
||||
(uint16_t)(USB_DESC_TYPE_DEVICE << 8), 0,
|
||||
dev->device_descriptor, sizeof(dev->device_descriptor));
|
||||
|
||||
dev->pending_transfer_slot_id = slot_id;
|
||||
/* Doorbell Array is indexed by slot ID; target 1 = Default Control
|
||||
@@ -528,6 +547,31 @@ int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t length)
|
||||
{
|
||||
if (!dev || !dev->ep0_ring) return -1;
|
||||
|
||||
/* Cap to the fixed buffer size -- a device whose real Configuration
|
||||
* descriptor set exceeds this would be truncated, not overflowed;
|
||||
* 128 bytes comfortably covers a single-interface Mass Storage
|
||||
* device (Config 9 + Interface 9 + 2 Endpoints * 7 = 32 bytes
|
||||
* typical), so this is a defensive cap, not an expected path. */
|
||||
if (length > sizeof(dev->config_descriptor)) {
|
||||
length = (uint16_t)sizeof(dev->config_descriptor);
|
||||
}
|
||||
|
||||
dev->transfer_purpose = (length <= 9) ? XHCI_XFER_CONFIG_DESC_SHORT
|
||||
: XHCI_XFER_CONFIG_DESC_FULL;
|
||||
xhci_ep0_control_read(dev, USB_REQ_GET_DESCRIPTOR,
|
||||
(uint16_t)(USB_DESC_TYPE_CONFIG << 8), 0,
|
||||
dev->config_descriptor, length);
|
||||
|
||||
dev->pending_transfer_slot_id = slot_id;
|
||||
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
|
||||
console_println("xhci: get config descriptor submitted");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Milestone 2d: Event Ring servicing, polled from sk_repl_idle().
|
||||
*
|
||||
@@ -659,10 +703,11 @@ void xhci_poll_events(void)
|
||||
console_println("xhci: address device succeeded");
|
||||
/* Milestone 2f: enumeration starts here -- the
|
||||
* device now has a USB address and EP0 is
|
||||
* usable for control transfers. */
|
||||
if (xhci_ep0_get_device_descriptor(dev, dev->pending_connect_slot_id) != 0) {
|
||||
console_println("xhci: device descriptor request setup failed");
|
||||
}
|
||||
* usable for control transfers. Deferred (see
|
||||
* xhci_dev_t's doc comment on next_action) rather
|
||||
* than called directly here. */
|
||||
dev->next_action = XHCI_NEXT_ACTION_GET_DEVICE_DESC;
|
||||
dev->next_action_slot_id = dev->pending_connect_slot_id;
|
||||
} else {
|
||||
console_println("xhci: address device failed");
|
||||
}
|
||||
@@ -676,24 +721,100 @@ void xhci_poll_events(void)
|
||||
case XHCI_TRB_TYPE_TRANSFER_EVENT: {
|
||||
uint32_t code = XHCI_EVT_COMPLETION_CODE(trb->status);
|
||||
if (dev->pending_transfer_slot_id != 0) {
|
||||
uint32_t xfer_slot_id = dev->pending_transfer_slot_id;
|
||||
uint32_t purpose = dev->transfer_purpose;
|
||||
dev->pending_transfer_slot_id = 0;
|
||||
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
|
||||
console_println("xhci: device descriptor received");
|
||||
/* USB 2.0 spec table 9-8 layout. Logged, not yet
|
||||
* acted on -- 2f's own punch list asks whether
|
||||
* vendor/product IDs are even needed for this
|
||||
* project, or class-only detection suffices;
|
||||
* this surfaces the real values to help decide,
|
||||
* doesn't decide it here. */
|
||||
uint32_t id_vendor = dev->device_descriptor[8] |
|
||||
((uint32_t)dev->device_descriptor[9] << 8);
|
||||
uint32_t id_product = dev->device_descriptor[10] |
|
||||
((uint32_t)dev->device_descriptor[11] << 8);
|
||||
xhci_log_hex32("xhci: idVendor=", id_vendor);
|
||||
xhci_log_hex32("xhci: idProduct=", id_product);
|
||||
xhci_log_hex32("xhci: bDeviceClass=", dev->device_descriptor[4]);
|
||||
} else {
|
||||
console_println("xhci: device descriptor request failed");
|
||||
dev->transfer_purpose = XHCI_XFER_NONE;
|
||||
|
||||
if (code != XHCI_COMPLETION_CODE_SUCCESS) {
|
||||
console_println("xhci: control transfer failed");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (purpose) {
|
||||
case XHCI_XFER_DEVICE_DESC: {
|
||||
console_println("xhci: device descriptor received");
|
||||
/* USB 2.0 spec table 9-8 layout. Logged --
|
||||
* 2f's own punch list asked whether vendor/
|
||||
* product IDs are even needed, or class-only
|
||||
* detection suffices; this surfaces the real
|
||||
* values, doesn't decide it. */
|
||||
uint32_t id_vendor = dev->device_descriptor[8] |
|
||||
((uint32_t)dev->device_descriptor[9] << 8);
|
||||
uint32_t id_product = dev->device_descriptor[10] |
|
||||
((uint32_t)dev->device_descriptor[11] << 8);
|
||||
xhci_log_hex32("xhci: idVendor=", id_vendor);
|
||||
xhci_log_hex32("xhci: idProduct=", id_product);
|
||||
xhci_log_hex32("xhci: bDeviceClass=", dev->device_descriptor[4]);
|
||||
/* Chain: request just the Configuration
|
||||
* descriptor's 9-byte header first, to learn
|
||||
* wTotalLength before requesting everything.
|
||||
* Deferred (see xhci_dev_t's doc comment on
|
||||
* next_action) rather than called directly --
|
||||
* a doorbell rung synchronously here, still
|
||||
* inside this event-processing loop and
|
||||
* before ERDP is updated, hung the guest
|
||||
* outright (confirmed live via checkpoint
|
||||
* logging, amd64 QEMU, 2026-08-22). */
|
||||
dev->next_action = XHCI_NEXT_ACTION_GET_CONFIG_DESC;
|
||||
dev->next_action_slot_id = xfer_slot_id;
|
||||
dev->next_action_length = 9;
|
||||
break;
|
||||
}
|
||||
case XHCI_XFER_CONFIG_DESC_SHORT: {
|
||||
uint16_t total_len = (uint16_t)(dev->config_descriptor[USB_CONFIG_OFF_TOTAL_LENGTH] |
|
||||
((uint16_t)dev->config_descriptor[USB_CONFIG_OFF_TOTAL_LENGTH + 1] << 8));
|
||||
dev->config_total_length = total_len;
|
||||
xhci_log_hex32("xhci: config wTotalLength=", total_len);
|
||||
dev->next_action = XHCI_NEXT_ACTION_GET_CONFIG_DESC;
|
||||
dev->next_action_slot_id = xfer_slot_id;
|
||||
dev->next_action_length = total_len;
|
||||
break;
|
||||
}
|
||||
case XHCI_XFER_CONFIG_DESC_FULL: {
|
||||
console_println("xhci: full config descriptor received");
|
||||
/* Walk the concatenated descriptor stream
|
||||
* (Config + Interface + Endpoint descriptors
|
||||
* back to back) looking for the Interface
|
||||
* descriptor -- its fixed offset within the
|
||||
* stream isn't guaranteed, has to be found by
|
||||
* bDescriptorType, not assumed. */
|
||||
uint16_t len = dev->config_total_length;
|
||||
if (len > sizeof(dev->config_descriptor)) len = (uint16_t)sizeof(dev->config_descriptor);
|
||||
uint16_t off = 0;
|
||||
int found = 0;
|
||||
while (off + 2 <= len) {
|
||||
uint8_t desc_len = dev->config_descriptor[off + USB_DESC_OFF_LENGTH];
|
||||
uint8_t desc_type = dev->config_descriptor[off + USB_DESC_OFF_TYPE];
|
||||
if (desc_len == 0) break; /* malformed -- avoid an infinite loop */
|
||||
if (desc_type == USB_DESC_TYPE_INTERFACE &&
|
||||
off + USB_IFACE_OFF_PROTOCOL < len) {
|
||||
uint8_t iface_class = dev->config_descriptor[off + USB_IFACE_OFF_CLASS];
|
||||
uint8_t iface_subclass = dev->config_descriptor[off + USB_IFACE_OFF_SUBCLASS];
|
||||
uint8_t iface_protocol = dev->config_descriptor[off + USB_IFACE_OFF_PROTOCOL];
|
||||
xhci_log_hex32("xhci: bInterfaceClass=", iface_class);
|
||||
xhci_log_hex32("xhci: bInterfaceSubClass=", iface_subclass);
|
||||
xhci_log_hex32("xhci: bInterfaceProtocol=", iface_protocol);
|
||||
if (iface_class == USB_CLASS_MASS_STORAGE &&
|
||||
iface_subclass == USB_SUBCLASS_SCSI &&
|
||||
iface_protocol == USB_PROTOCOL_BOT) {
|
||||
console_println("xhci: confirmed Mass Storage / SCSI / BOT device");
|
||||
} else {
|
||||
console_println("xhci: not a Mass Storage/SCSI/BOT device -- not usable as a drive");
|
||||
}
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
off = (uint16_t)(off + desc_len);
|
||||
}
|
||||
if (!found) {
|
||||
console_println("xhci: no Interface descriptor found in config set");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console_println("xhci: transfer event");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
console_println("xhci: transfer event");
|
||||
@@ -723,4 +844,22 @@ void xhci_poll_events(void)
|
||||
* have nothing to clear. */
|
||||
dev->intr0->erdp = ((uint64_t)(uintptr_t)&dev->evt_ring[dev->evt_ring_deq]
|
||||
& XHCI_ERDP_PTR_MASK) | XHCI_ERDP_EHB;
|
||||
|
||||
/* Deferred chained request, if event processing above set one --
|
||||
* see xhci_dev_t's own doc comment on why this must happen here,
|
||||
* after ERDP is updated, not synchronously inside the loop above. */
|
||||
if (dev->next_action == XHCI_NEXT_ACTION_GET_DEVICE_DESC) {
|
||||
uint32_t next_slot_id = dev->next_action_slot_id;
|
||||
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
||||
if (xhci_ep0_get_device_descriptor(dev, next_slot_id) != 0) {
|
||||
console_println("xhci: deferred device descriptor request setup failed");
|
||||
}
|
||||
} else if (dev->next_action == XHCI_NEXT_ACTION_GET_CONFIG_DESC) {
|
||||
uint32_t next_slot_id = dev->next_action_slot_id;
|
||||
uint16_t next_length = dev->next_action_length;
|
||||
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
||||
if (xhci_ep0_get_config_descriptor(dev, next_slot_id, next_length) != 0) {
|
||||
console_println("xhci: deferred config descriptor request setup failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user