Files
LithosAnanke/src/starkernel/usb/xhci.c
T
Robert Allan JamesandClaude Sonnet 5 2b16daba16 Artemis Milestone 2d: xHCI Event Ring servicing, polled not interrupt-driven
Implements Event Ring TRB parsing and ERDP dequeue-pointer update
(xhci_poll_events(), src/starkernel/usb/xhci.c), called from
sk_repl_idle()'s existing ~1s idle cadence rather than a per-arch
interrupt handler.

A first attempt wired real interrupt delivery (PCI->IOAPIC GSI routing,
a dedicated isr_stub34/vector 0x22, GIC/PLIC routing mirroring
virtio_input.c). Checked live via QMP query-pci before trusting it: the
amd64 PIRQ swizzle formula predicted GSI 16 for the xHCI controller at
PCI slot 4; the real QEMU-assigned IRQ was 10, and embedded ICH9
functions contradicted the same formula too. Reverted all of it back to
the exact committed baseline rather than chasing chipset PIRQ routing
further, and reframed around Section U item 6's own design intent
("interrupt-driven, coarse cadence, cheap early-exit... quick check
blocks... done") via sk_repl_idle() instead -- USB insertion is a
human-timescale event, not a hot path.

Added -device qemu-xhci to all three QEMU launch targets (required for
any of this to be testable). Verified end to end via genuine post-boot
hotplug (QMP device_add/device_del usb-storage): all three architectures
detect a live attach within seconds. A false-alarm heartbeat "freeze"
found mid-verification traced to querying the wrong counter
(vm->heartbeat.tick_count, which only advances during word execution,
not the kernel's real ISR-driven heartbeat_ticks()) -- confirmed via a
temporary diagnostic word, captured and reverted.

Full writeup, including the discarded interrupt-routing attempt and the
false-alarm investigation, in FABRIC-2.md's Milestone 2c/2d entries.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
2026-08-22 09:25:25 -04:00

302 lines
12 KiB
C

/*
* xhci.c — xHCI USB host controller driver for StarKernel: PCI discovery
* (Milestone 2b), controller bring-up (2c), and polled Event Ring
* servicing (2d). Hotplug/enumeration/BOT read-write (2e-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 "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;
}
}
}
/* 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;
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->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;
}
console_println("xhci: controller running");
g_xhci_dev = dev;
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).
* ------------------------------------------------------------------------- */
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:
/* Hotplug trigger -- full port-read/slot-enable handling is
* Milestone 2e's scope, not this one's. Logged so the
* event-ring path is visibly exercised before 2e consumes
* it for real. */
console_println("xhci: port status change event");
break;
case XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT:
/* No commands are issued yet (Milestone 2e is the first
* command-ring user) -- logged for the same reason. */
console_println("xhci: command completion event");
break;
case XHCI_TRB_TYPE_TRANSFER_EVENT:
/* No transfer rings exist yet (Milestone 2g) -- logged. */
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;
}