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:
Robert Allan James
2026-08-25 07:27:07 -04:00
co-authored by Claude Sonnet 5
parent 2c34e45d05
commit b9c540a78b
22 changed files with 101974 additions and 52 deletions
+24
View File
@@ -231,8 +231,32 @@ typedef struct {
#define USB_REQ_GET_DESCRIPTOR 6u
#define USB_DESC_TYPE_DEVICE 1u
#define USB_DESC_TYPE_CONFIG 2u
#define USB_DIR_DEVICE_TO_HOST 0x80u
/* Standard USB Interface descriptor field offsets (9 bytes, USB 2.0 spec
* table 9-12) -- Mass Storage class detection reads these three fields.
* Not decoded via a struct like usb_setup_packet_t: the Interface
* descriptor's exact position within a Configuration descriptor's byte
* stream isn't fixed (depends on the device's actual interface/endpoint
* layout), so it's found by walking the byte stream looking for
* bDescriptorType == USB_DESC_TYPE_INTERFACE, not by a fixed struct
* offset into the whole buffer. */
/* Every standard USB descriptor starts with these two bytes (bLength,
* bDescriptorType) -- used to walk the concatenated descriptor stream a
* full Configuration descriptor read returns (Config + Interface +
* Endpoint descriptors back to back), not just the Interface one. */
#define USB_DESC_OFF_LENGTH 0u
#define USB_DESC_OFF_TYPE 1u
#define USB_CONFIG_OFF_TOTAL_LENGTH 2u /* wTotalLength, 2 bytes, Configuration descriptor only */
#define USB_DESC_TYPE_INTERFACE 4u
#define USB_IFACE_OFF_CLASS 5u
#define USB_IFACE_OFF_SUBCLASS 6u
#define USB_IFACE_OFF_PROTOCOL 7u
#define USB_CLASS_MASS_STORAGE 0x08u
#define USB_SUBCLASS_SCSI 0x06u /* SCSI transparent command set */
#define USB_PROTOCOL_BOT 0x50u /* Bulk-Only Transport */
/* 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
+65 -10
View File
@@ -83,11 +83,47 @@ typedef struct {
* driver only ever has one control transfer outstanding at a time --
* pending_transfer_slot_id is 0 when idle, else the slot ID whose
* Transfer Event (posted only by the Status Stage TRB, which alone
* has IOC set) is still outstanding. device_descriptor is the
* (reused, not per-slot) buffer control transfers read into; 18
* bytes is the full standard USB device descriptor size. */
* has IOC set) is still outstanding. transfer_purpose says which
* request that is, since xhci_poll_events() needs to know which
* buffer to interpret and what (if anything) to chain next on
* success -- e.g. a successful short Configuration descriptor read
* chains into a full-length read once wTotalLength is known.
* device_descriptor is the full 18-byte standard USB device
* descriptor; config_descriptor holds the Configuration descriptor
* and everything after it in the same read (Interface + Endpoint
* descriptors, concatenated, per USB spec) -- fixed 128 bytes,
* comfortably covers a single-interface Mass Storage device's full
* descriptor set without a dynamic allocation. All reused (not
* per-slot), matching this driver's single-device scope. */
enum {
XHCI_XFER_NONE = 0,
XHCI_XFER_DEVICE_DESC,
XHCI_XFER_CONFIG_DESC_SHORT,
XHCI_XFER_CONFIG_DESC_FULL
} transfer_purpose;
uint32_t pending_transfer_slot_id;
uint8_t device_descriptor[18];
uint8_t config_descriptor[128];
uint16_t config_total_length;
/* 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
* currently being handled -- confirmed live (amd64 QEMU) to hang the
* guest outright when tried (a doorbell rung mid-acknowledgment of
* the previous event, evidenced by checkpoint logging showing
* execution stop exactly at the doorbell MMIO write). Chained
* requests (device descriptor -> short config read -> full config
* read) instead set these fields during event processing; the actual
* doorbell ring happens once, after the main loop and the ERDP
* write, from a small dispatch at the end of xhci_poll_events(). */
enum {
XHCI_NEXT_ACTION_NONE = 0,
XHCI_NEXT_ACTION_GET_DEVICE_DESC,
XHCI_NEXT_ACTION_GET_CONFIG_DESC
} next_action;
uint32_t next_action_slot_id;
uint16_t next_action_length;
} xhci_dev_t;
/*
@@ -197,19 +233,38 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
* Data-IN + Status-OUT stages) on
* slot_id's EP0, reading the 18-byte
* result into dev->device_descriptor.
* Does not wait for completion -- the
* result arrives asynchronously via
* xhci_poll_events()'s Transfer Event
* handling, which currently only logs
* success/failure (parsing the fields
* is the next increment).
* Sets dev->transfer_purpose so
* xhci_poll_events() knows how to
* interpret the completion.
*
* Called once Address Device succeeds -- not called directly by other
* code yet (Milestone 2f is still in progress).
* code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not
* set up.
*/
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_ep0_get_config_descriptor — issue a GET_DESCRIPTOR (Configuration)
* control transfer for `length` bytes,
* reading into dev->config_descriptor
* (capped to its fixed size). Used
* twice per device: once for a short
* 9-byte read (just the Configuration
* descriptor header, to learn
* wTotalLength) and once for the full
* read once that length is known --
* xhci_poll_events() chains the second
* call automatically on the first
* read's success.
*
* Called once the device descriptor read succeeds -- not called directly
* by other code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not
* set up.
*/
int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t length);
#endif /* STARKERNEL_XHCI_DRIVER_H */