<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-04-11T18:06:53+00:00</updated><id>/feed.xml</id><title type="html">Bryan Keller’s Dev Blog</title><subtitle>I&apos;m Bryan Keller. I work on iOS apps for work and low-level systems projects on the side. Sometimes I&apos;ll post interesting things here.</subtitle><entry><title type="html">Porting Mac OS X to the Nintendo Wii</title><link href="/2026/04/08/porting-mac-os-x-nintendo-wii.html" rel="alternate" type="text/html" title="Porting Mac OS X to the Nintendo Wii" /><published>2026-04-08T00:00:00+00:00</published><updated>2026-04-08T00:00:00+00:00</updated><id>/2026/04/08/porting-mac-os-x-nintendo-wii</id><content type="html" xml:base="/2026/04/08/porting-mac-os-x-nintendo-wii.html"><![CDATA[<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/IMG_9159.jpeg"><img src="/assets/images/porting-mac-os-x-nintendo-wii/IMG_9159.jpeg" alt="Mac OS X Cheetah running on the Nintendo Wii" /></a>
<em>Mac OS X 10.0 (Cheetah) running natively on the Nintendo Wii</em></p>

<p>Since its launch in 2007, the Wii has seen several operating systems ported to it: Linux, NetBSD, and most-recently, Windows NT. Today, Mac OS X joins that list.</p>

<p>In this post, I’ll share how I ported the first version of Mac OS X, 10.0 Cheetah, to the Nintendo Wii. If you’re not an operating systems expert or low-level engineer, you’re in good company; this project was all about learning and navigating countless “unknown unknowns”. Join me as we explore the Wii’s hardware, bootloader development, kernel patching, and writing drivers - and give the PowerPC versions of Mac OS X a new life on the Nintendo Wii.</p>

<p><em>Visit the <a href="https://github.com/bryankeller/wiiMac">wiiMac bootloader repository</a> for instructions on how to try this project yourself.</em></p>

<h2 id="feasibility-investigation">Feasibility Investigation</h2>

<p>Before figuring out how to tackle this project, I needed to know whether it would even be possible. According to a 2021 <a href="https://www.reddit.com/r/wii/comments/mm8i8w/is_it_possible_to_run_old_versions_of_os_x_on_the/gts4glp/">Reddit comment</a>:</p>

<blockquote>
  <p>There is a zero percent chance of this ever happening.</p>
</blockquote>

<p>Feeling encouraged, I started with the basics: what hardware is in the Wii, and how does it compare to the hardware used in real Macs from the era.</p>

<h3 id="hardware-compatibility">Hardware Compatibility</h3>

<p>The Wii uses a PowerPC 750CL processor - an evolution of the PowerPC 750CXe that was used in G3 iBooks and some G3 iMacs. Given this close lineage, I felt confident that the CPU wouldn’t be a blocker.</p>

<p>As for RAM, the Wii has a unique configuration: 88 MB total, split across 24 MB of 1T-SRAM (MEM1) and 64 MB of slower GDDR3 SDRAM (MEM2); unconventional, but technically enough for Mac OS X Cheetah, which officially calls for 128 MB of RAM but will unofficially boot with less. To be safe, I used QEMU to boot Cheetah with 64 MB of RAM and verified that there were no issues.</p>

<p>Other hardware I’d eventually need to support included:</p>

<ul>
  <li>Serial debug output via a <a href="https://wiibrew.org/wiki/USB_Gecko">USB Gecko</a></li>
  <li>The SD card for booting the rest of the system once the kernel was running</li>
  <li>Interrupt controllers</li>
  <li>Video output via a framebuffer that lives in RAM</li>
  <li>The Wii’s USB ports for using a mouse and keyboard</li>
</ul>

<p>Convinced that the Wii’s hardware wasn’t fundamentally incompatible with Mac OS X, I moved my attention to investigating the software stack I’d be porting.</p>

<h3 id="software-compatibility">Software Compatibility</h3>

<p>Mac OS X has an open source core (Darwin, with XNU as the kernel and IOKit as the driver model), with closed-source components layered on top (Quartz, Dock, Finder, system apps and frameworks). In theory, if I could modify the open-source parts enough to get Darwin running, the closed-source parts would run without additional patches.</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/architecture.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/architecture.png" alt="Mac OS X Architecture" /></a>
<em>Source: <a href="https://en.wikipedia.org/wiki/Architecture_of_macOS">Wikipedia: Architecture of macOS</a></em></p>

<p>Porting Mac OS X would also require understanding how a real Mac boots. PowerPC Macs from the early 2000s use Open Firmware as their lowest-level software environment; for simplicity, it can be thought of as the first code that runs when a Mac is powered on. Open Firmware has several responsibilities, including:</p>

<ul>
  <li>Hardware detection and configuration</li>
  <li>Device tree construction (based on the detected hardware)</li>
  <li>Providing useful functions for I/O, drawing, and hardware communication</li>
  <li>Loading and executing an operating system bootloader from the filesystem</li>
</ul>

<p>Open Firmware eventually hands off control to BootX, the bootloader for Mac OS X. BootX prepares the system so that it can eventually pass control to the kernel. The responsibilities of BootX include:</p>

<ul>
  <li>Reading the device tree from Open Firmware</li>
  <li>Loading and decoding the XNU kernel, a Mach-O executable, from the root filesystem</li>
  <li>Passing control to the kernel</li>
</ul>

<p>Once XNU is running, there are no dependencies on BootX or Open Firmware. XNU continues on to initialize processors, virtual memory, IOKit, BSD, and eventually continue booting by loading and running other executables from the root filesystem.</p>

<p>The last piece of the puzzle was how to run my own custom code on the Wii - a trivial task thanks to the Wii being “jailbroken”, allowing anyone to run homebrew with full access to the hardware via the <a href="https://wiibrew.org/wiki/Homebrew_Channel#google_vignette">Homebrew Channel</a> and <a href="https://bootmii.org/about/">BootMii</a>.</p>

<h2 id="porting-approach">Porting Approach</h2>

<p>Armed with knowledge of how the boot process works on a real Mac, along with how to run low-level code on the Wii, I needed to select an approach for booting Mac OS X on the Wii. I evaluated three options:</p>

<ol>
  <li>Port Open Firmware, use that to run unmodified BootX to boot Mac OS X</li>
  <li>Port BootX and modify it to not rely on Open Firmware, use that to boot Mac OS X</li>
  <li>Write a custom bootloader that performs the bare-minimum setup to boot Mac OS X</li>
</ol>

<p>Since Mac OS X doesn’t depend on Open Firmware or BootX once running, spending time porting either of those seemed like an unnecessary distraction. Additionally, both Open Firmware and BootX contain added complexity for supporting many different hardware configurations - complexity that I wouldn’t need since this only needs to run on the Wii. Following in the footsteps of the Wii Linux project, I decided to write my own bootloader from scratch. The bootloader would need to, at a minimum:</p>

<ul>
  <li>Initialize the Wii’s hardware</li>
  <li>Load the kernel from the SD card</li>
  <li>Construct a device tree and boot arguments</li>
  <li>Pass control to the kernel</li>
</ul>

<p>Once the kernel was running, none of the bootloader code would matter. At that point, my focus would shift to patching the kernel and writing drivers.</p>

<h2 id="writing-a-bootloader">Writing a Bootloader</h2>

<p>I decided to base my bootloader on some low-level example code for the Wii called <a href="https://github.com/AndrewPiroli/ppcskel">ppcskel</a>. ppcskel puts the system into a sane initial state, and provides useful functions for common things like reading files from the SD card, drawing text to the framebuffer, and logging debug messages to a USB Gecko.</p>

<h3 id="loading-the-kernel">Loading the Kernel</h3>

<p>Next, I had to figure out how to load the XNU kernel into memory so that I could pass control to it. The kernel is stored in a special binary format called <a href="https://en.wikipedia.org/wiki/Mach-O">Mach-O</a>, and needs to be properly decoded before being used.</p>

<p>The Mach-O executable format is well-documented, and can be thought of as a list of load commands that tell the loader where to place different sections of the binary file in memory. For example, a load command might instruct the loader to read the data from <em>file offset</em> <code class="language-plaintext highlighter-rouge">0x2cf000</code> and store it at the <em>memory address</em> <code class="language-plaintext highlighter-rouge">0x2e0000</code>. After processing all of the kernel’s load commands, we end up with this memory layout:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0x00000000: Exception vectors
0x00011000: LC_SEGMENT __TEXT
0x002e0000: LC_SEGMENT __DATA
0x00367000: LC_SEGMENT __KLD
0x00395000: LC_SEGMENT __LINKEDIT
0x00434000: LC_SEGMENT __SYMTAB
0x004d3000: LC_SEGMENT __HEADER
</code></pre></div></div>

<p>The kernel file also specifies the memory address where execution should begin. Once the bootloader jumps to this address, the kernel is in full control and the bootloader is no longer running.</p>

<h3 id="calling-the-kernel">Calling the Kernel</h3>

<p>To jump to the kernel-entry-point’s memory address, I needed to cast the address to a function and call it:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="p">)())</span><span class="n">kernel_entry_point</span><span class="p">)(</span><span class="n">boot_args_address</span><span class="p">,</span> <span class="n">MAC_OS_X_SIGNATURE</span><span class="p">);</span>
</code></pre></div></div>

