Der XHCI-Treiber (kernel/xhci.lyx) implementiert den USB 3.x Host-Controller-Treiber für Lyx OS. Er läuft vollständig in Ring-0 und wird von keiner externen USB-Stack-Library abhängig. Die Ring-3-Syscalls für USB sind noch nicht vergeben — Syscalls 150–155 sind durch Vega-Fensterverwaltung (150–152), RAM-Disk (153–154) und IOFS-mkfs (155) belegt. USB-Syscalls werden im XHCI-WP in einem freien Bereich ab 156+ zugewiesen.
→ Syscall-ABI · Kernel-Interna · Übersicht
XHCI (Extensible Host Controller Interface) ist der USB-Host-Controller-Standard ab USB 3.0. Er ersetzt EHCI (USB 2.0), OHCI und UHCI und unterstützt alle USB-Generationen (1.0–3.2) in einem einzigen Controller.
Zuständigkeiten des Treibers:
| Schicht | Zuständigkeit |
|---|---|
| PCI-Erkennung | XHCI-Controller auf dem PCI-Bus finden (Class 0x0C / Sub 0x03 / ProgIF 0x30) |
| MMIO-Mapping | Capability-, Operational-, Runtime- und Doorbell-Register in Kernel-Address-Space mappen |
| HC-Initialisierung | Controller-Reset, Slot-Enable, Command-Ring und Event-Ring einrichten |
| Port-Management | Port-Reset, Speed-Erkennung, Gerät adressieren |
| Transfer-Engine | TRB-Ringe für Control-, Bulk- und Interrupt-Transfers verwalten |
| Ring-3-Interface | USB-Syscalls TBD (ab 156+, erst mit XHCI-WP vergeben; 150–155 = Vega/RAM-Disk/IOFS) |
Quelldatei: kernel/xhci.lyx
Der XHCI-Controller wird als PCI-Gerät erkannt:
| Feld | Wert | Bedeutung |
|---|---|---|
| Class Code | 0x0C | Serial Bus Controller |
| Subclass | 0x03 | USB Controller |
| Prog IF | 0x30 | xHCI |
// PCI-Scan: XHCI-Controller finden
con XHCI_PCI_CLASS : int64 := 0x0C;
con XHCI_PCI_SUB : int64 := 0x03;
con XHCI_PCI_PROGIF : int64 := 0x30;
Der Treiber liest die BAR0-Basisadresse aus dem PCI-Konfigurationsraum (BAR0 ist immer MMIO, 64-bit). Die physische Adresse wird via sys_mmap_device in den Kernel-Address-Space gemappt.
XHCI kennt vier Register-Bereiche, alle relativ zur BAR0-Basisadresse:
BAR0 ──┬── Capability Registers (Offset 0, Länge = CAPLENGTH)
├── Operational Registers (Offset = CAPLENGTH)
├── Runtime Registers (Offset = RTSOFF, aus CapRegs)
└── Doorbell Array (Offset = DBOFF, aus CapRegs)
Nur lesbar. Beschreiben die Hardware-Eigenschaften des Controllers.
| Offset | Name | Beschreibung |
|---|---|---|
| +0 | CAPLENGTH (uint8) | Länge des CapRegs-Blocks; Startadresse der OpRegs |
| +2 | HCIVERSION (uint16) | xHCI-Spezifikationsversion (z.B. 0x0100 = v1.0) |
| +4 | HCSPARAMS1 (uint32) | Bits[7:0]=MaxSlots, Bits[18:8]=MaxIntrs, Bits[31:24]=MaxPorts |
| +8 | HCSPARAMS2 (uint32) | IST, ERST_MAX, MAX_SCRATCHPAD_BUFS |
| +12 | HCSPARAMS3 (uint32) | U1/U2-Exit-Latenzzeiten |
| +16 | HCCPARAMS1 (uint32) | Capability-Bits: 64-bit Addressing, BNC, AC64, CSZ usw. |
| +20 | DBOFF (uint32) | Byte-Offset der Doorbell-Array-Base relativ zu BAR0 |
| +24 | RTSOFF (uint32) | Byte-Offset der Runtime-Register-Base relativ zu BAR0 |
| +28 | HCCPARAMS2 (uint32) | Erweiterte Capability-Bits (USB 3.1+) |
con XHCI_CAP_CAPLENGTH : int64 := 0;
con XHCI_CAP_HCIVERSION : int64 := 2;
con XHCI_CAP_HCSPARAMS1 : int64 := 4;
con XHCI_CAP_HCSPARAMS2 : int64 := 8;
con XHCI_CAP_HCCPARAMS1 : int64 := 16;
con XHCI_CAP_DBOFF : int64 := 20;
con XHCI_CAP_RTSOFF : int64 := 24;
Lese-/schreibbar. Steuern den Controller-Betrieb.
| Offset | Name | Beschreibung | |
|---|---|---|---|
| +0 | USBCMD (uint32) | Run/Stop (Bit 0), HC-Reset (Bit 1), Interrupt-Enable (Bit 2) | |
| +4 | USBSTS (uint32) | HCHalted (Bit 0), Host-Error (Bit 2), Event-Int (Bit 3) | |
| +8 | PAGESIZE (uint32) | Bits[15:0]: unterstützte Page-Größen (Bit N = 2 | (N+12) Bytes) |
| +20 | DNCTRL (uint32) | Device-Notification-Control | |
| +24 | CRCR (uint64) | Command-Ring-Control: RCS, CS, CA, CRR, Ring-Pointer | |
| +48 | DCBAAP (uint64) | Device-Context-Base-Address-Array-Pointer | |
| +56 | CONFIG (uint32) | Bits[7:0]: MaxSlotsEn (max. aktivierte Slots) |
con XHCI_OP_USBCMD : int64 := 0;
con XHCI_OP_USBSTS : int64 := 4;
con XHCI_OP_PAGESIZE: int64 := 8;
con XHCI_OP_DNCTRL : int64 := 20;
con XHCI_OP_CRCR : int64 := 24;
con XHCI_OP_DCBAAP : int64 := 48;
con XHCI_OP_CONFIG : int64 := 56;
// USBCMD-Bits
con XHCI_CMD_RUN : int64 := 1; // Run/Stop
con XHCI_CMD_HCRST : int64 := 2; // HC-Reset
con XHCI_CMD_INTE : int64 := 4; // Interrupter-Enable
con XHCI_CMD_HSEE : int64 := 8; // Host-System-Error-Enable
// USBSTS-Bits
con XHCI_STS_HCH : int64 := 1; // HC Halted
con XHCI_STS_HSE : int64 := 4; // Host System Error
con XHCI_STS_EINT : int64 := 8; // Event Interrupt
con XHCI_STS_PCD : int64 := 16; // Port Change Detected
con XHCI_STS_CNR : int64 := 0x800; // Controller Not Ready
| Offset | Name | Beschreibung |
|---|---|---|
| +0 | MFINDEX (uint32) | Microframe-Index (125 µs-Ticks) |
| +32 | IR[0] | Interrupter-Register-Set 0 (IMAN, IMOD, ERSTSZ, ERSTBA, ERDP) |
Interrupter-0-Offsets (relativ zu IR[0]-Basis = RTSOFF + 32):
| Offset | Name | Beschreibung |
|---|---|---|
| +0 | IMAN (uint32) | Interrupt-Management: IE (Bit 1), IP (Bit 0) |
| +4 | IMOD (uint32) | Interrupt-Moderation: IMODI (Bits[15:0]) |
| +8 | ERSTSZ (uint32) | Event-Ring-Segment-Table-Size |
| +16 | ERSTBA (uint64) | Event-Ring-Segment-Table-Base-Address |
| +24 | ERDP (uint64) | Event-Ring-Dequeue-Pointer |
con XHCI_RT_MFINDEX : int64 := 0;
con XHCI_RT_IR0 : int64 := 32; // Basis des ersten Interrupters
// Offsets innerhalb IR[0]
con XHCI_IR_IMAN : int64 := 0;
con XHCI_IR_IMOD : int64 := 4;
con XHCI_IR_ERSTSZ : int64 := 8;
con XHCI_IR_ERSTBA : int64 := 16;
con XHCI_IR_ERDP : int64 := 24;
Ein 32-bit-Eintrag pro Slot (Slot 0 = Host-Controller-Command-Ring). Schreiben löst einen Transfer aus.
con XHCI_DB_HC : int64 := 0; // Doorbell 0: Command-Ring
// Doorbell N (N = Slot-ID): DB_TARGET = Endpoint-Index (Bits[7:0])
Jede Operation in XHCI ist ein 16-Byte-TRB. TRBs werden in Ringen arrangiert.
Byte 0– 7: Parameter (Adresse, Daten, abhängig vom TRB-Typ)
Byte 8–11: Status (Byte-Count, Interrupter-Target)
Byte 12–15: Control (Bits[9:0]=TRB-Typ, Bit15=C=Cycle-Bit, typ-spezifische Flags)
con XHCI_TRB_SIZE : int64 := 16;
// TRB-Typen (Control-Bits[9:0])
con TRB_NORMAL : int64 := 1; // Bulk / Interrupt Transfer
con TRB_SETUP : int64 := 2; // Control Setup-Paket
con TRB_DATA : int64 := 3; // Control Data-Phase
con TRB_STATUS : int64 := 4; // Control Status-Phase
con TRB_ISOCH : int64 := 5; // Isochroner Transfer
con TRB_LINK : int64 := 6; // Ring-Link (letzter TRB im Ring)
con TRB_EVT_DATA : int64 := 7; // Event-Data
con TRB_NOOP : int64 := 8; // No-Op Transfer
con TRB_ENABLE_SLOT : int64 := 9; // Command: Slot aktivieren
con TRB_DISABLE_SLOT : int64 := 10; // Command: Slot deaktivieren
con TRB_ADDRESS_DEV : int64 := 11; // Command: Gerät adressieren
con TRB_CONFIG_EP : int64 := 12; // Command: Endpoint konfigurieren
con TRB_EVAL_CTX : int64 := 13; // Command: Context evaluieren
con TRB_NOOP_CMD : int64 := 23; // No-Op Command
con TRB_XFER_EVENT : int64 := 32; // Event: Transfer abgeschlossen
con TRB_CMD_COMPL : int64 := 33; // Event: Command abgeschlossen
con TRB_PORT_STATUS : int64 := 34; // Event: Port-Status geändert
// TRB-Control-Bits
con TRB_C : int64 := 1; // Cycle-Bit
con TRB_TC : int64 := 2; // Toggle-Cycle (nur LINK-TRB)
con TRB_ISP : int64 := 4; // Interrupt-on-Short-Packet
con TRB_IOC : int64 := 0x20; // Interrupt-on-Completion
con TRB_IDT : int64 := 0x40; // Immediate-Data (≤8 Bytes direkt im TRB)
con XHCI_ERST_SIZE : int64 := 16;
// +0 (uint64): Ring-Segment-Base-Address (4K-aligned)
// +8 (uint32): Ring-Segment-Size (Anzahl TRBs im Segment)
// +12 (uint32): reserviert
Ein Array von uint64-Zeigern (Slot 0 bis MaxSlots). Slot 0 zeigt auf den Scratchpad-Buffer-Array (oder 0 wenn MaxScratchpad=0). Jeder weitere Eintrag zeigt auf den Output-Device-Context des entsprechenden Slots.
Wird für ADDRESS_DEVICE- und CONFIGURE_ENDPOINT-Commands genutzt. Besteht aus:
con XHCI_CTX_SIZE : int64 := 32; // Wenn CSZ=0 in HCCPARAMS1
con XHCI_CTX_SIZE_64 : int64 := 64; // Wenn CSZ=1 (64-Byte-Contexts)
// Slot-Context-Felder (+0 im Slot-Context-Block)
// Bits[19:10] = Root-Hub-Port-Num, Bits[23:20] = NumContextEntries,
// Bits[29:27] = Speed (1=Full, 2=Low, 3=High, 4=SuperSpeed)
con XHCI_SPEED_FS : int64 := 1;
con XHCI_SPEED_LS : int64 := 2;
con XHCI_SPEED_HS : int64 := 3;
con XHCI_SPEED_SS : int64 := 4;
// Endpoint-Typ (EP-Context Bits[5:3])
con XHCI_EP_ISOCH_OUT : int64 := 1;
con XHCI_EP_BULK_OUT : int64 := 2;
con XHCI_EP_INTR_OUT : int64 := 3;
con XHCI_EP_CTRL : int64 := 4;
con XHCI_EP_ISOCH_IN : int64 := 5;
con XHCI_EP_BULK_IN : int64 := 6;
con XHCI_EP_INTR_IN : int64 := 7;
Die Initialisierungsreihenfolge folgt dem xHCI-Spec §4.2:
1. BAR0 lesen, MMIO mappen
2. Warten bis USBSTS.CNR = 0 (Controller Ready)
3. USBCMD.RUN = 0 setzen → Controller anhalten
4. Warten bis USBSTS.HCH = 1
5. USBCMD.HCRST = 1 → Controller-Reset
6. Warten bis HCRST = 0 und CNR = 0
7. DCBAAP setzen (alloc + nullen)
8. Command-Ring initialisieren (CRCR setzen, Cycle-Bit = 1)
9. Event-Ring initialisieren (ERST alloc, ERSTBA + ERSTSZ im IR[0] setzen)
10. Interrupter-0 aktivieren: IMAN.IE = 1
11. MaxSlotsEn in CONFIG setzen
12. USBCMD.RUN = 1 → Controller starten
13. Warten bis USBSTS.HCH = 0
14. Ports scannen (→ Abschnitt 6)
Wichtig: Alle Ring-Puffer müssen physisch kontiguös und auf 64 Bytes aligned sein. Der Treiber nutzt den Kernel-PMM direkt (kein VFS-Alloc).
Jeder Port hat einen 16-Byte-Block ab OpRegs + 0x400:
con XHCI_PORT_BASE : int64 := 0x400; // Offset in OpRegs
con XHCI_PORT_STRIDE : int64 := 16; // Bytes pro Port
// Innerhalb eines Port-Blocks:
con XHCI_PORT_SC : int64 := 0; // Port-Status-and-Control (uint32)
con XHCI_PORT_PMSC : int64 := 4; // Port-Power-Management-Status-and-Control
con XHCI_PORT_LI : int64 := 8; // Port-Link-Info
con XHCI_PORT_HLPMC : int64 := 12; // Port-Hardware-LPM-Control
// PORTSC-Bits
con XHCI_PORTSC_CCS : int64 := 1; // Current Connect Status
con XHCI_PORTSC_PED : int64 := 2; // Port Enabled/Disabled
con XHCI_PORTSC_PR : int64 := 0x10; // Port Reset
con XHCI_PORTSC_PLS : int64 := 0x1E0; // Port-Link-State (Bits[8:5])
con XHCI_PORTSC_PP : int64 := 0x200; // Port Power
con XHCI_PORTSC_SPD : int64 := 0x3C00; // Port-Speed (Bits[13:10])
con XHCI_PORTSC_PRC : int64 := 0x200000; // Port-Reset-Change (quittieren)
1. PORTSC.CCS prüfen → Gerät angeschlossen?
2. PORTSC.PR = 1 setzen → Port-Reset
3. Warten auf PORTSC.PRC = 1 (Reset abgeschlossen), dann quittieren
4. PORTSC.SPD lesen → Geschwindigkeit bestimmen
5. Command: ENABLE_SLOT → Slot-ID erhalten
6. Input-Context allozieren, Slot-Context befüllen (Port, Geschwindigkeit)
7. Control-Endpoint-0-Context befüllen (MaxPacketSize nach Speed)
8. DCBAA[slot_id] = Output-Context-Adresse
9. Command: ADDRESS_DEVICE (BSR=1 = Block Set Address Request für ersten Get-Descriptor)
10. Control-Transfer: GET_DESCRIPTOR (Device, 8 Bytes) → bMaxPacketSize0 lesen
11. Command: ADDRESS_DEVICE (BSR=0) → SET_ADDRESS automatisch vom HC
12. Control-Transfer: GET_DESCRIPTOR (Device, 18 Bytes) → VID/PID, Class, Configs
13. Gerät in interner Tabelle eintragen (vid, pid, slot_id, port)
Jeder aktive Endpoint hat einen eigenen Transfer-Ring (TR). Der TR ist ein zirkulärer Puffer aus TRBs, abgeschlossen durch einen LINK-TRB der zurück auf den Ring-Anfang zeigt.
Consumer: XHCI-Hardware liest TRBs vom Dequeue-Pointer
Producer: Treiber schreibt TRBs ab Enqueue-Pointer
Cycle-Bit (C): unterscheidet alte von neuen TRBs (toggelt bei jedem Rindumlauf)
Ein Control-Transfer besteht aus 3 TRBs (Setup → Data → Status):
// 1. Setup-TRB (TRB_SETUP)
// Parameter[0..7] = 8-Byte-USB-Setup-Packet (bmReqType, bReq, wVal, wIdx, wLen)
// Status: Bits[16:0] = 8 (Länge des Setup-Packets)
// Control: TRB_SETUP | IDT=1 (Immediate Data) | TRT (Transfer Type: 2=OUT, 3=IN)
// 2. Data-TRB (TRB_DATA) — optional, nur wenn wLength > 0
// Parameter = physische Adresse des Datenpuffers
// Status: Bits[16:0] = wLength
// Control: TRB_DATA | DIR=1 wenn IN | IOC=1
// 3. Status-TRB (TRB_STATUS)
// Control: TRB_STATUS | DIR (umgekehrt zur Data-Phase) | IOC=1
Einzelner NORMAL-TRB (oder Kette via Chain-Bit für >64 KB):
// Normal-TRB (TRB_NORMAL)
// Parameter = physische Adresse des Datenpuffers
// Status: Bits[16:0] = Byte-Count, Bits[31:22] = Interrupter-Target (0)
// Control: TRB_NORMAL | IOC=1
Nach dem Schreiben des TRB: Doorbell klingeln (poke32 auf DB[slot_id], EP-Index).
1. ERDP aus IR[0] lesen (aktueller Dequeue-Pointer)
2. Auf TRB_XFER_EVENT oder TRB_CMD_COMPL warten:
- Cycle-Bit am aktuellen ERDP muss gleich dem Producer-Cycle-Bit sein
- Event-TRB enthält: Completion-Code (Bits[31:24] in Status), Slot-ID, EP-Index
3. ERDP um XHCI_TRB_SIZE vorwärtssetzen (mit HC-Handshake-Bit = 1)
4. IMOD-Timer zurücksetzen (IMAN.IP = 1 schreiben)
Completion-Codes:
| Code | Wert | Bedeutung |
|---|---|---|
CC_SUCCESS | 1 | Transfer erfolgreich |
CC_DATA_BUFFER_ERROR | 2 | DMA-Fehler |
CC_BABBLE_DETECTED | 3 | Gerät sendet zu viele Daten |
CC_USB_TRANSACTION_ERROR | 4 | USB-Bus-Fehler (CRC, Timeout) |
CC_TRB_ERROR | 5 | Ungültiger TRB |
CC_STALL_ERROR | 6 | Endpoint STALL |
CC_SHORT_PACKET | 13 | Weniger Daten als angefordert (kein Fehler) |
CC_STOPPED | 26 | Transfer durch STOP_ENDPOINT-Command gestoppt |
con XHCI_CC_SUCCESS : int64 := 1;
con XHCI_CC_TRB_ERROR : int64 := 5;
con XHCI_CC_STALL : int64 := 6;
con XHCI_CC_SHORT : int64 := 13;
Die USB-Syscall-Nummern sind noch nicht vergeben. Syscalls 150–155 sind belegt:
| Syscall-Nr | Belegt durch |
|---|---|
| 150 | sys_win_get_title (WP27) |
| 151 | sys_win_get_geom (WP29) |
| 152 | sys_win_get_pid (WP29) |
| 153 | sys_ramdisk_create (WP30) → RAM-Disk |
| 154 | sys_ramdisk_fmt (WP30) → RAM-Disk |
| 155 | sys_mkfs_iofs (WP01+WP09) → IOFS |
Die geplante USB-Ring-3-Schnittstelle wird im XHCI-WP auf freien Nummern ab 156+ definiert:
| Geplante Funktion | Treiber-Funktion | Bemerkung |
|---|---|---|
sys_usb_enum (TBD) | XhciEnumDevices(buf, max) | Geräteliste in Ring-3-Puffer kopieren |
sys_usb_open (TBD) | XhciOpenByVidPid(vid, pid) | Gerät per VID/PID öffnen → usb_fd |
sys_usb_ctrl (TBD) | XhciControlTransfer(slot, setup, data, len) | Control Transfer mit Bounce-Buffer |
sys_usb_bulk (TBD) | XhciBulkTransfer(slot, ep, buf, len) | Bulk Transfer via DMA |
sys_usb_intr_read (TBD) | XhciIntrRead(slot, ep, buf, len) | Interrupt-Endpoint, blockierend |
sys_usb_close (TBD) | XhciClose(slot) | DISABLE_SLOT + Tabellenplatz freigeben |
Bounce-Buffer-Strategie: Control-Transfers nutzen einen kleinen Kernel-seitigen Puffer (max. 4096 Bytes), der sicher ins Ring-3-VA zurückkopiert wird. Bulk/Interrupt nutzen direkte DMA auf die physische Adresse des Ring-3-Puffers (Boundary: Ring-3-Seiten müssen im PMM bekannt sein).
Der Treiber hält eine statische Tabelle für max. 32 gleichzeitig angeschlossene Geräte:
// Eintrag: 64 Bytes
// +0 vid (int64)
// +8 pid (int64)
// +16 slot_id (int64) — XHCI-Slot (1–MaxSlots)
// +24 port (int64) — Root-Hub-Port-Nummer
// +32 speed (int64) — XHCI_SPEED_*
// +40 ep0_ring (int64) — physische Adresse des EP0-Transfer-Rings
// +48 state (int64) — 0=frei, 1=aktiv, 2=error
// +56 (reserviert)
con XHCI_DEV_ENTRY_SIZE : int64 := 64;
con XHCI_MAX_DEVICES : int64 := 32;
PmmAllocContig).HCSPARAMS2.MaxScratchpadBufs > 0, muss der Treiber entsprechend viele 4K-Seiten allozieren und deren Adressen in ein Array schreiben — Slot-0 des DCBAA zeigt auf dieses Array.HCCPARAMS1 » 16 × 4 = Offset der Ext-Cap-Liste) für USB2-Port-Mapping.sys_irq_bind wird als Ausbaustufe implementiert.| Modul | Datei |
|---|---|
| XHCI-Treiber | kernel/xhci.lyx |
Letzte Aktualisierung: 2026-06-17