In der Theorie klingt ein Generationswechsel bei Apple Silicon für Außenstehende oft unspektakulär: Neuer Chip, modernere Fertigung, etwas mehr IPC, vielleicht ein paar zusätzliche Funktionseinheiten. Unten drunter bleibt es schließlich 64-Bit-ARM. Wer Linux auf x86-Blech oder standardisierten Server-Boards betreibt, erwartet meist, dass ein funktionierender Kernel auf der nächsten CPU-Generation mindestens bis zu einer frühen Textausgabe durchläuft.
Auf Apples Plattform gilt diese Annahme nicht. Hier gibt es weder BIOS noch ACPI – also keine standardisierte Firmware-Schicht, die dem Betriebssystem beim Start erklärt, wo Speicher, Timer oder serielle Schnittstellen überhaupt liegen. Jeder neue Chip ist zunächst eine Blackbox, bei der man im Dunkeln nach den Schaltern tasten muss.
Wie zäh und kleinteilig dieser Prozess in der Praxis ist, zeigt ein technischer Bericht von Entwicklerin Yureka Lilian: The forgetful CPU (Linux on M4). Darin dokumentiert sie den mühsamen Weg, Linux auf einem M4 Mac mini überhaupt erst zu einer Shell zu bewegen. Es ist ein anschaulicher Einblick in die Realität des Hardware-Bring-ups: Hier beginnt die Arbeit nicht mit sauber dokumentierten Schnittstellen, sondern mit rudimentärem Low-Level-Debugging, stummen seriellen Konsolen und Prozessoren, die mitten im Leerlauf ihre eigenen Register vergessen.
Das m1n1-Fundament und neue Mauern
Um zu verstehen, warum ein Kernel auf einem Mac überhaupt anspringt, muss man auf das Fundament blicken: m1n1. Das ist der Bootloader und die Testumgebung des Asahi-Linux-Projekts. Normalerweise dient m1n1 nicht nur als Bindeglied zwischen Apples Bootkette (iBoot) und U-Boot oder Linux, sondern auch als schlanker Hypervisor.
In früheren Generationen (M1 bis M3) war das der Schlüssel zur Portierung:
macOS wurde in dieser virtuellen Umgebung gestartet, während m1n1 im Hintergrund alle MMIO-Zugriffe mitschrieb – also die Lese- und Schreibzugriffe, mit denen das System über definierte Speicheradressen direkt mit Hardware-Controllern wie Display, PCIe oder USB spricht (Memory-Mapped I/O). Aus diesen Traces ließ sich ableiten, wie die undokumentierten Chips angesteuert werden wollen.
Beim M4 stand diese Methode vor einer neuen Hürde. Apple hat mit dieser Chipgeneration den Secure Page Table Monitor (SPTM) verpflichtend gemacht – einen Sicherheitsmechanismus, der die Integrität der Seitentabellen des macOS-Kernels (XNU) schützt. Damit funktionierte der bisherige Hypervisor-Weg nicht mehr einfach weiter. Um macOS wieder unter m1n1 laufen und sauber tracen zu können, braucht es deutlich tiefere Anpassungen an der Virtualisierungsschicht.
Stattdessen blieb für den initialen Bring-up der direktere Bare-Metal-Weg:
Das System über das macOS-Wiederherstellungsmenü in den Modus mit reduzierter Boot-Sicherheit versetzen, m1n1 als alternatives Boot-Objekt hinterlegen und versuchen, Linux direkt auf die Hardware zu setzen.
Schon dabei zeigte sich, wie empfindlich die neue Plattform auf Routinen reagiert, die auf älteren Generationen noch Standard waren. Frühe m1n1-Versionen stürzten auf dem M4 sofort ab. Wie Yureka Lilian in ihrem Blog dokumentiert, lag das an festen Schreibzugriffen auf Register, die Apple auf den neuen SoCs gesperrt hatte.
Dazu gehörten Initialisierungsroutinen für Apples Guarded Execution Framework (GXF) sowie Zugriffe auf das RVBAR (Reset Vector Base Address Register), das bestimmt, an welcher Adresse ein CPU-Kern nach dem Einschalten anläuft. Was auf älteren Chips initialisiert werden musste, war auf dem M4 bereits hardwareseitig verriegelt oder mit dem korrekten Wert belegt. Ein Schreibversuch endete mit einem frühen Absturz, noch bevor Linux überhaupt sinnvoll ins Spiel kam.
Erst als m1n1 diese Schreibzugriffe gezielt übersprang, meldete sich der Bootloader wieder ansprechbar über die serielle Konsole.
Ein einziges Zeichen: Debugging im absoluten Blindflug
Wer noch nie ein Betriebssystem auf unbekanntem Silizium hochgezogen hat, stellt sich frühe Fehler gern als Fehlermeldung auf dem Bildschirm vor. Vielleicht ein blinkender Cursor, eine Kernel-Panic oder eine kryptische Zeile Text. Die Realität beim Hardware-Bring-up sieht anders aus:
Es passiert einfach gar nichts.
Nach dem Befehl Vectoring to next stage, mit dem m1n1 die Kontrolle an den Linux-Kernel übergibt, blieb die serielle Konsole des Mac mini stumm. Kein Fehlercode, kein Register-Dump, kein Lebenszeichen.
In einer solchen Phase greifen gängige Linux-Werkzeuge nicht. Wenn der Kernel noch nicht weit genug gelaufen ist, um Treiber zu laden oder den regulären Konsolen-Treiber zu initialisieren, nutzt man üblicherweise earlycon. Dieser Parameter weist den Kernel an, Ausgaben schon sehr früh über primitiven, fest verdrahteten Code direkt an die Register der seriellen Schnittstelle (UART) zu schicken. Doch der naheliegende Weg über earlycon half zunächst ebenfalls nicht weiter, solange der Kernel nicht sauber wusste, welche serielle Schnittstelle er überhaupt benutzen sollte.
Wenn man nicht weiß, ob der Code überhaupt ausgeführt wird oder der Prozessor schon im ersten Taktzyklus hängenbleibt, hilft nur die roheste Form des Debuggings: Printf-Debugging in Assembler.
Yureka Lilian lieh sich dafür eine minimalistische Routine namens debug_putc aus m1n1 aus.
Das ist kein komplexer Treiber, sondern eine Handvoll Assembler-Befehle, die ein fest definiertes Byte direkt an die physikalische Adresse des seriellen Chips schreiben. In diesem Fall: ein einzelnes ASCII-Zeichen, der Buchstabe a.
Sie pflanzte diese Routine in die allererste Startphase des ARM64-Kernels (arch/arm64/kernel/head.S) ein – und die serielle Konsole spuckte tatsächlich ein a aus.
Das klingt banal, ist beim Bring-up aber der entscheidende Moment:
Die CPU führt tatsächlich Linux-Code aus. Sie ist am Leben.
Von dort an begann die Fehlersuche per Bisektion: Die Debug-Ausgabe wird Zeile für Zeile, Block für Block weiter nach hinten im Boot-Code verschoben, um herauszufinden, an welcher exakten Anweisung das System verstummt. Die Spur verlor sich schnell an einer neuralgischen Stelle: der Aktivierung der Memory Management Unit (MMU).
Die MMU ist der Baustein im Prozessor, der festlegt, wie Speicher adressiert wird. Vor ihrer Aktivierung greift die CPU direkt und ungefiltert auf reale, physikalische Adressen im RAM und auf Controller zu. Sobald die MMU aber eingeschaltet ist, arbeitet der Prozessor ausschließlich mit virtuellen Adressen. Er schlägt für jeden Zugriff in sogenannten Seitentabellen (Page Tables) nach, wo diese Adresse in der echten Hardware tatsächlich liegt.
Das Problem beim Booten:
m1n1 hatte für seine eigene Umgebung zwar eine Eins-zu-eins-Übersetzung eingerichtet, bei der die virtuellen Adressen der Controller genau ihren physikalischen entsprachen. Linux tut das in seinen frühen Seitentabellen aber nicht automatisch. Sobald die MMU ansprang, zeigte der Schreibbefehl von debug_putc plötzlich auf eine virtuelle Adresse, für die es in den Tabellen keinen Eintrag gab. Der Zugriff landete im nicht gemappten Adressraum. Aus der Notausgabe wurde damit selbst der nächste Fehler.
Erst als Lilian die initialen Seitentabellen des Kernels im Quelltext anpasste und den Adressbereich für die serielle Schnittstelle auch unter aktiver MMU fest eins-zu-eins einhängte, wanderte das kleine a weiter durch den Boot-Pfad.
Kurz darauf stolperte der Bootpfad noch über einen implementierungsspezifischen Apple-Registerzugriff (SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2) im Umfeld der frühen Interrupt-Initialisierung. Dieses Register, das mit Virtualisierung zusammenhängt und in späteren iBoot-Versionen von Apple freigegeben wurde, brachte den Kernel erneut zu Fall; erst nach dem Auskommentieren des Schreibzugriffs bootete das System durch bis zu einer Shell.
Und auch das Rätsel um das anfangs stumme earlycon löste sich schließlich unspektakulär auf:
Mit der expliziten Angabe der Schnittstelle per earlycon=s5l,0x3ad200000 und dem Nachpflegen von stdout-path = "serial0" im Device Tree – der Hardwarebeschreibung, über die der Kernel erfährt, welche Geräte, Adressen und Interrupts auf der Plattform existieren – lieferte Linux endlich das, was man für systematisches Arbeiten braucht: vollständige Register-Dumps und Stacktraces bei frühen Abstürzen.
Ein vergessenes Attribut in der Hardwarebeschreibung, ein nicht gemappter Adressbereich und tagelanges Suchen nach einem einzelnen Buchstaben im Terminal: Genau so sieht echter Hardware-Bring-up an vorderster Front aus.
Wenn WFI nicht wartet, sondern löscht
Bis hierhin lief Linux immerhin auf einem einzelnen Kern. Aber niemand kauft einen modernen Rechner, um ihn als Ein-Kern-System zu betreiben. Sobald man versucht, die restlichen Kerne zuzuschalten und das System in den regulären Betrieb zu überführen, wartet auf dem M4 die eigentliche technische Hürde. Sie erklärt auch den Titel von Yureka Lilians Blogbeitrag:
Die CPU vergisst schlichtweg ihren Zustand.
Im ARM64-Befehlssatz gibt es dafür eine fundamentale Instruktion:
WFI (Wait For Interrupt), ergänzt um neuere Varianten wie WFIT (Wait For Interrupt with Timeout). Das Prinzip: Wenn der Kernel einen CPU-Kern in einen Idle-Zustand schicken will, nutzt er dafür solche WFI-/WFIT-Pfade. Der Kern schaltet in einen stromsparenden Wartezustand, bis ihn ein Hardware-Interrupt – etwa ein Timer, ein Geräteereignis oder ein anderer Interrupt – wieder aufweckt. Danach läuft der Code genau an der Stelle weiter, an der er angehalten wurde.
Dabei gilt eine grundlegende Regel der Rechnerarchitektur:
Ein Wartezustand darf die Arbeitsdaten des Prozessors nicht zerstören. Lilian verweist in ihrem Blog auf die ARM64-Spezifikation: Wenn ein System so konfiguriert ist, dass ein WFI abgeschlossen werden kann, darf der Befehl keinen Verlust des architektonischen Zustands zur Folge haben. Was vor dem Schlafen in den CPU-Registern lag, muss beim Aufwachen exakt so wieder da sein.
Genau das aber passiert auf dem M4 im Standardzustand nicht.
Die Universalregister x0 bis x31 – das Kurzzeitgedächtnis, in dem der Prozessor Adressen, Zeiger, Schleifenzähler und Zwischenergebnisse für die laufende Ausführung hält – werden beim Aufwachen nach dem Ruhezustand genullt. Für ein Betriebssystem ist das die Höchststrafe: Der Kernel legt sich kurz schlafen, wacht auf, will den nächsten Schritt ausführen, greift auf die Register zu und findet dort nur noch Nullen vor. Dann stürzt nicht bloß eine Anwendung ab; dem Betriebssystem bricht mitten im Herzschlag der Boden unter den Füßen weg.
Neu ist dieses Verhalten bei Apple Silicon im Prinzip jedoch nicht.
Schon frühere Generationen (M1 bis M3) zeigten diese Eigenart, die eng mit Apples Energiemanagement zusammenhängt: Um Kerne in besonders tiefe Schlafzustände zu schicken, räumt die Hardware aggressiv auf. Apples eigener Betriebssystem-Kernel XNU ist darauf ausgelegt. Er sichert die relevanten Register vor dem Aufruf von WFI auf den Stack und stellt sie nach dem Aufwachen manuell wieder her.
Zudem gab es auf älteren Chips ein sogenanntes Chicken Bit – ein internes Konfigurationsbit im Prozessor ARM64_REG_CYC_OVRD_ok2pwrdn_force_mask, mit dem man dieses aggressive Verhalten abschalten konnte, falls Probleme auftraten. Der Bootloader m1n1 nutzte das bisher, um den M1–M3-Chips das registerlöschende Verhalten abzugewöhnen, sodass sie sich wie standardkonforme ARM64-Prozessoren verhielten. Der Asahi-Linux-Kernel kann dieses Verhalten später gezielt wieder nutzen, wo tiefe Stromsparmodi und Turbotakte gesteuert werden sollen.
Auf dem M4 ist dieses Chicken Bit laut Lilians Analyse entweder verriegelt oder schlicht nicht mehr vorhanden. Apple baut Hardware primär für das hauseigene macOS und den XNU-Kernel – dort ist das manuelle Sichern und Wiederherstellen längst implementiert, weshalb außerhalb des Apple-Kosmos kaum jemand an dieser Architektur-Eigenheit Anstoß nahm. Für Linux bedeutete das jedoch: Der bisherige Hebel, die Hardware einfach per Firmware-Schalter zu einem für Linux erwartbaren WFI-Verhalten zu zwingen, war verschwunden.
Vom Hack zur sauberen Upstream-Lösung
Der erste funktionierende Durchstich beim Bring-up ist fast immer grob. Um zu überprüfen, ob tatsächlich nur die WFI-Logik den Start der weiteren Kerne verhindert, griff Yureka Lilian zu einer radikalen Maßnahme: Sie ersetzte im Kernel kurzerhand alle WFI- und WFIT-Befehle durch NOP (No Operation).
Ein NOP tut schlicht erst mal gar nichts. Der Prozessor wartet nicht, schläft nicht ein, behält seine Register und läuft einfach zur nächsten Instruktion weiter.
Das Ergebnis: Linux bootete plötzlich mit sämtlichen CPU-Kernen auf dem M4 durch bis zu einer Shell.
Als Machbarkeitsnachweis war das Gold wert. Als Dauerlösung für ein Produktivsystem oder gar den Linux-Hauptzweig (Mainline) war es aber zunächst unbrauchbar: Es kostet permanent Energie, verhindert saubere Idle-Zustände und wäre für Laptops spätestens beim Akku ein schlechter Scherz.
Der nächste naheliegende Gedanke wäre ein klassischer Errata-Patch gewesen.
Der ARM64-Port von Linux besitzt ausgereifte Mechanismen, um bekannte Hardware-Macken bestimmter Chip-Revisionen während des frühen Bootens im Speicher zu überbügeln. Doch hier lauerte die nächste Falle:
Wie erkennt der Kernel zweifelsfrei, dass er gerade auf einem nackten M4-Chip läuft, der seine Register verliert?
Auf einem Mac laufen Linux-Systeme nicht nur nativ über m1n1, sondern sehr häufig auch virtualisiert in Gästen unter macOS. Der macOS-Hypervisor fängt WFI-Befehle seiner virtuellen Maschinen gezielt ab, um CPU-Zeit effizient an andere Prozesse zu verteilen. Würde der Kernel WFI anhand von CPU-Identifikationsnummern pauschal lahmlegen, würde er auch Linux-Gäste in macOS-VMs ausbremsen. Das Erkennen von Virtualisierung – insbesondere bei verschachtelter Virtualisierung (Nested Virtualization) – ist zur frühen Bootzeit hochgradig fehleranfällig.
Den pragmatischen Weg aus der Sackgasse schlug ARM64-Maintainer Will Deacon vor: Statt einer fehleranfälligen Automatik im Kernel sollte Linux schlicht die Fähigkeit erhalten, das Verhalten über dedizierte Bootparameter gezielt von außen zu steuern
Die Arbeit teilte sich dadurch sauber in zwei Hälften:
- Im Linux-Kernel: Über den Parameter
idle=noplässt sich der reguläre CPU-Idle-Pfad anweisen, auf WFI zu verzichten. Ergänzend dazu brachte der Upstream-Commit arm64: Add override for WFxT die Optionarm64.nowfxt, um die WFIT-/WFxT-Unterstützung im Kernel gezielt auszublenden. - Im Bootloader m1n1: Die Erkennung der konkreten Hardware wandert dorthin, wo sie hingehört – in den Bootloader. m1n1 erkennt anhand der Plattform- und CPU-Features betroffene Bare-Metal-Systeme und hängt dem Kernel beim Start automatisch die beiden Argumente
idle=nop arm64.nowfxtan (siehe Commit kboot: disable wfi/wfit if wfi loses state).
Damit ist das registerlöschende Verhalten auf dem M4 zwar nicht aus der Welt geschafft, aber kontrolliert eingezäunt:
Linux stürzt beim Start nicht mehr ab, virtuelle Maschinen bleiben unberührt, und der Code fand seinen Weg in den Linux-Upstream. Für tiefere Stromsparmodi greift das Asahi-Projekt vorerst auf seinen eigenen cpuidle-apple-Treiber zurück, während an saubereren EFI/PSCI-Schnittstellen für die Zukunft gearbeitet wird.
Genau hier trennt sich das Basteln am Küchentisch von nachhaltiger Betriebssystem-Entwicklung: Nicht der erste rohe Hack im Assembler-Code ist die eigentliche Leistung, sondern die Geduld, daraus eine saubere Lösung zu bauen, die sich in die bestehenden Mechanismen von Linux einfügt.
Was Apple nicht dokumentiert, baut die Community nach
Apples Rechnerarchitektur ist technisch beeindruckend, effizient und konsequent für das eigene Ökosystem optimiert. Aber dieser M4-Bring-up zeigt wieder einmal ungeschminkt, was diese Geschlossenheit in der Praxis bedeutet: Linux-Support entsteht hier nicht, weil jemand ein Datenblatt aufschlägt und Treiber implementiert. Er entsteht, weil Entwickler tagelang im Dunkeln stochern, bis eine serielle Schnittstelle ein einzelnes ASCII-Zeichen ausspuckt.
Aus Apples Sicht ist das nachvollziehbar.
Apple verkauft integrierte Gesamtsysteme, keine offenen Entwicklerboards. Genau das merkt man hier: Solange macOS und der XNU-Kernel stabil laufen, ist Linux-Support für Apple kein Produktversprechen. Es reicht, wenn die eigene Software die Eigenheiten der eigenen Hardware kennt und versteht.
Für freie Betriebssysteme ist das unbequem.
Jammern hilft dabei wenig. Was Projekte wie Asahi Linux und Entwickler wie Yureka Lilian leisten, ist keine heroische Romantik, sondern zähe, unsichtbare Fleißarbeit: Reverse Engineering an der Schmerzgrenze, das Schreiben eigener Bootloader-Stufen und das mühsame Vermessen von Chips, zu denen es öffentlich keine Spezifikation gibt.
Die eigentliche Leistung besteht dabei nicht darin, Linux auf einer Messebühne mit ein paar schmutzigen Hacks irgendwie zum Booten zu prügeln. Die Kunst liegt darin, diese Eigenheiten so sauber einzufangen, dass sie als reguläre Patches in den weltweiten Linux-Upstream wandern können – ohne andere ARM64-Plattformen zu beschädigen oder Virtualisierungsumgebungen auszubremsen.
Geschlossene Plattformen sparen dem Hersteller Support- und Dokumentationsaufwand. Die Rechnung dafür verschwindet aber nicht; sie wird nur weitergereicht. Bezahlen müssen sie diejenigen, die darauf bestehen, Hardware nicht nur zu benutzen, sondern tatsächlich kontrollieren zu wollen – und selbst zu entscheiden, welches Betriebssystem darauf läuft.
Warum das auch Admins interessieren sollte
In der täglichen Praxis fühlt sich Linux heute meistens handzahm an. Wir starten vorgefertigte ISO-Images, lassen Paketmanager Abhängigkeiten auflösen, orchestrieren Container, rollen Setups per Ansible aus und klicken uns durch schicke Desktops. Die Schnittstellen sind so gut geworden, dass man leicht vergessen kann, wie viel unbequeme Maschinenrealität unter dieser Bequemlichkeit liegt.
Man gewöhnt sich an die Abstraktion. Wir behandeln Compute-Ressourcen wie Strom aus der Steckdose:
Rechner an, Kernel lädt, System läuft.
Der M4-Bring-up führt vor Augen, dass diese Bequemlichkeit eine Illusion ist.
Unter jedem Systemd-Dienst, unter jedem Container und unter jedem glattpolierten Desktop liegen nach wie vor die harten, ungeschminkten Grundlagen: Bootloader, die Speicheradressen zuordnen, MMU-Seitentabellen, Interrupt-Controller, CPU-Register und Timer.
Wenn diese Schicht nicht funktioniert, hilft das beste Oberflächen-Werkzeug nichts. Kein Installer der Welt rettet ein System, wenn der Prozessor nach dem Einschlafen seine eigenen Register nullt. Keine Container-Laufzeitumgebung startet, wenn eine Notausgabe den Adressraum verfehlt. In diesem Moment gibt es keine Logs, kein journalctl und keine Rettungsshell. Da ist schlicht Feierabend.
Für Admins, Infrastrukturleute und jeden, der Systeme professionell betreibt, ist das eine heilsame Erinnerung. Man muss nicht täglich ARM64-Assembler schreiben oder undokumentierte Apple-Register entschlüsseln, um seinen Job gut zu machen. Aber man sollte nie den Respekt vor den Schichten verlieren, die den eigenen Alltag überhaupt erst ermöglichen.
Gute Systemadministration beginnt oft genau an der Grenze, an der die vorgefertigten Werkzeuge aufhören. Wenn ein Hypervisor unter Last seltsam reagiert, wenn ein Kernel nach einem Update auf bestimmter Hardware reproduzierbar hängt oder wenn Kernel-Panics im Log auftauchen, die nach Speicherfehlern riechen:
Dann zählt das grundlegende Verständnis dafür, wie Hardware und Kernel ineinandergreifen.
Wer Systeme nur als Ansammlung von Konfigurationsdateien begreift, steht hilflos da, sobald die Abstraktion Risse bekommt. Wer aber weiß, wie tief die Wurzeln reichen und wie viel mühsame Kleinarbeit nötig ist, damit ein Rechner überhaupt ein einziges Zeichen auf einem Terminal anzeigt, blickt anders auf den eigenen Stack: mit etwas mehr Demut vor der Maschine – und deutlich weniger Geduld für Marketing-Märchen von Systemen, die angeblich „einfach magisch funktionieren“.
Ein Buchstabe als Meilenstein
Am Ende steht kein fertiger Desktop, kein polierter Installer und keine Hardwarebeschleunigung, die Benchmark-Balken nach oben treibt. Wer heute Linux auf einem Apple M4 startet, landet in einer schlichten Shell. Bis die übrigen Plattformkomponenten auf dem Niveau laufen, das man von älteren M-Generationen unter Asahi Linux kennt, ist noch einiges an Arbeit offen.
Aber der erste harte Widerstand ist überwunden.
Aus einem einzelnen Zeichen – dem winzigen Buchstaben a, den eine rohe Assembler-Routine über eine serielle Leitung quetschte – wurde ein bootendes System. Aus dem ersten Lebenszeichen erwuchs systematisches Debugging, daraus wurden präzise Workarounds und am Ende Code, der seinen Weg in mainline Linux gefunden hat. Nicht durch Magie oder Wohlwollen eines Herstellers, sondern durch methodisches Zerlegen von Problemen, die vorher niemand dokumentiert hatte.
Bevor Linux auf neuer Hardware bequem wirkt, muss es zuerst beweisen, dass es überhaupt lebt. Auf dem M4 war dieser Beweis ein einzelnes a. Mehr braucht es manchmal nicht, um den Stein ins Rollen zu bringen.