<p>After this code ran, the screen went black and my debug logs stopped arriving via the serial debug connection - while anticlimactic, this was an indicator that the kernel was running.</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/blink.jpeg"><img src="/assets/images/porting-mac-os-x-nintendo-wii/blink.jpeg" alt="LED blink after calling kernel" /></a></p>

<p>The question then became: how far was I making it into the boot process? To answer this, I had to start looking at XNU source code. The first code that runs is a PowerPC assembly <code class="language-plaintext highlighter-rouge">_start</code> routine. This code reconfigures the hardware, overriding all of the Wii-specific setup that the bootloader performed and, in the process, disables bootloader functionality for serial debugging and video output. Without normal debug-output facilities, I’d need to track progress a different way.</p>

<p>The approach that I came up with was a bit of a hack: binary-patch the kernel, replacing instructions with ones that illuminate one of the front-panel LEDs on the Wii. If the LED illuminated after jumping to the kernel, then I’d know that the kernel was making it at least that far. Turning on one of these LEDs is as simple as writing a value to a specific memory address. In PowerPC assembly, those instructions are:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lis    r5, 0xd80       ; load upper half of 0x0D8000C0 into r5
ori    r5, r5, 0xc0    ; load lower half of 0x0D8000C0 into r5
lwz    r4, (r5)        ; read the 32-bit value from 0x0D8000C0
sync                   ; memory barrier
xori   r4, r4, 0x20    ; toggle bit 5
stw    r4, (r5)        ; write the value back to 0x0D8000C0
</code></pre></div></div>

<p>To know which parts of the kernel to patch, I cross-referenced function names in XNU source code with function offsets in the compiled kernel binary, using Hopper Disassembler to make the process easier. Once I identified the correct offset in the binary that corresponded to the code I wanted to patch, I just needed to replace the existing instructions at that offset with the ones to blink the LED.</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/xcode.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/xcode.png" alt="Xcode Screenshot" /></a>
<a href="/assets/images/porting-mac-os-x-nintendo-wii/hopper.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/hopper.png" alt="Hopper Screenshot" /></a></p>

<p>To make this patching process easier, I added some code to the bootloader to patch the kernel binary on the fly, enabling me to try different offsets without manually modifying the kernel file on disk.</p>

<p>After tracing through many kernel startup routines, I eventually mapped out this path of execution:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. start.s: start
2. start.s: allStart
3. start.s: nextPVR
4. start.s: donePVR
5. start.s: doOurInit
6. start.s: noFloat
7. start.s: noVector
8. start.s: noSMP
9. start.s: noThermometer
10. ppc_init.c: ppcInit
11. pe_init.c: PE_INIT_PLATFORM
12. device_tree.c: find_entry (crash with 300 exception)
</code></pre></div></div>

<p>This was an exciting milestone - the kernel was definitely running, and I had even made it into some higher-level C code. To make it past the 300 exception crash, the bootloader would need to pass a pointer to a valid device tree.</p>

<h3 id="creating-and-passing-a-device-tree">Creating and Passing a Device Tree</h3>

<p>The <a href="https://en.wikipedia.org/wiki/Devicetree">device tree</a> is a data structure representing all of the hardware in the system that should be exposed to the operating system. As the name suggests, it’s a tree made up of nodes, each capable of holding properties and references to child nodes.</p>

<p>On real Mac computers, the bootloader scans the hardware and constructs a device tree based on what it finds. Since the Wii’s hardware is always the same, this scanning step can be skipped. I ended up hard-coding the device tree in the bootloader, taking inspiration from the device tree that the <a href="https://github.com/torvalds/linux/blob/master/arch/powerpc/boot/dts/wii.dts">Wii Linux project</a> uses.</p>

<p>Since I wasn’t sure how much of the Wii’s hardware I’d need to support in order to get the boot process further along, I started with a minimal device tree: a root node with children for the cpus and memory:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/
└── cpus
    └── PowerPC,750
└── memory
</code></pre></div></div>

<p>My plan was to expand the device tree with more pieces of hardware as I got further along in the boot process - eventually constructing a complete representation of all of the Wii’s hardware that I planned to support in Mac OS X.</p>

