💻 C and Assembly with Raspberry Pi
🌐 This article is currently only available in German.

USB Einleitung (RPI4, C)

1. Einleitung: Warum xHCI?

Im ersten Teil haben wir die Grundlagen unseres Bare-Metal-Systems geschaffen:

  • ✅ MMU und Speicherverwaltung
  • ✅ Interrupt-Controller (GIC-400)
  • ✅ Timer-System
  • ✅ Framebuffer und Terminal-Ausgabe
  • ✅ PCIe funktionsfähig

Jetzt wollen wir USB-Geräteansprechen – Mäuse, Tastaturen, USB-Sticks. Doch USB ist kein einfaches Protokoll. Es erfordert einen speziellen Hardware-Controller, der die komplexe USB-Kommunikation für uns übernimmt.

Auf dem Raspberry Pi 4 ist dieser Controller ein xHCI-Controller (eXtensible Host Controller Interface). Er ist die modernste Generation der USB-Host-Controller und unterstützt USB 2.0, 3.0 und 3.1 – alles über eine einheitliche Schnittstelle.

💡 Wichtig: Der xHCI-Controller hängt nicht direkt am SoC-Bus, sondern am PCIe-Busdes Raspberry Pi 4. Das bedeutet: Bevor wir den xHCI-Controller ansprechen können, müssen wir zuerst die PCIe-Host-Bridge aktivieren!


2. Die Architektur im Überblick

Bevor wir Code schreiben, schauen wir uns das große Ganze an:

┌────────────────────────────────────────────────────────────────┐
│                     RASPBERRY PI 4 (BCM2711)                   │
│                                                                │
│  ┌──────────┐    ┌──────────────┐    ┌───────────────────────┐ │
│  │  ARM CPU │◄──►│  PCIe Host   │◄──►│   xHCI Controller     │ │
│  │ (Cortex- │    │   Bridge     │    │   (USB 3.0 Host)      │ │
│  │   A72)   │    │  (0xFD500000)│    │   (0x600000000 virt.) │ │
│  └──────────┘    └──────────────┘    └───────────┬───────────┘ │
│                                                  │             │
│                                        ┌─────────▼─────────┐   │
│                                        │    Root Hub       │   │
│                                        │  (5 USB-Ports)    │   │
│                                        └─────────┬─────────┘   │
└──────────────────────────────────────────────────┼─────────────┘
                                                   │
                    ┌──────────────────────────────┼─────────────┐
                    │                              │             │
              ┌─────▼─────┐                 ┌──────▼─────┐  ┌────▼────┐
              │   Maus    │                 │  Tastatur  │  │  Stick  │
              └───────────┘                 └────────────┘  └─────────┘

Die Kommunikationskette:

  1. Die CPU schreibt Befehle in den Command-Ring(CRCR)
  2. Der xHCI-Controllerholt diese Befehle ab und führt sie aus
  3. Bei Fertigstellung legt er ein Event in den Event-Ring(ERST)
  4. Die CPU wird per Interruptbenachrichtigt und holt das Event ab
  5. Die Ergebnisse stehen im Device-Context(DCBAA)

🎓 Analogie: Stell dir den xHCI-Controller wie einen Dolmetschervor. Die CPU spricht "TRB-Sprache" (Transfer Request Blocks), die USB-Geräte sprechen "USB-Protokoll". Der Controller übersetzt in beide Richtungen.


3. Einleitende Worte

Nachdem wir die PCIe-Schnittstelle erfolgreich initialisiert haben, wenden wir uns nun dem USB-Subsystem zu.

Für die PCIe-Initialisierung konnte ich mich stark am Linux-Kernel orientieren. Beim USB-Treiber stößt man im Linux-Kernel jedoch auf eine extreme Komplexität, die den Rahmen dieses Tutors sprengen würde. Es gibt jedoch ein hervorragendes Bare-Metal-Projekt namens "Circle" von R. Stange (https://github.com/rsta2/circle), das eine sehr robuste USB-Implementierung bietet. Mein System orientiert sich konzeptionell daran, ist aber bewusst vereinfacht und in C (statt C++) geschrieben, um die zugrundeliegenden Mechanismen klarer sichtbar zu machen.

Unser Ziel: Unterstützung für einfache Mäuse, Tastaturen und USB-Massenspeicher (Bulk-Sticks). Damit decken wir die grundlegenden Eingabe- und Speicherfunktionen ab, die ein Betriebssystem benötigt.

Hinweis zum Sourcecode: Um diesen Tutor effizient zu gestalten, habe ich die usb.h von Anfang an in ihrer Gesamtheit angelegt (über 1200 Zeilen Code). Ausserdem werden die Funktionen nochmals detaliert im Sourcecode beschrieben. Auch hier wird es im Tutorial unübersichtlich, wenn wir auf alles einzelne eingehen. Wenn du mehr Informationen haben möchtest, dann schaue dort hinein.

Problematische TimeOuts: Damit der Sourcecode verständlich bleibt, wurden im Sourcecode viele Ausgaben per Debug-Ausgaben erstellt. Diese Debugausgaben können unter Umständen zu TimeOuts führen und den USB-Kontroller zum Absturz bringen (Meine größtes Hinternis bei der Entwicklung dieses Tutorials). In jeden Kapitel werden die vorhergehenden Debug-Ausgaben auskommentiert und nur noch diese belassen, die einen Mehrwert haben, wenn zum Beispiel etwas nicht funktioniert.