Files
LithosAnanake/src/starkernel/usb/xhci.c
T
Robert Allan JamesandClaude Sonnet 5 b9c540a78b 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
2026-08-25 07:27:07 -04:00

866 lines
42 KiB
C

/*
* xhci.c — xHCI USB host controller driver for StarKernel: PCI discovery
* (Milestone 2b), controller bring-up (2c), polled Event Ring servicing
* (2d), and Command Ring submission + PORTSC connect/disconnect detection
* (2e, in progress). Enumeration/BOT read-write (2f-2g) follow in later
* increments.
*
* Memory model: BAR0 is mapped identity (virtual address == physical
* address), matching virtio_blk.c's precedent and pci_map_bar()'s own
* documented behavior (amd64: vmm_map_range(phys, phys, ...); other
* arches: no-op, UEFI identity map already covers it).
*/
#ifndef __STARKERNEL__
#error "xhci.c is kernel-only"
#endif
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "starkernel/pci.h"
#include "starkernel/xhci.h"
#include "starkernel/xhci_driver.h"
#include "starkernel/kmalloc.h"
#include "starkernel/timer.h"
#include "console.h"
/* Conservative fixed BAR0 mapping size. xHCI has no self-describing
* capability-region length the way virtio PCI capabilities do (that's
* virtio_blk.c's approach, not available here) — 64 KiB comfortably
* covers Capability + Operational + Port registers + Runtime + Doorbell
* Array + typical extended-capability space for QEMU's qemu-xhci and for
* real hardware controllers with modest port counts. Revisit if a real
* device's actual BAR size (via PCI BAR-sizing probe, not yet
* implemented in pci.c) proves this insufficient. */
#define XHCI_BAR0_MAP_SIZE 0x10000ull
int xhci_find_and_map(xhci_dev_t *dev)
{
if (!dev) return -1;
if (pci_find_first(XHCI_PCI_VENDOR_ID, XHCI_PCI_DEVICE_ID, &dev->pci) != 0) {
console_println("xhci: no controller found on PCI bus 0");
return -1;
}
pci_enable(&dev->pci);
dev->bar0_phys = pci_bar(&dev->pci, 0);
if (!dev->bar0_phys) {
console_println("xhci: BAR0 read failed (zero or I/O BAR)");
return -1;
}
if (pci_map_bar(dev->bar0_phys, XHCI_BAR0_MAP_SIZE) != 0) {
console_println("xhci: BAR0 mapping failed");
return -2;
}
dev->cap = (xhci_cap_regs_t *)(uintptr_t)dev->bar0_phys;
dev->op = (xhci_op_regs_t *)((uint8_t *)dev->cap + dev->cap->cap_length);
dev->runtime = (xhci_runtime_regs_t *)((uint8_t *)dev->cap +
(dev->cap->rts_off & ~0x1Fu));
dev->doorbell = (xhci_doorbell_t *)((uint8_t *)dev->cap +
(dev->cap->db_off & ~0x3u));
uint32_t hcs1 = dev->cap->hcs_params1;
dev->max_slots = XHCI_HCSPARAMS1_MAX_SLOTS(hcs1);
dev->max_intrs = XHCI_HCSPARAMS1_MAX_INTRS(hcs1);
dev->max_ports = XHCI_HCSPARAMS1_MAX_PORTS(hcs1);
dev->max_scratchpad_bufs = XHCI_HCSPARAMS2_MAX_SCRATCHPAD_BUFS(dev->cap->hcs_params2);
console_println("xhci: controller found, BAR0 mapped");
return 0;
}
/* Spin-count-bounded busy-wait, matching kernel_main.c's established
* pattern (heartbeat_ticks() elapsed + a hard spin-count safety cap, not
* just one or the other). Polls *reg for (val & mask) to equal want_set
* (0 or 1), re-reading the register itself each iteration. */
static int xhci_wait_bit(volatile uint32_t *reg, uint32_t mask, int want_set,
uint64_t max_ticks)
{
uint64_t start = heartbeat_ticks();
uint64_t spins = 0;
for (;;) {
uint32_t val = *reg;
int is_set = (val & mask) != 0;
if (is_set == want_set) return 0;
spins++;
if (heartbeat_ticks() - start >= max_ticks || spins >= 100000000ULL) {
return -1;
}
}
}
/* console_println() only takes a string literal -- no formatted print
* exists on this driver's console path. Matches the established pattern
* elsewhere in this kernel (e.g. src/starkernel/vm/parity.c's
* print_hex64()) rather than adding one: a small static hex-dump helper,
* "label: 0xXXXXXXXX". Debugging register values without this is
* guesswork. */
static void xhci_log_hex32(const char *label, uint32_t val)
{
char buf[11];
buf[0] = '0';
buf[1] = 'x';
for (int i = 9; i >= 2; i--) {
int d = val & 0xF;
buf[i] = (d < 10) ? ('0' + d) : ('a' + d - 10);
val >>= 4;
}
buf[10] = '\0';
console_puts(label);
console_println(buf);
}
/* Only one controller is supported (matches xhci_dev_t's own doc comment);
* latched at the end of a successful xhci_bringup() for
* xhci_poll_events()'s use. */
static xhci_dev_t *g_xhci_dev = NULL;
/* Forward declaration -- xhci_bringup() below issues one Enable Slot as a
* command-ring smoke test; the implementation lives after xhci_bringup()
* (see the "Command Ring submission" section) so it can stay close to
* xhci_poll_events(), the read side of the same ring pair. */
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)
{
if (!dev || !dev->op) return -2;
/* 1. If running, stop first (Run/Stop must be cleared before HCRST is
* guaranteed to behave per spec on some implementations). */
if (dev->op->usb_cmd & XHCI_USBCMD_RUN) {
dev->op->usb_cmd &= ~XHCI_USBCMD_RUN;
if (xhci_wait_bit(&dev->op->usb_sts, XHCI_USBSTS_HCH, 1, 10000) != 0) {
console_println("xhci: timeout waiting for halt before reset");
return -1;
}
}
/* 2. Host Controller Reset. HCRST self-clears; CNR (Controller Not
* Ready) must also clear before touching any other operational
* register. */
dev->op->usb_cmd |= XHCI_USBCMD_HCRST;
if (xhci_wait_bit(&dev->op->usb_cmd, XHCI_USBCMD_HCRST, 0, 10000) != 0) {
console_println("xhci: timeout waiting for HCRST to self-clear");
return -1;
}
if (xhci_wait_bit(&dev->op->usb_sts, XHCI_USBSTS_CNR, 0, 10000) != 0) {
console_println("xhci: timeout waiting for CNR to clear");
return -1;
}
/* 3. Device Context Base Address Array — (max_slots+1) x 8-byte
* pointers, 64-byte aligned, zeroed (DCBAAP requires 64-byte
* alignment per spec; kmalloc_aligned enforces it). */
size_t dcbaa_bytes = (size_t)(dev->max_slots + 1) * sizeof(uint64_t);
dev->dcbaa = kmalloc_aligned(dcbaa_bytes, 64);
if (!dev->dcbaa) {
console_println("xhci: DCBAA allocation failed");
return -2;
}
for (size_t i = 0; i < dcbaa_bytes / sizeof(uint64_t); i++) {
((uint64_t *)dev->dcbaa)[i] = 0;
}
/* 3b. Scratchpad buffers, only if the controller asks for them (slot 0
* of the DCBAA points at the scratchpad buffer array, not a device
* context, when max_scratchpad_bufs > 0). PAGESIZE register: bit N
* set means 2^(N+12)-byte pages; use the lowest set bit found. */
if (dev->max_scratchpad_bufs > 0) {
uint32_t pagesize_bits = dev->op->page_size;
uint32_t page_bytes = 4096u;
for (uint32_t b = 0; b < 16; b++) {
if (pagesize_bits & (1u << b)) { page_bytes = 1u << (b + 12); break; }
}
size_t arr_bytes = (size_t)dev->max_scratchpad_bufs * sizeof(uint64_t);
dev->scratchpad_arr = kmalloc_aligned(arr_bytes, 64);
if (!dev->scratchpad_arr) {
console_println("xhci: scratchpad array allocation failed");
return -2;
}
for (uint32_t i = 0; i < dev->max_scratchpad_bufs; i++) {
void *buf = kmalloc_aligned(page_bytes, page_bytes);
if (!buf) {
console_println("xhci: scratchpad buffer allocation failed");
return -2;
}
((uint64_t *)dev->scratchpad_arr)[i] = (uint64_t)(uintptr_t)buf;
}
((uint64_t *)dev->dcbaa)[0] = (uint64_t)(uintptr_t)dev->scratchpad_arr;
}
dev->op->dcbaap = (uint64_t)(uintptr_t)dev->dcbaa;
/* 4. Command Ring — XHCI_RING_TRB_COUNT TRBs, 64-byte aligned, zeroed.
* Initial Ring Cycle State = 1 (software convention; the ring is
* "owned" by software until the first TRB with a matching cycle bit
* is consumed). CRCR low bits carry RCS, not the TRBs themselves. */
dev->cmd_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
if (!dev->cmd_ring) {
console_println("xhci: command ring allocation failed");
return -2;
}
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
dev->cmd_ring[i].parameter = 0;
dev->cmd_ring[i].status = 0;
dev->cmd_ring[i].control = 0;
}
dev->cmd_ring_cycle = 1;
dev->cmd_ring_enq = 0;
/* Last slot is a permanent Link TRB back to index 0 (xHCI 1.2 spec
* §4.9.2 — software must terminate every ring segment with one; without
* it the controller reads uninitialised memory past the segment instead
* of wrapping). Toggle Cycle (TC) tells the controller to flip its own
* consumer cycle state when it processes this TRB, matching the
* producer-side toggle xhci_submit_command() below performs on wrap. */
dev->cmd_ring[XHCI_RING_TRB_COUNT - 1].parameter =
(uint64_t)(uintptr_t)dev->cmd_ring;
dev->cmd_ring[XHCI_RING_TRB_COUNT - 1].control =
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_TC | XHCI_TRB_CONTROL_CYCLE;
dev->op->crcr = ((uint64_t)(uintptr_t)dev->cmd_ring & XHCI_CRCR_PTR_MASK) |
XHCI_CRCR_RCS;
/* 5. Event Ring — one segment (Event Ring Segment Table with a single
* 16-byte entry: base address + size), wired to Interrupter 0.
* Interrupter Register Sets start at runtime_base + 0x20; each is
* sizeof(xhci_intr_regs_t) apart, but only Interrupter 0 is used
* (single-interrupter design decided in 2a). */
dev->evt_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
if (!dev->evt_ring) {
console_println("xhci: event ring allocation failed");
return -2;
}
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
dev->evt_ring[i].parameter = 0;
dev->evt_ring[i].status = 0;
dev->evt_ring[i].control = 0;
}
dev->evt_ring_cycle = 1;
dev->evt_ring_deq = 0;
/* Event Ring Segment Table entry layout: u64 base + u32 size + u32
* reserved = 16 bytes. One segment is enough (ERST Max >= 1 always). */
dev->evt_ring_seg_table = kmalloc_aligned(16, 64);
if (!dev->evt_ring_seg_table) {
console_println("xhci: event ring segment table allocation failed");
return -2;
}
uint64_t *erst = (uint64_t *)dev->evt_ring_seg_table;
erst[0] = (uint64_t)(uintptr_t)dev->evt_ring; /* base address */
erst[1] = (uint64_t)XHCI_RING_TRB_COUNT; /* size, low 32 bits used */
dev->intr0 = (xhci_intr_regs_t *)((uint8_t *)dev->runtime + 0x20);
xhci_intr_regs_t *intr0 = dev->intr0;
intr0->erstsz = 1;
intr0->erstba = (uint64_t)(uintptr_t)dev->evt_ring_seg_table;
intr0->erdp = ((uint64_t)(uintptr_t)dev->evt_ring & XHCI_ERDP_PTR_MASK);
/* 6. Enable device slots (all of them — no reason to restrict for a
* single-drive-at-a-time driver) and start the controller. */
dev->op->config = XHCI_CONFIG_MAX_SLOTS_EN(dev->max_slots);
dev->op->usb_cmd |= XHCI_USBCMD_RUN;
if (xhci_wait_bit(&dev->op->usb_sts, XHCI_USBSTS_HCH, 0, 10000) != 0) {
console_println("xhci: controller did not leave halted state after RUN");
return -3;
}
/* Milestone 2e: per-port Enable Slot correlation state -- fixed array,
* see xhci_dev_t's own doc comment; no allocation needed. */
for (uint32_t i = 0; i < XHCI_MAX_TRACKED_PORTS; i++) dev->port_slot_id[i] = 0;
dev->pending_connect_port_id = 0;
dev->pending_connect_speed = 0;
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_connect_slot_id = 0;
dev->input_ctx = NULL;
dev->device_ctx = NULL;
dev->ep0_ring = NULL;
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/
* Endpoint/Input Context layout for Address Device -- must be read
* live, not assumed, before any context structure is designed. */
xhci_log_hex32("xhci: hcc_params1=", dev->cap->hcc_params1);
console_println(XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)
? "xhci: context size = 64 bytes"
: "xhci: context size = 32 bytes");
g_xhci_dev = dev;
return 0;
}
/* -------------------------------------------------------------------------
* Command Ring submission -- Milestone 2e. Shared by Enable Slot now and
* Address Device next; xhci_poll_events() above is the read side of this
* same ring pair, already verified live for Port Status Change events.
* ------------------------------------------------------------------------- */
/* extra_control_bits ORs additional fields into the TRB's control dword
* beyond type/cycle -- e.g. Address Device's Slot ID at bits[31:24]
* (Enable Slot needs none, passes 0). Never includes the cycle bit itself;
* that's computed here from cmd_ring_cycle so callers can't get it wrong. */
static void xhci_submit_command(xhci_dev_t *dev, uint64_t parameter,
uint32_t status, uint32_t trb_type,
uint32_t extra_control_bits)
{
xhci_trb_t *trb = &dev->cmd_ring[dev->cmd_ring_enq];
trb->parameter = parameter;
trb->status = status;
trb->control = (trb_type << XHCI_TRB_CONTROL_TYPE_SHIFT) | extra_control_bits |
(dev->cmd_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
dev->cmd_ring_enq++;
if (dev->cmd_ring_enq == XHCI_RING_TRB_COUNT - 1) {
/* About to hand the Link TRB to the controller -- its cycle bit
* must match the producer cycle state at the moment of handoff,
* and the producer state flips here too (this is the wrap). */
dev->cmd_ring[XHCI_RING_TRB_COUNT - 1].control =
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_TC |
(dev->cmd_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
dev->cmd_ring_enq = 0;
dev->cmd_ring_cycle ^= 1u;
}
/* Doorbell 0 targets the Command Ring (XHCI_DB_TARGET(0)); write-only,
* one write per new TRB posted -- xhci.h's own doc comment on the
* Doorbell Array. */
dev->doorbell[0] = XHCI_DB_TARGET(0);
}
int xhci_cmd_enable_slot(xhci_dev_t *dev)
{
if (!dev || !dev->cmd_ring) return -1;
xhci_submit_command(dev, 0, 0, XHCI_TRB_TYPE_ENABLE_SLOT_CMD, 0);
console_println("xhci: enable slot command submitted");
return 0;
}
/* Default EP0 Max Packet Size by PORTSC.Port Speed, used before any device
* descriptor has been read (xHCI 1.2 spec's own recommended defaults --
* the real value comes from bMaxPacketSize0 once 2f reads the device
* descriptor and issues an Evaluate Context to correct it if needed). */
static uint32_t xhci_default_ep0_max_packet(uint32_t speed)
{
switch (speed) {
case 4: return 512; /* SuperSpeed */
case 3: return 64; /* High Speed */
case 2: return 8; /* Low Speed */
default: return 64; /* Full Speed (1) and anything unrecognised */
}
}
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
uint32_t port_id, uint32_t speed)
{
if (!dev || !dev->cmd_ring || !dev->dcbaa) return -1;
if (XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)) {
console_println("xhci: 64-byte contexts required, not implemented -- refusing");
return -2;
}
/* Lazily allocate once; reused across every connect (single-device
* scope -- see xhci_dev_t's doc comment). All three re-initialised
* fully below regardless of whether this is the first call. */
if (!dev->input_ctx) {
dev->input_ctx = kmalloc_aligned(
sizeof(xhci_input_ctrl_ctx32_t) + sizeof(xhci_slot_ctx32_t) +
sizeof(xhci_ep_ctx32_t), 64);
if (!dev->input_ctx) return -1;
}
if (!dev->device_ctx) {
dev->device_ctx = kmalloc_aligned(
sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t), 64);
if (!dev->device_ctx) return -1;
}
if (!dev->ep0_ring) {
dev->ep0_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
if (!dev->ep0_ring) return -1;
}
/* EP0 Transfer Ring: same fixed-ring-plus-Link-TRB pattern as the
* Command Ring (xhci_bringup()'s own comment on why). Freshly
* reinitialised on every call, not just the first -- cheap (4KiB) and
* avoids carrying stale TRBs from a previous connect. */
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
dev->ep0_ring[i].parameter = 0;
dev->ep0_ring[i].status = 0;
dev->ep0_ring[i].control = 0;
}
dev->ep0_ring[XHCI_RING_TRB_COUNT - 1].parameter = (uint64_t)(uintptr_t)dev->ep0_ring;
dev->ep0_ring[XHCI_RING_TRB_COUNT - 1].control =
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_TC | XHCI_TRB_CONTROL_CYCLE;
dev->ep0_ring_cycle = 1;
dev->ep0_ring_enq = 0;
/* Device Context: Slot Context followed by EP0 Context, no Input
* Control Context (that only exists in the Input Context below).
* DCBAA[slot_id] must point here, per spec -- zeroed since the
* controller writes this on Address Device success, software must not
* pre-fill it. */
uint8_t *dctx = (uint8_t *)dev->device_ctx;
for (size_t i = 0; i < sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t); i++) dctx[i] = 0;
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)dev->device_ctx;
/* Input Context: Input Control Context, then Slot Context, then EP0
* Context -- this is what the command TRB's parameter points at (never
* the Device Context; conflating the two is the standard mistake
* here). */
uint8_t *ictx = (uint8_t *)dev->input_ctx;
size_t total = sizeof(xhci_input_ctrl_ctx32_t) + sizeof(xhci_slot_ctx32_t) +
sizeof(xhci_ep_ctx32_t);
for (size_t i = 0; i < total; i++) ictx[i] = 0;
xhci_input_ctrl_ctx32_t *ctrl = (xhci_input_ctrl_ctx32_t *)ictx;
ctrl->add_flags = XHCI_INPUT_CTRL_ADD_SLOT | XHCI_INPUT_CTRL_ADD_EP0;
xhci_slot_ctx32_t *slot = (xhci_slot_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t));
slot->dword0 = (speed << XHCI_SLOT_CTX_SPEED_SHIFT) | (1u << XHCI_SLOT_CTX_CONTEXT_ENTRIES_SHIFT);
slot->dword1 = port_id << XHCI_SLOT_CTX_ROOT_PORT_SHIFT;
slot->dword2 = 0u << XHCI_SLOT_CTX_INTR_TARGET_SHIFT; /* Interrupter 0 */
xhci_ep_ctx32_t *ep0 = (xhci_ep_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t) +
sizeof(xhci_slot_ctx32_t));
ep0->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
(XHCI_EP_CTX_TYPE_CONTROL_BIDI << XHCI_EP_CTX_TYPE_SHIFT) |
(xhci_default_ep0_max_packet(speed) << XHCI_EP_CTX_MAX_PACKET_SHIFT);
ep0->tr_dequeue_ptr = ((uint64_t)(uintptr_t)dev->ep0_ring) | 1u; /* DCS = 1 */
ep0->dword4 = 8u; /* Average TRB Length -- spec's own recommended default for EP0 */
xhci_submit_command(dev, (uint64_t)(uintptr_t)dev->input_ctx, 0,
XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD, slot_id << 24);
console_println("xhci: address device command 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
* 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;
}
}
/* 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)
{
usb_setup_packet_t setup = {
.bmRequestType = USB_DIR_DEVICE_TO_HOST,
.bRequest = bRequest,
.wValue = wValue,
.wIndex = wIndex,
.wLength = len
};
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 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);
/* 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);
}
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
* 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;
}
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().
*
* Only one controller is supported (matches xhci_dev_t's own doc comment),
* so xhci_poll_events() is a self-contained singleton call, no argument
* needed -- it recovers the device pointer latched by xhci_bringup() above
* rather than taking one. See xhci_driver.h's own doc comment for why this
* is polled rather than interrupt-driven (a real, checked-live finding,
* not a shortcut: the amd64 PCI INTx routing formula tried first turned
* out to be simply wrong).
* ------------------------------------------------------------------------- */
/* Port Register Set array lives at Operational base + 0x400 (xhci.h's own
* doc comment on xhci_port_regs_t) -- not reachable through xhci_op_regs_t
* itself since it isn't a contiguous struct member. port_id is 1-based,
* matching XHCI_PSC_EVT_PORT_ID()'s decode and the spec's own numbering. */
static xhci_port_regs_t *xhci_port_regs(xhci_dev_t *dev, uint32_t port_id)
{
if (port_id < 1 || port_id > dev->max_ports) return NULL;
return (xhci_port_regs_t *)((uint8_t *)dev->op + XHCI_PORT_REGS_OFFSET +
(port_id - 1) * sizeof(xhci_port_regs_t));
}
void xhci_poll_events(void)
{
xhci_dev_t *dev = g_xhci_dev;
if (!dev) return;
while (((dev->evt_ring[dev->evt_ring_deq].control & XHCI_TRB_CONTROL_CYCLE) != 0)
== (dev->evt_ring_cycle != 0)) {
xhci_trb_t *trb = &dev->evt_ring[dev->evt_ring_deq];
uint32_t type = XHCI_TRB_TYPE(trb->control);
switch (type) {
case XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVT: {
/* Milestone 2e: identify which port changed and whether it
* now reads connected or disconnected. Slot allocation/
* addressing on connect is the next increment -- this only
* detects and acknowledges the change for now. */
uint32_t port_id = XHCI_PSC_EVT_PORT_ID(trb->parameter);
xhci_port_regs_t *port = xhci_port_regs(dev, port_id);
if (!port) {
console_println("xhci: port status change event (bad port id)");
break;
}
uint32_t portsc = port->portsc;
if (portsc & XHCI_PORTSC_CCS) {
console_println("xhci: port status change -- device connected");
/* Milestone 2e prep: Address Device requires the port
* in Default state. USB3 links train and enable
* themselves; USB2 needs software to drive PORTSC.PR
* and wait for PRC/PED before the device will respond
* to addressing -- not yet known which this driver's
* ports need, so log raw PORTSC and PED rather than
* assume. */
xhci_log_hex32("xhci: portsc=", portsc);
console_println((portsc & XHCI_PORTSC_PED)
? "xhci: port enabled (PED set)"
: "xhci: port not yet enabled (PED clear)");
/* Only one Enable Slot in flight at a time (see
* xhci_dev_t's doc comment) -- if another connect's
* slot request is still outstanding, this one is
* dropped rather than queued. Acceptable for this
* milestone's single-device testing scope; revisit if
* multi-port simultaneous connects become a real
* scenario. */
if (port_id > XHCI_MAX_TRACKED_PORTS) {
console_println("xhci: port beyond tracked range -- enable slot skipped");
} else if (dev->connect_state == XHCI_CONN_IDLE) {
dev->pending_connect_port_id = port_id;
dev->pending_connect_speed = XHCI_PORTSC_SPEED(portsc);
dev->connect_state = XHCI_CONN_AWAIT_ENABLE_SLOT;
xhci_cmd_enable_slot(dev);
} else {
console_println("xhci: enable slot already pending -- dropped");
}
} else {
console_println("xhci: port status change -- device disconnected");
if (port_id >= 1 && port_id <= XHCI_MAX_TRACKED_PORTS &&
dev->port_slot_id[port_id - 1] != 0) {
/* Real teardown (Disable Slot command, DCBAA entry
* clear, callback to Section U's code) is a later
* increment -- for now just stop tracking the slot
* so a future connect on this port isn't confused
* for one already in progress. */
dev->port_slot_id[port_id - 1] = 0;
}
}
/* Acknowledge only CSC (RW1CS): preserve PP, write 0 for
* PED/PR (writing 1 there disables the port / starts a new
* reset -- not intended here) and for every other _C bit
* (writing 0 leaves them untouched, not cleared) -- the
* same discipline this driver already applies to ERDP.EHB. */
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_CSC;
break;
}
case XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT: {
uint32_t code = XHCI_EVT_COMPLETION_CODE(trb->status);
uint32_t slot_id = XHCI_EVT_SLOT_ID(trb->control);
/* Correlates to connect_state, not to the Command TRB
* Pointer in trb->parameter -- Enable Slot and Address
* Device are issued sequentially for a given connect (see
* xhci_dev_t's doc comment), never concurrently, so
* connect_state alone identifies which command this
* completion answers. A real Command TRB Pointer match
* becomes necessary once commands for different connects
* can overlap in flight. */
if (dev->connect_state == XHCI_CONN_AWAIT_ENABLE_SLOT) {
uint32_t port_id = dev->pending_connect_port_id;
if (code == XHCI_COMPLETION_CODE_SUCCESS &&
port_id >= 1 && port_id <= XHCI_MAX_TRACKED_PORTS) {
dev->port_slot_id[port_id - 1] = slot_id;
dev->pending_connect_slot_id = slot_id;
console_println("xhci: enable slot succeeded");
dev->connect_state = XHCI_CONN_AWAIT_ADDRESS_DEVICE;
if (xhci_cmd_address_device(dev, slot_id, port_id,
dev->pending_connect_speed) != 0) {
console_println("xhci: address device setup failed");
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_connect_port_id = 0;
}
} else {
console_println("xhci: enable slot failed");
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_connect_port_id = 0;
}
} 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. 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");
}
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_connect_port_id = 0;
} else {
console_println("xhci: command completion event");
}
break;
}
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;
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");
}
break;
}
default:
break;
}
dev->evt_ring_deq++;
if (dev->evt_ring_deq == XHCI_RING_TRB_COUNT) {
dev->evt_ring_deq = 0;
dev->evt_ring_cycle ^= 1u;
}
}
/* Event Ring dequeue-pointer update (xHCI 1.2 spec §4.9.4): write the
* new dequeue pointer back to ERDP with bit3 (EHB, Event Handler Busy,
* RW1C) set -- writing 1 to EHB is what clears it, per spec, not a
* read-modify-write of the current value. Skipping this leaves the
* controller believing the event handler is still busy and it will
* not post further events on this interrupter. IMAN.IP/USBSTS.EINT
* are deliberately not touched here: this driver never sets
* USBCMD.INTE/IMAN.IE (polled, not interrupt-driven -- see this
* function's own doc comment), so those RW1C bits never latch and
* 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");
}
}
}