Skip to content

xHCI Implementation Plan

Living document — tracks design decisions, architecture, progress, and open questions for the xHCI (USB 3.0) host controller driver.


0. Progress Tracker

Stage Status Notes
Stage 1: Constants & Types Done Register offsets, TRB types, packed records
Stage 2: MMIO & Allocation Helpers Done 32/64-bit MMIO, ring/context allocation
Stage 3: Ring Management Done Enqueue/dequeue, cycle bit, command ring
Stage 4: Init/Reset/Start/Stop Done BIOS handoff, DCBAA, scratchpad, event ring
Stage 5: Port Status & Reset Done PORTSC, Enable Slot, Address Device
Stage 6: Submit & Poll Done TRB submission, event ring processing
Stage 7: Load + UnitTests Done PCI scan, HC registration, unit tests
Stage 8: kernel.pas Integration Done uses clause, UnitTest call, build+test

Estimated total: ~1800-2000 lines (EHCI was 1717 lines; xHCI is more complex but has less legacy baggage)


1. Architecture Overview

xHCI (eXtensible Host Controller Interface) is fundamentally different from UHCI/OHCI/EHCI. Instead of frame lists, QHs, and TDs, xHCI uses a ring-based command/transfer/event model.

Key Differences from EHCI

Concept EHCI xHCI
I/O model MMIO (BAR0 32-bit) MMIO (BAR0, 64-bit capable)
PCI prog_if $20 $30
Transfer descriptors QH + qTD chains TRB (Transfer Request Block) rings
Scheduling Frame list + async list Command Ring + per-EP Transfer Rings
Completions Poll qTD ACTIVE bits Event Ring with dequeue pointer
Device management Software SET_ADDRESS Controller manages slots (Enable Slot, Address Device commands)
Speed support High-speed only (companions for LS/FS) All speeds (LS/FS/HS/SS) natively
Notifications Interrupts + polling Doorbell Registers

Register Layout (MMIO)

BAR0 + 0x00      Capability Registers (read-only)
  CAPLENGTH (8-bit)   → offset to Operational regs
  HCIVERSION (16-bit) → spec version
  HCSPARAMS1 (32-bit) → MaxSlots, MaxIntrs, MaxPorts
  HCSPARAMS2 (32-bit) → scratchpad info
  HCSPARAMS3 (32-bit) → latency
  HCCPARAMS1 (32-bit) → addressing capability, XECP pointer
  DBOFF (32-bit)       → Doorbell Array offset from BAR0
  RTSOFF (32-bit)      → Runtime Register Space offset from BAR0

BAR0 + CAPLENGTH  Operational Registers
  USBCMD    ($00) → Run/Stop, HCRST, etc.
  USBSTS    ($04) → HCHalted, HSE, EINT, PCD
  DNCTRL    ($14) → Device Notification Control
  CRCR      ($18) → Command Ring Control (64-bit)
  DCBAAP    ($30) → Device Context Base Address Array Pointer (64-bit)
  CONFIG    ($38) → Max Device Slots Enabled

  PORTSC[n] ($400 + 16*n) → Port Status & Control

BAR0 + RTSOFF     Runtime Registers
  Interrupter[n]:
    IMAN   ($20 + 32*n + $00) → Interrupt Management
    IMOD   ($20 + 32*n + $04) → Interrupt Moderation
    ERSTSZ ($20 + 32*n + $08) → Event Ring Segment Table Size
    ERSTBA ($20 + 32*n + $10) → Event Ring Segment Table Base Address (64-bit)
    ERDP   ($20 + 32*n + $18) → Event Ring Dequeue Pointer (64-bit)

BAR0 + DBOFF      Doorbell Registers
  DB[0]     → Host Controller (Command Ring)
  DB[n]     → Device Slot n (Transfer Ring for target endpoint)

Data Structures (Hardware)

TRB (Transfer Request Block) — 16 bytes, 16-byte aligned

Offset  Size  Field
$00     8     Parameter (address or immediate data)
$04     4     Status (transfer length, completion code, etc.)
$08     4     Control (TRB Type, Cycle bit, flags)

