Bare metal coding on a Raspberry Pi 4 B
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Macoy Madson cbefbeaae1 Still flailing 3 weeks ago
Dependencies Got text rendering from bitmap atlases 1 month ago
data/Fonts Initial commit 1 month ago
src Still flailing 3 weeks ago
.gitignore Update gitignore to ignore binary files 1 month ago
.gitmodules Got text rendering from bitmap atlases 1 month ago Commit with useful stuff 1 month ago Got text rendering from bitmap atlases 1 month ago Start doing colored fonts and glyph table 1 month ago
COPYING Commit with useful stuff 1 month ago
LICENSE Initial commit 1 month ago Investigation on virtual memory continues 3 weeks ago Initial commit 1 month ago

Raspberry Pi Bare Metal


  • Download the Arm GNU toolchain: x86_64 Linux hosted cross toolchains, AArch64 bare-metal target (aarch64-none-elf) link

  • Download the latest firmware (use the Download ZIP button, aka this; a slightly smaller repo is here)

  • Format an SD card as msdos partitioned, with a FAT32 at least as large as the firmware/boot contents + your kernel8.img

  • Copy the contents of the firmware boot/ onto the SD card

  • Remove all kernel*.img files


Put the following for config.txt:

  # Mode 82 = 1920x1080 60Hz


  • Run (only need to do this once or if fonts change)

  • Run

  • Copy kernel8.img to the SD card

My stumbling blocks

  • Not having all the firmware on the SD card

  • Not writing the LED blink code correctly, i.e. I wasn't waiting while the LED was off, so it was just solid on. Viewing the disassembly helped clue me in to it.

  • Frame buffer pointer needed to be converted from a GPU address to a CPU address

Unaligned memory access is not supported without the MMU enabled

GCC started using NEON/floating point registers to set integer values due to optimizations, but I don't think I had initialized NEON yet.

NEON/SIMD was accessible, and alignment checking was disabled. The actual problem was made clear when I started playing with the alignment of e.g. this sequence:

  ldr x1, =__bss_start
  ldr d0, [x1, #8] // works
  ldr d0, [x1, #4] // exception.

This reddit thread finally cleared it up:

With the MMU off, the ARM core must treat all memory as device memory (which does not support unaligned access) since it doesn't actually know what's MMIO and what's DRAM. You have to provide that information to the core via page tables and MAIR bits if you want unaligned accesses to be allowed.

This is confirmed in ARM DDI 0487J.a, The AArch64 Application Level Memory Model B2.5 Alignment support that device memory faults on unaligned access.

Using the floating point/SIMD registers before initializing

Hack around it by passing -mgeneral-regs-only.

  00000000000000e8 <initializeFramebuffer>:
  e8:	a9b37bfd 	stp	x29, x30, [sp, #-208]!
  ec:	910003fd 	mov	x29, sp
  f0:	f9000fe0 	str	x0, [sp, #24]
  f4:	910083e0 	add	x0, sp, #0x20
  f8:	4f000400 	movi	v0.4s, #0x0

Initializing the stack pointer after exception level change

This was finally pointed out to me while reading ARM DAI 0527A (Bare-metal Boot Code for ARMv8-A Processors):

5.2.2 Initializing stack pointer registers The stack pointer register is implicitly used in some instructions, for example, push and pop. You must initialize it with a proper value before using it. In an MPCore system, different stack pointers must point to different memory addresses to avoid overwriting the stack area. If SPs in different Exception levels are used, you must initialize all of them.