Learn how to build and deploy bare-metal firmware for Raspberry Pi 3/4 using ARM64 assembly and C.
- About this firmware
- Prerequisites
- Building the firmware
- Testing with QEMU
- Deploying to hardware
- API reference
- Troubleshooting
This is enterprise-grade bare-metal firmware for Raspberry Pi 3/4, designed with production standards and ARM best practices. The firmware implements multi-core awareness, memory safety, and modular architecture with comprehensive error handling.
The firmware provides:
- Multi-core support: Proper CPU core initialization and management
- Memory safety: Comprehensive memory layout and stack management
- Driver abstraction: Clean GPIO and system utilities interface
- Error handling: Robust error checking and panic handling
- Professional build system: Makefile with multiple targets and configurations
This guide assumes that you are familiar with ARM64 assembly, C programming, and embedded systems development. For more information about ARM architecture, see the ARM Architecture Reference Manual.
You must install the ARM64 cross-compilation toolchain and QEMU emulator to build and test the firmware. This guide uses standard GNU toolchain conventions.
Required tools:
aarch64-linux-gnu-gcc(ARM64 cross-compiler)- GNU Make 4.0+
- QEMU system emulator
- Linux/macOS/WSL environment
macOS:
brew install aarch64-elf-gcc qemuUbuntu/Debian:
sudo apt-get install gcc-aarch64-linux-gnu qemu-system-armTo build the firmware image, run the make command in the project root:
# Build firmware image
makeThe build system will compile all source files and generate dist/kernel8.img - the bootable firmware image.
# Show build information
make info
# Clean build artifacts
make clean
# Remove all generated files
make distcleanfirmware/
βββ include/ # Public headers
β βββ platform.h # Platform-specific definitions
β βββ gpio.h # GPIO driver interface
β βββ system.h # System utilities
βββ src/ # Source implementations
β βββ gpio.c # GPIO driver
β βββ system.c # System utilities
βββ boot.S # ARM64 boot assembly
βββ main.c # Application entry point
βββ linker.ld # Memory layout script
βββ Makefile # Build system
Warning
The firmware requires proper SD card emulation to boot correctly. Using QEMU's -kernel flag bypasses the Raspberry Pi boot sequence and causes the firmware to load at the wrong memory address.
First, create a proper SD card image with the firmware and boot configuration:
# Create SD card image with proper boot setup
dd if=/dev/zero of=boot.img bs=1M count=64
mkfs.fat -F32 boot.img
mkdir -p mnt
sudo mount -o loop boot.img mnt
cp dist/kernel8.img mnt/
echo "arm_64bit=1" > mnt/config.txt
echo "kernel=kernel8.img" >> mnt/config.txt
sudo umount mnt# Standard emulation
qemu-system-aarch64 -M raspi3b -drive file=boot.img,if=sd,format=raw -nographic
# With debugging monitor
qemu-system-aarch64 -M raspi3b -drive file=boot.img,if=sd,format=raw -nographic -monitor stdioWhen using -monitor stdio, you can access the QEMU monitor for debugging. The monitor allows you to inspect the system state and control execution:
(qemu) info registers # View CPU register state
(qemu) info mtree # Show memory layout
(qemu) c # Continue execution
(qemu) q # Quit QEMU
Expected boot state:
- PC should be at
0x80000(firmware load address) - SP should be initialized to stack top
- PSTATE should show EL1 execution level
- Format SD card: Use FAT32 filesystem
- Copy firmware:
cp dist/kernel8.img /path/to/sdcard/ - Configure boot: Create
config.txtwith:arm_64bit=1 kernel=kernel8.img disable_overscan=1 - Boot: Insert SD card and power on Raspberry Pi
Modify include/platform.h for hardware-specific settings:
#define LED_PIN 18U // GPIO pin for LED
#define LED_ON_TIME_MS 500U // LED on duration
#define LED_OFF_TIME_MS 500U // LED off durationThe GPIO driver provides a clean interface for pin control:
// Initialize GPIO pin
gpio_result_t gpio_init(uint32_t pin, gpio_function_t function);
// Set pin high/low
void gpio_set(uint32_t pin);
void gpio_clear(uint32_t pin);System utilities handle initialization and timing:
// System initialization
void system_init(void);
// Precise delay
void system_delay_ms(uint32_t milliseconds);
// Panic handler
void system_panic(const char* message);| Region | Start Address | Size | Purpose |
|---|---|---|---|
| Code | 0x80000 | ~64KB | Firmware image |
| Data | ~0x90000 | ~16KB | Initialized data |
| BSS | ~0x94000 | ~16KB | Uninitialized data |
| Stack | ~0x98000 | 16KB | System stack |
Problem: PC starts at 0x200 instead of 0x80000
Solution: Use SD card emulation instead of -kernel flag. The -kernel option bypasses the normal Raspberry Pi boot sequence.
Problem: Firmware doesn't execute
Solution: Ensure proper boot configuration in config.txt and verify the SD card image was created correctly.
Problem: Stack pointer is zero
Solution: Verify linker script defines _stack_top and boot.S initializes the stack pointer correctly.
If you encounter boot issues, use the QEMU monitor to inspect the system state:
qemu-system-aarch64 -M raspi3b -drive file=boot.img,if=sd,format=raw -nographic -monitor stdioThen check the registers:
(qemu) info registers
Look for:
- PC at 0x80000 (correct firmware load address)
- Non-zero SP (stack pointer initialized)
- PSTATE showing EL1 (correct execution level)
The firmware provides a simple LED blink test to verify:
- Boot sequence initialization
- GPIO driver functionality
- System timer operation
- Memory layout correctness
Here is a complete example of building and testing the firmware:
# Clone and build
git clone <repository-url>
cd firmware
make
# Create boot image
dd if=/dev/zero of=boot.img bs=1M count=64
mkfs.fat -F32 boot.img
mkdir -p mnt
sudo mount -o loop boot.img mnt
cp dist/kernel8.img mnt/
echo "arm_64bit=1" > mnt/config.txt
echo "kernel=kernel8.img" >> mnt/config.txt
sudo umount mnt
# Test in QEMU
qemu-system-aarch64 -M raspi3b -drive file=boot.img,if=sd,format=raw -nographicNote
This is a basic example. In production, you may want to add additional error handling, logging, and hardware-specific optimizations.
Copyright (c) 2026 ARM Limited. All rights reserved.
Licensed under the BSD-3-Clause License. See LICENSE for details.