💻 C y ensamblador con Raspberry Pi
🌐 Este artículo solo está disponible en alemán por el momento.

Ports initialisieren (Root-Port Setup) (RPI4, C)

Schritt 10: Ports initialisieren (Root-Port Setup)

In Schritt 10 bereiten wir die physikalischen USB-Ports des Root Hubs vor, damit der Controller weiß, an welchen Buchsen Signale anliegen und welche Geschwindigkeiten dort verhandelt werden.

Da die vollständige Geräte-Enumeration (das Anfordern von Deskriptoren, Zuweisen von Adressen und Binden von Treibern) ein sehr umfangreiches Thema ist, widmen wir ihr das komplette nächste Kapitel. Hier beschränken wir uns zunächst darauf, die Ports hardwareseitig aufzuwecken und das Geräte-Objekt für den nächsten Schritt vorzubereiten.

Im Code von usb_Initialize() sieht Schritt 10 wie folgt aus:

    // ========================================================================
    // [10/10] Root Ports initialisieren und konfigurieren
    // ========================================================================
    USB_LOG(cyan, "\n[10/10] Root Ports initialisieren und konfigurieren...");
    USB_LOG(white, "       -> Warte 300ms, damit sich die Hardware stabilisieren kann...");
    
    unsigned nMsDelay = 300;
    Timer_SimpleMsDelay(nMsDelay);
    
    // Alle Ports initialisieren
    for (unsigned nPort = 0; nPort < s_xhciRootHub.m_nPorts; nPort++)
    {
        USB_LOG(white, "       -> Initialisiere Port %u...", nPort + 1);
        CXHCIRootPort_Initialize(&s_xhciRootHub.m_pRootPort[nPort]);
    }

Bevor wir die Ports anfassen, geben wir der Hardware eine Pause von 300 Millisekunden (Timer_SimpleMsDelay(300)). Warum? Nach dem Starten des Controllers in Schritt 9 müssen sich die elektrischen Pegel an den PHYs (den physikalischen Schnittstellen des SoCs) erst stabilisieren.


Was passiert in CXHCIRootPort_Initialize()?

Die Funktion CXHCIRootPort_Initialize() geht Port für Port durch (beim Raspberry Pi 4 sind das typischerweise die Ports des xHCI-Controllers) und führt ein festes Schema aus:

+-------------------------------------------------------+
|  Schritt 1: Verbindung prüfen (CCS-Bit im PORTSC)     |
+-------------------------------------------------------+
                            |
                            v
+-------------------------------------------------------+
|  Schritt 2: Port-Reset (USB 2) / U0-Zustand (USB 3)   |
+-------------------------------------------------------+
                            |
                            v
+-------------------------------------------------------+
|  Schritt 3: Port Speed auslesen (Low/Full/High/Super) |
+-------------------------------------------------------+
                            |
                            v
+-------------------------------------------------------+
|  Schritt 4: USB Device Objekt & Speicher allozieren   |
+-------------------------------------------------------+
                            |
                            v
+-------------------------------------------------------+
|  Schritt 5: Over-Current Prüfung (OCA-Bit)            |
+-------------------------------------------------------+

Schauen wir uns die einzelnen Unterpunkte im Detail an:

1. Prüfe Verbindung (CCS-Bit)

Über das Register PORTSC (Port Status and Control) prüfen wir das CCS-Bit (Current Connect Status):

u32 nPortSC = read32(XHCIMMIOSpace_PtBase + pPort->m_nPortIndex * XHCI_REG_OP_PORTS_PORT__SIZE + XHCI_REG_OP_PORTS_PORTSC);

if (!(nPortSC & XHCI_REG_OP_PORTS_PORTSC_CCS))
{
    USB_LOG(yellow, "[Port %u] Kein Gerät angeschlossen -> Abbruch", pPort->m_nPortIndex + 1);
    return FALSE;
}

Ist kein Stecker eingesteckt (CCS == 0), wird die Initialisierung für diesen Port sofort abgebrochen.

2. Signal-Reset bzw. Link-State Abgleich

Je nachdem, welcher USB-Standard am Port anliegt, läuft das Signalisieren unterschiedlich ab:

  • USB 2.0-Ports: Wir lösen einen manuellen Port Reset aus, indem wir das PR-Bit (Port Reset) im PORTSC-Register setzen. Danach warten wir in einer Schleife (max. 100 ms), bis die Hardware das Bit wieder auf 0 setzt – ein Zeichen dafür, dass der Reset abgeschlossen ist.
  • USB 3.0-Ports (SuperSpeed): USB 3.0 führt den Handshake in der Hardware durch. Wir prüfen schlicht, ob der Link-State PLS den aktiven Betriebs-Zustand U0 (XHCI_REG_OP_PORTS_PORTSC_PLS_U0) erreicht hat.

3. Geschwindigkeit (Port Speed) auslesen

Nach dem Reset verrät uns das Port Speed-Feld (PSI) im PORTSC-Register, mit welcher Geschwindigkeit das angeschlossene Gerät kommunizieren kann:

  • Low Speed (1.5 Mbit/s) – z. B. einfache Tastaturen/Mäuse
  • Full Speed (12 Mbit/s) – z. B. ältere USB-Geräte
  • High Speed (480 Mbit/s) – z. B. USB 2.0 Sticks
  • SuperSpeed (5 Gbit/s) – USB 3.0 Geräte

4. Vorbereitung der Datenstruktur für das Gerät

Steckt ein Gerät am Port, allozieren wir den Speicher für unser logisches Geräte-Objekt CXHCIUSBDevice:

pPort->m_pUSBDevice = (CXHCIUSBDevice *)malloc(sizeof(CXHCIUSBDevice));
CXHCIUSBDevice_Create(pPort->m_pUSBDevice, XHCI_PSI_TO_USB_SPEED(uchSpeedID), pPort);

5. Schutzschaltung: Over-Current (OCA) prüfen

Bevor wir den Port freigeben, prüfen wir noch das OCA-Bit (Over-Current Active). Zieht das angeschlossene Gerät zu viel Strom (z. B. bei einem Kurzschluss im Kabel), schalten wir sofort die Stromversorgung des Ports ab (PP-Bit löschen), um eine Beschädigung des Raspberry Pi zu verhindern:

if (nPortSC & XHCI_REG_OP_PORTS_PORTSC_OCA)
{
    USB_LOG(red, "[Port %u] OVER-CURRENT DETECTED -> Power Off!", pPort->m_nPortIndex + 1);
    nPortSC &= ~XHCI_REG_OP_PORTS_PORTSC_PP;
    write32(nPortSC, ...); // Port ausschalten
    return FALSE;
}

Fazit & Ausblick auf das nächste Kapitel

Damit haben wir die 10 Schritte der Controller-Initialisierung fast komplett abgeschlossen! Der xHCI-Controller läuft, die Interrupts sind scharf geschaltet, Ringe und Tabellen liegen im Coherent Memory und die Root-Ports sind zurückgesetzt und einsatzbereit.

Was jetzt noch fehlt, ist die eigentliche Geräte-Erkennung (Enumeration):

  1. Zuweisen einer SlotID via Enable Slot Command
  2. Zuweisen einer eindeutigen USB-Adresse via Address Device Command
  3. Auslesen der Descriptors (Hersteller-ID, Produkt-ID, Interfaces)
  4. Laden und Binden der passenden Treiber (z. B. Tastatur, Maus, Hub)

Genau das schauen wir uns im nächsten Kapitel im Detail an!

Den Sourcecode für dieses Kapitel bekommst du hier: https://www.satyria.de/arm/sources/RPI4/C/USB3_PortsInit.zip