xhci_bringup() (HC reset, DCBAA, Command/Event rings, RUN/STOP) was uncommitted and referenced an XHCI_WAIT_FOR macro that was never defined, breaking the build. Wired all four wait sites to the existing xhci_wait_bit() helper instead, matching each register/bit/polarity needed (halt-before-reset waits for HCH set; HCRST, CNR, and post-RUN HCH waits all wait for their bit to clear). Also flipped g_doe_log_enabled's default from 1 to 0 -- the per-tick [HADES][DOE] CSV export was flooding every boot log and slowing interactive verification for no reason during ordinary acceptance runs; HB-ON still re-enables it at the REPL for anyone running an actual DoE campaign. Three-arch acceptance: amd64/aarch64/riscv64 all boot clean to ok>, zero DoE rows in any log. aarch64 and riscv64 both exited cleanly via BYE with no exception, confirming the earlier SMC->HVC PSCI fix still holds. Logs and DoE CSV artifacts from this run included. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
228 lines
9.0 KiB
C
228 lines
9.0 KiB
C
/*
|
|
* xhci.c — xHCI USB host controller driver for StarKernel: discovery and
|
|
* register-region mapping (Milestone 2b). Controller bring-up (2c) and
|
|
* beyond 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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 */
|
|
|
|
xhci_intr_regs_t *intr0 = (xhci_intr_regs_t *)((uint8_t *)dev->runtime + 0x20);
|
|
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");
|
|
return 0;
|
|
}
|