Background
About a year ago I decided that I wanted to learn some supervisor level programming on the PowerPC and what better/other way to do it than to write my own kernel. I started digging through the source code of various kernel projects (with PowerPC ports) that I could find on the internet as well as reading the excellent PowerPC manuals provided by Motorola/Freescale. After a couple of weeks of hard work I had managed to construct something that could boot on the Pegasos, and more importantly I had reached that glorious point where I felt that I understood what happened in my code and that it no longer consisted of ideas stolen from others but rather was based on my own knowledge of how the CPU worked. At that point I decided that it would be a great idea to write a guide on how to make your own kernel for the Pegasos/PowerPC platform. That turned out to be a bad idea because I never had enough time to write the guide while the kernel (and my head) contained a lot of information that others would have found useful. I held off releasing the source code as I felt users might get lost in there without the guide and it just sat there collecting dust.
It took me some time but I finally realized (mostly thanks to BBRV and Neko) that I should release the source code here on PPCZone and start a forum post (as opposed to writing the guide). That way I can get the source code out into the hands of the developers all the while I'll be able to provide guidance via the forum and this thread.
Everything is released under a
BSD-style to give you the maximum freedom in order to reuse it for your own project(s).
Introduction
There are two source trees which I am going to make available. One is called
loader and it consists of the bare minimum required to produce a bootable kernel which is able to communicate with the
Open Firmware implementation known as SmartFirmware on the Pegasos. It should work on other Open Firmware implementations as well (such as Apple's) but I have only tried it once so your mileage may vary.
The other is CrabOS which is an unfinished micro kernel mostly based on the
L4 design. Right now only two syscalls are implemented, a very basic IPC method, sc_ipc, and a call to switch threads, sc_thread_switch. The kernel can do some basic memory management but most of it is supposed to happen in the unimplemented
pager module.
All the source code has been written by me
except for the vsprintf method which I borrowed from the
Sanos project and queue.h which I borrowed from
NetBSD.
I'm going to assume that you have some basic PowerPC assembly knowledge as well as C programming knowledge. If anything seems confusing and you cannot find an answer in the PowerPC reference manual please ask me.
Compilation
You need a GCC compiler (others might work but you are on your own) capable of producing ppc code. I have written all the code on my Pegasos using Linux. If you intend to use Linux on another arch than ppc, or some other OS (i.e. MorphOS) you might have to modify
config.mak.
Loader
Loader contains the basics for producing a bootable kernel, in C, which can communicate with SmartFirmware. Booting loader on a Pegasos should produce
"Welcome to loader 1.0.0" followed by
"End of the line". It will not return to the firmware at that point and you will have to reset your Pegasos.
crt0.S
crt0 is where it all starts. On boot execution is transferred to the
_start label as defined by
LDFLAGS in
config.mak ("-e_start"). The first thing that happens there is to clear the
BSS section. This is required by ANSI C if you want to be standards conforming but you can get away with skipping this although personally I prefer sticking with accepted standards. The __bss_start and _end labels are defined by the linker (GNU ld) so you do not need to worry about defining these yourself.
@ha means you only want the upper 16 bits of the value,
@l signifies the lower 16 bits. The reason why we cannot load a 32-bit value in one operation is because of the RISC design of the PowerPC. Each instruction in the PowerPC occupies
exactly 32-bits, this includes the operation identifier, register indices etc, and because of that you simply cannot fit a load operation identifier, register index
and a 32-bit constant into one operation.
The next step is to load the stack pointer into the second general purpose register (r1). The PowerPC ABI specifies that the stack pointer should be stored in r1 and if you want to store it in a different register you will have to modify GCC as it assumes the stack is always available via r1. We have already allocated 4kB of memory in the BSS section which we will use as our stack. Please note that if your kernel ever needs more than 4kB of stack space you will be in trouble with this kind of static stack allocation. r2 and r13 are not used by us so we set them to 0, please see the PowerPC ABI for further information about the use of these registers.
Finally we jump to the label
loader which is where we switch from assembly to C.
Note that while programming in assembly you aren't forced to adhere to the PowerPC ABI, but it can be a good thing, especially if you intend to mix assembly and C and don't want to modify GCC. If you do decide to make your own standard and you allow others to write code for your kernel you better make sure they understand that you do not follow the PowerPC ABI standard or otherwise they will be in big trouble.
There is one register which you should refrain from trashing at this point and that is r5. r5 holds the Open Firmware entry point and without the address stored in it you will be unable to call the firmware! When calling the firmware you must
always use the PowerPC ABI.
loader.c
This file contains the
loader function which is called by crt0. As specified by Open Firmware the firmware entry point is passed as the
third parameter (argument) when loading a kernel. According to the PowerPC ABI paramters are passed via general purpose registers r3 through r10 making r5 the third one. Since GCC (ppc-elf) conforms to the PowerPC ABI that means the first argument in a function call is stored in r3, the second one in r4 and so on. Since we are only interested in r5 there are two ways to access it in our C-function. The way I'm doing it is to define the function as "
void loader(int r3 __attribute__((unused)), int r4 __attribute__((unused)), unsigned int r5)".
__attribute__((unused)) tells GCC that we really aren't interested in these variables and that it is free to use the registers they are occupying any way it likes. Please don't attempt to access the variables "r3" and "r4" (regard them as variables and not as registers) without removing these attributes first, otherwise you might get unexpected behaviour in your code.
The other way of accessing r5 would be to have declared the function as "void loader(void)" and then declare a variable that is bound to the r5 register. This is done by declaring a variable like "
int openfirmware asm("r5");". This tells GCC that the int variable "openfirmware" should represents register r5. Any of the two methods is fine and it's up to you which one you prefer.
Of course you do not need to name your variables "rX" or "openfirmware" I just did so for the sake of readability. Avoid considering the variable rX to be synonymous with the register rX.
CrabOS
On booting CrabOS it will enable the MMU and load a page table in order to control access to memory and to prepare paging for the pager process. It will also load two tasks,
pager and
init. pager is supposed to handle memory pages for
everything in the system whereas init is supposed to be the task that does all the necessary work to boot the rest of the OS, which amounts to everything but the kernel and pager.
Running tasks are scheduled based on their IPC paths and when the kernel runs out of active paths it will deadlock. The idea was to implement preemption so that scheduling could be based on the real time clock rather than IPC paths but I didn't have time to finish that.
Source code
Loader
CrabOS
Recommended reading
Open Firmware
IEEE-1275 This is an old version of the Open Firmware specification, but it's free. I found this to cover more than I needed and I have never read a newer version of it.
Freescale
PowerPC Reference Manual
PowerPC Programming Environment Manual
PowerPC ABI
Postlude
I'm running out of time and I wanted to post this before the weekend. I will add more information to this post, hopefully next week.
Feel free to ask any questions regarding kernels on the Pegasos that you like in this thread and we will do our best to answer them. As time goes by I will fill in more and more information in this original post and perhaps some day it will reach an "article"-stage.