libpayload: arm64: Make exception handling closer to arm32
This patch reworks the arm64 exception handling to be more similar to how it works on arm32. This includes a bunch of features like actually saving and restoring more exception state in the exception_state structure and supporting the same sort of partial reentrancy that is useful for GDB. Since there's no instruction to directly load into or store out of SP on arm64, we can't do quite the same thing where we use that to read an exception_state_ptr variable right after exception entry when no other register is available. But we can do something very similar by (ab-)using the "high" stack pointer (SP_EL2) as a pointer to the exception_state struct and providing a function to change it. Change-Id: Ia16a1124be1824392a309ae1f4cb031547d184c1 Signed-off-by: Julius Werner <jwerner@chromium.org> Reviewed-on: https://review.coreboot.org/29018 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Aaron Durbin <adurbin@chromium.org>
This commit is contained in:
@@ -27,19 +27,25 @@
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <arch/asm.h>
|
||||
#include <arch/exception.h>
|
||||
|
||||
/* Macro for exception entry
|
||||
* Store x30 before any branch
|
||||
* Branch to exception_prologue to save rest of the registers
|
||||
* Branch to exception_prologue to save rest and switch stacks
|
||||
* Move exception id into x1
|
||||
* Branch to exception_handler
|
||||
* Branch to exception_dispatch (exception C entry point)
|
||||
* Branch to exception_return to return from exception
|
||||
*/
|
||||
.macro eentry lbl id
|
||||
.align 7
|
||||
\lbl:
|
||||
stp x30, xzr, [sp, #-16]!
|
||||
/* Note: SP points to exception_state (see exception_set_state_ptr) */
|
||||
str x30, [sp, #EXCEPTION_STATE_REG(30)]
|
||||
bl exception_prologue
|
||||
mov x1, \id
|
||||
bl exception_handler
|
||||
bl exception_dispatch
|
||||
b exception_return
|
||||
.endm
|
||||
|
||||
/* Exception table has 16 entries and each of 128 bytes
|
||||
@@ -68,64 +74,100 @@ eentry irq_elx_32,#13
|
||||
eentry fiq_elx_32,#14
|
||||
eentry serror_elx_32,#15
|
||||
|
||||
exception_prologue:
|
||||
/* Save all registers x0-x29 */
|
||||
stp x28, x29, [sp, #-16]!
|
||||
stp x26, x27, [sp, #-16]!
|
||||
stp x24, x25, [sp, #-16]!
|
||||
stp x22, x23, [sp, #-16]!
|
||||
stp x20, x21, [sp, #-16]!
|
||||
stp x18, x19, [sp, #-16]!
|
||||
stp x16, x17, [sp, #-16]!
|
||||
stp x14, x15, [sp, #-16]!
|
||||
stp x12, x13, [sp, #-16]!
|
||||
stp x10, x11, [sp, #-16]!
|
||||
stp x8, x9, [sp, #-16]!
|
||||
stp x6, x7, [sp, #-16]!
|
||||
stp x4, x5, [sp, #-16]!
|
||||
stp x2, x3, [sp, #-16]!
|
||||
stp x0, x1, [sp, #-16]!
|
||||
/* This code must match the layout of struct exception_state (minus x30) */
|
||||
ENTRY(exception_prologue)
|
||||
/* Save registers x0-x29 */
|
||||
stp x28, x29, [sp, #EXCEPTION_STATE_REG(28)]
|
||||
stp x26, x27, [sp, #EXCEPTION_STATE_REG(26)]
|
||||
stp x24, x25, [sp, #EXCEPTION_STATE_REG(24)]
|
||||
stp x22, x23, [sp, #EXCEPTION_STATE_REG(22)]
|
||||
stp x20, x21, [sp, #EXCEPTION_STATE_REG(20)]
|
||||
stp x18, x19, [sp, #EXCEPTION_STATE_REG(18)]
|
||||
stp x16, x17, [sp, #EXCEPTION_STATE_REG(16)]
|
||||
stp x14, x15, [sp, #EXCEPTION_STATE_REG(14)]
|
||||
stp x12, x13, [sp, #EXCEPTION_STATE_REG(12)]
|
||||
stp x10, x11, [sp, #EXCEPTION_STATE_REG(10)]
|
||||
stp x8, x9, [sp, #EXCEPTION_STATE_REG(8)]
|
||||
stp x6, x7, [sp, #EXCEPTION_STATE_REG(6)]
|
||||
stp x4, x5, [sp, #EXCEPTION_STATE_REG(4)]
|
||||
stp x2, x3, [sp, #EXCEPTION_STATE_REG(2)]
|
||||
stp x0, x1, [sp, #EXCEPTION_STATE_REG(0)]
|
||||
|
||||
/* Save the exception reason on stack */
|
||||
/* Save the stack pointer and SPSR */
|
||||
mrs x1, sp_el0
|
||||
mrs x0, spsr_el2
|
||||
stp x0, x1, [sp, #EXCEPTION_STATE_SPSR]
|
||||
|
||||
/* Save return address (ELR) and exception syndrome */
|
||||
mrs x1, esr_el2
|
||||
|
||||
/* Save the return address on stack */
|
||||
mrs x0, elr_el2
|
||||
stp x0, x1, [sp, #-16]!
|
||||
stp x0, x1, [sp, #EXCEPTION_STATE_ELR]
|
||||
|
||||
/* Now switch to the actual exception stack. Keep a pointer to the
|
||||
exception_state structure in x0 as an argument for dispatch(). */
|
||||
mov x0, sp
|
||||
adrp x1, exception_stack_end
|
||||
ldr x1, [x1, :lo12:exception_stack_end]
|
||||
msr SPSel, #0
|
||||
mov sp, x1
|
||||
|
||||
ret
|
||||
ENDPROC(exception_prologue)
|
||||
|
||||
exception_handler:
|
||||
/* Save address of saved registers into x0
|
||||
* This acts as first argument to exception_dispatch
|
||||
*/
|
||||
mov x0, sp
|
||||
bl exception_dispatch
|
||||
ENTRY(exception_return)
|
||||
/* Switch SP back to the exception_state structure */
|
||||
msr SPSel, #1
|
||||
|
||||
/* Pop return address saved on stack */
|
||||
ldp x0, x1, [sp], #16
|
||||
/* Restore return address (ELR) -- skip ESR (unneeded for return) */
|
||||
ldr x0, [sp, #EXCEPTION_STATE_ELR]
|
||||
msr elr_el2, x0
|
||||
msr esr_el2, x1
|
||||
/* Pop exception reason saved on stack, followed by regs x0-x30 */
|
||||
ldp x0, x1, [sp], #16
|
||||
ldp x2, x3, [sp], #16
|
||||
ldp x4, x5, [sp], #16
|
||||
ldp x6, x7, [sp], #16
|
||||
ldp x8, x9, [sp], #16
|
||||
ldp x10, x11, [sp], #16
|
||||
ldp x12, x13, [sp], #16
|
||||
ldp x14, x15, [sp], #16
|
||||
ldp x16, x17, [sp], #16
|
||||
ldp x18, x19, [sp], #16
|
||||
ldp x20, x21, [sp], #16
|
||||
ldp x22, x23, [sp], #16
|
||||
ldp x24, x25, [sp], #16
|
||||
ldp x26, x27, [sp], #16
|
||||
ldp x28, x29, [sp], #16
|
||||
ldp x30, xzr, [sp], #16
|
||||
eret
|
||||
|
||||
.global set_vbar
|
||||
set_vbar:
|
||||
msr vbar_el2, x0
|
||||
/* Restore stack pointer and SPSR */
|
||||
ldp x0, x1, [sp, #EXCEPTION_STATE_SPSR]
|
||||
msr spsr_el2, x0
|
||||
msr sp_el0, x1
|
||||
|
||||
/* Restore all registers (x0-x30) */
|
||||
ldp x0, x1, [sp, #EXCEPTION_STATE_REG(0)]
|
||||
ldp x2, x3, [sp, #EXCEPTION_STATE_REG(2)]
|
||||
ldp x4, x5, [sp, #EXCEPTION_STATE_REG(4)]
|
||||
ldp x6, x7, [sp, #EXCEPTION_STATE_REG(6)]
|
||||
ldp x8, x9, [sp, #EXCEPTION_STATE_REG(8)]
|
||||
ldp x10, x11, [sp, #EXCEPTION_STATE_REG(10)]
|
||||
ldp x12, x13, [sp, #EXCEPTION_STATE_REG(12)]
|
||||
ldp x14, x15, [sp, #EXCEPTION_STATE_REG(14)]
|
||||
ldp x16, x17, [sp, #EXCEPTION_STATE_REG(16)]
|
||||
ldp x18, x19, [sp, #EXCEPTION_STATE_REG(18)]
|
||||
ldp x20, x21, [sp, #EXCEPTION_STATE_REG(20)]
|
||||
ldp x22, x23, [sp, #EXCEPTION_STATE_REG(22)]
|
||||
ldp x24, x25, [sp, #EXCEPTION_STATE_REG(24)]
|
||||
ldp x26, x27, [sp, #EXCEPTION_STATE_REG(26)]
|
||||
ldp x28, x29, [sp, #EXCEPTION_STATE_REG(28)]
|
||||
ldr x30, [sp, #EXCEPTION_STATE_REG(30)]
|
||||
|
||||
/* Return from exception */
|
||||
eret
|
||||
ENDPROC(exception_return)
|
||||
|
||||
/*
|
||||
* We have two stack pointers on AArch64: SP_EL0 (which despite the
|
||||
* naming is used in all ELs) and SP_EL2. We can select which one to
|
||||
* use by writing to SPSel. Normally we're using SP_EL0, but on
|
||||
* exception entry it automatically switches to SP_EL2.
|
||||
*
|
||||
* It is important for exception reentrancy to switch back to SP_EL0
|
||||
* while handling the exception. We only need SP_EL2 for the assembly
|
||||
* exception entry and exit code that stores all register state
|
||||
* (including the old SP_EL0, before we switch to the real exception
|
||||
* stack). Rather than having yet another stack to push/pop those
|
||||
* register values on so that we can later sort them into the
|
||||
* exception_state structure, it's much easier to just make SP_EL2 point
|
||||
* directly to exception_state and just use it as a normal base register
|
||||
* rather than a real stack. This function sets that up.
|
||||
*/
|
||||
ENTRY(exception_set_state_ptr)
|
||||
msr SPSel, #1
|
||||
mov sp, x0
|
||||
msr SPSel, #0
|
||||
ret
|
||||
ENDPROC(exception_set_state_ptr)
|
||||
|
Reference in New Issue
Block a user