Generate comprehensive documentation for all 173 source units and integrate mkdocs-material into the build toolchain. Documentation covers kernel core, x86 architecture, drivers, applications, and all subsystems with a tabbed navigation structure. - Add mkdocs.yml with material theme, mermaid support, and tabbed nav - Add doc/index.md landing page - Generate doc/src/ markdown for every Pascal unit in src/ - Reorganize planning docs into doc/planning/ - Add compat, lvglh, and toolchain doc sections - Update Dockerfile to install python3 and mkdocs-material (pinned <2) - Replace pasdoc-based compile_docs.sh with mkdocs build --strict - Add compile_docs.sh to the main build pipeline in compile.sh - Add deploy-docs step in .drone.yml with host volume mount to nginx - Add site/.gitkeep for build output directory
467 lines
25 KiB
Markdown
467 lines
25 KiB
Markdown
# 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)`:
|
||
1. Clear USBCMD.RS, wait for USBSTS.HCH=1
|
||
2. Set USBCMD.HCRST, wait for self-clear AND USBSTS.CNR=0 (Controller Not Ready)
|
||
3. Allocate DCBAA: `(MaxSlots + 1) * 8` bytes, 64-byte aligned
|
||
4. Allocate Command Ring: 256 TRBs (4KB page), Link TRB at entry 255
|
||
5. Allocate Event Ring: 256 TRBs (4KB page)
|
||
6. Allocate Event Ring Segment Table: 1 entry (16 bytes), 64-byte aligned
|
||
7. Allocate scratchpad buffers if HCSPARAMS2 indicates any needed
|
||
8. Allocate slot arrays (SlotPort, SlotSpeed, SlotRing, SlotCtx) sized to MaxSlots
|
||
- `xhci_start(hc)`:
|
||
1. Write CONFIG register: MaxSlots Enabled = MaxSlots
|
||
2. Write DCBAAP (64-bit) = physical address of DCBAA
|
||
3. Write CRCR (64-bit) = physical address of Command Ring | initial cycle bit
|
||
4. Program Interrupter 0: ERSTSZ=1, ERSTBA=physical ERST, ERDP=physical Event Ring base
|
||
5. Set USBCMD.RS=1, USBCMD.INTE=1, USBCMD.HSEE=1
|
||
6. Wait for USBSTS.HCH=0
|
||
- `xhci_stop(hc)`:
|
||
1. Clear USBCMD.RS
|
||
2. Wait for USBSTS.HCH=1
|
||
- `xhci_alloc_scratchpad(priv)`:
|
||
1. Read Max Scratchpad Bufs from HCSPARAMS2 (bits 25:21 high, bits 4:0 from HCSPARAMS2 high word)
|
||
2. 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)`:
|
||
1. Read PORTSC, check CCS (Current Connect Status)
|
||
2. Determine if USB2 or USB3 port (xHCI assigns port numbers: USB2 ports first, then USB3)
|
||
3. For USB2: set PORTSC.PR (Port Reset, bit 4), wait for PRC (bit 21)
|
||
4. For USB3: set PORTSC.WPR (Warm Port Reset, bit 31) if needed, wait for PRC
|
||
5. Clear change bits (CSC, PRC, etc.) by writing 1s
|
||
6. Read speed from PORTSC
|
||
7. **Enable Slot:** Send Enable Slot command TRB, wait for completion → get Slot ID
|
||
8. **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)
|
||
9. **Address Device:** Send Address Device command TRB (Input Context pointer, Slot ID), wait for completion
|
||
10. Store slot→port mapping, store EP0 ring pointer, store output device context pointer (from DCBAA[SlotID])
|
||
11. Set `hc^.NextAddress` to slot ID so usbcore assigns this as the device address
|
||
12. 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)`:
|
||
1. **Intercept SET_ADDRESS:** If `transfer^.Setup.bRequest = USB_REQ_SET_ADDRESS`, set `transfer^.Status := tsSuccess`, return true
|
||
2. Find slot ID from `transfer^.Device^.Address`
|
||
3. Get EP0 Transfer Ring for this slot
|
||
4. Build **Setup Stage TRB**: IDT=1 (Immediate Data), TRT (Transfer Type) based on direction, 8 bytes of setup data in Parameter fields
|
||
5. Build **Data Stage TRB(s)** if BufferLen > 0: physical buffer address, transfer length, direction bit, chain if multiple
|
||
6. Build **Status Stage TRB**: direction opposite to data (or IN if no data), IOC=1
|
||
7. Ring doorbell for slot (DB[SlotID], target = 1 for EP0)
|
||
8. Store transfer reference for poll completion
|
||
- `xhci_submit_bulk_intr(hc, transfer)`:
|
||
1. Find slot ID
|
||
2. Determine EP ring from endpoint address → DCI (Device Context Index = epNum * 2 + direction)
|
||
3. Build **Normal TRB(s)**: buffer physical address, transfer length, IOC on last TRB
|
||
4. Ring doorbell (DB[SlotID], target = DCI)
|
||
- `xhci_submit(hc, transfer)` — dispatch: control → `xhci_submit_control`, bulk/interrupt → `xhci_submit_bulk_intr`
|
||
- `xhci_poll(hc)`:
|
||
1. 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
|
||
2. Write ERDP to acknowledge processed events
|
||
|
||
### Stage 7: Load + UnitTests (~250 lines)
|
||
- `load`:
|
||
1. `PCI.getDeviceInfo($0C, $03, $30, count)` — find xHCI controllers
|
||
2. 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:
|
||
1. Add `XHCI` to kernel.pas uses clause (after EHCI)
|
||
2. Add `XHCI.UnitTest;` call after `EHCI.UnitTest;`
|
||
3. Build: `docker-compose run builder | Tee-Object -FilePath .\build.log`
|
||
4. Check: `Select-String -Path .\build.log -Pattern "Error|Fatal|Success|No errors|Failed" -CaseSensitive`
|
||
5. 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)
|
||
```pascal
|
||
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)
|
||
```pascal
|
||
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)
|
||
```pascal
|
||
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)
|
||
```pascal
|
||
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)
|
||
```pascal
|
||
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)
|
||
```pascal
|
||
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
|
||
```pascal
|
||
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
|