-
Pl
chevron_right
Alberto Ruiz: Booting with Rust: Chapter 3
news.movim.eu / PlanetGnome • 1 day ago • 5 minutes
In Chapter 1 I gave the context for this project and in Chapter 2 I showed the bare minimum: an ELF that Open Firmware loads, a firmware service call, and an infinite loop.
That was July 2024. Since then, the project has gone from that infinite loop to a bootloader that actually boots Linux kernels. This post covers the journey.
The filesystem problem
The
Boot Loader Specification
expects BLS snippets in a FAT filesystem under
loaders/entries/
. So the bootloader needs to parse partition tables, mount FAT, traverse directories, and read files. All
#![no_std]
, all big-endian PowerPC.
I tried writing my own minimal FAT32 implementation, then integrating
simple-fatfs
and
fatfs
. None worked well in a freestanding big-endian environment.
Hadris
The breakthrough was
hadris
, a
no_std
Rust crate supporting FAT12/16/32 and ISO9660. It needed some work to get going on PowerPC though. I submitted fixes upstream for:
-
thiserrorpulling instd: default features were not disabled, preventingno_stdbuilds. -
Endianness bug
: the FAT table code read cluster entries as native-endian
u32. On x86 that’s invisible; on big-endian PowerPC it produced garbage cluster chains. -
Performance
: every cluster lookup hit the firmware’s block I/O separately. I implemented a 4MiB readahead cache for the FAT table, made the window size parametric at build time, and improved
read_to_vec()to coalesce contiguous fragments into a single I/O. This made kernel loading practical.
All patches were merged upstream.
Disk I/O
Hadris expects
Read + Seek
traits. I wrote a
PROMDisk
adapter that forwards to OF’s
read
and
seek
client calls, and a
Partition
wrapper that restricts I/O to a byte range. The filesystem code has no idea it’s talking to Open Firmware.
Partition tables: GPT, MBR, and CHRP
PowerVM with modern disks uses GPT (via the
gpt-parser
crate): a PReP partition for the bootloader and an ESP for kernels and BLS entries.
Installation media uses MBR. I wrote a small
mbr-parser
subcrate using
explicit-endian
types so little-endian LBA fields decode correctly on big-endian hosts. It recognizes FAT32, FAT16, EFI ESP, and CHRP (type
0x96
) partitions.
The CHRP type is what CD/DVD boot uses on PowerPC. For ISO9660 I integrated
hadris-iso
with the same
Read + Seek
pattern.
Boot strategy? Try GPT first, fall back to MBR, then try raw ISO9660 on the whole device (CD-ROM). This covers disk, USB, and optical media.
The firmware allocator wall
This cost me a lot of time.
Open Firmware provides
claim
and
release
for memory allocation. My initial approach was to implement Rust’s
GlobalAlloc
by calling
claim
for every allocation. This worked fine until I started doing real work: parsing partitions, mounting filesystems, building vectors, sorting strings. The allocation count went through the roof and the firmware started crashing.
It turns out SLOF has a limited number of tracked allocations. Once you exhaust that internal table,
claim
either fails or silently corrupts state. There is no documented limit; you discover it when things break.
The fix was to
claim
a single large region at startup (1/4 of physical RAM, clamped to 16-512 MB) and implement a free-list allocator on top of it with block splitting and coalescing. Getting this right was painful: the allocator handles arbitrary alignment, coalesces adjacent free blocks, and does all this without itself allocating. Early versions had coalescing bugs that caused crashes which were extremely hard to debug – no debugger, no backtrace, just writing strings to the OF console on a 32-bit big-endian target.
And the kernel boots!
March 7, 2026. The commit message says it all: “And the kernel boots!”
The sequence:
-
BLS discovery : walk
loaders/entries/*.conf, parse intoBLSEntrystructs, filter by architecture (ppc64le), sort by version usingrpmvercmp. -
ELF loading : parse the kernel ELF, iterate
PT_LOADsegments,claima contiguous region, copy segments to their virtual address offsets, zero BSS. -
Initrd :
claimmemory, load the initramfs. -
Bootargs : set
/chosen/bootargsviasetprop. -
Jump : inline assembly trampoline – r3=initrd address, r4=initrd size, r5=OF client interface, branch to kernel:
core::arch::asm!(
"mr 7, 3", // save of_client
"mr 0, 4", // r0 = kernel_entry
"mr 3, 5", // r3 = initrd_addr
"mr 4, 6", // r4 = initrd_size
"mr 5, 7", // r5 = of_client
"mtctr 0",
"bctr",
in("r3") of_client,
in("r4") kernel_entry,
in("r5") initrd_addr as usize,
in("r6") initrd_size as usize,
options(nostack, noreturn)
)
One gotcha: do NOT close stdout/stdin before jumping. On some firmware, closing them corrupts
/chosen
and the kernel hits a machine check. We also skip calling
exit
or
release
– the kernel gets its memory map from the device tree and avoids claimed regions naturally.
The boot menu
I implemented a GRUB-style interactive menu:
- Countdown : boots the default after 5 seconds unless interrupted.
- Arrow/PgUp/PgDn/Home/End navigation .
- ESC : type an entry number directly.
-
e: edit the kernel command line with cursor navigation and word jumping (Ctrl+arrows).
This runs on the OF console with ANSI escape sequences. Terminal size comes from OF’s Forth
interpret
service (
#columns
/
#lines
), with serial forced to 80×24 because SLOF reports nonsensical values.
Secure boot (initial, untested)
IBM POWER has its own secure boot: the
ibm,secure-boot
device tree property (0=disabled, 1=audit, 2=enforce, 3=enforce+OS). The Linux kernel uses an appended signature format – PKCS#7 signed data appended to the kernel file, same format GRUB2 uses on IEEE 1275.
I wrote an
appended-sig
crate that parses the appended signature layout, extracts an RSA key from a DER X.509 certificate (compiled in via
include_bytes!
), and verifies the signature (SHA-256/SHA-512) using the RustCrypto crates, all
no_std
.
The unit tests pass, including an end-to-end sign-and-verify test. But I have not tested this on real firmware yet. It needs a PowerVM LPAR with secure boot enforced and properly signed kernels, which QEMU/SLOF cannot emulate. High on my list.
The ieee1275-rs crate
The crate has grown well beyond Chapter 2. It now provides:
claim
/
release
, the custom heap allocator, device tree access (
finddevice
,
getprop
,
instance-to-package
), block I/O, console I/O with
read_stdin
, a Forth
interpret
interface,
milliseconds
for timing, and a
GlobalAlloc
implementation so
Vec
and
String
just work.
Published on crates.io at github.com/rust-osdev/ieee1275-rs .
What’s next
I would like to test the Secure Boot feature on an end to end setup but I have not gotten around to request access to a PowerVM PAR. Beyond that I want to refine the menu. Another idea would be to perhaps support the equivalent of the Unified Kernel Image using ELF. Who knows, if anybody finds this interesting let me know!
The source is at the powerpc-bootloader repository . Contributions welcome, especially from anyone with POWER hardware access.