Device Context — 32 or 64 bytes per slot/endpoint context entry - Slot Context: route string, speed, hub info, etc. - Endpoint Context[0..30]: EP type, max packet, TR dequeue pointer, etc.

Input Context — Same layout, used to program device/endpoint changes

DCBAA — Device Context Base Address Array: array of 64-bit pointers, one per slot + slot 0 (scratchpad)

Event Ring Segment Table Entry — 16 bytes

Offset  Size  Field
$00     8     Ring Segment Base Address
$08     4     Ring Segment Size (number of TRBs)
$0C     4     Reserved

Ring Model

  • Command Ring: Kernel enqueues command TRBs, rings doorbell 0. Controller processes and posts Command Completion Event on Event Ring.
  • Transfer Ring: Per-endpoint. Kernel enqueues transfer TRBs, rings the device's doorbell. Controller processes and posts Transfer Event on Event Ring.
  • Event Ring: Controller writes completion TRBs. Software reads and advances dequeue pointer.
  • Cycle Bit: Producer toggles cycle bit at wrap; consumer checks it to detect new entries without needing a separate write pointer.

Integration with usbcore Abstraction

The existing TUSBHCDriver function pointers map to xHCI as follows:

Callback xHCI Implementation
fnReset Halt + HCRST, wait, allocate DCBAA/rings/scratchpad
fnStart Set MaxSlots, DCBAAP, CRCR, Interrupter 0, set RS=1
fnStop Clear RS, wait for HCHalted
fnPortStatus Read PORTSC[port], return bit 0 = CCS
fnPortReset Set PORTSC.PR, wait for PRC, Enable Slot + Address Device
fnSubmit Build TRBs on transfer ring, ring doorbell
fnPoll Read Event Ring, process completions, advance ERDP

SET_ADDRESS Interception (Critical)

xHCI manages USB addresses internally via the Address Device command (issued during fnPortReset). The usbcore enumerate_device flow calls usb_set_address() which submits a SET_ADDRESS control transfer. Our fnSubmit must intercept SET_ADDRESS requests:

  1. Detect: transfer^.PipeType = ptControl AND transfer^.Setup.bRequest = USB_REQ_SET_ADDRESS
  2. Action: Set transfer^.Status := tsSuccess, return true — do NOT send to hardware
  3. Reason: xHCI already assigned the address during Enable Slot / Address Device in fnPortReset

The usbcore then sets dev^.Address := addr and increments hc^.NextAddress. For xHCI, the actual USB address is managed by the controller (stored in the Device Context), and the address used by software is the Slot ID (1-based). We set dev^.Address to the slot ID during fnPortReset so subsequent transfers target the correct slot.

Approach: In xhci_port_reset, after Address Device succeeds, store the slot ID in a slot→port mapping table in TXHCI_PrivData, and set the device's address in the HC's NextAddress. When usbcore calls usb_set_address(dev, addr), our submit intercepts it and returns success. The device's Address field effectively becomes the slot ID.

Slot ↔ Port Mapping

xHCI uses slot IDs (1–MaxSlots) to identify devices. We need a mapping:

TXHCI_PrivData:
    SlotPort   : array[1..256] of uint8   { slot → root port number }
    SlotSpeed  : array[1..256] of uint8   { slot → USB_SPEED_* }
    SlotRing   : array[1..256] of Pointer { slot → EP0 Transfer Ring }
    SlotCtx    : array[1..256] of Pointer { slot → Output Device Context }

These arrays are dynamically allocated (MaxSlots * element_size) to avoid kernel bloat. MaxSlots is capped at 256 (xHCI spec max) but typically 32–64 in practice.


2. Implementation Stages

