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>
173 lines
6.3 KiB
C
173 lines
6.3 KiB
C
/*
|
||
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 */
|
||
}
|