<p>Once I had a device tree created and stored in memory, I needed to pass it to the kernel as part of <code class="language-plaintext highlighter-rouge">boot_args</code>:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">boot_args</span> <span class="p">{</span>
    <span class="n">u16</span>	<span class="n">Revision</span><span class="p">;</span>	                <span class="cm">/* Revision of boot_args structure */</span>
    <span class="n">u16</span>	<span class="n">Version</span><span class="p">;</span>	                <span class="cm">/* Version of boot_args structure */</span>
    <span class="kt">char</span> <span class="n">CommandLine</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>	        <span class="cm">/* Passed in command line */</span>
    <span class="n">DRAMBank</span> <span class="n">PhysicalDRAM</span><span class="p">[</span><span class="mi">26</span><span class="p">];</span>	    <span class="cm">/* base and range pairs for the 26 DRAM banks */</span>
    <span class="n">Boot_Video</span> <span class="n">Video</span><span class="p">;</span>		        <span class="cm">/* Video Information */</span>
    <span class="n">u32</span>	<span class="n">machineType</span><span class="p">;</span>	            <span class="cm">/* Machine Type (gestalt) */</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">deviceTreeP</span><span class="p">;</span>	            <span class="cm">/* Base of flattened device tree */</span>
    <span class="n">u32</span>	<span class="n">deviceTreeLength</span><span class="p">;</span>           <span class="cm">/* Length of flattened tree */</span>
    <span class="n">u32</span>	<span class="n">topOfKernelData</span><span class="p">;</span>            <span class="cm">/* Highest address used in kernel data area */</span>
<span class="p">}</span> <span class="n">boot_args_t</span><span class="p">;</span>
</code></pre></div></div>

<p>With the device tree in memory, I had made it past the <code class="language-plaintext highlighter-rouge">device_tree.c</code> crash. The bootloader was performing the basics well: loading the kernel, creating boot arguments and a device tree, and ultimately, calling the kernel. To make additional progress, I’d need to shift my attention toward patching the kernel source code to fix remaining compatibility issues.</p>

<h2 id="patching-the-kernel">Patching the Kernel</h2>

<p>At this point, the kernel was getting stuck while running some code to set up video and I/O memory. XNU from this era makes assumptions about where video and I/O memory can be, and reconfigures <a href="https://www.nxp.com/docs/en/supporting-information/TPQ2CH09.pdf">Block Address Translations</a> (BATs) in a way that doesn’t play nicely with the Wii’s memory layout (MEM1 starting at <code class="language-plaintext highlighter-rouge">0x00000000</code>, MEM2 starting at <code class="language-plaintext highlighter-rouge">0x10000000</code>). To work around these limitations, it was time to modify the kernel’s source code and boot a modified kernel binary.</p>

<p>Figuring out a sane development environment to build an OS kernel from 25 years ago took some effort. Here’s what I landed on:</p>

<ul>
  <li>Mac OS X Cheetah guest (running via <a href="https://en.wikipedia.org/wiki/QEMU">QEMU</a>), headless, on a modern macOS host</li>
  <li>XNU source code lives on the host’s filesystem, and is exposed via an NFS server</li>
  <li>The guest accesses the XNU source via an <a href="https://en.wikipedia.org/wiki/Network_File_System">NFS</a> mount</li>
  <li>The host uses SSH to control the guest</li>
  <li>Edit XNU source on host, kick off a build via SSH on the guest, build artifacts end up on the filesystem accessible by host and guest</li>
</ul>

<p>To set up the dependencies needed to build the Mac OS X Cheetah kernel on the Mac OS X Cheetah guest, I followed the instructions <a href="https://dinomite.net/blog/2006/01/13/darwin-kernel-compile/">here</a>. They mostly matched up with what I needed to do. Relevant sources are available from Apple <a href="https://opensource.apple.com/releases/">here</a>.</p>

<p>After fixing the BAT setup and adding some small patches to reroute console output to my USB Gecko, I now had video output and serial debug logs working - making future development and debugging significantly easier. Thanks to this new visibility into what was going on, I could see that the virtual memory, IOKit, and BSD subsystems were all initialized and running - without crashing. This was a significant milestone, and gave me confidence that I was on the right path to getting a full system working.</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/still_waiting.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/still_waiting.png" alt="Verbose boot logs, ending with Still waiting for root device" /></a></p>

<p>Readers who have attempted to run Mac OS X on a PC via “<a href="https://en.wikipedia.org/wiki/Hackintosh">hackintoshing</a>” may recognize the last line in the boot logs: the dreaded “Still waiting for root device”. This occurs when the system can’t find a root filesystem from which to continue booting. In my case, this was expected: the kernel had done all it could and was ready to load the rest of the Mac OS X system from the filesystem, but it didn’t know where to locate this filesystem. To make progress, I would need to tell the kernel how to read from the Wii’s SD card. To do this, I’d need to tackle the next phase of this project: writing drivers.</p>

<h2 id="writing-drivers">Writing Drivers</h2>

<h3 id="understanding-the-iokit-driver-model">Understanding the IOKit Driver Model</h3>

<p>Mac OS X drivers are built using IOKit - a collection of software components that aim to make it easy to extend the kernel to support different hardware devices. Drivers are written using a subset of C++, and make extensive use of object-oriented programming concepts like inheritance and composition. Many pieces of useful functionality are provided, including:</p>

<ul>
  <li>Base classes and “families” that implement common behavior for different types of hardware</li>
  <li>A layered runtime architecture representing provider-client relationships</li>
  <li>Probing and matching drivers to hardware present in the device tree</li>
  <li>Abstractions for accessing device memory</li>
</ul>

<p>In IOKit, there are two kinds of drivers: a specific device driver and a nub. A specific device driver is an object that manages a specific piece of hardware. A nub is an object that serves as an attach-point for a specific device driver, and also provides the ability for that attached driver to communicate with the driver that created the nub. It’s this chain of driver-to-nub-to-driver that creates the aforementioned provider-client relationships. I struggled for a while to grasp this concept, and found a concrete example useful.</p>

<p>Real Macs can have a <a href="https://en.wikipedia.org/wiki/Peripheral_Component_Interconnect">PCI</a> bus with several PCI ports. In this example, consider an ethernet card being plugged into one of the PCI ports. A driver, <code class="language-plaintext highlighter-rouge">IOPCIBridge</code>, handles communicating with the PCI bus hardware on the motherboard. This driver scans the bus, creating <code class="language-plaintext highlighter-rouge">IOPCIDevice</code> nubs (attach-points) for each plugged-in device that it finds. A hypothetical driver for the plugged-in ethernet card (let’s call it <code class="language-plaintext highlighter-rouge">SomeEthernetCard</code>) can attach to the nub, using it as its proxy to call into PCI functionality provided by the <code class="language-plaintext highlighter-rouge">IOPCIBridge</code> driver on the other side. The <code class="language-plaintext highlighter-rouge">SomeEthernetCard</code> driver can also create its own <code class="language-plaintext highlighter-rouge">IOEthernetInterface</code> nubs so that higher-level parts of the IOKit networking stack can attach to it.</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/iokit.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/iokit.png" alt="IOKit Provider / Client Relationship" /></a></p>