Stage 1: Constants & Types (~200 lines)

  • All register offsets (capability, operational, runtime, doorbell)
  • Bit definitions for USBCMD, USBSTS, PORTSC, HCSPARAMS1/2/3, HCCPARAMS1
  • PORTSC speed bits (Port Speed field bits 13:10)
  • TRB type codes: Normal ($01), Setup Stage ($02), Data Stage ($03), Status Stage ($04), Link ($06), Enable Slot ($09), Disable Slot ($0A), Address Device ($0B), Configure Endpoint ($0C), Evaluate Context ($0D), Reset Endpoint ($0E), No Op Command ($17), Transfer Event ($20), Command Completion Event ($21), Port Status Change Event ($22)
  • TRB flag bits: Cycle ($01), ENT ($02), ISP ($04), NS ($08), CH ($10), IOC ($20), IDT ($40)
  • TRB completion codes: Success ($01), Data Buffer Error ($02), Babble ($03), USB Transaction Error ($04), TRB Error ($05), Stall ($06), Short Packet ($0D), etc.
  • Packed records:
  • TXHCI_TRB — 16 bytes (Param: uint32[2], Status: uint32, Control: uint32)
  • TXHCI_SlotCtx — 32 bytes (route, speed, hub info, port, max exit latency, etc.)
  • TXHCI_EPCtx — 32 bytes (EP type, max packet, interval, TR dequeue ptr, avg TRB len, etc.)
  • TXHCI_InputCtrlCtx — 32 bytes (Drop/Add context flags)
  • TXHCI_ERSTE — 16 bytes (Ring Segment Base Address, Ring Segment Size, Reserved)
  • Private data record: TXHCI_PrivData (see below)
  • Ring descriptor: TXHCI_Ring (Base pointer, Size, Enqueue index, Cycle bit)

Stage 2: MMIO & Allocation Helpers (~100 lines)

  • xhci_readl(base, reg) / xhci_writel(base, reg, val) — 32-bit MMIO (same pattern as EHCI)
  • xhci_readq(base, reg) / xhci_writeq(base, reg, val) — 64-bit MMIO (two 32-bit reads/writes, low then high, for i386)
  • xhci_alloc_ring(size) — allocate a TRB ring (page-aligned, zeroed), place Link TRB at last entry pointing back to start
  • xhci_free_ring(ring) — free ring via kfree_aligned
  • xhci_alloc_ctx(ctxSize) — allocate device/input context (64-byte aligned, zeroed)
  • xhci_free_ctx(ctx) — free context via kfree_aligned
  • xhci_context_size(hcc) — returns 32 or 64 based on HCCPARAMS1.AC64 bit

Stage 3: Ring Management (~150 lines)

  • xhci_ring_enqueue(ring, trb) — copy TRB to ring at enqueue index, set cycle bit, advance enqueue. If next slot is Link TRB, toggle cycle and wrap to index 0.
  • xhci_ring_doorbell(priv, slotID, target) — write to doorbell register: DB[slotID] = target
  • xhci_event_pending(priv) — check if Event Ring has a pending event (cycle bit matches software CCS)
  • xhci_event_dequeue(priv, trb) — read next event TRB into trb, advance dequeue, handle segment wrap
  • xhci_event_advance_erdp(priv) — write current dequeue pointer to ERDP register
  • xhci_send_command(priv, trb) — enqueue command TRB, ring doorbell 0
  • xhci_wait_command(priv, completionTrb, timeout) — spin-poll Event Ring for Command Completion Event, return completion code
  • xhci_make_trb(param0, param1, status, control) — helper to build a TRB from components

Stage 4: Init/Reset/Start/Stop (~250 lines)

  • xhci_bios_handoff(priv, pciDev) — Walk XECP chain (HCCPARAMS1 bits 31:16) to find USBLEGSUP capability (ID=1). Set OS Owned Semaphore (bit 24), wait for BIOS Owned (bit 16) to clear. Same pattern as EHCI but via XECP pointer, not EECP.
  • xhci_reset(hc):
  • Clear USBCMD.RS, wait for USBSTS.HCH=1
  • Set USBCMD.HCRST, wait for self-clear AND USBSTS.CNR=0 (Controller Not Ready)
  • Allocate DCBAA: (MaxSlots + 1) * 8 bytes, 64-byte aligned
  • Allocate Command Ring: 256 TRBs (4KB page), Link TRB at entry 255
  • Allocate Event Ring: 256 TRBs (4KB page)
  • Allocate Event Ring Segment Table: 1 entry (16 bytes), 64-byte aligned
  • Allocate scratchpad buffers if HCSPARAMS2 indicates any needed
  • Allocate slot arrays (SlotPort, SlotSpeed, SlotRing, SlotCtx) sized to MaxSlots
  • xhci_start(hc):
  • Write CONFIG register: MaxSlots Enabled = MaxSlots
  • Write DCBAAP (64-bit) = physical address of DCBAA
  • Write CRCR (64-bit) = physical address of Command Ring | initial cycle bit
  • Program Interrupter 0: ERSTSZ=1, ERSTBA=physical ERST, ERDP=physical Event Ring base
  • Set USBCMD.RS=1, USBCMD.INTE=1, USBCMD.HSEE=1
  • Wait for USBSTS.HCH=0
  • xhci_stop(hc):
  • Clear USBCMD.RS
  • Wait for USBSTS.HCH=1
  • xhci_alloc_scratchpad(priv):
  • Read Max Scratchpad Bufs from HCSPARAMS2 (bits 25:21 high, bits 4:0 from HCSPARAMS2 high word)
  • If > 0: allocate scratchpad buffer array (N * 8 bytes, 64-byte aligned), allocate N pages, fill array with physical addresses, store array pointer at DCBAA[0]

