PowerPC Bare Bones

From OSDev Wiki
Jump to navigation Jump to search

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.

External links