<p>Someone developing a PCI ethernet card driver would only need to write <code class="language-plaintext highlighter-rouge">SomeEthernetCard</code>; the lower-level PCI bus communication and the higher-level networking stack code is all provided by existing IOKit driver families. As long as <code class="language-plaintext highlighter-rouge">SomeEthernetCard</code> can attach to an <code class="language-plaintext highlighter-rouge">IOPCIDevice</code> nub and publish its own <code class="language-plaintext highlighter-rouge">IOEthernetInterface</code> nubs, it can sandwich itself between two existing families in the driver stack, benefiting from all of the functionality provided by <code class="language-plaintext highlighter-rouge">IOPCIFamily</code> while also satisfying the needs of <code class="language-plaintext highlighter-rouge">IONetworkingFamily</code>.</p>

<h3 id="representing-the-wiis-hardware">Representing the Wii’s Hardware</h3>

<p>Unlike Macs from the same era, the Wii doesn’t use PCI to connect its various pieces of hardware to its motherboard. Instead, it uses a custom <a href="https://en.wikipedia.org/wiki/System_on_a_chip">system-on-a-chip</a> (SoC) called the <a href="https://en.wikipedia.org/wiki/Hollywood_(graphics_chip)">Hollywood</a>. Through the Hollywood, many pieces of hardware can be accessed: the GPU, SD card, WiFi, Bluetooth, interrupt controllers, USB ports, and more. The Hollywood also contains an ARM coprocessor, nicknamed the <a href="https://wiibrew.org/wiki/Hardware/Starlet">Starlet</a>, that exposes hardware functionality to the main PowerPC processor via <a href="https://wiibrew.org/wiki/Hardware/IPC">inter-processor-communication</a> (IPC).</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/wii_hw_diagram.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/wii_hw_diagram.png" alt="Nintendo Wii Hardware Diagram" /></a>
<em>Source: <a href="https://wiibrew.org/wiki/Hardware">WiiBrew: Hardware</a></em></p>

<p>This unique hardware layout and communication protocol meant that I couldn’t piggy-back off of an existing IOKit driver family like <code class="language-plaintext highlighter-rouge">IOPCIFamily</code>. Instead, I would need to implement an equivalent driver for the Hollywood SoC, creating nubs that represent attach-points for all of the hardware it contains. I landed on this layout of drivers and nubs (note that this is only showing a subset of the drivers that had to be written):</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/wii_iokit.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/wii_iokit.png" alt="Wii IOKit Driver Layout" /></a></p>

<p>Now that I had a better idea of how to represent the Wii’s hardware in IOKit, I began work on my Hollywood driver.</p>

<h3 id="writing-a-hollywood-driver">Writing a Hollywood Driver</h3>

