6.6 KiB
ARM targets (arm and aarch64)
danos aims to run on Raspberry Pi hardware eventually. "ARM" isn't one target,
though — the Pis span two different CPU architectures (32-bit arm and 64-bit
aarch64) and (stock) a different boot protocol from x86-64's UEFI. danos targets
aarch64 only (see the decision below); the arm/aarch64 distinction still
matters for understanding why. This page maps the landscape so the
arch split and build system can be planned for it.
arm vs aarch64 — 32-bit vs 64-bit
arm= 32-bit ARM (the AArch32 state, A32/T32 instruction sets). ARMv7 and earlier, plus the 32-bit compatibility mode of newer cores. 16 × 32-bit registers.aarch64= 64-bit ARM (the AArch64 state, A64 instruction set), from ARMv8-A on. Also called arm64. 31 × 64-bit registers, a fixed 32-bit instruction width, a redesigned exception model — not a widening of A32, a clean new ISA.
They are as different from each other as either is from x86-64: separate registers,
page-table formats, and calling conventions. Each needs its own src/kernel/arch/<name>/.
The Raspberry Pi models
| Model | SoC | Core | Architecture | danos target |
|---|---|---|---|---|
| Pi Zero / Zero W | BCM2835 | ARM1176JZF-S | ARMv6, 32-bit only | arm (not planned) |
| Pi Zero 2 W ← target | BCM2710 | Cortex-A53 | ARMv8-A, 64-bit | aarch64 |
| Pi 3 / 3B+ | BCM2837 | Cortex-A53 | ARMv8-A, 64-bit | aarch64 |
| Pi 4 | BCM2711 | Cortex-A72 | ARMv8-A, 64-bit | aarch64 |
| Pi 5 | BCM2712 | Cortex-A76 | ARMv8.2-A, 64-bit | aarch64 |
Decision:
aarch64only. The target small board is a Pi Zero 2 W (BCM2710, Cortex-A53) — which isaarch64, not the original Zero W's 32-bit ARMv6. So every ARM board danos targets (Zero 2 W and Pi 3-5) isaarch64, and the 32-bitarm/ARMv6 backend is not planned — one ARM CPU port, not two. The original Zero W (ARMv6) would only re-enter scope if that specific older board were ever needed; the row above is kept only to explain the distinction.
Booting: UEFI is not x86-only
The boot protocol is a separate axis from the CPU (see arch.md):
- UEFI exists for ARM too — ARM servers require it (SBSA/SBBR), QEMU boots it with AAVMF (the AArch64 build of the same EDK2 firmware as x86's OVMF), and the Pi can even run it with community UEFI firmware. Under UEFI the handoff is the same as x86-64: system table, boot services, memory map, GOP framebuffer — so the loader logic largely carries over.
- Device tree / firmware boot — the stock Raspberry Pi firmware (VideoCore bootloader) is not UEFI: it loads the kernel and jumps to it with a device-tree blob (DTB) pointer. Both the Zero W and stock Pi 3-5 boot this way.
Note that even under UEFI on ARM, the OS still gets its hardware description from ACPI or a device tree (often the DTB passed via a UEFI configuration table). So "UEFI on ARM" doesn't remove the device tree — UEFI gives you memory + framebuffer; the DTB/ACPI tells you what devices exist.
What danos needs, layer by layer
- One CPU arch module:
src/kernel/arch/aarch64/— covering the Zero 2 W and Pi 3-5, providing the samearchinterface as x86_64:halt, context switch, interrupt/exception vectors, page tables, a UART, a timer. Nosrc/kernel/arch/arm/is planned (see the decision above), so there's a single ARM backend to write. - A device-tree boot path. Since stock Pis boot via DTB, danos needs an entry
that parses the DTB's
/memoryand/reserved-memoryinto the neutralMemoryMap— the same neutral handoffefi.zigproduces, just from a different source. This is where keeping boot-protocol knowledge on the loader side (as we did for the UEFI memory-map classification) pays off. - The UEFI loader mostly carries over.
src/boot/efi.zigis largely boot-protocol code (std.os.uefiprotocol calls), not x86 code. Its only truly x86-specific bits are the ELF machine check (.X86_64) and the SysV calling convention for the kernel jump. So anaarch64-UEFI target (QEMUvirt+ AAVMF) can reuse it — which makes aarch64-UEFI the easiest second target, easier than the device-tree Pi.
Pi hardware quirks (for when we port)
The Pi is not a "standard" ARM platform — expect Broadcom-specific peripherals:
- Peripheral base moves per SoC:
0x2000_0000(BCM2835, Zero W),0x3F00_0000(BCM2837, Pi 3),0xFE00_0000(BCM2711, Pi 4), different again on Pi 5. Everything below is an offset from it. - UART: a PL011 (at base +
0x20_1000) plus a mini-UART; on some boards the PL011 is wired to Bluetooth, so which one is the console varies. This is theaarch64/armequivalent of our x86 COM1 serial. - Interrupt controller: not a standard ARM GIC on the older parts — the Zero W
and Pi 3 use Broadcom's own ARMCTRL controller (Pi 3 adds a per-core "local"
controller for timers/mailboxes). The Pi 4 and 5 do have a GIC-400. So the
interrupt backend differs even within the
aarch64Pis. - Timer: the ARM generic timer (
CNTPCT/CNTFRQ) on ARMv8, or the BCM system timer — the counterpart to our calibrated LAPIC/TSC clock.
Building each (intended)
build.zig currently pins the kernel to x86_64; supporting these means selecting
the target and arch module together (e.g. a -Darch= option). The Zig target
queries would be roughly:
- Pi Zero W:
.cpu_arch = .arm,.cpu_model = arm1176jzf_s,.os_tag = .freestanding - Pi 3:
.cpu_arch = .aarch64,.cpu_model = cortex_a53,.os_tag = .freestanding - Pi 4:
.cpu_arch = .aarch64,.cpu_model = cortex_a72 - Pi 5:
.cpu_arch = .aarch64,.cpu_model = cortex_a76
Testing in QEMU
Two routes, mirroring how we test x86-64 with OVMF:
- Board emulation:
qemu-system-aarch64 -machine raspi3b(andraspi4bon recent QEMU) for the Pi 3/4;qemu-system-arm -machine raspi0/raspi1apfor the ARMv6 Zero-class board — closest to real hardware, device-tree boot. - Generic aarch64-UEFI:
qemu-system-aarch64 -machine virt+ AAVMF — the cleanest way to bring up theaarch64kernel via the reused UEFI loader before tackling Pi-specific boards. A futurerun-aarch64build step would use this.