Stage 5: Port Status & Reset (~200 lines)

  • xhci_port_status(hc, port):
  • Read PORTSC[port] at OpBase + $400 + (port * $10)
  • Return raw PORTSC value (bit 0 = CCS for usbcore compatibility)
  • xhci_port_speed(portsc):
  • Extract Port Speed field (bits 13:10)
  • Map: 1=Full, 2=Low, 3=High, 4=Super → USB_SPEED_*
  • xhci_port_reset(hc, port):
  • Read PORTSC, check CCS (Current Connect Status)
  • Determine if USB2 or USB3 port (xHCI assigns port numbers: USB2 ports first, then USB3)
  • For USB2: set PORTSC.PR (Port Reset, bit 4), wait for PRC (bit 21)
  • For USB3: set PORTSC.WPR (Warm Port Reset, bit 31) if needed, wait for PRC
  • Clear change bits (CSC, PRC, etc.) by writing 1s
  • Read speed from PORTSC
  • Enable Slot: Send Enable Slot command TRB, wait for completion → get Slot ID
  • Build Input Context: Allocate Input Context, set Input Control Context (Add flags for Slot + EP0), fill Slot Context (speed, route, root hub port, context entries=1), fill EP0 Context (EP type=Control Bidirectional, MaxPacketSize based on speed, TR Dequeue Pointer = EP0 ring, CErr=3, avg TRB length=8)
  • Address Device: Send Address Device command TRB (Input Context pointer, Slot ID), wait for completion
  • Store slot→port mapping, store EP0 ring pointer, store output device context pointer (from DCBAA[SlotID])
  • Set hc^.NextAddress to slot ID so usbcore assigns this as the device address
  • Return true on success

Stage 6: Submit & Poll (~250 lines)

  • xhci_find_slot(priv, devAddr) — look up slot ID from device address (for xHCI, dev address IS the slot ID)
  • xhci_submit_control(hc, transfer):
  • Intercept SET_ADDRESS: If transfer^.Setup.bRequest = USB_REQ_SET_ADDRESS, set transfer^.Status := tsSuccess, return true
  • Find slot ID from transfer^.Device^.Address
  • Get EP0 Transfer Ring for this slot
  • Build Setup Stage TRB: IDT=1 (Immediate Data), TRT (Transfer Type) based on direction, 8 bytes of setup data in Parameter fields
  • Build Data Stage TRB(s) if BufferLen > 0: physical buffer address, transfer length, direction bit, chain if multiple
  • Build Status Stage TRB: direction opposite to data (or IN if no data), IOC=1
  • Ring doorbell for slot (DB[SlotID], target = 1 for EP0)
  • Store transfer reference for poll completion
  • xhci_submit_bulk_intr(hc, transfer):
  • Find slot ID
  • Determine EP ring from endpoint address → DCI (Device Context Index = epNum * 2 + direction)
  • Build Normal TRB(s): buffer physical address, transfer length, IOC on last TRB
  • Ring doorbell (DB[SlotID], target = DCI)
  • xhci_submit(hc, transfer) — dispatch: control → xhci_submit_control, bulk/interrupt → xhci_submit_bulk_intr
  • xhci_poll(hc):
  • While event pending on Event Ring: a. Read event TRB b. Check TRB type: - Transfer Event ($20): Extract Slot ID, Endpoint ID, completion code, transfer length. Find matching pending transfer. Update transfer^.Status and transfer^.ActualLen. Free EP ring resources if needed. - Command Completion Event ($21): Handled by xhci_wait_command (set a flag/store result in priv data for the blocking waiter) - Port Status Change Event ($22): Log the event, could trigger hot-plug enumeration (future) c. Advance dequeue pointer
  • Write ERDP to acknowledge processed events

