summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--payloads/libpayload/arch/arm64/exception.c15
-rw-r--r--payloads/libpayload/arch/arm64/exception_asm.S152
-rw-r--r--payloads/libpayload/arch/arm64/head.S12
-rw-r--r--payloads/libpayload/arch/arm64/libpayload.ldscript9
-rw-r--r--payloads/libpayload/include/arm64/arch/exception.h90
5 files changed, 191 insertions, 87 deletions
diff --git a/payloads/libpayload/arch/arm64/exception.c b/payloads/libpayload/arch/arm64/exception.c
index a84daf1563..a5e55163a8 100644
--- a/payloads/libpayload/arch/arm64/exception.c
+++ b/payloads/libpayload/arch/arm64/exception.c
@@ -31,6 +31,8 @@
#include <libpayload.h>
#include <stdint.h>
+u64 exception_stack[0x200] __attribute__((aligned(16)));
+u64 *exception_stack_end = exception_stack + ARRAY_SIZE(exception_stack);
extern unsigned int test_exc;
struct exception_handler_info
@@ -39,7 +41,7 @@ struct exception_handler_info
};
static exception_hook hook;
-struct exception_state *exception_state;
+struct exception_state exception_state;
static struct exception_handler_info exceptions[EXC_COUNT] = {
[EXC_SYNC_SP0] = { "_sync_sp_el0" },
@@ -88,14 +90,12 @@ static void print_regs(struct exception_state *state)
i, state->regs[i], i + 1, state->regs[i + 1]);
}
printf("X30 = 0x%016llx SP = 0x%016llx\n",
- state->regs[30], raw_read_sp_el0());
+ state->regs[30], state->sp);
}
void exception_dispatch(struct exception_state *state, int idx);
void exception_dispatch(struct exception_state *state, int idx)
{
- exception_state = state;
-
if (idx >= EXC_COUNT) {
printf("Bad exception index %d.\n", idx);
} else {
@@ -110,7 +110,7 @@ void exception_dispatch(struct exception_state *state, int idx)
}
print_regs(state);
/* Few words below SP in case we need state from a returned function. */
- dump_stack(raw_read_sp_el0() - 32, 512);
+ dump_stack(state->sp - 32, 512);
if (test_exc) {
state->elr += 4;
@@ -123,8 +123,9 @@ void exception_dispatch(struct exception_state *state, int idx)
void exception_init(void)
{
- extern void* exception_table;
- set_vbar(&exception_table);
+ extern uint64_t exception_table[];
+ raw_write_vbar_el2((uintptr_t)exception_table);
+ exception_set_state_ptr(&exception_state);
}
void exception_install_hook(exception_hook h)
diff --git a/payloads/libpayload/arch/arm64/exception_asm.S b/payloads/libpayload/arch/arm64/exception_asm.S
index d428940730..efa3f9d7e2 100644
--- a/payloads/libpayload/arch/arm64/exception_asm.S
+++ b/payloads/libpayload/arch/arm64/exception_asm.S
@@ -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]!
-
- /* Save the exception reason on stack */
- mrs x1, esr_el2
+/* 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 stack pointer and SPSR */
+ mrs x1, sp_el0
+ mrs x0, spsr_el2
+ stp x0, x1, [sp, #EXCEPTION_STATE_SPSR]
- /* Save the return address on stack */
+ /* Save return address (ELR) and exception syndrome */
+ mrs x1, esr_el2
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
+
+ /* 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)
- .global set_vbar
-set_vbar:
- msr vbar_el2, x0
+ /*
+ * 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)
diff --git a/payloads/libpayload/arch/arm64/head.S b/payloads/libpayload/arch/arm64/head.S
index 349dfd43ee..8bac70fee5 100644
--- a/payloads/libpayload/arch/arm64/head.S
+++ b/payloads/libpayload/arch/arm64/head.S
@@ -38,18 +38,8 @@ ENTRY(_entry)
ldr x1, 1f
str x0, [x1]
- /* Setup exception stack */
- ldr x1, 3f
- msr SPSel, #1
- isb
-
- mov sp, x1
-
/* Setup new stack */
ldr x1, 2f
- msr SPSel, #0
- isb
-
mov sp, x1
/* Let's rock. */
@@ -63,5 +53,3 @@ ENDPROC(_entry)
.quad cb_header_ptr
2:
.quad _stack
-3:
-.quad _exc_stack
diff --git a/payloads/libpayload/arch/arm64/libpayload.ldscript b/payloads/libpayload/arch/arm64/libpayload.ldscript
index 5c807cef17..ceb0711680 100644
--- a/payloads/libpayload/arch/arm64/libpayload.ldscript
+++ b/payloads/libpayload/arch/arm64/libpayload.ldscript
@@ -77,15 +77,6 @@ SECTIONS
. += CONFIG_LP_STACK_SIZE;
. = ALIGN(16);
_stack = .;
-
- /* Exception stack. Having a separate exception stack
- * allows us to have later stages running in non-EL3 levels.
- */
-
- _exc_estack = .;
- . += CONFIG_LP_STACK_SIZE;
- . = ALIGN(16);
- _exc_stack = .;
}
_end = .;
diff --git a/payloads/libpayload/include/arm64/arch/exception.h b/payloads/libpayload/include/arm64/arch/exception.h
index 8afe39103b..f699d3d6ed 100644
--- a/payloads/libpayload/include/arm64/arch/exception.h
+++ b/payloads/libpayload/include/arm64/arch/exception.h
@@ -30,18 +30,98 @@
#ifndef _ARCH_EXCEPTION_H
#define _ARCH_EXCEPTION_H
-#include <stdint.h>
+#define EXCEPTION_STATE_ELR 0x0
+#define EXCEPTION_STATE_ESR 0x8
+#define EXCEPTION_STATE_SPSR 0x10
+#define EXCEPTION_STATE_SP 0x18
+#define EXCEPTION_STATE_REG(r) (0x20 + r * 0x8)
+
+#define ESR_EC_UNKNOWN 0b000000
+#define ESR_EC_SVC_64 0b010101
+#define ESR_EC_INSN_ABT_LOWER 0b100000
+#define ESR_EC_INSN_ABT_SAME 0b100001
+#define ESR_EC_DATA_ABT_LOWER 0b100100
+#define ESR_EC_DATA_ABT_SAME 0b100101
+#define ESR_EC_SERROR 0b101111
+#define ESR_EC_SS_SAME 0b110011
+#define ESR_EC_BKPT_64 0b111100
+
+#define MDCR_TDE (1 << 8)
-void set_vbar(void* vbar);
+#define MDSCR_SS (1 << 0)
+#define MDSCR_KDE (1 << 13)
+#define MDSCR_MDE (1 << 15)
+
+#ifndef __ASSEMBLER__
+
+#include <stddef.h>
+#include <stdint.h>
struct exception_state
{
uint64_t elr;
- uint64_t esr;
+ union {
+ uint64_t esr;
+ union {
+ struct {
+ uint64_t iss : 25;
+ uint64_t il : 1;
+ uint64_t ec : 6;
+ uint64_t _res0 : 32;
+ };
+ struct {
+ uint64_t isfc : 6;
+ uint64_t _res0 : 1;
+ uint64_t s1ptw : 1;
+ uint64_t _res1 : 1;
+ uint64_t ea : 1;
+ uint64_t fnv : 1;
+ uint64_t _res2 : 53;
+ } insn_abt;
+ };
+ };
+ union {
+ uint32_t spsr;
+ struct {
+ uint32_t sp : 1; /* M[0] */
+ uint32_t _res0 : 1; /* M[1] */
+ uint32_t el : 2; /* M[3:2] */
+ uint32_t arch : 1; /* M[4] */
+ uint32_t _res1 : 1;
+ uint32_t f : 1;
+ uint32_t i : 1;
+ uint32_t a : 1;
+ uint32_t d : 1;
+ uint32_t _res2 : 10;
+ uint32_t il : 1;
+ uint32_t ss : 1;
+ uint32_t _res3 : 6;
+ uint32_t v : 1;
+ uint32_t c : 1;
+ uint32_t z : 1;
+ uint32_t n : 1;
+ } pstate;
+ };
+ uint32_t spsr_high_unused;
+ uint64_t sp;
uint64_t regs[31];
} __packed;
-extern struct exception_state *exception_state;
+#define CHECK_ES(field, constant) \
+ _Static_assert(offsetof(struct exception_state, field) == constant, \
+ "(struct exception_state)." #field " doesn't match constant " #constant)
+CHECK_ES(elr, EXCEPTION_STATE_ELR);
+CHECK_ES(esr, EXCEPTION_STATE_ESR);
+CHECK_ES(spsr, EXCEPTION_STATE_SPSR);
+CHECK_ES(sp, EXCEPTION_STATE_SP);
+CHECK_ES(regs[0], EXCEPTION_STATE_REG(0));
+CHECK_ES(regs[30], EXCEPTION_STATE_REG(30));
+
+extern struct exception_state exception_state;
+extern u64 exception_stack[];
+extern u64 *exception_stack_end;
+
+void exception_set_state_ptr(struct exception_state *exception_state_ptr);
enum {
EXC_SYNC_SP0 = 0,
@@ -63,4 +143,6 @@ enum {
EXC_COUNT
};
+#endif /* !__ASSEMBLER__ */
+
#endif