02. Understanding the Raspberry Pi Boot Process¶
How the Raspberry Pi 4B Boots¶
Unlike your laptop, which has a BIOS or UEFI, the Raspberry Pi has a unique, GPU-driven boot sequence.
The Sequence¶
graph TD
Power["Power On"] --> ROM["Boot ROM (SoC)"]
ROM -->|Reads SD Card| SD["Start4.elf (GPU Firmware)"]
SD -->|Reads config.txt| Config["Configure System"]
Config -->|Loads kernel8.img| Kernel["Load Kernel to 0x80000"]
Kernel --> CPU["Release CPU Core 0"]
CPU --> Start["Execute _start"]
- Boot ROM: When powered on, the small code inside the chip (SoC) wakes up and looks for an SD card.
- GPU Takes Charge: The GPU (not the CPU!) loads
start4.elf(firmware) from the SD card. - Kernel Load: The firmware reads
config.txt, configures hardware, and loads yourkernel8.imginto memory at address0x80000. - Action!: Finally, the GPU wakes up the ARM CPU (Core 0) and tells it: "Go to address
0x80000and start running!"
Raspberry Pi 4 vs Earlier Models
- Pi 3 and earlier: Required
bootcode.binon the SD card - Pi 4: Has internal boot ROM, so
bootcode.binis not needed
Why kernel8.img?
kernel.img= ARMv6 (32-bit)kernel7.img= ARMv7 (32-bit)kernel8.img= ARMv8 (64-bit) ← We're using this
The Linker Script¶
The linker script (kernel/linker.ld) is like a map for the compiler. It tells the compiler exactly where to place different parts of our code in the Raspberry Pi's RAM.
block-beta
columns 1
space
block:Code
text0["0x80000"]
text[".text (Your Code)"]
end
block:Data
rodata[".rodata (Constants)"]
data[".data (Variables)"]
bss[".bss (Zeroed Data)"]
end
block:Stack
stack["Stack (Grows Down)"]
top["0x84000 (approx)"]
end
space
Key Points to Remember¶
- 0x80000: This is the "Meeting Point". The GPU promises to put your code here.
- BSS Section: This is where "uninitalized variables" live. We must manually clear this area to 0.
- Stack: This is the "scratchpad" memory for function calls. We set it up to grow downwards from a safe location.
Writing the Boot Code¶
File: kernel/boot.S
Code Explanation¶
Code Explanation (Beginner Friendly)¶
Assembly might look scary, but it's just a sequence of very small steps. The CPU has "Registers" which are like small sticky notes or scratchpads where it can hold numbers temporarily.
x0,x1, ...x30: These are general-purpose registers (your scratchpads).w0vsx0:w0is the 32-bit version (half the sticky note),x0is the full 64-bit version.
1. Multi-Core Handling ("Who am I?")¶
The Raspberry Pi 4 has 4 CPU Cores (Brain 0, Brain 1, Brain 2, Brain 3). When powered on, all of them wake up at once. If they all try to run our code, chaos happens! We need to put everyone to sleep except "Brain 0".
Clearing BSS¶
The BSS section contains uninitialized global variables. We must zero them out before running C code.
Setting the Stack¶
The C code needs a stack. We point the stack pointer (sp) to our reserved 16KB region.
Testing the Build¶
You should see kernel8.img (around 1-2KB).
Deploying to the Raspberry Pi¶
Important: Raspberry Pi 4 Boot Requirements
The Raspberry Pi 4 has an internal boot ROM, so bootcode.bin is not required (unlike Pi 3 and earlier). However, bare-metal kernels require proper device tree files (DTB) and overlays to boot correctly.
Recommended Method: Using Raspberry Pi OS Boot Partition¶
The easiest and most reliable way to test your bare-metal kernel:
- Prepare an SD card with Raspberry Pi OS (using Raspberry Pi Imager)
- Mount the boot partition on your PC
- Backup the original kernel:
- Copy your kernel:
- Insert SD card into Raspberry Pi and power on
This method works because the Raspberry Pi OS boot partition already contains all required files:
start4.elf/fixup4.dat- GPU firmwarebcm2711-rpi-4-b.dtb- Device tree for Pi 4Boverlays/- Device tree overlaysconfig.txt- Boot configuration
To restore Raspberry Pi OS, simply rename kernel8.img.backup back to kernel8.img.
Alternative: Minimal Boot Partition (Advanced)¶
If you want to create a minimal boot partition from scratch:
-
Format SD Card as FAT32 (MBR, not GPT)
-
Download required files:
-
Create
config.txt:!!! warning "Mini UART Timing Requirements" The Raspberry Pi 4's Mini UART (UART1) baud rate depends on the GPU core frequency. Dynamic frequency scaling causes timing issues, making serial output garbled or unreadable.
1 2 3 4
- **`initial_turbo=0`**: Disables initial CPU turbo mode which affects UART timing - **`core_freq_min=500`**: Locks minimum GPU core frequency to 500MHz Without these settings, you may see **garbled characters** or **no UART output** even if your code is correct. This is especially critical when using USB-to-TTL serial adapters for debugging.The
disable_overscan=1setting is important for framebuffer graphics to work correctly with the requested resolution. -
Copy all files to SD card:
-
Power on
Configuring Screen Resolution (config.txt)¶
If you are using a display and want to set a specific resolution (e.g., for the framebuffer driver later), you can add HDMI settings to config.txt.
Example: Force 1920x1080 (Full HD)
Common hdmi_mode values (for hdmi_group=2):
- 82: 1920x1080
- 85: 1280x720
- 16: 1024x768
- 9: 800x600
Troubleshooting: Black Screen
If your kernel boots but the screen remains black (and you are sure your code is correct), it might be a resolution mismatch.
1. Try adding disable_overscan=1 to config.txt.
2. Ensure framebuffer_init() in your code matches the resolution in config.txt (if set).
3. If using a specific hdmi_mode, ensure your monitor supports it.
Debugging Tip
If you see a rainbow screen that stays forever, the GPU firmware loaded but your kernel failed to start. Check that all required files are present and that kernel8.img is a valid AArch64 binary.
At this point, the Raspberry Pi will boot your kernel, but you won't see any output yet since we haven't implemented UART communication.
Next Steps¶
In the next article, we'll implement the UART driver and print "Hello World" to see our kernel actually running!