Stage 7: Load + UnitTests (~250 lines)

  • load:
  • PCI.getDeviceInfo($0C, $03, $30, count) — find xHCI controllers
  • For each controller: a. Read BAR0 (handle 64-bit BAR: BAR0 low + BAR1 high, mask low 4 bits; for i386 we only use the low 32 bits) b. Map MMIO: block := mmioBase SHR 22; force_alloc_block(block, 0); map_page(block, block) c. Enable PCI bus mastering: PCI.setBusMaster(bus, slot, func, true) d. Read capability registers: CAPLENGTH, HCIVERSION, HCSPARAMS1/2/3, HCCPARAMS1, DBOFF, RTSOFF e. Compute OpBase = mmioBase + CAPLENGTH, RTBase = mmioBase + RTSOFF, DBBase = mmioBase + DBOFF f. Allocate TXHCI_PrivData, populate fields g. BIOS handoff h. Build TUSBHCDriver record (same pattern as EHCI.load) i. Reset → Start → usbcore.register_hc → usbcore.scan_ports
  • UnitTest:
  • Structure size checks: TXHCI_TRB=16, TXHCI_SlotCtx=32, TXHCI_EPCtx=32, TXHCI_InputCtrlCtx=32, TXHCI_ERSTE=16
  • TRB construction helpers: verify type encoding, cycle bit, parameter placement
  • Ring allocation: verify page alignment, Link TRB at end
  • Ring enqueue/dequeue: verify cycle bit toggling on wrap
  • Context size helper: AC64=0 → 32, AC64=1 → 64
  • Port speed mapping: PORTSC speed field → USB_SPEED_*
  • Constant sanity checks (register offsets, TRB types, bit masks)
  • DCI calculation: epNum, direction → DCI
  • Estimated: ~80-100 assertions

Stage 8: kernel.pas Integration + Build

  • kernel.pas already has XHCI in the uses chain via USB.pas → XHCI (USB.pas imports XHCI)
  • But kernel.pas does NOT currently import XHCI directly or call XHCI.UnitTest
  • Changes needed:
  • Add XHCI to kernel.pas uses clause (after EHCI)
  • Add XHCI.UnitTest; call after EHCI.UnitTest;
  • Build: docker-compose run builder | Tee-Object -FilePath .\build.log
  • Check: Select-String -Path .\build.log -Pattern "Error|Fatal|Success|No errors|Failed" -CaseSensitive
  • Test: Enable USB 3.0 (xHCI) controller in VirtualBox VM settings, boot, check serial log

3. Concrete Record Definitions

TXHCI_TRB (16 bytes, 16-byte aligned)

PXHCI_TRB = ^TXHCI_TRB;
TXHCI_TRB = packed record
    Param0  : uint32;  { Parameter low dword (address low or immediate data) }
    Param1  : uint32;  { Parameter high dword (address high or immediate data) }
    Status  : uint32;  { Status / Transfer Length / Completion Code }
    Control : uint32;  { Cycle bit, TRB Type (bits 15:10), flags }
end;

TXHCI_Ring (software descriptor for a TRB ring)

PXHCI_Ring = ^TXHCI_Ring;
TXHCI_Ring = record
    Base      : PXHCI_TRB;  { Virtual base address of ring buffer }
    Size      : uint32;      { Number of TRB entries (including Link TRB) }
    Enqueue   : uint32;      { Current enqueue index }
    Dequeue   : uint32;      { Current dequeue index (Event Ring only) }
    CycleBit  : uint8;       { Current producer cycle state (0 or 1) }
end;

TXHCI_SlotCtx (32 bytes)

