Files
StarForth/src/starkernel/arch/aarch64/arch.c
T
Robert Allan JamesandClaude Sonnet 5 f43f3f4482 aarch64: detect exception level at runtime, cached accessor
Punch list §25 item 0.4 complete.

Adds aarch64_current_el() in arch.c: reads CurrentEL[3:2] on first call,
caches the result (CurrentEL cannot change post-ExitBootServices, so every
consumer gets the same answer without repeating the MRS). Called from
arch_interrupts_init() in interrupts.c -- the earliest point with both a
working console (up since M1) and a genuine first consumer (vector
installation is the first EL-dependent operation) -- and the detected level
is printed to the boot log there.

Declared via extern-in-place in interrupts.c rather than added to the shared
arch.h: "exception level" has no amd64/riscv64 equivalent, matching the
convention already used for riscv64_timer_rearm() in item 0.3.

Verified on real QEMU output: "AArch64: running at EL1", correctly positioned
immediately before "IDT installed." in the serial log. Boots clean, dict_hash
0x3d4e1daf289da94f unchanged from the item 0.1-0.3 baseline.

Scope: this item establishes the detection and exposes it; it does not yet
change VBAR/ELR/SPSR or timer-register selection to use it. arch_interrupts_init()
still writes VBAR_EL1 unconditionally, and now says so explicitly in its own
doc comment -- if aarch64_current_el() ever reports 2 on real hardware,
exceptions taken at EL2 vector through VBAR_EL2, which nothing programs yet.
That gap is items 0.5 (vectors/saved-state) and 0.7 (CNTP vs CNTHP) to close,
per FABRIC.md's GAP-B3 finding. The boot-log EL2 case prints a note pointing
at both.

Only aarch64-scoped files touched (arch.c, interrupts.c) -- no shared loader
or header changed, so amd64 and riscv64 are provably unaffected; not rebuilt
for this item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:30:25 -04:00

