PowerPC Bare Bones
PowerPC is a RISC processor most notably used by Apple between the late 90s and mid 2000s. This article covers PowerPC computers that use the CHRP platform, being Macintosh's and RS/6000's.
Boot Process
OpenFirmware, the BIOS, initializes the system and then starts a Forth console. It looks for a file called /ppc/bootinfo.txt
in every partition it can read. OpenBIOS, the default implementation used by QEMU, supports Ext2, HFS, HFS+, and ISO9660. It is worth noting that OpenFirmware uses the Apple Partition Map (APM) instead of an MBR or GPT.
/ppc/bootinfo.txt
This is an SGML file that tells the BIOS how to load a bootloader on the same partition.
<CHRP-BOOT>
<DESCRIPTION>My Bootloader</DESCRIPTION>
<OS-NAME>My OS</OS-NAME>
<VERSION>1</VERSION>
<COMPATIBLE>
MacRISC MacRISC3 MacRISC4
</COMPATIBLE>
<BOOT-SCRIPT>
" screen" output
." test string" cr
</BOOT-SCRIPT>
</CHRP-BOOT>
This bootinfo.txt should output "test string" to your console.
A Simple Infinite Loop
OpenFirmware requires a "boot" function to be defined that runs an executable. Add the following line to the boot script:
boot &device;:&partition;,\ppc\boot.bin
This will run a binary at /ppc/boot.bin
. Next, assemble the following file and link it using a binutils targeting powerpc-eabi
:
.globl _start
_start:
b _start
Place the resulting binary at /ppc/boot.bin
, and you should get an output similar to this:
Welcome to OpenBIOS v1.1 built on Mar 7 2023 22:21
Trying hd:,\\:tbxi...
Trying hd:,\ppc\bootinfo.txt...
test string
>> switching to new context:
State Upon Entry
If you attach a debugger to QEMU and place a breakpoint on _start, you can see the state of the registers. In particular, only 2 have values:
Register | Value |
---|---|
%r1 | Stack Pointer |
%r5 | OpenFirmware callback function |
We can move the callback around and pass it to a C function like this:
.globl _start
_start:
mr %r3,%r5
bl entry
WARNING: Do not use main() as a function name in your kernel! GCC will try adding runtime initialization even if you use -ffreestanding and -nostdlib
We can then make a simple C file like this:
int entry(int (*ofw)(void*)) {
while (1) ;
}
We now have another infinite loop, but in C this time.
Interacting with OpenFirmware
In order to do anything with the computer, we need to figure out what hardware we have. We do this by asking OpenFirmware through that callback passed to our entry function. By passing the peer
command with an argument of 0
, it gives us a handle to the root of the device tree.
Using the callback
The callback function takes an array of 32-bit numbers, consisting of a pointer to the command name, parameters, and spots for return values.
Name | Argument Count | Return Count | Arguments | Return Values |
---|---|---|---|---|
"peer" | 0x1 | 0x1 | 0x0 | 0x0 |
Here is what that looks like in C source:
#include <stdint.h>
extern int (*ofw)(void*);
struct of_peer_t {
uint32_t name;
uint32_t nargs;
uint32_t nrets;
uint32_t node;
uint32_t retval;
};
uint32_t OF_peer(uint32_t node) {
struct of_peer_t cmd = {
.name = (uint32_t)"peer",
.nargs = 1,
.nrets = 1,
.node = node,
.retval = 0,
};
ofw(&cmd);
return cmd.retval;
}
Call the OF_peer
function from your entrypoint, and you will get a handle to the root node. Of course, to do anything with this handle, you will need more OpenFirmware functions.