Dit artikel beschrijft het technische proces van het porteren van classic Mac OS naar systemen met ArtX-chipsets, specifiek de Nintendo GameCube, Wii en Wii U. De auteur heeft OpenBIOS geporteerd en diverse hardware-specifieke uitdagingen overwonnen om het besturingssysteem tot aan het bureaublad te laten booten.
De belangrijkste technische stappen waren:
- Bootproces: Het aanpassen van de 'Trampoline' bootloader en het implementeren van drivers voor SD-kaarten via de EXI-bus.
- Geheugenbeheer: Het patchen van de fysieke adresruimte en MMIO-mappings om overeen te komen met de Nintendo-architectuur.
- Hardware-patches: Het vervalsen van de device tree en het aanpassen van de interrupt-controllerdrivers.
- Kernel en m68k: Het aanpassen van de 'SuperMario' m68k-code en het implementeren van stubs voor niet-bestaande hardware.
- Opslag: Het creëren van een 'fake-SCSI' driver om HFS-bootsectoren correct te kunnen lezen.
Het resultaat is dat Mac OS 9.2.2 succesvol boot in een Dolphin-emulatieomgeving en Mac OS 7.6 functioneel is op echte hardware. Toekomstige plannen omvatten de implementatie van de Wii SD-controller, USB-ondersteuning en de port naar de Wii U.
Mijn favoriete PowerMac is een Nintendo Wii
Waarom classic Mac OS porteren naar deze hardware?
Ik had natuurlijk al NT geporteerd, en tijdens dat werk heb ik enkele PowerPC Mac-laptops aangeschaft waar ik vervolgens ook NT op heb geïnstalleerd. Het werk aan de ene poort verbeterde de andere. Hierdoor raakte ik bekend met classic Mac OS en raakte ik gefascineerd door de "jank" (de opeenstapeling van hacks die teruggaan tot de jaren 80). Bovendien heet het m68k-gedeelte van de TBXI "SuperMario", en dat was gedurende het hele PowerPC-tijdperk zo. Ik vraag me af of een ontwikkelaar in de vroege jaren 90 een fan was.
Voor de volledigheid: ik ben met dit project begonnen op 22 juni 2025 (toen ik de upstream OpenBIOS forkte). Er is geen gebruik gemaakt van coding agents of LLM's; al het coderen, reverse engineering en debugging is door mijzelf gedaan. Ik doe dit voor mijn plezier en om te leren; als ik AI zou gebruiken, zou het niet leuk zijn en zou ik niets leren.
Het initiële bootproces
"New World" classic Mac OS start met een bootloader genaamd de "Trampoline", die draait in een Open Firmware-omgeving. Daarom moest ik OpenBIOS porteren. Gelukkig was Goldfish64 rond die tijd begonnen aan zijn eigen poort van Mac OS X naar de Wii en had hij een OpenBIOS-poort gemaakt waarvan ik enkele drivers kon gebruiken. Ik wilde echter niet direct zijn poort gebruiken, omdat deze alleen op de Wii/Wii U was gericht, terwijl ik ook de GameCube wilde ondersteunen. Ook besloot ik bestaande code te hergebruiken om de ontwikkeling van drivers te vergemakkelijken (specifiek de displaydriver), waardoor ik mijn bestaande NT ARC-firmwareloader aanpaste om een big-endian ELF te laden.
Voor de eerste bring-up had ik een klein aantal schijfdrivers nodig. Ik besloot mijn twee drivers voor apparaten op de EXI-bus te porteren (de naam die Nintendo en Macronix gebruiken voor SPI):
- De SD Gecko (een directe adapter voor een GameCube-geheugenkaartslot die SD over SPI mogelijk maakt).
- De EXI-IDE (deze driver is feitelijk niet correct voor echte hardware, maar voor mijn onnauwkeurige emulatie in mijn Dolphin-tree, wat testen met low-level debugging mogelijk maakt).
Omdat ik niet wist welk on-disk formaat ik wilde gebruiken, plaatste ik voor de eerste tests het "Mac OS ROM" TBXI-bestand direct op een FAT-bestandssysteem als \ppc\bootinfo.txt, zodat OpenBIOS dit bij het opstarten kon laden. Hiermee kon ik de Trampoline werkend krijgen. Mijn doel was om zoveel mogelijk patches op het image toe te passen vlak voor runtime, zodat er uiteindelijk ongewijzigde CD-images gebruikt kunnen worden.
Uitdagingen met het geheugen en MMIO
Het eerste probleem was dat de Trampoline bepaalde aannames doet over de fysieke adresruimte die niet kloppen voor dit systeem. Het fysieke adres van een buffer werd berekend als een ongeldig adres; ik heb dit gepatcht naar een gegarandeerd geldig adres, inclusief de bijbehorende code die de geheugenlijsten en paginatabel-entries voor de kernel configureert.
Daarnaast verwacht de Trampoline dat fysieke adressen vanaf 0x8000_0000 overeenkomen met memory-mapped I/O (MMIO), wat op bestaande systemen overeenkwam met de PCI-bus adresruimte. Dit is hier niet het geval; op de Wii U komt dit adres bijvoorbeeld overeen met een deel van het RAM. Ook waren er gaten in de fysieke adresruimte tussen RAM-gebieden, waarvan sommige gedeeltelijk gevuld waren door MMIO.
Ik heb dit opgelost door de initialisatie van deze MMIO-mapping in de kernel-geheugenlijst te patchen naar een map van fysiek adres 0x01000000 naar virtueel adres 0xE0000000, met een lengte van 0x1000_0000. Dit mapt de volledige MMIO, plus het einde van één RAM-gebied en (voor post-GameCube systemen) het begin van een ander.
Hardware-specifieke patches
Er volgden diverse patches die specifieke hardware aannemen, waaronder:
- Het verwijderen van bitflags die wijzen op de aanwezigheid van een microcontroller die in alle PowerPC Macintosh-hardware voorkomt.
- Pogingen om de busfrequentie te herberekenen met een specifieke hardwaretimer.
- Diverse register-schrijfacties en fixups gerelateerd aan een specifieke interrupt-controller (die little-endian is omdat hij op de PCI-bus zit, en dus niet aanwezig is op dit systeem).
De finale aanroep om naar de geladen kernel-initialisatiecode te springen is gehookt, zodat er later aanvullende patches kunnen worden gemaakt nadat alles in het geheugen is geladen.
Daarnaast moest de device tree worden aangepast:
- De interrupt-controller is vervalst als een van de erkende controllers (de little-endian variant), omdat deze op register-niveau het dichtst bij de Flipper-interrupt-controller van de ArtX-chipsets ligt.
- Het adres van de framebuffer in de device tree is gewijzigd naar het ongecachete virtuele adres.
- Er is een nep Mac99-compatibel stroombeheerstapparaat aan de device tree toegevoegd, zodat de Trampoline een stroombeheersdriver hiervoor voorbereidt, die later vervangen kan worden.
Kernel- en m68k-aanpassingen
Aan de kant van de kernel is er één belangrijke patch: de kernel bevat verschillende interrupt-controllerdrivers. De gekozen driver is aangepast om overeen te komen met de Flipper-controller. Omdat de verwachte controller een legacy-modus heeft waarbij hij een PowerPC-interrupt genereert telkens wanneer interrupts veranderen (en niet alleen wanneer ze worden opgeheven), moet dit worden opgelost door interrupts in de kernel te maskeren en later in de interrupt-handlers van de drivers weer te onmaskeren.
Verder zijn er patches nodig in de "SuperMario" m68k-code en in PowerPC-native executables:
- Codepaden in de m68k-code die direct lezen en schrijven naar native interrupt-controller registers moeten worden gepatcht.
- Functiepointers voor het in- en uitschakelen van audio-interrupts zijn vervangen door stubs.
- Een functie voor de initialisatie van ADB en de stroombeheersmicrocontroller controleert niet of de hardware daadwerkelijk bestaat, wat delen van het lage geheugen kan overschrijven; dit is gecorrigeerd.
- Een bug in de hardware-interrupt initialisatie (veroorzaakt door een copy-paste fout) leidde tot een oneindige lus als seriële poort 2 niet bestond; dit is hersteld.
- De
USBShimKeyboard-driver initialisatie crashte wanneer er geen USB-controller aanwezig was; dit is aangepast.
- De stroombeheersdriver is volledig vervangen door een stub-driver uit BlueBox.
Opslag en de weg naar het bureaublad
Na deze stappen kon de TBXI booten tot aan de "blinking floppy". Ik schreef een displaydriver en een driver voor de GameCube-controllerpoorten, zodat ik de muiscursor kon bewegen zonder USB.
Vervolgens implementeerde ik een block IO-driver voor EXI-apparaten. Ik koos uiteindelijk voor een methode waarbij ik een SCSI-driver simuleerde (fake-SCSI). Hierbij stuitte ik op een interessante quirk: de optische schijfdriver emuleert 512-byte sectoren op 2048- of 2352-byte sector block-apparaten door drie scatter-gather buffers in te stellen. Omdat de driver niet controleerde of de onderliggende SCSI-driver scatter-gather ondersteunde, moest ik dit implementeren.
Ondanks dit zag ik nog steeds de "blinking floppy". Debugging wees uit dat de functie StartLib!GetStartupDevice aannames deed over de device tree en de aanwezigheid van een ingebouwde SCSI-device. Door interne structuren aan te passen en indexen in drie verschillende plaatsen (inclusief de device tree) te corrigeren, werd dit opgelost.
Daarna verscheen voor het eerst het "Happy Mac"-icoon, maar volgde een fout bij de HFS-bootsector. Dit bleek een bug in mijn eigen driver te zijn: bij het lezen/schrijven in PIO-modus voor IDE-apparaten wordt in 16-bit woorden gewerkt. Mijn code kon niet omgaan met een niet-uitgelijnd oneven bufferadres. Door de low-level driver aan te passen zodat deze geen EXI DMA probeert voor dergelijke adressen, werd dit opgelost.
Resultaten en toekomst
Na deze aanpassingen begon Mac OS 9.2.2 te booten in mijn aangepaste Dolphin-omgeving, zij het zeer langzaam. Na het verwijderen van MacsBug en Open Transport (dat waarschijnlijk crasht als er geen seriële of netwerkapparaten aanwezig zijn) bootte het systeem perfect.
Op echte hardware (via SD over SPI) bootte Mac OS 7.6 binnen 40 seconden. Dit was verrassend, aangezien dit theoretisch de traagste toegangsmodus voor een SD-kaart zou moeten zijn. Het verschil met mijn ervaringen met NT is dat ik hier de volledige CPU kan claimen tijdens een transfer.
Mijn poort is nog lang niet klaar voor release. De volgende stappen zijn:
- De SD-controller van de Wii operationeel maken.
- De USB-controller implementeren.
- De poort naar de Wii U brengen.
- Experimenteren met patches om bepaalde RAM-barrières te doorbreken.
Mijn favoriete PowerMac is een Nintendo Wii
Waarom classic Mac OS porteren naar deze hardware?
Ik had natuurlijk al NT geporteerd, en tijdens dat werk heb ik enkele PowerPC Mac-laptops aangeschaft waar ik vervolgens ook NT op heb geïnstalleerd. Het werk aan de ene poort verbeterde de andere. Hierdoor raakte ik bekend met classic Mac OS en raakte ik gefascineerd door de "jank" (de opeenstapeling van hacks die teruggaan tot de jaren 80). Bovendien heet het m68k-gedeelte van de TBXI "SuperMario", en dat was gedurende het hele PowerPC-tijdperk zo. Ik vraag me af of een ontwikkelaar in de vroege jaren 90 een fan was.
Voor de volledigheid: ik ben met dit project begonnen op 22 juni 2025 (toen ik de upstream OpenBIOS forkte). Er is geen gebruik gemaakt van coding agents of LLM's; al het coderen, reverse engineering en debugging is door mijzelf gedaan. Ik doe dit voor mijn plezier en om te leren; als ik AI zou gebruiken, zou het niet leuk zijn en zou ik niets leren.
Het initiële bootproces
"New World" classic Mac OS start met een bootloader genaamd de "Trampoline", die draait in een Open Firmware-omgeving. Daarom moest ik OpenBIOS porteren. Gelukkig was Goldfish64 rond die tijd begonnen aan zijn eigen poort van Mac OS X naar de Wii en had hij een OpenBIOS-poort gemaakt waarvan ik enkele drivers kon gebruiken. Ik wilde echter niet direct zijn poort gebruiken, omdat deze alleen op de Wii/Wii U was gericht, terwijl ik ook de GameCube wilde ondersteunen. Ook besloot ik bestaande code te hergebruiken om de ontwikkeling van drivers te vergemakkelijken (specifiek de displaydriver), waardoor ik mijn bestaande NT ARC-firmwareloader aanpaste om een big-endian ELF te laden.
Voor de eerste bring-up had ik een klein aantal schijfdrivers nodig. Ik besloot mijn twee drivers voor apparaten op de EXI-bus te porteren (de naam die Nintendo en Macronix gebruiken voor SPI):
- De SD Gecko (een directe adapter voor een GameCube-geheugenkaartslot die SD over SPI mogelijk maakt).
- De EXI-IDE (deze driver is feitelijk niet correct voor echte hardware, maar voor mijn onnauwkeurige emulatie in mijn Dolphin-tree, wat testen met low-level debugging mogelijk maakt).
Omdat ik niet wist welk on-disk formaat ik wilde gebruiken, plaatste ik voor de eerste tests het "Mac OS ROM" TBXI-bestand direct op een FAT-bestandssysteem als \ppc\bootinfo.txt, zodat OpenBIOS dit bij het opstarten kon laden. Hiermee kon ik de Trampoline werkend krijgen. Mijn doel was om zoveel mogelijk patches op het image toe te passen vlak voor runtime, zodat er uiteindelijk ongewijzigde CD-images gebruikt kunnen worden.
Uitdagingen met het geheugen en MMIO
Het eerste probleem was dat de Trampoline bepaalde aannames doet over de fysieke adresruimte die niet kloppen voor dit systeem. Het fysieke adres van een buffer werd berekend als een ongeldig adres; ik heb dit gepatcht naar een gegarandeerd geldig adres, inclusief de bijbehorende code die de geheugenlijsten en paginatabel-entries voor de kernel configureert.
Daarnaast verwacht de Trampoline dat fysieke adressen vanaf 0x8000_0000 overeenkomen met memory-mapped I/O (MMIO), wat op bestaande systemen overeenkwam met de PCI-bus adresruimte. Dit is hier niet het geval; op de Wii U komt dit adres bijvoorbeeld overeen met een deel van het RAM. Ook waren er gaten in de fysieke adresruimte tussen RAM-gebieden, waarvan sommige gedeeltelijk gevuld waren door MMIO.
Ik heb dit opgelost door de initialisatie van deze MMIO-mapping in de kernel-geheugenlijst te patchen naar een map van fysiek adres 0x01000000 naar virtueel adres 0xE0000000, met een lengte van 0x1000_0000. Dit mapt de volledige MMIO, plus het einde van één RAM-gebied en (voor post-GameCube systemen) het begin van een ander.
Hardware-specifieke patches
Er volgden diverse patches die specifieke hardware aannemen, waaronder:
- Het verwijderen van bitflags die wijzen op de aanwezigheid van een microcontroller die in alle PowerPC Macintosh-hardware voorkomt.
- Pogingen om de busfrequentie te herberekenen met een specifieke hardwaretimer.
- Diverse register-schrijfacties en fixups gerelateerd aan een specifieke interrupt-controller (die little-endian is omdat hij op de PCI-bus zit, en dus niet aanwezig is op dit systeem).
De finale aanroep om naar de geladen kernel-initialisatiecode te springen is gehookt, zodat er later aanvullende patches kunnen worden gemaakt nadat alles in het geheugen is geladen.
Daarnaast moest de device tree worden aangepast:
- De interrupt-controller is vervalst als een van de erkende controllers (de little-endian variant), omdat deze op register-niveau het dichtst bij de Flipper-interrupt-controller van de ArtX-chipsets ligt.
- Het adres van de framebuffer in de device tree is gewijzigd naar het ongecachete virtuele adres.
- Er is een nep Mac99-compatibel stroombeheerstapparaat aan de device tree toegevoegd, zodat de Trampoline een stroombeheersdriver hiervoor voorbereidt, die later vervangen kan worden.
Kernel- en m68k-aanpassingen
Aan de kant van de kernel is er één belangrijke patch: de kernel bevat verschillende interrupt-controllerdrivers. De gekozen driver is aangepast om overeen te komen met de Flipper-controller. Omdat de verwachte controller een legacy-modus heeft waarbij hij een PowerPC-interrupt genereert telkens wanneer interrupts veranderen (en niet alleen wanneer ze worden opgeheven), moet dit worden opgelost door interrupts in de kernel te maskeren en later in de interrupt-handlers van de drivers weer te onmaskeren.
Verder zijn er patches nodig in de "SuperMario" m68k-code en in PowerPC-native executables:
- Codepaden in de m68k-code die direct lezen en schrijven naar native interrupt-controller registers moeten worden gepatcht.
- Functiepointers voor het in- en uitschakelen van audio-interrupts zijn vervangen door stubs.
- Een functie voor de initialisatie van ADB en de stroombeheersmicrocontroller controleert niet of de hardware daadwerkelijk bestaat, wat delen van het lage geheugen kan overschrijven; dit is gecorrigeerd.
- Een bug in de hardware-interrupt initialisatie (veroorzaakt door een copy-paste fout) leidde tot een oneindige lus als seriële poort 2 niet bestond; dit is hersteld.
- De
USBShimKeyboard-driver initialisatie crashte wanneer er geen USB-controller aanwezig was; dit is aangepast.
- De stroombeheersdriver is volledig vervangen door een stub-driver uit BlueBox.
Opslag en de weg naar het bureaublad
Na deze stappen kon de TBXI booten tot aan de "blinking floppy". Ik schreef een displaydriver en een driver voor de GameCube-controllerpoorten, zodat ik de muiscursor kon bewegen zonder USB.
Vervolgens implementeerde ik een block IO-driver voor EXI-apparaten. Ik koos uiteindelijk voor een methode waarbij ik een SCSI-driver simuleerde (fake-SCSI). Hierbij stuitte ik op een interessante quirk: de optische schijfdriver emuleert 512-byte sectoren op 2048- of 2352-byte sector block-apparaten door drie scatter-gather buffers in te stellen. Omdat de driver niet controleerde of de onderliggende SCSI-driver scatter-gather ondersteunde, moest ik dit implementeren.
Ondanks dit zag ik nog steeds de "blinking floppy". Debugging wees uit dat de functie StartLib!GetStartupDevice aannames deed over de device tree en de aanwezigheid van een ingebouwde SCSI-device. Door interne structuren aan te passen en indexen in drie verschillende plaatsen (inclusief de device tree) te corrigeren, werd dit opgelost.
Daarna verscheen voor het eerst het "Happy Mac"-icoon, maar volgde een fout bij de HFS-bootsector. Dit bleek een bug in mijn eigen driver te zijn: bij het lezen/schrijven in PIO-modus voor IDE-apparaten wordt in 16-bit woorden gewerkt. Mijn code kon niet omgaan met een niet-uitgelijnd oneven bufferadres. Door de low-level driver aan te passen zodat deze geen EXI DMA probeert voor dergelijke adressen, werd dit opgelost.
Resultaten en toekomst
Na deze aanpassingen begon Mac OS 9.2.2 te booten in mijn aangepaste Dolphin-omgeving, zij het zeer langzaam. Na het verwijderen van MacsBug en Open Transport (dat waarschijnlijk crasht als er geen seriële of netwerkapparaten aanwezig zijn) bootte het systeem perfect.
Op echte hardware (via SD over SPI) bootte Mac OS 7.6 binnen 40 seconden. Dit was verrassend, aangezien dit theoretisch de traagste toegangsmodus voor een SD-kaart zou moeten zijn. Het verschil met mijn ervaringen met NT is dat ik hier de volledige CPU kan claimen tijdens een transfer.
Mijn poort is nog lang niet klaar voor release. De volgende stappen zijn:
- De SD-controller van de Wii operationeel maken.
- De USB-controller implementeren.
- De poort naar de Wii U brengen.
- Experimenteren met patches om bepaalde RAM-barrières te doorbreken.