173 lines
6.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* arch.c (aarch64) - Architecture abstractions for StarKernel on AArch64
*/
#include "arch.h"
#include <stdint.h>
/* Installed by isr.S — installs VBAR_EL1 */
extern void aarch64_install_vectors(void);
/* -1 = not yet read. Cached rather than re-read on every call: CurrentEL
* cannot change during the kernel's lifetime post-ExitBootServices, and a
* cached value gives every consumer (vector install, GIC timer register
* selection) the same answer without repeating the MRS. */
static int s_current_el = -1;
/**
* @brief Return the AArch64 exception level the kernel is running at.
*
* Reads @c CurrentEL (bits [3:2] hold the EL number, [1:0] are RES0) on
* first call and caches the result; every later call returns the cached
* value. Safe to call at any point in boot, including before
* @c console_init() — the read itself has no dependency, only the
* printing of it does (see @c arch_interrupts_init() in @c interrupts.c,
* the earliest point at which both this and a working console are
* available).
*
* EDK2 on QEMU's aarch64 @c virt machine has been observed to leave the
* kernel at either EL1 or EL2 depending on firmware build; nothing in this
* tree may assume one over the other (FABRIC.md §25.7.1 GAP-B3). Every
* EL-dependent choice — @c VBAR_EL1 vs @c VBAR_EL2, the @c ELR_ELx /
* @c SPSR_ELx saved-state pair, and @c CNTP_*_EL0 vs @c CNTHP_*_EL2 — must
* read this accessor rather than hardcode a level.
*
* @return 1 or 2. Never 0 or 3: a UEFI application that survived far enough
* to reach this call cannot be running at EL0 (unprivileged) or EL3
* (secure monitor — QEMU's virt firmware runs the kernel at EL1/EL2).
*/
int aarch64_current_el(void)
{
if (s_current_el < 0) {
uint64_t current_el;
__asm__ volatile ("mrs %0, CurrentEL" : "=r"(current_el));
s_current_el = (int)((current_el >> 2) & 0x3);
}
return s_current_el;
}
/**
* @brief Perform the earliest AArch64 architecture initialisation (no-op).
*
* On x86-64 this function loads the GDT; AArch64 has no equivalent because
* privilege separation is provided by the exception-level architecture
* (EL0/EL1/EL2/EL3) rather than segment descriptors. The exception vector
* table is installed later in @c arch_interrupts_init() by calling
* @c aarch64_install_vectors() which writes @c VBAR_EL1.
*
* This stub exists to satisfy the common @c arch_early_init() call site in
* @c kernel_main() so that the same milestone sequence compiles across all
* three supported ISAs (amd64 / aarch64 / riscv64).
*/
void arch_early_init(void)
{
/* No GDT on AArch64; privilege separation via exception levels.
* Interrupt vectors installed later in arch_interrupts_init(). */
}
/**
* @brief Enable IRQ delivery on the current core (AArch64 DAIFCLR).
*
* Issues @c MSR DAIFCLR, #2 which clears the @c I bit (IRQ mask) in the
* @c DAIF (Debug / Abort / IRQ / FIQ) flags of the current exception level.
* After this instruction the core will take IRQ exceptions from the GIC.
* The @c memory clobber prevents the compiler from reordering stores across
* the barrier.
*
* Must be called after @c arch_interrupts_init() has installed the vector
* table in @c VBAR_EL1 and the GIC timer driver has been configured.
*/
void arch_enable_interrupts(void)
{
__asm__ volatile ("msr daifclr, #2" ::: "memory");
}
/**
* @brief Disable IRQ delivery on the current core (AArch64 DAIFSET).
*
* Issues @c MSR DAIFSET, #2 which sets the @c I bit (IRQ mask) in the
* @c DAIF flags, masking IRQ exceptions. FIQ, SError, and Debug exceptions
* are unaffected. Used around critical sections in the single-threaded
* kernel where interrupt delivery would corrupt shared state.
*
* The @c memory clobber acts as a compiler-level barrier; for a full
* hardware memory barrier use @c DSB before calling this function.
*/
void arch_disable_interrupts(void)
{
__asm__ volatile ("msr daifset, #2" ::: "memory");
}
/**
* @brief Halt the core until the next event (AArch64 WFI).
*
* Issues the @c WFI (Wait For Interrupt) instruction. The core enters a
* low-power standby state and resumes execution when an IRQ, FIQ, SError,
* debug event, or reset is detected. In the kernel panic path this is
* called inside an infinite loop with interrupts disabled (@c DAIFSET)
* to stop the processor permanently. In the idle path it reduces power
* consumption between heartbeat ticks.
*
* The @c memory clobber prevents the compiler from sinking stores across
* the @c WFI.
*/
void arch_halt(void)
{
__asm__ volatile ("wfi" ::: "memory");
}
void arch_cold_reset(void)
{
arch_disable_interrupts();
/* PSCI SYSTEM_RESET (SMC64 function 0xC4000009) */
__asm__ volatile (
"mov x0, #0xC4000000\n"
"movk x0, #0x0009\n"
"smc #0\n"
::: "x0", "memory"
);
for (;;) __asm__ volatile ("wfi" ::: "memory");
}
/**
* @brief Read the AArch64 system counter (CNTPCT_EL0).
*
* Issues @c MRS @c %0, @c cntpct_el0 to read the 64-bit physical system
* counter. On AArch64 the system counter is the architectural equivalent
* of the x86-64 TSC: a monotonically incrementing clock whose frequency
* is reported by @c CNTFRQ_EL0 and set by firmware at boot.
*
* Unlike @c RDTSC on x86-64, @c CNTPCT_EL0 is fully synchronised across
* cores (no per-core skew), invariant across power-state changes, and
* does not require serialisation for most measurement use cases. It is
* used as the time base for @c timer_now_ns() and @c heartbeat_tick().
*
* @return Current 64-bit system counter value in counter ticks
* (frequency given by @c CNTFRQ_EL0, typically 10–62.5 MHz).
*/
uint64_t arch_read_timestamp(void)
{
uint64_t val;
__asm__ volatile ("mrs %0, cntpct_el0" : "=r"(val));
return val;
}
/**
* @brief Initialise the AArch64 MMU (stub — deferred to a later milestone).
*
* Full page-table setup (TTBR0/TTBR1, MAIR, TCR, SCTLR.M) is deferred to a
* later milestone. This stub exists to satisfy the common @c arch_mmu_init()
* call site shared across all three ISAs. The VMM subsystem (@c vmm.c)
* handles page-table management independently of this hook.
*/
void arch_mmu_init(void)
{
/* Stub: MMU bring-up deferred for later milestone */
}