I’ve recently begun exploration into the Raspberry Pi’s low-level graphics stack, for whimsey. Here I outline how I got started developing kernel code for the Raspberry Pi 3. If you’re not interested in the nuts and bolts of Raspberry Pi development sans OS, you can skip over this and head straight to Exploring Hardware Compositing With the Raspberry Pi, where the real fun begins. But if you’re interested in running the code for yourself, do read along.
In a nutshell, here’s what you need:
- Host machine
- Raspberry Pi
- ARM toolchain for your host
Technically optional, but highly recommended:
- USB-to-TTL serial cable
- Serial bootloader
First off, you’ll need a host machine. That is, a computer other than the Pi. The host machine is where we’ll write and compile the OS kernel. I’m using a 2017 15” MacBook Pro that I’ve had great success with. I’m fairly confident that Windows and Linux are compatible as well, though I haven’t had any firsthand experience using them for RPi development.
Of course, you’ll need a Pi as well. At the time of writing, the latest model is the Raspberry Pi 3 model B. I bought mine on Amazon as part of a kit which came with everything you need to get started with Pi development. It’s called the CanaKit Raspberry Pi 3 Complete Starter Kit and I highly recommend it. It comes with the Pi, power supply, SD card, HDMI cable, and even some heat sinks.
I also highly recommend getting a USB-to-TTL serial cable for debugging purposes. We’re going to be writing code that runs bare on the Pi’s ARM processor itself- without the luxuries of an OS to help us debug. The serial cable allows us to send and receive data to the Pi through a host machine. We’ll be using it to print output to our host so we can see what’s going on when things go awry. But perhaps more importantly, we’ll be compiling the OS on a host machine and using the serial connection to send the kernel to the Pi without needing to copy it to an SD card every time we want to make a modification (which will be quite often if you make as many mistakes as I do). If you’re using a Mac, follow this tutorial to install the drivers for UART communication over the USB-to-TTL cable.
The Pi has an ARM processor, so we need an ARM toolchain. I’m using a release of GCC that includes the ARM 64 target. It’s called
aarch64-none-elf and I installed through Homebrew using this tap. If you’re on Mac OS, it’s just:
$ brew tap SergioBenitez/osxct
I didn’t want to start from complete scratch, so I forked a neat open source project called raspberry-pi-os which is a series of lessons on building a RPi homebrew OS from the ground up. I choose this as a starting point because it gives us a nice, minimal framework to mess around in. The result is a tiny OS that only communicates via the serial terminal, without ever outputing anything to the monitor actually connected to the Pi. We’re going to change that.
My project is hosted here on GitHub. I based my changes off of lesson 1, which is a nice, minimal starting point. The project provides a working Makefile and some code to power the Pi’s mini UART. I copied the
printf implementation from lesson 2, which allows us to
printf directly to UART. I also added a
debug_print_memory which prints a region of memory in hexadecimal to the screen. This will be essential later on.
How do we get our compiled kernel on to the Raspberry Pi? Indeed, we could copy it to the Pi’s SD card every time we compile. If you’re just planning on running the demo without doing any tinkering, this is probably the easiest path for you. But this gets tedious when we need to iterate and debug. Instead, we’ll use the UART connection to send our kernel from our host machine to the Pi every time we boot. We load the SD card with a tiny bootloader kernel that listens on UART for the kernel to be sent down from our host machine. When it receives it, it moves itself out of the way, copies the new kernel in its place, and begins executing it. The Pi thinks it’s just executing a normal kernel loaded from the SD card.
There’s a few serial bootloader projects out there, but the only one I had success with was one by the name of c3r3s. It supports 64 bit kernels and the Raspberry Pi 3.
Iterating on the kernel goes something like this:
- Re-compile the kernel (see below)
- Reboot the Pi and wait for a blank screen
- Launch c3r3s with the path to our kernel
To compile the kerenel, type make inside the
src/lesson01 directory (or whichever lesson / project you wish to build). If successful, the kernel will appear in the same directory named
kernel8.img. This is what you’d normally copy to your SD card.
At this point, you should be able to compile lesson01 of the raspberry-pi-os kernel. We’ll use the
fling command from the c3r3s project to send it to the Pi.
First, follow c3r3s’ installation instructions to get the “loader” kernel on the Pi. Then power on the Pi and wait a few moments for a blank screen. To send our kernel to the Pi, we’ll run the following command. The
screen command is run immediately after to capture the serial output from the Pi:
$ fling -v /dev/cu.SLAB_USBtoUART path/to/kernel8.img && screen /dev/cu.SLAB_USBtoUART 115200
Remember to replace the paths in the command to your kernel and your USBtoUART mount point.
If you see
hello world printed to your terminal afterwards, congrats! You’ve just writen your first Raspberry Pi kernel. From here, continue on to Exploring Hardware Compositing With the Raspberry Pi and we’ll get some pretty shapes on the monitor in no time.