Artemis Milestone 2f: EP0 control transfer, device descriptor request

Adds Setup/Data/Status stage TRB types and control bits (IDT, TRT, DIR)
to xhci.h, and xhci_ep0_enqueue_trb()/xhci_ep0_get_device_descriptor() to
xhci.c -- the first real control transfer this driver has issued.
Follows the same enqueue-then-doorbell-once pattern as the Command Ring,
operating on the EP0 Transfer Ring built during 2e's Address Device work.
Setup Stage uses Immediate Data (parameter IS the 8-byte setup packet);
Data Stage reads into a reused 18-byte device_descriptor buffer; Status
Stage alone carries IOC, so exactly one Transfer Event signals transfer
completion, correlated via a new pending_transfer_slot_id (same
single-outstanding-operation pattern as connect/Enable Slot/Address
Device).

Automatically triggered once Address Device succeeds. Verified live via
QMP hotplug, all three architectures, worked first try with identical
results everywhere: idVendor=0x46f4, idProduct=0x0001, bDeviceClass=0x00
-- the class=0 confirms Mass Storage class detection needs the
Configuration/Interface descriptor (2f's next item), not the device
descriptor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
This commit is contained in:
Robert Allan James
2026-08-22 12:59:49 -04:00
co-authored by Claude Sonnet 5
parent 2e7e957680
commit 2c34e45d05
13 changed files with 36397 additions and 6 deletions
+22 -2
View File
@@ -3323,8 +3323,11 @@ increment's test device was SuperSpeed and self-enabled), disconnect teardown (D
command, DCBAA entry clear), and the callback surface into Section U's higher-level code.
**2f. USB device enumeration (post-connect, before it's usable as storage)**
- [ ] Request and parse the device descriptor (confirm vendor/product IDs are even needed,
or if class-only detection suffices for this project's purposes)
- [x] Request and parse the device descriptor **done 2026-08-22**, see writeup below.
Answered its own open question: `bDeviceClass=0x00` on the real test device (QEMU's
`usb-storage`), meaning class is defined at the interface level, not device level, for
standard Mass Storage devices — confirms the "is this a USB drive" check has to read the
Configuration/Interface descriptor (next item below), not the device descriptor
- [ ] Request and parse the configuration descriptor
- [ ] Confirm the device reports the Mass Storage class / Bulk-Only Transport subclass/
protocol (this is the actual "is this a USB drive" check — separate from, and prior
@@ -3332,6 +3335,23 @@ command, DCBAA entry clear), and the callback surface into Section U's higher-le
layer up, after the drive is already known to be USB mass storage)
- [ ] Set the device configuration (SET_CONFIGURATION control transfer)
**Device descriptor request, done 2026-08-22.** Extends the EP0 Transfer Ring built in 2e's
Address Device work with real control-transfer submission: a new `xhci_ep0_enqueue_trb()`
(same fixed-ring-plus-Link-TRB pattern as the Command Ring) posts Setup/Data/Status stage TRBs
as a group, doorbell rung once after all three — matching how xHCI actually processes a
control transfer, not one doorbell ring per TRB. Setup Stage uses `IDT` (Immediate Data — the
TRB's `parameter` field IS the 8-byte USB setup packet, not a pointer to one) and `TRT=3` (IN
Data Stage); Data Stage points at a reused `device_descriptor[18]` buffer with `DIR=IN`;
Status Stage has `DIR=OUT` (the reverse handshake direction) and is the only one of the three
with `IOC` set, so exactly one Transfer Event signals "the whole control transfer is done" —
correlated via a new `dev->pending_transfer_slot_id`, same single-outstanding-operation pattern
already used for connect/Enable Slot/Address Device.
Verified live via QMP hotplug, all three architectures, worked first try, byte-identical
results everywhere: `idVendor=0x46f4` (QEMU's own USB vendor ID), `idProduct=0x0001`,
`bDeviceClass=0x00`. `logs/20260822-125515/amd64/`, `logs/20260822-125624/aarch64/`,
`logs/20260822-125800/riscv64/`.
**2g. Bulk-Only Transport (BOT) — the actual read/write path**
- [ ] Identify and configure the device's bulk IN and bulk OUT endpoints
- [ ] Implement CBW (Command Block Wrapper) construction and send, for a SCSI READ(10)
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-22T16:38:40Z -->
<!-- Generated by mkcapsule --manifest 2026-08-22T16:57:57Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
+33
View File
@@ -193,6 +193,9 @@ typedef struct {
#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_ADDRESS_DEVICE_CMD 11
#define XHCI_TRB_TYPE_SETUP_STAGE 2 /* Transfer Ring, control transfers only */
#define XHCI_TRB_TYPE_DATA_STAGE 3
#define XHCI_TRB_TYPE_STATUS_STAGE 4
#define XHCI_TRB_TYPE_TRANSFER_EVENT 32
#define XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT 33
#define XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVT 34
@@ -200,6 +203,36 @@ typedef struct {
/* Control bits used only by Link TRBs */
#define XHCI_TRB_CONTROL_TC (1u << 1) /* Toggle Cycle */
/* Control bits for control-transfer TRBs (xHCI 1.2 spec section 4.11.2.2 /
* table 6-23..6-25). IDT (Immediate Data) tells the controller the Setup
* Stage TRB's parameter field IS the 8-byte setup packet, not a pointer
* to one -- required for every Setup Stage TRB. TRT/DIR select data
* direction: TRT=3 (IN Data Stage) for the standard "read a descriptor"
* case this driver needs first; DIR must match TRT's direction on the
* Data Stage TRB, and the Status Stage TRB's DIR is the OPPOSITE
* direction of the Data Stage (status is always the reverse handshake). */
#define XHCI_TRB_CONTROL_IDT (1u << 6)
#define XHCI_TRB_CONTROL_IOC (1u << 5) /* Interrupt On Completion */
#define XHCI_TRB_CONTROL_DIR_IN (1u << 16) /* Data/Status Stage: 1=IN, 0=OUT */
#define XHCI_SETUP_TRT_NO_DATA 0u
#define XHCI_SETUP_TRT_OUT_DATA 2u
#define XHCI_SETUP_TRT_IN_DATA 3u
#define XHCI_TRB_CONTROL_TRT_SHIFT 16 /* Setup Stage TRB only; Data/Status Stage overlays DIR at the same bit */
/* Standard USB Setup packet (8 bytes) -- the exact bytes placed in a
* Setup Stage TRB's parameter field via IDT. */
typedef struct {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} usb_setup_packet_t;
#define USB_REQ_GET_DESCRIPTOR 6u
#define USB_DESC_TYPE_DEVICE 1u
#define USB_DIR_DEVICE_TO_HOST 0x80u
/* 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
+31
View File
@@ -78,6 +78,16 @@ typedef struct {
xhci_trb_t *ep0_ring; /* EP0 Transfer Ring, XHCI_RING_TRB_COUNT TRBs */
uint32_t ep0_ring_cycle;
uint32_t ep0_ring_enq;
/* Milestone 2f: EP0 control transfers. Like connect_state, this
* 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. */
uint32_t pending_transfer_slot_id;
uint8_t device_descriptor[18];
} xhci_dev_t;
/*
@@ -181,4 +191,25 @@ 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);
/*
* xhci_ep0_get_device_descriptor — issue a standard GET_DESCRIPTOR
* (Device) control transfer (Setup +
* 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).
*
* Called once Address Device succeeds -- not called directly by other
* code yet (Milestone 2f is still in progress).
*
* 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);
#endif /* STARKERNEL_XHCI_DRIVER_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+114 -3
View File
@@ -17,6 +17,7 @@
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "starkernel/pci.h"
#include "starkernel/xhci.h"
@@ -127,6 +128,7 @@ static xhci_dev_t *g_xhci_dev = NULL;
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_bringup(xhci_dev_t *dev)
{
@@ -286,6 +288,7 @@ int xhci_bringup(xhci_dev_t *dev)
dev->ep0_ring = NULL;
dev->ep0_ring_cycle = 1;
dev->ep0_ring_enq = 0;
dev->pending_transfer_slot_id = 0;
console_println("xhci: controller running");
/* Milestone 2e prep: HCCPARAMS1.CSZ decides 32- vs 64-byte Slot/
@@ -446,6 +449,85 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
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
* xHCI control transfer is submitted (the controller processes queued
* TRBs as a unit once notified, not one doorbell ring per TRB). Same
* fixed-ring-plus-Link-TRB wraparound pattern as xhci_submit_command(),
* operating on ep0_ring/ep0_ring_enq/ep0_ring_cycle instead of the
* Command Ring's fields. */
static void xhci_ep0_enqueue_trb(xhci_dev_t *dev, uint64_t parameter,
uint32_t status, uint32_t control_bits)
{
xhci_trb_t *trb = &dev->ep0_ring[dev->ep0_ring_enq];
trb->parameter = parameter;
trb->status = status;
trb->control = control_bits | (dev->ep0_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
dev->ep0_ring_enq++;
if (dev->ep0_ring_enq == XHCI_RING_TRB_COUNT - 1) {
dev->ep0_ring[XHCI_RING_TRB_COUNT - 1].control =
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_TC |
(dev->ep0_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
dev->ep0_ring_enq = 0;
dev->ep0_ring_cycle ^= 1u;
}
}
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id)
{
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)
};
uint64_t setup_bits;
memcpy(&setup_bits, &setup, sizeof(setup_bits));
/* Setup Stage: IDT set (parameter IS the 8-byte packet, not a
* pointer), TRT = IN Data Stage since this request reads data back. */
xhci_ep0_enqueue_trb(dev, setup_bits, 8u,
(XHCI_TRB_TYPE_SETUP_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IDT |
(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),
(XHCI_TRB_TYPE_DATA_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_DIR_IN);
/* Status Stage: DIR=OUT (opposite of the Data Stage's IN) -- the
* status handshake always runs the reverse direction. IOC set here
* only: this is the sole TRB of the three whose completion signals
* "the whole control transfer is done" to xhci_poll_events(). */
xhci_ep0_enqueue_trb(dev, 0, 0,
(XHCI_TRB_TYPE_STATUS_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IOC);
dev->pending_transfer_slot_id = slot_id;
/* Doorbell Array is indexed by slot ID; target 1 = Default Control
* Endpoint (EP0)'s Device Context Index, per xHCI 1.2 spec table
* 6-25 -- distinct from doorbell[0], which is always the Command
* Ring regardless of slot. */
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
console_println("xhci: get device descriptor submitted");
return 0;
}
/* -------------------------------------------------------------------------
* Milestone 2d: Event Ring servicing, polled from sk_repl_idle().
*
@@ -575,6 +657,12 @@ void xhci_poll_events(void)
} else if (dev->connect_state == XHCI_CONN_AWAIT_ADDRESS_DEVICE) {
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
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");
}
} else {
console_println("xhci: address device failed");
}
@@ -585,10 +673,33 @@ void xhci_poll_events(void)
}
break;
}
case XHCI_TRB_TYPE_TRANSFER_EVENT:
/* No transfer rings exist yet (Milestone 2g) -- logged. */
console_println("xhci: transfer event");
case XHCI_TRB_TYPE_TRANSFER_EVENT: {
uint32_t code = XHCI_EVT_COMPLETION_CODE(trb->status);
if (dev->pending_transfer_slot_id != 0) {
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");
}
} else {
console_println("xhci: transfer event");
}
break;
}
default:
break;
}