<p>I started by creating a new C++ header and implementation file for a <code class="language-plaintext highlighter-rouge">NintendoWiiHollywood</code> driver. Its driver “personality” enabled it to be matched to a node in the device tree with the name “hollywood”`. Once the driver was matched and running, it was time to publish nubs for all of its child devices.</p>

<p>Once again leaning on the device tree as the source of truth for what hardware lives under the Hollywood, I iterated through all of the Hollywood node’s children, creating and publishing <code class="language-plaintext highlighter-rouge">NintendoWiiHollywoodDevice</code> nubs for each:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="n">NintendoWiiHollywood</span><span class="o">::</span><span class="n">publishBelow</span><span class="p">(</span><span class="n">OSIterator</span> <span class="o">*</span><span class="n">iter</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">IORegistryEntry</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
  <span class="n">IOService</span> <span class="o">*</span><span class="n">nub</span><span class="p">;</span>
  
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">iter</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// loop through all children of /hollywood</span>
  <span class="k">while</span> <span class="p">((</span><span class="n">next</span> <span class="o">=</span> <span class="p">(</span><span class="n">IORegistryEntry</span> <span class="o">*</span><span class="p">)</span><span class="n">iter</span><span class="o">-&gt;</span><span class="n">getNextObject</span><span class="p">()))</span>
  <span class="p">{</span>
    <span class="c1">// create a nub</span>
    <span class="n">nub</span> <span class="o">=</span> <span class="n">createNub</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">nub</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="k">continue</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// publish nubs so that drivers can attach to them</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">nub</span><span class="o">-&gt;</span><span class="n">attach</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
    <span class="p">{</span>
      <span class="n">nub</span><span class="o">-&gt;</span><span class="n">registerService</span><span class="p">();</span>
    <span class="p">}</span>
    
    <span class="n">nub</span><span class="o">-&gt;</span><span class="n">release</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="n">iter</span><span class="o">-&gt;</span><span class="n">release</span><span class="p">();</span>
  
  <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">IOService</span> <span class="o">*</span><span class="n">NintendoWiiHollywood</span><span class="o">::</span><span class="n">createNub</span><span class="p">(</span><span class="n">IORegistryEntry</span> <span class="o">*</span><span class="n">from</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">NintendoWiiHollywoodDevice</span> <span class="o">*</span><span class="n">nub</span> <span class="o">=</span> <span class="k">new</span> <span class="n">NintendoWiiHollywoodDevice</span><span class="p">;</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">nub</span> <span class="o">&amp;&amp;</span> <span class="n">nub</span><span class="o">-&gt;</span><span class="n">init</span><span class="p">(</span><span class="n">from</span><span class="p">,</span> <span class="n">gIODTPlane</span><span class="p">))</span>
  <span class="p">{</span>
    <span class="c1">// give the nub a reference back to its hollywood "provider"</span>
    <span class="n">nub</span><span class="o">-&gt;</span><span class="n">hollywood</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">nub</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">nub</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">nub</span><span class="o">-&gt;</span><span class="n">release</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Once <code class="language-plaintext highlighter-rouge">NintendoWiiHollywoodDevice</code> nubs were created and published, the system would be able to have other device drivers, like an SD card driver, attach to them.</p>

<h3 id="writing-an-sd-card-driver">Writing an SD Card Driver</h3>

<p>Next, I moved on to writing a driver to enable the system to read and write from the Wii’s SD card. This driver is what would enable the system to continue booting, since it was currently stuck looking for a root filesystem from which to load additional startup files.</p>

<p>I began by subclassing <code class="language-plaintext highlighter-rouge">IOBlockStorageDevice</code>, which has many abstract methods intended to be implemented by subclassers:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">doAsyncReadWrite</span><span class="p">(</span><span class="n">IOMemoryDescriptor</span> <span class="o">*</span><span class="n">buffer</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">block</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">nblks</span><span class="p">,</span> <span class="n">IOStorageCompletion</span> <span class="n">completion</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">doSyncReadWrite</span><span class="p">(</span><span class="n">IOMemoryDescriptor</span> <span class="o">*</span><span class="n">buffer</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">block</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">nblks</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">doEjectMedia</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">doFormatMedia</span><span class="p">(</span><span class="n">UInt64</span> <span class="n">byteCapacity</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">UInt32</span> <span class="n">doGetFormatCapacities</span><span class="p">(</span><span class="n">UInt64</span> <span class="o">*</span><span class="n">capacities</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">capacitiesMaxCount</span><span class="p">)</span> <span class="k">const</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">doLockUnlockMedia</span><span class="p">(</span><span class="kt">bool</span> <span class="n">doLock</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">doSynchronizeCache</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="kt">char</span> <span class="o">*</span><span class="n">getVendorString</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="kt">char</span> <span class="o">*</span><span class="n">getProductString</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="kt">char</span> <span class="o">*</span><span class="n">getRevisionString</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="kt">char</span> <span class="o">*</span><span class="n">getAdditionalDeviceInfoString</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportBlockSize</span><span class="p">(</span><span class="n">UInt64</span> <span class="o">*</span><span class="n">blockSize</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportEjectability</span><span class="p">(</span><span class="kt">bool</span> <span class="o">*</span><span class="n">isEjectable</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportLockability</span><span class="p">(</span><span class="kt">bool</span> <span class="o">*</span><span class="n">isLockable</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportMaxReadTransfer</span><span class="p">(</span><span class="n">UInt64</span> <span class="n">blockSize</span><span class="p">,</span> <span class="n">UInt64</span> <span class="o">*</span><span class="n">max</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportMaxWriteTransfer</span><span class="p">(</span><span class="n">UInt64</span> <span class="n">blockSize</span><span class="p">,</span> <span class="n">UInt64</span> <span class="o">*</span><span class="n">max</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportMaxValidBlock</span><span class="p">(</span><span class="n">UInt64</span> <span class="o">*</span><span class="n">maxBlock</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportMediaState</span><span class="p">(</span><span class="kt">bool</span> <span class="o">*</span><span class="n">mediaPresent</span><span class="p">,</span> <span class="kt">bool</span> <span class="o">*</span><span class="n">changedState</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportPollRequirements</span><span class="p">(</span><span class="kt">bool</span> <span class="o">*</span><span class="n">pollRequired</span><span class="p">,</span> <span class="kt">bool</span> <span class="o">*</span><span class="n">pollIsExpensive</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportRemovability</span><span class="p">(</span><span class="kt">bool</span> <span class="o">*</span><span class="n">isRemovable</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportWriteProtection</span><span class="p">(</span><span class="kt">bool</span> <span class="o">*</span><span class="n">isWriteProtected</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</code></pre></div></div>

<p>For most of these methods, I could implement them with hard-coded values that matched the Wii’s SD card hardware; vendor string, block size, max read and write transfer size, ejectability, and many others all return constant values, and were trivial to implement.</p>

<p>The more interesting methods to implement were the ones that needed to actually communicate with the currently-inserted SD card: getting the capacity of the SD card, reading from the SD card, and writing to the SD card:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">doAsyncReadWrite</span><span class="p">(</span><span class="n">IOMemoryDescriptor</span> <span class="o">*</span><span class="n">buffer</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">block</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">nblks</span><span class="p">,</span> <span class="n">IOStorageCompletion</span> <span class="n">completion</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">doSyncReadWrite</span><span class="p">(</span><span class="n">IOMemoryDescriptor</span> <span class="o">*</span><span class="n">buffer</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">block</span><span class="p">,</span> <span class="n">UInt32</span> <span class="n">nblks</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="n">reportMaxValidBlock</span><span class="p">(</span><span class="n">UInt64</span> <span class="o">*</span><span class="n">maxBlock</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</code></pre></div></div>

<p>To communicate with the SD card, I utilized the IPC functionality provided by <a href="https://wiibrew.org/wiki/MINI">MINI</a> running on the Starlet co-processor. By writing data to certain reserved memory addresses, the SD card driver was able to issue commands to MINI. MINI would then execute those commands, communicating back any result data by writing to a different reserved memory address that the driver could monitor.</p>

<p>MINI supports many useful command types. The ones used by the SD card driver are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">IPC_SDMMC_SIZE</code>: Returns the number of sectors on the currently-inserted SD card</li>
  <li><code class="language-plaintext highlighter-rouge">IPC_SDMMC_READ</code>: Reads data from a sector into a memory buffer</li>
  <li><code class="language-plaintext highlighter-rouge">IPC_SDMMC_WRITE</code>: Writes data from a memory buffer to a sector</li>
</ul>

<p>With these three command types, reads, writes, and capacity-checks could all be implemented, enabling me to satisfy the core requirements of the block storage device subclass.</p>

<p>Like with most programming endevours, things rarely work on the first try. To investigate issues, my primary debugging tool was sending log messages to the serial debugger via calls to <code class="language-plaintext highlighter-rouge">IOLog</code>. With this technique, I was able to see which methods were being called on my driver, what values were being passed in, and what values my IPC implementation was <em>sending to</em> and <em>receiving from</em> MINI - but I had no ability to set breakpoints or analyze execution dynamically while the kernel was running.</p>

<p>One of the trickier bugs that I encountered had to do with cached memory. When the SD card driver wants to read from the SD card, the command it issues to MINI (running on the ARM CPU) includes a memory address at which to store any loaded data. After MINI finishes writing to memory, the SD card driver (running on the PowerPC CPU) might not be able to see the updated contents if that region is mapped as cacheable. In that case, the PowerPC will read from its cache lines rather than RAM, returning stale data instead of the newly loaded contents. To work around this, the SD card driver must use uncached memory for its buffers.</p>

<p>After several days of bug-fixing, I reached a new milestone: <code class="language-plaintext highlighter-rouge">IOBlockStorageDriver</code>, which attached to my SD card driver, had started publishing <code class="language-plaintext highlighter-rouge">IOMedia</code> nubs representing the logical partitions present on the SD. Through these nubs, higher-level parts of the system were able to attach and begin using the SD card. Importantly, the system was now able to find a root filesystem from which to continue booting, and I was no longer stuck at “Still waiting for root device”:</p>

<p>My boot logs now looked like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Waiting on &lt;dict ID="0"&gt;&lt;key&gt;IOProviderClass&lt;/key&gt;&lt;string ID="1"&gt;IOService&lt;/string&gt;&lt;key&gt;BSD Name&lt;/key&gt;&lt;string ID="2"&gt;disk0s4&lt;/string&gt;&lt;/dict&gt;
NintendoWiiSDCard: started
Got boot device = IOService:/NintendoWiiPE/hollywood/NintendoWiiHollywood/sdhc@D070000/NintendoWiiSDCard/IOBlockStorageDriver/Nintendo Nintendo Wii SD Media/IOApplePartitionScheme/Untitled 4@4
BSD root: disk0s4, major 14, minor 3
devfs on /dev
</code></pre></div></div>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/IMG_3257.jpeg"><img src="/assets/images/porting-mac-os-x-nintendo-wii/IMG_3257.jpeg" alt="Getting past &quot;Still waiting for root device” while traveling on a train" /></a></p>

<p>After some more rounds of bug fixes (while on the go), I was able to boot past single-user mode:</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/IMG_3935.jpeg"><img src="/assets/images/porting-mac-os-x-nintendo-wii/IMG_3935.jpeg" alt="Booting to “Tuning system&quot; while traveling on a plane" /></a></p>

<p>And eventually, make it through the entire verbose-mode startup sequence, which ends with the message: “Startup complete”:</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/IMG_4208.jpeg"><img src="/assets/images/porting-mac-os-x-nintendo-wii/IMG_4208.jpeg" alt="Booting to “Startup complete”, then crashing" /></a></p>

<p>At this point, the system was trying to find a framebuffer driver so that the Mac OS X <a href="https://en.wikipedia.org/wiki/Graphical_user_interface">GUI</a> could be shown. As indicated in the logs, <code class="language-plaintext highlighter-rouge">WindowServer</code> was not happy - to fix this, I’d need to write my own framebuffer driver.</p>

<h3 id="writing-a-framebuffer-driver">Writing a Framebuffer Driver</h3>

<p>A framebuffer is a region of RAM that stores the pixel data used to produce an image on a display. This data is typically made up of color component values for each pixel. To change what’s displayed, new pixel data is written into the framebuffer, which is then shown the next time the display refreshes. For the Wii, the framebuffer usually lives somewhere in MEM1 due to it being slightly faster than MEM2. I chose to place my framebuffer in the last megabyte of MEM1 at <code class="language-plaintext highlighter-rouge">0x01700000</code>. At 640x480 resolution, and 16 bits per pixel, the pixel data for the framebuffer fit comfortably in less than one megabyte of memory.</p>

<p>Early in the boot process, Mac OS X uses the bootloader-provided framebuffer address to display simple boot graphics via <code class="language-plaintext highlighter-rouge">video_console.c</code>. In the case of a verbose-mode boot, font-character bitmaps are written into the framebuffer to produce a visual log of what’s happening while starting up. Once the system boots far enough, it can no longer use this initial framebuffer code; the desktop, window server, dock, and all of the other GUI-related processes that comprise the Mac OS X <a href="https://en.wikipedia.org/wiki/Aqua_(user_interface)">Aqua</a> user interface require a real, IOKit-aware framebuffer driver.</p>

<p>To tackle this next driver, I subclassed <code class="language-plaintext highlighter-rouge">IOFramebuffer</code>. Similar to subclassing <code class="language-plaintext highlighter-rouge">IOBlockStorageDevice</code> for the SD card driver, <code class="language-plaintext highlighter-rouge">IOFramebuffer</code> also had several abstract methods for my framebuffer subclass to implement:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">virtual</span> <span class="k">class</span> <span class="nc">IODeviceMemory</span><span class="o">*</span> <span class="nf">getApertureRange</span><span class="p">(</span><span class="n">IOPixelAperture</span> <span class="n">aperture</span><span class="p">);</span>
<span class="k">virtual</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="nf">getPixelFormats</span><span class="p">();</span>
<span class="k">virtual</span> <span class="n">IOItemCount</span> <span class="nf">getDisplayModeCount</span><span class="p">();</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="nf">getDisplayModes</span><span class="p">(</span><span class="n">IODisplayModeID</span> <span class="o">*</span><span class="p">);</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="nf">getInformationForDisplayMode</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span><span class="p">,</span> <span class="n">IODisplayModeInformation</span> <span class="o">*</span><span class="p">);</span>
<span class="k">virtual</span> <span class="n">UInt64</span> <span class="nf">getPixelFormatsForDisplayMode</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span><span class="p">,</span> <span class="kt">long</span> <span class="kt">int</span><span class="p">);</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="nf">getPixelInformation</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span><span class="p">,</span> <span class="kt">long</span> <span class="kt">int</span><span class="p">,</span> <span class="kt">long</span> <span class="kt">int</span><span class="p">,</span> <span class="n">IOPixelInformation</span> <span class="o">*</span><span class="p">);</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="nf">getCurrentDisplayMode</span><span class="p">(</span><span class="n">IODisplayModeID</span> <span class="o">*</span><span class="p">,</span> <span class="n">IOIndex</span> <span class="o">*</span><span class="p">);</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="nf">setGammaTable</span><span class="p">(</span><span class="n">UInt32</span><span class="p">,</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">UInt32</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">);</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="nf">setDisplayMode</span><span class="p">(</span><span class="n">IODisplayModeID</span><span class="p">,</span> <span class="n">IOIndex</span><span class="p">);</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="nf">setApertureEnable</span><span class="p">(</span><span class="n">IOPixelAperture</span><span class="p">,</span> <span class="n">IOOptionBits</span><span class="p">);</span>
<span class="k">virtual</span> <span class="n">IOReturn</span> <span class="nf">newUserClient</span><span class="p">(</span><span class="n">task_t</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">,</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">IOUserClient</span> <span class="o">**</span><span class="p">);</span>
<span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">isConsoleDevice</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
</code></pre></div></div>

<p>Once again, most of these were trivial to implement, and simply required returning hard-coded Wii-compatible values that accurately described the hardware. One of the most important methods to implement is <code class="language-plaintext highlighter-rouge">getApertureRange</code>, which returns an <code class="language-plaintext highlighter-rouge">IODeviceMemory</code> instance whose base address and size describe the location of the framebuffer in memory:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">IODeviceMemory</span><span class="o">*</span> <span class="n">NintendoWiiFramebuffer</span><span class="o">::</span><span class="n">getApertureRange</span><span class="p">(</span><span class="n">IOPixelAperture</span> <span class="n">aperature</span><span class="p">)</span>
<span class="p">{</span>
  <span class="c1">// 0x01700000, 640x480 resoluton, 2 bytes (16 bits) per pixel</span>
  <span class="k">return</span> <span class="n">IODeviceMemory</span><span class="o">::</span><span class="n">withRange</span><span class="p">(</span><span class="mh">0x01700000</span><span class="p">,</span> <span class="mi">640</span> <span class="o">*</span> <span class="mi">480</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After returning the correct device memory instance from this method, the system was able to transition from the early-boot text-output framebuffer, to a framebuffer capable of displaying the full Mac OS X GUI. I was even able to boot the Mac OS X installer:</p>

<video controls="" width="100%">
    <source src="/assets/images/porting-mac-os-x-nintendo-wii/bad_fb.mp4" />
    Your browser does not support the video tag.
</video>

<p>Readers with a keen eye might notice some issues:</p>

<ul>
  <li>The verbose-mode text framebuffer is still active, causing text to be displayed and the framebuffer to be scrolled</li>
  <li>Everything is magenta</li>
</ul>

<p>The fix for the early-boot video console still writing text output to the framebuffer was simple: tell the system that our new, IOKit framebuffer is the same as the one that was previously in use by returning <code class="language-plaintext highlighter-rouge">true</code> from <code class="language-plaintext highlighter-rouge">isConsoleDevice</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="n">NintendoWiiFramebuffer</span><span class="o">::</span><span class="n">isConsoleDevice</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
  <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/IMG_7174.jpeg"><img src="/assets/images/porting-mac-os-x-nintendo-wii/IMG_7174.jpeg" alt="Mac OS X installer with incorrect colors" /></a></p>

<p>The fix for the incorrect colors was much more involved, as it relates to a fundamental incompatibility between the Wii’s video hardware and the graphics code that Mac OS X uses.</p>

<p>The Nintendo Wii’s video encoder hardware is optimized for analogue TV signal output, and as a result, expects 16-bit <a href="https://en.wikipedia.org/wiki/Y′UV">YUV</a> pixel data in its framebuffer. This is a problem, since Mac OS X expects the framebuffer to contain RGB pixel data. If the framebuffer that the Wii displays contains non-YUV pixel data, then colors will be completely wrong.</p>

<p>To work around this incompatibility, I took inspiration from the Wii Linux project, which had solved this problem many years ago. The strategy is to use two framebuffers: an RGB framebuffer that Mac OS X interacts with, and a YUV framebuffer that the Wii’s video hardware outputs to the attached display. 60 times per second, the framebuffer driver converts the pixel data in the RGB framebuffer to YUV pixel data, placing the converted data in the framebuffer that the Wii’s video hardware displays:</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/dual_fb.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/dual_fb.png" alt="Dual-framebuffer system" /></a></p>

<p>After implementing the dual-framebuffer strategy, I was able to boot into a correctly-colored Mac OS X system - <strong><em>for the first time, Mac OS X was running on a Nintendo Wii</em></strong>:</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/IMG_4645.jpeg"><img src="/assets/images/porting-mac-os-x-nintendo-wii/IMG_4645.jpeg" alt="Mac OS X installer with correct colors" /></a>
<a href="/assets/images/porting-mac-os-x-nintendo-wii/IMG_4958.jpeg"><img src="/assets/images/porting-mac-os-x-nintendo-wii/IMG_4958.jpeg" alt="Booted to the Mac OS X desktop" /></a>
<em>(Yes, I brought the Wii on a trip to Hawaii - it’s hard to put a project down when you’re on the verge of reaching a major milestone!)</em></p>

<p>The system was now booted all the way to the desktop - but there was a problem - I had no way to interact with anything. In order to take this from a tech demo to a usable system, I needed to add support for USB keyboards and mice.</p>

<h3 id="adding-usb-support">Adding USB Support</h3>

<p>To enable USB keyboard and mouse input, I needed to get the Wii’s rear USB ports working under Mac OS X - specifically, I needed to get the low-speed, USB 1.1 <a href="https://wiki.osdev.org/Open_Host_Controller_Interface">OHCI</a> host controller up and running. My hope was to reuse code from <code class="language-plaintext highlighter-rouge">IOUSBFamily</code> - a collection of USB drivers that abstracts away much of the complexity of communicating with USB hardware. The specific driver that I needed to get running was <code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code> - a driver that handles communicating with the exact kind of USB host controller that’s used by the Wii.</p>

<p>My hope quickly turned to disappointment as I encountered multiple roadblocks.</p>

<h4 id="roadblock-1">Roadblock 1:</h4>

<p><code class="language-plaintext highlighter-rouge">IOUSBFamily</code> source code for Mac OS X Cheetah and Puma is, for some reason, not part of the otherwise comprehensive collection of open source releases provided by Apple. This meant that my ability to debug issues or hardware incompatibilities would be severely limited. Basically, if the USB stack didn’t just magically work without any tweaks or modifications (<em>spoiler: of course it didn’t</em>), diagnosing the problem would be extremely difficult without access to the source.</p>

<h4 id="roadblock-2">Roadblock 2:</h4>

<p><code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code> didn’t match any hardware in the device tree, and therefore didn’t start running, due to its driver personality insisting that its provider class (the nub to which it attaches) be an <code class="language-plaintext highlighter-rouge">IOPCIDevice</code>. As I had already figured out, the Wii definitely <em>does not</em> use <code class="language-plaintext highlighter-rouge">IOPCIFamily</code>, meaning <code class="language-plaintext highlighter-rouge">IOPCIDevice</code> nubs would never be created and <code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code> would have nothing to attach to.</p>

<p>My solution to work around this was to create a new <code class="language-plaintext highlighter-rouge">NintendoWiiHollywoodDevice</code> nub, called <code class="language-plaintext highlighter-rouge">NintendoWiiHollywoodPCIDevice</code>, that subclassed <code class="language-plaintext highlighter-rouge">IOPCIDevice</code>. By having <code class="language-plaintext highlighter-rouge">NintendoWiiHollywood</code> publish a nub that inherited from <code class="language-plaintext highlighter-rouge">IOPCIDevice</code>, and tweaking <code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code>’s driver personality in its <code class="language-plaintext highlighter-rouge">Info.plist</code> to use <code class="language-plaintext highlighter-rouge">NintendoWiiHollywoodPCIDevice</code> as its provider class, I could get it to match and start running.</p>

<p>To figure out how <code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code> was using its PCI device nub, I used a mix of runtime logging, disassembly analysis, and source-code analysis of Mac OS X 10.2 Jaguar’s <code class="language-plaintext highlighter-rouge">IOUSBFamily</code> sources (which are the first available from Apple). To my relief, communication with the PCI hardware via the PCI device nub was limited - the main thing <code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code> needed from the PCI hardware was the base address of the USB host controller - something that it retrieved using PCI commands. I was able to intercept these commands in my fake PCI nub and return the base address of the Wii’s OHCI hardware.</p>

<p>With these workarounds, <code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code> was now running - however, my USB ports still failed to respond.</p>

<h4 id="roadblock-3">Roadblock 3:</h4>

<p>My next discovery was that <code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code> assumes <a href="https://en.wikipedia.org/wiki/Endianness">little-endian byte ordering</a> for register reads and writes. After doing some research, I learned that this is actually pretty standard behavior for OHCI hardware, even if the host hardware is a big-endian system (as is the case for PowerPC systems like the Wii and PowerPC Macs). So why wasn’t this working on the Wii?</p>

<p>The incompatibility comes down to a difference in how <code class="language-plaintext highlighter-rouge">IOUSBFamily</code> and the Wii handle endianness differences between USB hardware and the host processor - in the case of <code class="language-plaintext highlighter-rouge">IOUSBFamily</code>, data is byte-swapped <em>in software</em> when working with OHCI registers, while in the case of the Wii, data is byte-swapped <em>in hardware</em> via swapped byte lanes, making the OHCI registers automatically appear to be big-endian when read from or written to. This system on the Wii is known as <a href="https://wiibrew.org/wiki/Reversed_Little_Endian">reversed-little-endian</a>.</p>

<p>To work around this, I needed to prevent the “double swap” that was happening by removing the software byte-swapping that <code class="language-plaintext highlighter-rouge">IOUSBFamily</code> was performing - but without access to the source code, this wouldn’t be easy. Once again tackling a difficult problem while traveling, I spent several hours on a flight using <a href="https://en.wikipedia.org/wiki/Ghidra">Ghidra</a> to find and patch-out any byte-swapping instructions. This quickly got messy, as there are several places in the USB stack that have legitimate byte-swapping use cases, and should not be patched out.</p>

<p>In the end, my hand-patched <code class="language-plaintext highlighter-rouge">AppleUSBOHCI</code> binary was fragile, nearly impossible to edit, and almost certainly incorrect. Unsurprisingly, it didn’t work, and my USB ports continued to be unresponsive.</p>

<h4 id="unblocked">Unblocked:</h4>

<p>What I really needed was access to <code class="language-plaintext highlighter-rouge">IOUSBFamily</code> source code for Mac OS X Cheetah. If I had that, I could remove the dependency on <code class="language-plaintext highlighter-rouge">IOPCIFamily</code>, remove all of the unnecessary software byte-swaps, and hopefully build a working fork that works for the Wii’s hardware.</p>

<p>After spending several days searching old forums, browsing sites on the <a href="https://web.archive.org">Wayback Machine</a>, and attempting to access ancient FTP servers, I decided to return to one of the original places that I used to ask for help on the internet: <a href="https://en.wikipedia.org/wiki/IRC">IRC</a>.</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/irc.png"><img src="/assets/images/porting-mac-os-x-nintendo-wii/irc.png" alt="IRC chat getting access to IOUSBFamily" /></a></p>

<p>Sure enough, the CVS repository that @bbraun (of <a href="http://synack.net/~bbraun/">synack.net</a>) provided had every file needed to build <code class="language-plaintext highlighter-rouge">IOUSBFamily</code> for Mac OS X Cheetah. If you’re reading this - thank you for helping an internet-stranger out <code class="language-plaintext highlighter-rouge">:)</code></p>

<p>With <code class="language-plaintext highlighter-rouge">IOUSBFamily</code> patched and built from source, my USB keyboard and mouse were able to control the system, turning the Wii into a usable Mac OS X computer in the process.</p>

<video controls="" width="100%">
    <source src="/assets/images/porting-mac-os-x-nintendo-wii/usb.mp4" />
    Your browser does not support the video tag.
</video>

<h2 id="making-things-good-">Making Things Good ™</h2>

<h3 id="improving-the-bootloader">Improving the Bootloader</h3>

<p>To support the use case of going through a full Mac OS X installation flow, I needed to add support for booting from different partitions on the same SD card (one for the installer, one for the installed-system). The approach I took was to revamp the boot menu to list all bootable partitions, allowing the user to select the one they’d want to boot from by cycling through the available options.</p>

<p>To list the available partitions, I needed to parse the <a href="https://en.wikipedia.org/wiki/Apple_Partition_Map">Apple partition Map</a> (APM) at sector 1 of the SD card. Once parsed, I could get the offsets, filesystem types, and names of each partition on the disk:</p>

<p><a href="/assets/images/porting-mac-os-x-nintendo-wii/partitions.gif"><img src="/assets/images/porting-mac-os-x-nintendo-wii/partitions.gif" alt="Cycling through partitions in the bootloader" /></a></p>

<p>Next, I wanted to add the ability to boot from an unmodified installer and system partition - eliminating the need to replace drivers or the kernel after installing or updating Mac OS X. To accomplish this, I needed to make the bootloader, rather than the kernel, responsible for loading all of the Wii-specific drivers. Thankfully, Mac OS X has support for injecting bootloader-loaded drivers via the <code class="language-plaintext highlighter-rouge">/chosen/memory-map</code> node of the device tree. This node contains entries for each bootloader-loaded driver:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/
└── chosen
	└── memory-map
    	├── Driver-4d6000
      	├── Driver-4d7000
        ├── Driver-4d8000
        ├── Driver-4d9000
        ├── Driver-4da000
            etc.
</code></pre></div></div>

<p>Each entry contains an address that points to a driver entry structure in memory:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">driver_info</span>  <span class="p">{</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">info_plist_start</span><span class="p">;</span>
    <span class="n">u32</span> <span class="n">info_plist_size</span><span class="p">;</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">bin_start</span><span class="p">;</span>
    <span class="n">u32</span> <span class="n">bin_size</span><span class="p">;</span>
<span class="p">}</span> <span class="n">driver_info_t</span><span class="p">;</span>
</code></pre></div></div>

<p>Which itself contains pointers to driver binaries and <code class="language-plaintext highlighter-rouge">Info.plist</code> files that were loaded into memory.</p>

<p>To load drivers and build the <code class="language-plaintext highlighter-rouge">memory-map</code> node of the device tree, the bootloader recursively searches the FAT32 support partition for any kernel extension (kext) bundles, loading binary and <code class="language-plaintext highlighter-rouge">Info.plist</code> pairs for each one that it finds. Here’s an example kext bundle structure:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SomeDriver.kext
	└── Contents
    	├── Info.plist
        └── MacOS
        	└── SomeDriver
        └── PlugIns
        	└── SomeOtherDriver.kext
</code></pre></div></div>

<p>After implementing driver-loading in the bootloader, I could now boot from unmodified Mac OS X installer and system partitions, simplifying the installation process and making the Wii act even more like a real Mac.</p>

<h3 id="simplifying-the-kernel">Simplifying the Kernel</h3>

<p>By moving drivers out of the kernel, the number of kernel modifications needed to get the system running on the Wii was reduced to just the following:</p>

<ul>
  <li>Patched BAT setup for the Wii’s I/O address and framebuffer memory</li>
  <li>Support for getting the I/O base address from a device tree node named “hollywood”</li>
  <li>Framebuffer cache-coherency fixes</li>
</ul>

<p>Separating the drivers from the kernel makes it easier to reason about the kernel, reduces build times when developing drivers, and paves the way for supporting systems like Mac OS X 10.1 Puma, which moved several families of drivers out of the kernel and onto the root filesystem.</p>

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>There’s something deeply satisfying about accomplishing something that, at the start, you weren’t even sure was possible.</p>

<p>I first had the idea for this project back in 2013 - when I was a sophomore in college. For over a decade, it sat on the back burner; it’s easy to put off a project like this, especially when your day job already involves solving technical problems.</p>

<p>Last year, when I saw that Windows NT had been ported to the Wii, I felt a renewed sense of motivation. Even if my lack of low-level experience resulted in failure, attempting this project would still be an opportunity to learn something new.</p>

<p>In the end, I learned (and accomplished) far more than I ever expected - and perhaps more importantly, I was reminded that the projects that seem just out of reach are exactly the ones worth pursuing.</p>

<p><a href="/assets/icons/favicon-64x64.png"><img src="/assets/icons/favicon-64x64.png" alt="Happy Wii-mac" /></a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Mac OS X 10.0 (Cheetah) running natively on the Nintendo Wii]]></summary></entry></feed>