diff options
Diffstat (limited to 'arch')
| -rw-r--r-- | arch/arm64/kernel/monitor.S | 132 | ||||
| -rw-r--r-- | arch/arm64/kernel/start.S | 38 |
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 |
