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>
This commit is contained in:
Robert Allan James
2026-08-03 18:30:25 -04:00
co-authored by Claude Sonnet 5
parent 5784d8a1a8
commit f43f3f4482
6 changed files with 10473 additions and 5 deletions
+38
View File
@@ -14,6 +14,44 @@
/* 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).
*
+30 -3
View File
@@ -17,6 +17,12 @@ volatile const char *g_sk_fault_word = (void *)0;
/* Defined in isr.S */
extern void aarch64_install_vectors(void);
/* Defined in arch.c. Extern-declared here rather than in the shared arch.h:
* "exception level" has no amd64/riscv64 equivalent, so it does not belong
* in the cross-ISA header — same convention as riscv64_timer_rearm() in
* arch/riscv64/interrupts.c. */
extern int aarch64_current_el(void);
/**
* @brief Print a 64-bit value as "0xNNNNNNNNNNNNNNNN" to the kernel console.
*
@@ -91,12 +97,26 @@ void aarch64_exception_handler(void)
/**
* @brief Install the AArch64 exception vector table (M4 milestone).
*
* Calls @c aarch64_install_vectors() (defined in @c isr.S) which writes
* the address of the vector table base into @c VBAR_EL1 (Vector Base
* Address Register, EL1). After this instruction all EL1 exceptions
* First reads and reports the exception level via @c aarch64_current_el()
* (punch-list item 0.4) — this is the earliest point in boot with both a
* working console (up since M1) and a genuine need for the answer (vector
* installation is the first EL-dependent operation). The read itself is
* cheap and safe far earlier than this; only the console print is gated on
* this call site.
*
* Then calls @c aarch64_install_vectors() (defined in @c isr.S) which
* writes the address of the vector table base into @c VBAR_EL1 (Vector
* Base Address Register, EL1). After this instruction all EL1 exceptions
* (synchronous, IRQ, FIQ, SError) dispatch through the 512-byte-aligned
* vector table assembled in @c isr.S.
*
* **@c VBAR_EL1 is written unconditionally regardless of the detected
* level.** If @c aarch64_current_el() reports EL2, this is a known gap:
* exceptions taken at EL2 vector through @c VBAR_EL2, which nothing here
* programs, and installing EL2-aware vectors is punch-list item 0.5's
* scope, not this one's. Item 0.4 establishes the detection every later
* EL-dependent item must consult; it does not yet make use of it here.
*
* On AArch64 there is no IDT and no PIC to disable; the GIC replaces
* both. The @c arch_interrupts_init() name is kept identical across all
* ISAs so that @c kernel_main() can call the same symbol regardless of
@@ -107,5 +127,12 @@ void aarch64_exception_handler(void)
*/
void arch_interrupts_init(void)
{
int el = aarch64_current_el();
console_puts("AArch64: running at EL");
console_putc((char)('0' + el));
console_println(el == 2
? " (VBAR_EL2/CNTHP_*_EL2 required — not yet wired, see item 0.5/0.7)"
: "");
aarch64_install_vectors();
}