PXHCI_SlotCtx = ^TXHCI_SlotCtx;
TXHCI_SlotCtx = packed record
    RouteAndSpeed : uint32;  { Route String (19:0), Speed (23:20), MTT (25), Hub (26), Context Entries (31:27) }
    LatencyAndPort : uint32; { Max Exit Latency (15:0), Root Hub Port Number (23:16), Num Ports (31:24) }
    ParentInfo    : uint32;  { Parent Hub Slot ID (7:0), Parent Port (15:8), TTT (17:16), Interrupter Target (31:22) }
    DevStateAddr  : uint32;  { Device Address (7:0), Slot State (31:27) }
    Reserved      : array[0..3] of uint32; { Pad to 32 bytes }
end;

TXHCI_EPCtx (32 bytes)

PXHCI_EPCtx = ^TXHCI_EPCtx;
TXHCI_EPCtx = packed record
    EPInfo1     : uint32;  { Interval (23:16), LSA (15), MaxPStreams (14:10), Mult (9:8), EP State (2:0) }
    EPInfo2     : uint32;  { Max Packet Size (31:16), Max Burst Size (15:8), HID (7), EP Type (5:3), CErr (2:1) }
    TRDeqLo     : uint32;  { TR Dequeue Pointer Low (physical) | DCS (bit 0) }
    TRDeqHi     : uint32;  { TR Dequeue Pointer High }
    EPInfo3     : uint32;  { Average TRB Length (15:0), Max ESIT Payload (31:16) }
    Reserved    : array[0..2] of uint32; { Pad to 32 bytes }
end;

TXHCI_InputCtrlCtx (32 bytes)

PXHCI_InputCtrlCtx = ^TXHCI_InputCtrlCtx;
TXHCI_InputCtrlCtx = packed record
    DropFlags  : uint32;  { Drop Context flags (bits 31:2, bit 1=slot context) }
    AddFlags   : uint32;  { Add Context flags (bit 0=slot, bit 1=EP0, etc.) }
    Reserved   : array[0..5] of uint32; { Pad to 32 bytes }
end;

TXHCI_ERSTE (16 bytes, 64-byte aligned)

PXHCI_ERSTE = ^TXHCI_ERSTE;
TXHCI_ERSTE = packed record
    BaseAddrLo : uint32;  { Ring Segment Base Address Low (physical) }
    BaseAddrHi : uint32;  { Ring Segment Base Address High }
    SegSize    : uint32;  { Ring Segment Size (number of TRBs) }
    Reserved   : uint32;
end;

TXHCI_PrivData

PXHCI_PrivData = ^TXHCI_PrivData;
TXHCI_PrivData = record
    MMIOBase    : uint32;      { BAR0 MMIO base (virtual = physical) }
    OpBase      : uint32;      { Operational registers = MMIOBase + CAPLENGTH }
    RTBase      : uint32;      { Runtime registers = MMIOBase + RTSOFF }
    DBBase      : uint32;      { Doorbell array = MMIOBase + DBOFF }

    MaxSlots    : uint8;       { From HCSPARAMS1 bits 7:0 }
    MaxIntrs    : uint16;      { From HCSPARAMS1 bits 18:8 }
    MaxPorts    : uint8;       { From HCSPARAMS1 bits 31:24 }
    CtxSize     : uint8;       { 32 or 64 bytes per context entry }

    HCSPARAMS1  : uint32;      { Cached }
    HCSPARAMS2  : uint32;      { Cached }
    HCCPARAMS1  : uint32;      { Cached }

    DCBAA       : Pointer;     { Device Context Base Address Array (physical-accessible) }

    CmdRing     : TXHCI_Ring;  { Command Ring descriptor }
    EvtRing     : TXHCI_Ring;  { Event Ring descriptor (Interrupter 0) }
    ERST        : PXHCI_ERSTE; { Event Ring Segment Table }

    ScratchBufs : Pointer;     { Scratchpad buffer array (or nil) }
    NumScratch  : uint16;      { Number of scratchpad buffers }

    { Per-slot data (dynamically allocated arrays, indexed 1..MaxSlots) }
    SlotPort    : Pointer;     { ^array of uint8: slot → root port }
    SlotSpeed   : Pointer;     { ^array of uint8: slot → USB_SPEED_* }
    SlotRings   : Pointer;     { ^array of PXHCI_Ring: slot → EP0 Transfer Ring }
    SlotOutCtx  : Pointer;     { ^array of Pointer: slot → Output Device Context }

    { Pending command completion (for blocking xhci_wait_command) }
    CmdComplete   : boolean;
    CmdResult     : uint32;    { Completion TRB for last command }
    CmdResultCode : uint8;     { Completion code }

    PCIBus      : uint8;
    PCISlot     : uint8;
    PCIFunc     : uint8;
