121 lines
6.6 KiB
Markdown
121 lines
6.6 KiB
Markdown
# 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](arch.md) 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/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](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/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 `src/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`](memory-map.md) — 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.** `src/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](testing.md).
|
||
- **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.
|
||
|
||
## Related
|
||
|
||
- [arch.md](arch.md) — the arch-module boundary these targets plug into, and the
|
||
CPU-arch vs boot-protocol "two axes".
|
||
- [efi.md](efi.md) — the UEFI loader that carries over to aarch64-UEFI.
|
||
- [vision.md](vision.md) — why isolated, portable-across-architectures is the goal.
|