Der Coherent-Slot-Manager (DMA-Speicher ohne Cache-Fallen)
Nachdem wir unsere MMU und die CPU-Caches erfolgreich aktiviert haben, läuft unser Kernel auf Höchstgeschwindigkeit. Doch die extrem schnellen L1- und L2-Caches bergen eine riesige Gefahr, sobald externe Hardware-Controller (DMA) ins Spiel kommen.
In diesem Kapitel bauen wir einen einfachen, aber hocheffektiven Coherent-Slot-Manager, der unserem Betriebssystem garantiert ungecachte Speicherseiten für die Kommunikation mit Hardware wie USB, Mailboxen oder PCIe bereitstellt.
Das Problem: Das "Cache-Blindheits-Dilemma"
Wenn die CPU Daten verarbeitet, schreibt sie diese aus Performance-Gründen erst einmal in ihren schnellen L1/L2-Data-Cache und nicht sofort in den physischen RAM-Chip (Write-Back Caching).
Ein DMA-Controller (z. B. der USB-xHCI-Controller auf dem Pi 4 oder die GPU) liest und schreibt aber direkt in den physischen RAM-Chip – er hat keinen Zugriff auf die internen Caches der CPU!
CPU (L1/L2 Cache) DMA-Hardware (z.B. USB / PCIe)
│ │
▼ (Schreibt Daten) ▼ (Liest RAM)
┌──────────────────┐ ┌──────────────────┐
│ CACHE-ZEILE │ != (asynchron)│ RAM-CHIP │
│ (neue USB-Daten) │ │ (veraltete Daten)│
└──────────────────┘ └──────────────────┘
Beim Schreiben: Die CPU baut ein Datenpaket für USB im Cache auf. Die USB-Hardware liest den RAM-Chip und sieht nur Nullen. Folge: Der USB-Stick reagiert nicht.
Beim Lesen: Der USB-Stick schreibt empfangene Daten per DMA direkt in den RAM-Chip. Die CPU liest bei ihrer nächsten Abfrage die alten Daten aus ihrem Cache. Folge: Datenkorruption.
Die Lösung: Unsere COHERENT-Speicherregion
Erinnerst du dich an unser Linker-Skript und das Page-Table-Setup?
Im Linker-Skript haben wir den Bereich __coherent_start bis __coherent_end reserviert (z. B. 4 MB).
In der MMU haben wir diese Seiten mit dem Attribut ATTRINDX_COHERENT (Uncached / Non-Cacheable) versehen.
Wenn die CPU auf eine Adresse in diesem Bereich zugreift, umgeht sie den Cache vollständig. Jeder Schreib- und Lesezugriff landet sofort auf dem echten RAM-Chip.
Die Lösung im Code: Memory_GetCoherentPage()
Um diesen Speicher sauber zu verwalten, teilen wir den 4 MB großen Coherent-Bereich in feste 64-KB-Slots (Pages) auf.
In der memory.h definieren wir zunächst, welcher Treiber welchen Slot zugewiesen bekommt:
#define COHERENT_SLOT_PROP_MAILBOX 0 // Slot 0 für Mailbox-Kommunikation
#define COHERENT_SLOT_GPIO_VIRTBUF 1 // Slot 1 für GPIO Virtbuf
#define COHERENT_SLOT_TOUCHBUF 2 // Slot 2 für Touchscreen
#define COHERENT_SLOT_XHCI_START (MEGABYTE / PAGE_SIZE) // Slots für USB xHCI Controller
#define COHERENT_SLOT_XHCI_END (4*MEGABYTE / PAGE_SIZE - 1)
Neben unseren USB-xHCI-Slots finden sich im Header auch Reservierungen wie COHERENT_SLOT_VCHIQ_START. VCHIQ ist die High-Speed-Schnittstelle zwischen der ARM-CPU und der VideoCore-GPU. Wenn wir später komplexe Treiber wie HDMI-Audio oder die Raspberry Pi Kamera einbauen möchten, nutzen diese Treiber genau diesen reservierten DMA-Speicherbereich, um Audiosignale und Videoframes stoßfrei zur GPU zu streamen.
In memory.c programmieren wir die Funktion, die die physische Startadresse des gewünschten Slots berechnet:
/**
* @brief Liefert eine ungecachte (Coherent) 64KB-Speicherseite für DMA-Hardware.
* @param nSlot Die Slot-Nummer (0, 1, 2, ...)
* @return Startadresse der 64KB Page im Uncached RAM
*/
uintptr Memory_GetCoherentPage(unsigned nSlot)
{
uintptr coherent_start = (uintptr)&__coherent_start;
uintptr coherent_end = (uintptr)&__coherent_end;
// Berechne die Startadresse des gewünschten 64KB-Slots
uintptr slot_addr = coherent_start + (nSlot * PAGE_SIZE);
// Schutz: Prüfen, ob der Slot noch innerhalb des Coherent-Bereichs liegt
if (slot_addr + PAGE_SIZE > coherent_end)
{
printf("FEHLER: Coherent Slot %d liegt ausserhalb des reservierten Bereichs!\n", nSlot);
return 0;
}
return slot_addr;
}
Was haben wir damit erreicht?
Typsichere Hardware-Buffer: Ein Treiber wie pcie.c oder xhci.c ruft einfach Memory_GetCoherentPage(COHERENT_SLOT_XHCI_START) auf und bekommt eine saubere, 64KB große, ungecachte Speicherseite garantiert an einer 64KB-Grenze ausgeliefert.
Kein automatischer Cache-Flush nötig: Da alle Zugriffe auf diese Seite an den Caches vorbeigehen, sparen wir uns komplizierte Assembler-Flush-Befehle im laufenden Betrieb.
Schutz des normalen Heaps: Unser normaler Heap (malloc / palloc) bleibt zu 100 % gecacht und ultraschnell für C-Variablen und Kernel-Logik.
Damit ist das Fundament für den PCI-Express- und USB-Treiber (xHCI) vollständig gelegt!
Den Code bekommst du hier: https://www.satyria.de/arm/sources/RPI4/C/coherent.zip