aarch64

This page documents the aarch64 architecture backend in kernite/src/arch/aarch64/.

Module Structure

File Responsibility

mod.rs

Backend entry point, IRQ save/restore (DAIF manipulation), halt() (WFI), I/O port emulation stubs.

boot.rs

BSP bootstrap: GICv3, PSCI, generic timer, paging initialization.

ap_boot.rs

AP startup via PSCI CPU_ON. The logical CPU index rides along as PSCI’s context ID, delivered to the AP in x0 at the entry point.

gic.rs

GICv3 interrupt controller: distributor (GICD), redistributor (GICR), CPU interface (ICC system registers).

psci.rs

Power State Coordination Interface — CPU_ON, CPU_OFF, SYSTEM_OFF, SYSTEM_RESET via HVC.

timer.rs

Generic timer: EL1 physical timer (CNTP), PPI 30, 10 ms tick interval.

paging.rs

4-level page tables under TTBR0 (user) / TTBR1 (kernel), ASID management, TLB invalidation.

context.rs

Context switch: save/restore x0-x30, sp_el0, elr_el1, spsr_el1.

cpu.rs

Per-CPU data via TPIDR_EL1, CPU ID from MPIDR_EL1, kernel stack management.

exceptions.rs

Exception vector table: sync/IRQ/FIQ/SError handlers at EL1 and from EL0.

fpu.rs

NEON/FP eager context switch — Q0-Q31 + FPCR + FPSR saved/restored on every TCB switch, CPACR_EL1.FPEN held at 0b11.

pl011.rs

PL011 UART driver for early serial console output.

Initialization Order

init() follows this sequence:

  1. cpu::init_bsp() — per-CPU data via TPIDR_EL1, BSP CPU ID.

  2. mm::init() — frame allocator from boot info memory map.

  3. gic::init() — GICv3 distributor and redistributor, enable CPU interface.

  4. timer::init() — configure CNTP but leave it masked (timer starts later).

  5. paging::init() — kernel page tables under TTBR1, direct physical map, user TTBR0 setup.

  6. exceptions::init() — install exception vector table (VBAR_EL1).

Timer interrupts start via start_timer() after the scheduler is initialized.

Exception Levels

SaltyOS runs at EL1 (kernel) with user code at EL0. The bootloader enters at EL2 (hypervisor) and uses VHE (Virtualization Host Extensions) if available; otherwise drops to EL1 before entering the kernel.

Level Usage

EL0

User mode — all userspace processes.

EL1

Kernel mode — kernite runs here.

EL2

Hypervisor — bootloader enters here, drops to EL1. PSCI calls use HVC.

EL3

Secure monitor — firmware, not used by SaltyOS.

GICv3

The Generic Interrupt Controller version 3 handles all interrupt routing.

Components

Distributor (GICD)

System-wide. Memory-mapped. Routes SPIs (Shared Peripheral Interrupts) to redistributors. Configures trigger mode (level/edge), priority, and target CPU for each SPI.

Redistributor (GICR)

Per-CPU. Memory-mapped. Manages PPIs (Private Peripheral Interrupts, like the timer) and SGIs (Software Generated Interrupts, used for IPI).

CPU Interface (ICC)

Per-CPU. Accessed via system registers (ICC_*), not memory-mapped. ICC_IAR1_EL1 — acknowledge interrupt (returns interrupt ID). ICC_EOIR1_EL1 — end of interrupt (EOI). ICC_PMR_EL1 — priority mask register. ICC_SRE_EL1 — system register enable.

Interrupt Types

Type ID Range Examples

SGI

0-15

Software-generated for IPI (reschedule, TLB shootdown).

PPI

16-31

Per-CPU private: PPI 30 = EL1 physical timer (CNTP).

SPI

32-1019

Shared peripherals: virtio devices, UART, etc.

IRQ Flow

  1. Hardware asserts interrupt line.

  2. GIC distributor routes to target CPU’s redistributor.

  3. CPU takes IRQ exception (vector offset 0x480 from VBAR_EL1).

  4. Exception handler reads ICC_IAR1_EL1 to acknowledge and get interrupt ID.

  5. Dispatch to handler: timer tick (PPI 30), IPI (SGI), or device IRQ (SPI → dispatch_irq()).

  6. Write ICC_EOIR1_EL1 to signal end of interrupt.

PSCI

The Power State Coordination Interface provides CPU lifecycle management via HVC calls to the EL2 firmware.

Function ID Description

CPU_ON

0xC400_0003

Start an application processor. Arguments: target MPIDR, entry point, context ID.

CPU_OFF

0x8400_0002

Power off the calling CPU.

SYSTEM_OFF

0x8400_0008

Power off the entire system.

SYSTEM_RESET

0x8400_0009

Reset the system.

All calls use the SMC64/HVC64 calling convention.

Generic Timer

The EL1 physical timer (CNTP) provides the scheduler tick.

Timer

CNTP (Counter-timer Physical)

Interrupt

PPI 30

Tick interval

10 ms

Registers

