Artemis Milestone 2a complete: xHCI register header, PCI ID confirmed

live via QMP, ring sizing decided

include/starkernel/xhci.h: Capability/Operational/Runtime register
layouts, Port Register Set, Interrupter Register Set, Doorbell Array,
16-byte TRB struct -- all from the xHCI 1.2 spec, no existing
reference in this tree to build from (unlike virtio-blk). volatile
fields, no packed attribute, matching virtio_blk.c's documented
riscv64/QEMU-MMIO precedent. Compile-checked clean, sizeof(xhci_trb_t)
verified == 16.

QEMU qemu-xhci's PCI vendor:device ID (0x1B36:0x000D) confirmed live
via QMP query-pci against a real running instance -- not assumed from
memory, matches the Milestone 1 QMP infrastructure just built.

Ring sizing decided: fixed 256-TRB (one page) Command Ring and Event
Ring, single interrupter -- documented rationale in the header.

Bonus finding: src/starkernel/pci/pci.c already has more reusable
infrastructure than Milestone 2b assumed (pci_find_first is ID-based
lookup already existing; pci_bar/pci_map_bar/pci_enable are already
generic) -- 2b is smaller than originally scoped, noted in the punch
list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-22 07:53:16 -04:00
co-authored by Claude Sonnet 5
parent 7c6bdb6849
commit 94345c24b7
2 changed files with 226 additions and 12 deletions
+25 -12
View File
@@ -3067,18 +3067,31 @@ Nothing in Section U/V/W's home-blocks thread is testable past this point withou
roughly bottom-up: PCI discovery → controller bring-up → command/event rings → port/hotplug
detection → device enumeration → mass-storage class → read/write.
**2a. Research/spec groundwork (do first, nothing below can be scoped precisely without it)**
- [ ] Confirm xHCI is the right target controller class (vs. EHCI/OHCI) for QEMU's
`qemu-xhci` device and for realistic modern real hardware — xHCI is the USB 3.x
standard and QEMU's default modern USB controller, but confirm no fallback to EHCI is
needed for older/simpler test hardware
before writing controller-bringup code
- [ ] Pull the xHCI register-interface layout (Capability Registers, Operational Registers,
Runtime Registers, Doorbell array — the four MMIO regions) from the xHCI spec; this
project has no existing xHCI reference to build from (unlike virtio-blk, which had the
virtio spec's existing precedent already used in this tree)
- [ ] Decide command-ring and event-ring sizes/allocation strategy up front (fixed small
ring vs. dynamically sized) — affects the memory-management code below
**2a. Research/spec groundwork (do first, nothing below can be scoped precisely without it) ✅ DONE 2026-08-22**
- [x] Confirmed xHCI (not EHCI/OHCI) via `qemu-system-x86_64 -device help`: `qemu-xhci`,
`usb-storage` (bus `usb-bus`) both available in this environment. Vendor/device ID
confirmed **live via QMP** `query-pci` against a real running instance (not assumed
from memory) — `qemu-xhci` reports PCI vendor `0x1B36` (Red Hat, Inc.), device `0x000D`.
- [x] Pulled the xHCI register-interface layout into a real header:
`include/starkernel/xhci.h`Capability Registers, Operational Registers (incl. Port
Register Set), Runtime Registers (incl. Interrupter Register Set), Doorbell Array, and
the 16-byte TRB layout. Compile-checked clean (`gcc -std=c99 -Wall -Wextra`, zero
warnings) and `sizeof(xhci_trb_t) == 16` verified. Matches `virtio_blk.c`'s established
precedent: `volatile` fields, no `packed` attribute (that file's own documented
riscv64/QEMU-MMIO lesson about byte-wise access to packed structs applies identically
here, since the xHCI spec layout is naturally aligned at every offset already).
- [x] Ring sizing decided and documented in the header: fixed 256-TRB (one page, 4096-byte)
Command Ring and Event Ring, single Interrupter (0) only — matches this codebase's
existing preference for fixed page-sized allocations over dynamic growth (e.g.
`KRD_MAX_BLOCKS`'s fixed RAMDRIVE size), and this driver's actual need (one drive at a
time, hotplug detection + basic read/write, not high-throughput multi-device USB).
**Bonus finding, same pass:** `src/starkernel/pci/pci.c` already has more reusable
infrastructure than 2b's punch-list items assumed — `pci_find_first(vendor,device,*out)`
already exists (ID-based lookup, not class-code scanning — 2b's wording should be read as "ID
match" not "class code match"), and `pci_bar()`/`pci_map_bar()`/`pci_enable()` are already
generic, reusable as-is for the xHCI BAR0 mapping and bus-master enable. 2b is smaller than
originally scoped.
**2b. PCI discovery**
- [ ] Extend `src/starkernel/pci/pci.c`'s enumeration to recognize the xHCI PCI class code
+201
View File
@@ -0,0 +1,201 @@
/*
* xhci.h — xHCI (Extensible Host Controller Interface, USB 3.x) register
* layout and shared constants for StarKernel's USB host controller driver.
*
* Register layout from the xHCI 1.2 specification. Four MMIO regions, each
* reached via an offset from PCI BAR0:
* Capability Registers — at BAR0 + 0, fixed layout, CAPLENGTH gives the
* offset to Operational Registers
* Operational Registers — at BAR0 + CAPLENGTH
* Runtime Registers — at BAR0 + RTSOFF (read from Capability Registers)
* Doorbell Array — at BAR0 + DBOFF (read from Capability Registers)
*
* Struct fields are `volatile`, naturally aligned, NOT __attribute__((packed))
* — matching virtio_blk.c's precedent and its documented riscv64 lesson:
* packed structs force byte-wise loads/stores on strict-alignment targets,
* and QEMU's MMIO handlers for exact-width registers can misbehave under
* byte-wise access. The xHCI spec's register layout is naturally aligned at
* every offset, so this translates directly without padding tricks.
*
* QEMU's `qemu-xhci` device identifies as PCI vendor 0x1B36 (Red Hat, Inc.),
* device 0x000D — confirmed live via QMP `query-pci` against a real running
* instance (not assumed from memory), 2026-08-22.
*/
#ifndef STARKERNEL_XHCI_H
#define STARKERNEL_XHCI_H
#include <stdint.h>
/* -------------------------------------------------------------------------
* PCI identification
* ------------------------------------------------------------------------- */
#define XHCI_PCI_VENDOR_ID 0x1B36u /* Red Hat, Inc. (QEMU qemu-xhci) */
#define XHCI_PCI_DEVICE_ID 0x000Du
/* -------------------------------------------------------------------------
* Capability Registers (BAR0 + 0)
* ------------------------------------------------------------------------- */
typedef struct {
volatile uint8_t cap_length; /* offset to Operational Registers */
volatile uint8_t reserved0;
volatile uint16_t hci_version; /* BCD xHCI spec version */
volatile uint32_t hcs_params1; /* MaxSlots[7:0], MaxIntrs[18:8], MaxPorts[31:24] */
volatile uint32_t hcs_params2; /* IST, ERST Max, scratchpad buffer counts */
volatile uint32_t hcs_params3; /* U1/U2 device exit latencies */
volatile uint32_t hcc_params1; /* AC64, BNC, CSZ, xECP pointer[31:16], etc. */
volatile uint32_t db_off; /* Doorbell Array offset (low 2 bits reserved) */
volatile uint32_t rts_off; /* Runtime Register Space offset (low 5 bits reserved) */
volatile uint32_t hcc_params2;
} xhci_cap_regs_t;
#define XHCI_HCSPARAMS1_MAX_SLOTS(v) ((uint32_t)(v) & 0xFFu)
#define XHCI_HCSPARAMS1_MAX_INTRS(v) (((uint32_t)(v) >> 8) & 0x7FFu)
#define XHCI_HCSPARAMS1_MAX_PORTS(v) (((uint32_t)(v) >> 24) & 0xFFu)
/* -------------------------------------------------------------------------
* Operational Registers (BAR0 + cap_length)
* ------------------------------------------------------------------------- */
typedef struct {
volatile uint32_t usb_cmd; /* Run/Stop, HC Reset, Interrupter Enable, ... */
volatile uint32_t usb_sts; /* HCHalted, HSE, EINT, PCD, CNR, HCE */
volatile uint32_t page_size; /* bit N set => 2^(N+12)-byte pages supported */
volatile uint32_t reserved0[2];
volatile uint32_t dn_ctrl; /* Device Notification Control */
volatile uint64_t crcr; /* Command Ring Control Register */
volatile uint32_t reserved1[4];
volatile uint64_t dcbaap; /* Device Context Base Address Array Pointer */
volatile uint32_t config; /* MaxSlotsEn[7:0] */
/* Port Register Sets follow at a fixed offset (0x400 from Operational
* base), not contiguous with the fields above — accessed via
* xhci_port_regs() below, not as a struct member. */
} xhci_op_regs_t;
/* USBCMD bits */
#define XHCI_USBCMD_RUN (1u << 0) /* Run/Stop: 1 = run */
#define XHCI_USBCMD_HCRST (1u << 1) /* HC Reset */
#define XHCI_USBCMD_INTE (1u << 2) /* Interrupter Enable */
#define XHCI_USBCMD_HSEE (1u << 3) /* Host System Error Enable */
/* USBSTS bits */
#define XHCI_USBSTS_HCH (1u << 0) /* HC Halted */
#define XHCI_USBSTS_HSE (1u << 2) /* Host System Error */
#define XHCI_USBSTS_EINT (1u << 3) /* Event Interrupt */
#define XHCI_USBSTS_PCD (1u << 4) /* Port Change Detect */
#define XHCI_USBSTS_CNR (1u << 11) /* Controller Not Ready */
#define XHCI_USBSTS_HCE (1u << 12) /* Host Controller Error */
/* CRCR bits (low bits of the 64-bit register; pointer occupies bits[63:6]) */
#define XHCI_CRCR_RCS (1ull << 0) /* Ring Cycle State */
#define XHCI_CRCR_CS (1ull << 1) /* Command Stop */
#define XHCI_CRCR_CA (1ull << 2) /* Command Abort */
#define XHCI_CRCR_CRR (1ull << 3) /* Command Ring Running (read-only) */
#define XHCI_CRCR_PTR_MASK (~0x3Full) /* pointer must be 64-byte aligned */
/* CONFIG */
#define XHCI_CONFIG_MAX_SLOTS_EN(n) ((uint32_t)(n) & 0xFFu)
/* Port Register Set — array at Operational base + 0x400, 0x10 bytes each,
* indexed 0..(MaxPorts-1) for ports numbered 1..MaxPorts. */
typedef struct {
volatile uint32_t portsc; /* Port Status and Control */
volatile uint32_t portpmsc; /* Port Power Management Status and Control */
volatile uint32_t portli; /* Port Link Info */
volatile uint32_t porthlpmc; /* Port Hardware LPM Control */
} xhci_port_regs_t;
#define XHCI_PORT_REGS_OFFSET 0x400u
/* PORTSC bits (subset needed for hotplug + reset) */
#define XHCI_PORTSC_CCS (1u << 0) /* Current Connect Status */
#define XHCI_PORTSC_PED (1u << 1) /* Port Enabled/Disabled */
#define XHCI_PORTSC_PR (1u << 4) /* Port Reset */
#define XHCI_PORTSC_PLS_MASK (0xFu << 5) /* Port Link State */
#define XHCI_PORTSC_PP (1u << 9) /* Port Power */
#define XHCI_PORTSC_SPEED_MASK (0xFu << 10)
#define XHCI_PORTSC_CSC (1u << 17) /* Connect Status Change */
#define XHCI_PORTSC_PEC (1u << 18) /* Port Enabled/Disabled Change */
#define XHCI_PORTSC_PRC (1u << 21) /* Port Reset Change */
/* Writing 1 to a _C (change) bit clears it (RW1CS) — writing 0 has no effect.
* PORTSC also has RW1CS bits interleaved with RW bits; always read-modify-
* write with the change bits masked to 0 unless intentionally clearing one,
* to avoid accidentally acknowledging an event by a stray read-modify-write. */
/* -------------------------------------------------------------------------
* Runtime Registers (BAR0 + rts_off)
* ------------------------------------------------------------------------- */
typedef struct {
volatile uint32_t iman; /* Interrupt Management: bit0=IP, bit1=IE */
volatile uint32_t imod; /* Interrupt Moderation */
volatile uint32_t erstsz; /* Event Ring Segment Table Size */
volatile uint32_t reserved0;
volatile uint64_t erstba; /* Event Ring Segment Table Base Address */
volatile uint64_t erdp; /* Event Ring Dequeue Pointer; bit3=EHB */
} xhci_intr_regs_t;
typedef struct {
volatile uint32_t mf_index; /* Microframe Index */
volatile uint32_t reserved0[7];
/* Interrupter Register Sets follow, one xhci_intr_regs_t per interrupter,
* starting immediately after this 0x20-byte header. Interrupter 0 is
* accessed via xhci_intr_regs_t at (runtime_base + 0x20). */
} xhci_runtime_regs_t;
#define XHCI_IMAN_IP (1u << 0) /* Interrupt Pending */
#define XHCI_IMAN_IE (1u << 1) /* Interrupt Enable */
#define XHCI_ERDP_EHB (1ull << 3) /* Event Handler Busy */
#define XHCI_ERDP_PTR_MASK (~0xFull) /* pointer occupies bits[63:4] */
/* -------------------------------------------------------------------------
* Doorbell Array (BAR0 + db_off) — array of uint32_t, one per device slot
* plus doorbell 0 for the Command Ring. Write-only.
* ------------------------------------------------------------------------- */
typedef volatile uint32_t xhci_doorbell_t;
#define XHCI_DB_TARGET(ep) ((uint32_t)(ep) & 0xFFu) /* 0 = command ring */
#define XHCI_DB_STREAM_ID(sid) (((uint32_t)(sid) & 0xFFFFu) << 16)
/* -------------------------------------------------------------------------
* TRB (Transfer Request Block) — 16 bytes, the unit of both Command Ring
* and Event Ring entries (and Transfer Rings, used later for BOT I/O).
* ------------------------------------------------------------------------- */
typedef struct {
volatile uint64_t parameter;
volatile uint32_t status;
volatile uint32_t control;
} xhci_trb_t;
#define XHCI_TRB_CONTROL_CYCLE (1u << 0) /* Cycle bit */
#define XHCI_TRB_CONTROL_TYPE_SHIFT 10
#define XHCI_TRB_CONTROL_TYPE_MASK (0x3Fu << XHCI_TRB_CONTROL_TYPE_SHIFT)
#define XHCI_TRB_TYPE(ctrl) (((ctrl) & XHCI_TRB_CONTROL_TYPE_MASK) >> XHCI_TRB_CONTROL_TYPE_SHIFT)
/* TRB types used by this driver (subset — xHCI defines many more) */
#define XHCI_TRB_TYPE_ENABLE_SLOT_CMD 9
#define XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD 11
#define XHCI_TRB_TYPE_TRANSFER_EVENT 32
#define XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT 33
#define XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVT 34
/* -------------------------------------------------------------------------
* Ring sizing — decided up front per Milestone 2's punch list (2a).
*
* Fixed, single-page rings: 256 TRBs x 16 bytes = 4096 bytes = one page.
* This project's usage (MSC hotplug detection + read/write to one drive at
* a time) does not need a high-throughput, dynamically-growable ring —
* matches this codebase's existing preference for fixed, page-sized
* allocations over dynamic growth (e.g. KRD_MAX_BLOCKS's fixed 1024-block
* RAMDRIVE). One Command Ring, one Event Ring (Interrupter 0 only — this
* driver does not use multiple interrupters).
* ------------------------------------------------------------------------- */
#define XHCI_RING_TRB_COUNT 256u
#define XHCI_RING_BYTES (XHCI_RING_TRB_COUNT * sizeof(xhci_trb_t))
#endif /* STARKERNEL_XHCI_H */