summaryrefslogtreecommitdiff
path: root/arch/arm64
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm64')
-rw-r--r--arch/arm64/kernel/monitor.S132
-rw-r--r--arch/arm64/kernel/start.S38
2 files changed, 164 insertions, 6 deletions
diff --git a/arch/arm64/kernel/monitor.S b/arch/arm64/kernel/monitor.S
new file mode 100644
index 0000000..c6f5ec8
--- /dev/null
+++ b/arch/arm64/kernel/monitor.S
@@ -0,0 +1,132 @@
+/*
+ * monitor.S - the EL3 secure monitor, the resident layer real
+ * firmware ships. the loader drops to non-secure and never
+ * returns, but the kernel keeps calling into firmware: PSCI
+ * through the SMC conduit, and on hardware with the security
+ * extension the group routing of the interrupt controller is
+ * only writable from here.
+ *
+ * the entry path runs once per PE: configure EL3, install the
+ * monitor vectors, hand the next stage non-secure EL2 in the
+ * manual's boot state. SMCCC calls from the kernel trap into
+ * the SMC slot, the C dispatcher behind it is the same one the
+ * hvc path uses.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+/*
+ * the monitor stack. SP_EL3 needs memory no non-secure stage
+ * will touch, the region after the loader stack, sixteen
+ * bytes a call deep at most.
+ */
+.section .bss.el3stack, "aw", %nobits
+.align 4
+.globl __el3_stack_bottom
+__el3_stack_bottom:
+ .quad 0, 0, 0, 0
+ .quad 0, 0, 0, 0
+.globl __el3_stack_top
+__el3_stack_top:
+
+/*
+ * EL3 vectors, same sixteen slot layout every exception level
+ * uses. only the lower EL sync slot carries work, the SMC
+ * conduit, everything else parks.
+ */
+.balign 2048
+.globl tb_el3_vectors
+tb_el3_vectors:
+ /* 0x000: current EL, SP_EL0, unused */
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+
+ /* 0x200: current EL, SP_ELx, unused */
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+
+ /* 0x400: lower EL, AArch64, the SMC conduit lives here */
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_smc
+
+ /* 0x600: lower EL, AArch32, unused */
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+
+/*
+ * one time per PE, from the reset path. x30 = the next stage
+ * entry in non-secure EL2, x0 = the dtb pointer.
+ */
+.globl tb_monitor_init
+tb_monitor_init:
+ /* SP_EL3 on its own region */
+ adr x1, __el3_stack_top
+ msr spsel, #0
+ mov sp, x1
+ msr spsel, #1
+
+ /* the monitor vectors */
+ adr x1, tb_el3_vectors
+ msr vbar_el3, x1
+ isb
+
+ /*
+ * SMC as the conduit, SVE traps off, no interrupt routing
+ * into EL3: FIQ/IRQ stay whatever SCR_EL3.SCR left them,
+ * the kernel owns the world below.
+ */
+ mrs x1, scr_el3
+ bic x1, x1, #(1 << 2) /* SMD, SMC enabled */
+ msr scr_el3, x1
+ isb
+
+ ret
+
+el3_park:
+ b el3_park
+
+/*
+ * the SMC trap from lower EL. the SMCCC calling convention is
+ * the SMC register set, function id in x0, arguments x1 to
+ * x3, results in x0 to x3. x17 and x18 are caller save in
+ * this convention, the dispatcher clobbers x0 to x18.
+ */
+el3_smc:
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+ stp x19, x20, [sp, #-16]!
+ stp x21, x22, [sp, #-16]!
+ stp x23, x24, [sp, #-16]!
+
+ bl tb_psci_dispatch
+
+ ldp x23, x24, [sp], #16
+ ldp x21, x22, [sp], #16
+ ldp x19, x20, [sp], #16
+ ldp x29, x30, [sp], #16
+
+ eret
diff --git a/arch/arm64/kernel/start.S b/arch/arm64/kernel/start.S
index 3cf58d2..2a5e2ae 100644
--- a/arch/arm64/kernel/start.S
+++ b/arch/arm64/kernel/start.S
@@ -38,12 +38,16 @@ reset:
/* keep the dtb pointer before anything clobbers x0 */
mov x19, x0
- /* park secondary cores, they have nothing to do yet */
+ /*
+ * park secondary cores, they have nothing to do yet. at
+ * EL3 they still get the monitor: a firmware call on any
+ * PE must land in a handler, a secondary with no EL3
+ * vectors traps into nothing.
+ */
mrs x0, mpidr_el1
and x0, x0, #0xff
- cbnz x0, park
+ cbnz x0, secondary_boot
- /* which EL are we in, 0x8 per level shifted into bits 3:2 */
mrs x0, CurrentEL
lsr x0, x0, #2
cmp x0, #3
@@ -54,12 +58,34 @@ reset:
b.eq mmu_check
b park
+secondary_boot:
+ mrs x0, CurrentEL
+ lsr x0, x0, #2
+ cmp x0, #3
+ b.ne park
+ /*
+ * the same security state as the primary: SCR_EL3.NS
+ * clear leaves a PE secure, and a secondary released
+ * into the kernel secure is the inconsistent mode boot
+ * the kernel warns about, its calls trap to EL3 as if
+ * they were firmware's own.
+ */
+ mrs x0, scr_el3
+ orr x0, x0, #1
+ msr scr_el3, x0
+ isb
+ bl tb_monitor_init
+ b park
+
from_el3:
/*
- * EL3 holds the security state. the kernel runs non-secure, so
- * set SCR_EL3.NS before dropping to EL2, which the kernel
- * prefers (booting.rst, EL2 RECOMMENDED).
+ * EL3 holds the security state, so the monitor lives here:
+ * vectors, its own stack, the SMC conduit. it is resident
+ * after this, the kernel's firmware calls trap into it.
*/
+ bl tb_monitor_init
+
+ /* the kernel runs non-secure, drop to the EL2 it prefers */
mrs x0, scr_el3
orr x0, x0, #1 /* SCR_EL3.NS = 1, non-secure */
msr scr_el3, x0