CNTP_CTL_EL0 (control), CNTP_TVAL_EL0 (timer value), CNTP_CVAL_EL0 (compare value), CNTFRQ_EL0 (frequency)

On each tick:

  1. Timer fires PPI 30 → GIC → IRQ exception.

  2. Handler reads current counter, computes next deadline.

  3. Writes CNTP_TVAL_EL0 with the interval for the next tick.

  4. Calls into the scheduler’s timer_tick().

  5. Writes ICC_EOIR1_EL1 (EOI) before any schedulable code runs.

Paging

Page Table Format

4-level page tables (4 KB granule, 48-bit VA):

Level 0 (TTBR) → Level 1 → Level 2 → Level 3 (leaf)
  9 bits          9 bits     9 bits     9 bits   + 12-bit page offset
  • TTBR0_EL1: user address space (lower half, 0x0000_*).

  • TTBR1_EL1: kernel address space (upper half, 0xFFFF_*).

Descriptor Format

Bits Field Description

1:0

Type

0b11 = table/page descriptor, 0b01 = block descriptor, 0b00 = invalid.

47:12

Address

Physical address of next-level table or output page.

54

UXN

User execute-never.

53

PXN

Privileged execute-never.

7

AP[2]

Access permission: 0 = R/W, 1 = read-only.

6

AP[1]

Access permission: 1 = EL0 accessible, 0 = EL1 only.

4:2

AttrIdx

Memory attribute index into MAIR_EL1.

10

AF

Access flag (must be set or HW raises access flag fault).

ASID

Each VSpace is assigned an Address Space Identifier (ASID) to avoid full TLB flushes on context switch. The ASID pool is managed under ASID_LOCK. TTBR0_EL1 stores the ASID in bits 63:48.

TLB Invalidation

  • Per-address: TLBI VAE1IS, Xt (inner-shareable, matches ASID + VA).

  • Per-ASID: TLBI ASIDE1IS, Xt (flush all entries for an ASID).

  • Full: TLBI VMALLE1IS (flush all EL1 entries, all ASIDs).

Followed by DSB ISH + ISB barriers.

SMP

AP Startup

  1. BSP discovers CPU topology from the device tree or ACPI MADT and assigns each AP a logical CPU index.

  2. For each AP: call PSCI CPU_ON via HVC with the AP’s MPIDR, the shared ap_entry entry-point address, and the logical CPU index passed as PSCI’s context ID argument.

  3. PSCI delivers the context ID to the AP in x0 at the entry point, so the AP identifies itself without polling an external mailbox.

  4. The AP boots at EL2, drops to EL1, loads its per-CPU data (including the kernel stack pointer) indexed by the received logical CPU number.

  5. AP initializes: GIC redistributor, per-CPU timer, TPIDR_EL1, exception vectors.

  6. AP enters the idle loop.

IPI

SGI (Software Generated Interrupt) 0-2 are used for IPI:

SGI Kind Purpose

0

Reschedule

Wake remote CPU to run reschedule().

1

TlbShootdown

Invalidate TLB entry on remote CPU.

2

Shutdown

Request orderly halt.

SGIs are sent via ICC_SGI1_EL1 system register, targeting specific CPUs by affinity.

FPU / NEON

Eager context switching:

  1. At boot (BSP and each AP), CPACR_EL1.FPEN is programmed to 0b11, permanently disabling FPU/NEON traps from EL0 and EL1.

  2. On every TCB switch, fpu::switch() calls aarch64_fpsimd_save_state on the outgoing TCB and aarch64_fpsimd_restore_state on the incoming TCB unconditionally — there is no trap-and-demand-load path.

  3. flush_current() / reload_current() keep the live register file in sync after TCB_COPY_FPU or similar kernel-side mutations.

Save area: XSaveArea — 528 bytes: 32 × Q registers (512 bytes) + FPCR (4) + FPSR (4) + padding.

PL011 UART

The PL011 UART provides early serial console output on aarch64:

  • Memory-mapped at the device tree-specified address (typically 0x0900_0000 on QEMU virt).

  • putc(byte): poll UARTFR (flags register) for TX FIFO not full, then write to UARTDR.

  • Used by the raw serial output path (serial_puts_raw) for panic and early boot messages.

  • No interrupt-driven I/O — purely polled output.

Syscall Entry

Syscalls use svc #0. The exception vector dispatches EL0 synchronous exceptions:

  1. Save all user registers (x0-x30, sp_el0, elr_el1, spsr_el1) to the kernel stack.

  2. Read ESR_EL1 to confirm EC = 0x15 (SVC from AArch64).

  3. Syscall number in x8, arguments in x0-x5.

  4. Call syscall_handle_rust().

  5. Restore user registers and eret.

I/O Port Emulation

aarch64 has no I/O port instructions. For code that uses IoPort capabilities (PCI config space access), the aarch64 backend provides MMIO-based PCI I/O window functions:

  • pci_io_read8/16/32(port) — translate port address to MMIO offset within the PCI I/O window.

  • pci_io_write8/16/32(port, value) — same for writes.

Direct inb/outb calls on aarch64 are stubs that return 0 or panic.