diff options
| author | Bradley Morgan <brads@mainlining.org> | 2026-10-04 05:59:26 +0000 |
|---|---|---|
| committer | Bradley Morgan <brads@mainlining.org> | 2026-10-04 05:59:26 +0000 |
| commit | 4256361421a2d857e8a1d5cdef45bdbcfef477ad (patch) | |
| tree | 7aee83d4194098f49f2ad84f5080e1b6155d7638 /arch/arm64/kernel/monitor.S | |
| parent | 0567dd7e947d10a237405e4a9d965c57dd6b473e (diff) | |
tashaboot: el3 secure monitor
The resident firmware layer real machines ship, the thing the
gic group lesson pointed at. The reset path configures EL3,
SP_EL3 on its own region, the monitor vectors in VBAR_EL3,
then hands the next stage non-secure EL2 in the manual's boot
state and never comes back except through exceptions.
Secondaries that enter at EL3 get the monitor before they
park, a firmware call on any PE must land in a handler, and
SCR_EL3.NS is set to match the primary so a released PE does
not come up secure while the kernel runs non-secure.
The SMC conduit traps into the lower EL AArch64 sync slot and
dispatches through the same PSCI C code the hvc path uses,
SMCCC register convention kept whole across the trap.
On the emulator here the machine's own firmware shadow stands
in front of the conduit, its PSCI answers before the monitor
sees the call, and its secure memory map traps the kernel's
flash probe after init starts. The monitor mechanics, the
entry, the vectors, the stack, the eret, the SMC layout, are
live on every secure boot, the call dispatch itself is the
hardware receipt.
receipt: secure boot through the monitor to four cpus and the
init exec, plain boot unchanged to the busybox shell.
Diffstat (limited to 'arch/arm64/kernel/monitor.S')
| -rw-r--r-- | arch/arm64/kernel/monitor.S | 132 |
1 files changed, 132 insertions, 0 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 |