end;

4. Size Constraints

All hardware structures must be packed records with exact sizes: - TRB: 16 bytes, 16-byte aligned - Slot/Endpoint Context: 32 bytes each (standard, 64 bytes if AC64=1 in HCCPARAMS1) - Input Control Context: 32 bytes (or 64 if AC64=1) - Device Context: Slot + 31 EP Contexts = 32 * 32 = 1024 bytes (or 2048 with 64-byte contexts) - Input Context: Input Control + Slot + 31 EP = 33 * 32 = 1056 bytes (or 33 * 64 = 2112) - DCBAA: (MaxSlots + 1) * 8 bytes, 64-byte aligned - Event Ring Segment Table: 16 bytes per entry, 64-byte aligned - TRB Ring: N * 16 bytes (+ Link TRB), page aligned preferred - Scratchpad Buffer Array: MaxScratchpad * 8 bytes, 64-byte aligned


5. Known Constraints (Asuro-specific)

  • mod operator is broken on bare-metal i386 (promotes to int64). Use branch arithmetic instead.
  • PCI config access: PCI.requestConfig(bus, slot, func, row) + inl($CFC) for reads; PCI.writeConfig(bus, slot, func, row, val) for writes.
  • DMA addresses: use vtop() from vmemorymanager to convert virtual→physical.
  • MMIO mapping: block := mmioBase SHR 22; force_alloc_block(block, 0); map_page(block, block). May need multiple blocks if registers span a 4MB boundary.
  • No classes, no runtime library. Standard Pascal only.
  • Use kalloc()/kfree() for dynamic allocation. kalloc_aligned() for aligned allocations.
  • Large static arrays cause kernel bloat — prefer dynamic allocation.
  • Ring 0 execution — an exception = system hang. Defensive programming required.
  • 64-bit register access on i386: xHCI has many 64-bit registers (CRCR, DCBAAP, ERSTBA, ERDP). Must write as two 32-bit writes (low dword first, then high dword = 0 since we're 32-bit).

6. Lessons Learned from EHCI Implementation

These patterns from the EHCI driver (1717 lines) carry directly into xHCI:

  1. BIOS handoff — essential for VirtualBox; without it, BIOS may still own the controller
  2. MMIO block mapping — use force_alloc_block / map_page with mmioBase SHR 22
  3. PCI bus mastering — must call PCI.setBusMaster(bus, slot, func, true) for DMA
  4. vtop() for all hardware-visible pointers — every pointer written to HW registers or data structures must be physical
  5. HC driver record on stack, copied via register_hc — usbcore.register_hc copies the record into the LL; the returned hcEntry pointer is what gets passed to scan_ports
  6. Forward declarations for HC callbacks before implementation
  7. kalloc_aligned + memset(0) for all hardware structures
  8. Unit test pattern — local Assert/PrintSummary procedures, test helpers and constants without touching hardware
  9. Speed detection during port reset — determine speed from port status register, needed for device context setup

7. VirtualBox Testing Notes

  • Enable USB 3.0 (xHCI) controller in VM settings → Settings → USB → USB 3.0
  • VirtualBox emulates Intel 7 Series/C210 Series xHCI (VID=8086, DID=1E31) or similar
  • xHCI controller appears at PCI class $0C, subclass $03, prog_if $30
  • VirtualBox does NOT automatically attach USB devices — must add USB filters or manually attach
  • VBox emulated USB tablet (default pointing device) goes through OHCI/EHCI, not xHCI
  • For testing: attach a USB device via VBox USB filter, or use the built-in emulated devices
  • Serial log: Get-Content "C:\Temp\asuro.log" — filter for XHCI tag