danos/docs/arm.md

6.6 KiB
Raw Permalink Blame History

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 system/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: aarch64 only. The target small board is a Pi Zero 2 W (BCM2710, Cortex-A53) — which is aarch64, not the original Zero W's 32-bit ARMv6. So every ARM board danos targets (Zero 2 W and Pi 3-5) is aarch64, and the 32-bit arm/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: system/kernel/arch/aarch64/ — covering the Zero 2 W and Pi 3-5, providing the same arch interface as x86_64: halt, context switch, interrupt/exception vectors, page tables, a UART, a timer. No system/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 /memory and /reserved-memory into the neutral MemoryMap — the same neutral handoff efi.zig produces, 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. boot/efi.zig is largely boot-protocol code (std.os.uefi protocol 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 an aarch64-UEFI target (QEMU virt + 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 the aarch64/arm equivalent 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 aarch64 Pis.
  • 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 (and raspi4b on recent QEMU) for the Pi 3/4; qemu-system-arm -machine raspi0/raspi1ap for 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 the aarch64 kernel via the reused UEFI loader before tackling Pi-specific boards. A future run-aarch64 build step would use this.
  • arch.md — the arch-module boundary these targets plug into, and the CPU-arch vs boot-protocol "two axes".
  • efi.md — the UEFI loader that carries over to aarch64-UEFI.
  • vision.md — why isolated, portable-across-architectures is the goal.