1. Die 10 Schritte der Initialisierung
Im vorherigen Kapitel haben wir die Grundlagen erstellt, damit wir den USB-Kontroller Initialisieren können. Die ersten Strukturen sind erzeugt. Die Funktion usb_Initialize() führt genau 10 Schritte aus, um den Controller betriebsbereit zu machen. Schauen wir uns jeden einzelnen an:
📋 Übersicht der 10 Schritte
================================================================
xHCI HOST CONTROLLER INITIALISIERUNG (10 Schritte)
================================================================
[1 ] PCIe Host Bridge aktivieren (xHCI liegt am PCIe-Bus)
[2 ] xHCI-Gerät als PCIe-Device konfigurieren
[3 ] Slot Manager + DCBAA (Telefonbuch der USB-Geräte)
[4 ] Event Manager + ERST (Briefkasten für Controller-Events)
[5 ] Command Manager + CRCR (Befehls-Ring der CPU)
[6 ] Scratchpad-Buffer (temporärer Arbeitsspeicher)
[7 ] Root Hub (die physikalischen USB-Ports)
[8 ] IRQ-Handler verbinden
[9 ] Controller starten (RUN/STOP-Bit)
[10] Ports initialisieren + Geräte erkennen
================================================================
Den zehnten Schritt werden wir erst im nächsten Kapitel beschreiben, da bereits dieser Teil der Schritte 1 bis 9 enorm viel Code ist.
Schritt 1: PCIe Host Bridge aktivieren
PCIeHostBridge_Initialize();
Der xHCI-Controller ist ein PCIe-Gerät. Bevor wir ihn ansprechen können, muss die PCIe Host Bridge initialisiert werden. Diese Bridge ist das "Tor" zwischen dem ARM-SoC und dem PCIe-Bus.
Was passiert hier?
- Reset-Sequenz für den PCIe-Controller
- Konfiguration der Outbound Memory Windows (CPU ↔ PCIe Adress-Mapping)
- Einrichtung der MSI-Interrupts
- Aktivierung der PCIe-Verbindung
🔍 Register-Tiefblick: In
pcie.csiehst du, wie wir über das RegisterPCIE_MISC_MISC_CTRLden SCB-Zugriff aktivieren und überPCIE_RGR1_SW_INIT_1den Reset durchführen.
Schritt 2: xHCI-Device aktivieren
PCIeHostBridge_EnableDevice(XHCI_PCI_CLASS_CODE, XHCI_PCIE_SLOT, XHCI_PCIE_FUNC);
Jetzt aktivieren wir das xHCI-Device selbst. Es sitzt an Slot 0, Funktion 0 des PCIe-Bus 1.
Gleichzeitig lesen wir die Hardware-Capabilities aus:
| Register | Bedeutung | Typischer Wert |
| HCSPARAMS1 | Max. Slots, Max. Ports | z.B. 32 Slots, 5 Ports |
| HCSPARAMS2 | Scratchpad-Buffer, IST | z.B. 31 Scratchpad-Buffer |
💡 Wichtig: Diese Werte sind hardwareabhängig! Wir lesen sie aus, statt sie fest zu codieren. So funktioniert der Code auch auf anderen xHCI-Controllern.
Schritt 3: Slot Manager + DCBAA
s_slotManager.m_pDCBAA = (u64 *)CXHCIDevice_AllocateSharedMem(
(XHCI_CONFIG_MAX_SLOTS + 1) * sizeof(u64), 64, XHCI_PAGE_SIZE);
write32((u32)(dma & 0xFFFFFFFF), XHCIMMIOSpace_OpBase + XHCI_REG_OP_DCBAAP);
write32((u32)(dma >> 32), XHCIMMIOSpace_OpBase + XHCI_REG_OP_DCBAAP + 4);
DCBAA = Device Context Base Address Array
Das ist das Telefonbuch des xHCI-Controllers. Für jeden USB-Slot (jedes angeschlossene Gerät) steht hier die Adresse des Device-Contexts – also der kompletten Zustandsbeschreibung des Geräts.
🎓 Analogie: Stell dir vor, der xHCI-Controller ist ein Hotel-Rezeptionist. Das DCBAA ist das Gästebuch: "Zimmer 1 = Maus, Zimmer 2 = Tastatur, ..."
Ohne DCBAA: Der Controller weiß nicht, wo die Daten der angeschlossenen Geräte liegen!
Schritt 4: Event Manager + ERST
CXHCIRing_Create(&s_eventManager.m_EventRing, XHCIRingTypeEvent, XHCI_CONFIG_EVENT_RING_SIZE);
s_eventManager.m_pERST = (TXHCIERSTEntry *)CXHCIDevice_AllocateSharedMem(...);
ERST = Event Ring Segment Table
Der xHCI-Controller arbeitet asynchron. Wenn er einen Befehl abgearbeitet hat, legt er ein Event in den Event-Ring. Die CPU muss diese Events regelmäßig abholen.
🎓 Analogie: Der Event-Ring ist der Briefkasten des Controllers. Er wirft seine Antworten dort ein, und die CPU muss regelmäßig nachschauen, ob Post da ist.
Ohne ERST: Der Controller weiß nicht, wohin er seine Events schreiben soll!
Schritt 5: Command Manager + CRCR
CXHCIRing_Create(&s_commandManager.m_CmdRing, XHCIRingTypeCommand, XHCI_CONFIG_CMD_RING_SIZE);
write32(low, XHCIMMIOSpace_OpBase + XHCI_REG_OP_CRCR);
CRCR = Command Ring Control Register
Der Command-Ring ist der Briefkasten der CPU an den Controller. Wir schreiben Befehle (TRBs = Transfer Request Blocks) hinein und "läuten" an der Doorbell. Der Controller holt die Befehle ab und arbeitet sie ab.
Typische Befehle:
ENABLE_SLOT– Einen neuen Slot für ein Gerät reservierenADDRESS_DEVICE– Dem Gerät eine USB-Adresse zuweisenCONFIGURE_ENDPOINT– Endpunkte des Geräts konfigurieren
🎓 Analogie: Der Command-Ring ist die Wunschliste an den Controller. "Bitte aktiviere Slot 1", "Bitte konfiguriere Endpoint 2", ...
Ohne CRCR: Die CPU kann keine Befehle an den Controller senden!
Schritt 6: Scratchpad-Buffer
pXHCIDevice->m_pScratchpadBuffers = CXHCIDevice_AllocateSharedMem(
XHCI_PAGE_SIZE * nMaxScratchpadBufs, XHCI_PAGE_SIZE, XHCI_PAGE_SIZE);
Der Scratchpad-Buffer ist der temporäre Notizblock des Controllers. Manche xHCI-Controller brauchen Arbeitsspeicher für interne Berechnungen. Die Anzahl steht in HCSPARAMS2.
💡 Interessant: Die Adresse des Scratchpad-Arrays wird in DCBAA[0] geschrieben – also im "Telefonbuch" an der Stelle, wo sonst Geräte stehen. Das ist kein Zufall: Slot 0 ist für den Controller selbst reserviert!
Ohne Scratchpad: Manche Controller verweigern den Start komplett!
Schritt 7: Root Hub vorbereiten
s_xhciRootHub.m_nPorts = nMaxPorts;
for (unsigned nPort = 0; nPort < s_xhciRootHub.m_nPorts; nPort++) {
s_xhciRootHub.m_pRootPort[nPort].m_nPortIndex = nPort;
...
}
Der Root Hub ist der physikalische USB-Hub des Controllers. Er hat typischerweise 5 Ports – das sind die USB-A-Buchsen am Raspberry Pi 4.
Wir initialisieren hier nur die Port-Strukturen. Die eigentliche Konfiguration passiert in Schritt 10.
Schritt 8: IRQ verbinden
Interrupt_ConnectIRQ(ARM_IRQ_XHCI, CXHCIDevice_InterruptStub, pXHCIDevice);
Der xHCI-Controller löst Interrupts aus, wenn:
- Ein Befehl abgearbeitet wurde
- Ein USB-Gerät eingesteckt wurde
- Ein Transfer abgeschlossen ist
Unser Handler CXHCIDevice_InterruptHandler wird dann aufgerufen und holt die Events aus dem Event-Ring.
⚠️ Wichtig: Der IRQ-Handler muss extrem schnell sein! Keine
printf()-Ausgaben im IRQ-Kontext – das würde zu Timeouts führen.
Schritt 9: Controller starten
write32(read32(XHCIMMIOSpace_OpBase + XHCI_REG_OP_USBCMD) | XHCI_REG_OP_USBCMD_INTE, ...);
write32(read32(XHCIMMIOSpace_OpBase + XHCI_REG_OP_USBCMD) | XHCI_REG_OP_USBCMD_RUN_STOP, ...);
Jetzt wird der Controller gestartet:
INTE= Interrupt Enable – Interrupts erlaubenRUN/STOP= Controller aus dem HALT-Zustand holen
Kontrolle: Wir lesen das USBSTS-Register. Wenn das Bit HCH (Host Controller Halted) nicht gesetzt ist, läuft der Controller!
Schritt 10: Ports initialisieren (Ausblick auf das nächste Kapitel)
// Dieser Schritt wird im nächsten Tutorial-Teil behandelt!
for (unsigned nPort = 0; nPort < s_xhciRootHub.m_nPorts; nPort++) {
CXHCIRootPort_Initialize(&s_xhciRootHub.m_pRootPort[nPort]);
CXHCIRootPort_Configure(&s_xhciRootHub.m_pRootPort[nPort]);
}
Was in Schritt 10 passiert:
- Port-Reset durchführen (USB 2.0) bzw. auf U0-Zustand warten (USB 3.0)
- Port-Speed auslesen (Low/Full/High/SuperSpeed)
- Enable Slot Command an den Controller senden
- Address Device Command – dem Gerät eine USB-Adresse zuweisen
- Device Descriptor auslesen (Vendor ID, Product ID, ...)
- Configuration Descriptor auslesen (Interfaces, Endpoints)
- Treiber binden (Maus, Tastatur, Mass Storage)
Das ist der spannendste Teil – hier "erwachen" die USB-Geräte zum Leben! Aber er ist so umfangreich, dass wir ihm ein eigenes Kapitel widmen.
4. Der Shared-Memory-Allocator
Eine Besonderheit des xHCI-Codes ist der Shared-Memory-Allocator (CXHCISharedMemAllocator). Warum brauchen wir den?
Das Problem: Der xHCI-Controller greift über DMA (Direct Memory Access) auf den Speicher zu. Er sieht die physikalischen Adressen, nicht die virtuellen. Und er hat strenge Anforderungen:
- Alignment: Manche Strukturen müssen an 64-Byte-Grenzen liegen
- Boundary: Start und Ende einer Allokation dürfen keine 4KB-Grenze überschreiten
Die Lösung: Ein spezieller Allocator im Coherent Memory (nicht cachebar), der diese Anforderungen automatisch erfüllt.
🎓 Analogie: Stell dir vor, du musst Pakete verschicken, die genau in einen Briefkasten passen. Der Allocator ist dein "Packing-Service", der sicherstellt, dass jedes Paket die richtige Größe hat.
5. Zusammenfassung
In diesem Kapitel haben wir:
- ✅ Die PCIe Host Bridge aktiviert
- ✅ Den xHCI-Controller als PCIe-Device konfiguriert
- ✅ Das DCBAA (Telefonbuch der Geräte) angelegt
- ✅ Den Event-Ring (Briefkasten des Controllers) eingerichtet
- ✅ Den Command-Ring (Wunschliste der CPU) erstellt
- ✅ Den Scratchpad-Buffer alloziert
- ✅ Den Root Hub vorbereitet
- ✅ Den IRQ-Handler verbunden
- ✅ Den Controller gestartet
Was als Nächstes kommt:
Im nächsten Kapitel werden wir die USB-Ports initialisieren und die ersten Geräte erkennen. Du wirst sehen, wie eine Maus oder Tastatur "zum Leben erwacht"!
📚 Weiterführende Literatur
- xHCI Specification (Intel) – Das offizielle xHCI-Handbuch (1000+ Seiten!)
- Raspberry Pi 4 BCM2711 Peripherals – Die Hardware-Dokumentation
- Circle USB Stack – Die Original-Implementierung, auf der unser Code basiert
Source: https://www.satyria.de/arm/sources/RPI4/C/USB2_Init2.zip