aboutsummaryrefslogtreecommitdiff
path: root/target/arm
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2017-10-06 16:46:49 +0100
committerPeter Maydell <peter.maydell@linaro.org>2017-10-06 16:46:49 +0100
commitd3392718e1fcf0859fb7c0774a8e946bacb8419c (patch)
tree6abc0ea32b4392e92532b18606b7bbe5f51c72f4 /target/arm
parent907bedb3f3ce134c149599bd9cb61856d811b8ca (diff)
target/arm: Add v8M support to exception entry code
Add support for v8M and in particular the security extension to the exception entry code. This requires changes to: * calculation of the exception-return magic LR value * push the callee-saves registers in certain cases * clear registers when taking non-secure exceptions to avoid leaking information from the interrupted secure code * switch to the correct security state on entry * use the vector table for the security state we're targeting Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 1506092407-26985-13-git-send-email-peter.maydell@linaro.org
Diffstat (limited to 'target/arm')
-rw-r--r--target/arm/helper.c165
1 files changed, 145 insertions, 20 deletions
diff --git a/target/arm/helper.c b/target/arm/helper.c
index f93a2143da..707dbb7444 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -6210,12 +6210,12 @@ static uint32_t *get_v7m_sp_ptr(CPUARMState *env, bool secure, bool threadmode,
}
}
-static uint32_t arm_v7m_load_vector(ARMCPU *cpu)
+static uint32_t arm_v7m_load_vector(ARMCPU *cpu, bool targets_secure)
{
CPUState *cs = CPU(cpu);
CPUARMState *env = &cpu->env;
MemTxResult result;
- hwaddr vec = env->v7m.vecbase[env->v7m.secure] + env->v7m.exception * 4;
+ hwaddr vec = env->v7m.vecbase[targets_secure] + env->v7m.exception * 4;
uint32_t addr;
addr = address_space_ldl(cs->as, vec,
@@ -6227,13 +6227,48 @@ static uint32_t arm_v7m_load_vector(ARMCPU *cpu)
* Since we don't model Lockup, we just report this guest error
* via cpu_abort().
*/
- cpu_abort(cs, "Failed to read from exception vector table "
- "entry %08x\n", (unsigned)vec);
+ cpu_abort(cs, "Failed to read from %s exception vector table "
+ "entry %08x\n", targets_secure ? "secure" : "nonsecure",
+ (unsigned)vec);
}
return addr;
}
-static void v7m_exception_taken(ARMCPU *cpu, uint32_t lr)
+static void v7m_push_callee_stack(ARMCPU *cpu, uint32_t lr, bool dotailchain)
+{
+ /* For v8M, push the callee-saves register part of the stack frame.
+ * Compare the v8M pseudocode PushCalleeStack().
+ * In the tailchaining case this may not be the current stack.
+ */
+ CPUARMState *env = &cpu->env;
+ CPUState *cs = CPU(cpu);
+ uint32_t *frame_sp_p;
+ uint32_t frameptr;
+
+ if (dotailchain) {
+ frame_sp_p = get_v7m_sp_ptr(env, true,
+ lr & R_V7M_EXCRET_MODE_MASK,
+ lr & R_V7M_EXCRET_SPSEL_MASK);
+ } else {
+ frame_sp_p = &env->regs[13];
+ }
+
+ frameptr = *frame_sp_p - 0x28;
+
+ stl_phys(cs->as, frameptr, 0xfefa125b);
+ stl_phys(cs->as, frameptr + 0x8, env->regs[4]);
+ stl_phys(cs->as, frameptr + 0xc, env->regs[5]);
+ stl_phys(cs->as, frameptr + 0x10, env->regs[6]);
+ stl_phys(cs->as, frameptr + 0x14, env->regs[7]);
+ stl_phys(cs->as, frameptr + 0x18, env->regs[8]);
+ stl_phys(cs->as, frameptr + 0x1c, env->regs[9]);
+ stl_phys(cs->as, frameptr + 0x20, env->regs[10]);
+ stl_phys(cs->as, frameptr + 0x24, env->regs[11]);
+
+ *frame_sp_p = frameptr;
+}
+
+static void v7m_exception_taken(ARMCPU *cpu, uint32_t lr, bool dotailchain)
{
/* Do the "take the exception" parts of exception entry,
* but not the pushing of state to the stack. This is
@@ -6241,14 +6276,84 @@ static void v7m_exception_taken(ARMCPU *cpu, uint32_t lr)
*/
CPUARMState *env = &cpu->env;
uint32_t addr;
+ bool targets_secure;
+
+ targets_secure = armv7m_nvic_acknowledge_irq(env->nvic);
- armv7m_nvic_acknowledge_irq(env->nvic);
+ if (arm_feature(env, ARM_FEATURE_V8)) {
+ if (arm_feature(env, ARM_FEATURE_M_SECURITY) &&
+ (lr & R_V7M_EXCRET_S_MASK)) {
+ /* The background code (the owner of the registers in the
+ * exception frame) is Secure. This means it may either already
+ * have or now needs to push callee-saves registers.
+ */
+ if (targets_secure) {
+ if (dotailchain && !(lr & R_V7M_EXCRET_ES_MASK)) {
+ /* We took an exception from Secure to NonSecure
+ * (which means the callee-saved registers got stacked)
+ * and are now tailchaining to a Secure exception.
+ * Clear DCRS so eventual return from this Secure
+ * exception unstacks the callee-saved registers.
+ */
+ lr &= ~R_V7M_EXCRET_DCRS_MASK;
+ }
+ } else {
+ /* We're going to a non-secure exception; push the
+ * callee-saves registers to the stack now, if they're
+ * not already saved.
+ */
+ if (lr & R_V7M_EXCRET_DCRS_MASK &&
+ !(dotailchain && (lr & R_V7M_EXCRET_ES_MASK))) {
+ v7m_push_callee_stack(cpu, lr, dotailchain);
+ }
+ lr |= R_V7M_EXCRET_DCRS_MASK;
+ }
+ }
+
+ lr &= ~R_V7M_EXCRET_ES_MASK;
+ if (targets_secure || !arm_feature(env, ARM_FEATURE_M_SECURITY)) {
+ lr |= R_V7M_EXCRET_ES_MASK;
+ }
+ lr &= ~R_V7M_EXCRET_SPSEL_MASK;
+ if (env->v7m.control[targets_secure] & R_V7M_CONTROL_SPSEL_MASK) {
+ lr |= R_V7M_EXCRET_SPSEL_MASK;
+ }
+
+ /* Clear registers if necessary to prevent non-secure exception
+ * code being able to see register values from secure code.
+ * Where register values become architecturally UNKNOWN we leave
+ * them with their previous values.
+ */
+ if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
+ if (!targets_secure) {
+ /* Always clear the caller-saved registers (they have been
+ * pushed to the stack earlier in v7m_push_stack()).
+ * Clear callee-saved registers if the background code is
+ * Secure (in which case these regs were saved in
+ * v7m_push_callee_stack()).
+ */
+ int i;
+
+ for (i = 0; i < 13; i++) {
+ /* r4..r11 are callee-saves, zero only if EXCRET.S == 1 */
+ if (i < 4 || i > 11 || (lr & R_V7M_EXCRET_S_MASK)) {
+ env->regs[i] = 0;
+ }
+ }
+ /* Clear EAPSR */
+ xpsr_write(env, 0, XPSR_NZCV | XPSR_Q | XPSR_GE | XPSR_IT);
+ }
+ }
+ }
+
+ /* Switch to target security state -- must do this before writing SPSEL */
+ switch_v7m_security_state(env, targets_secure);
write_v7m_control_spsel(env, 0);
arm_clear_exclusive(env);
/* Clear IT bits */
env->condexec_bits = 0;
env->regs[14] = lr;
- addr = arm_v7m_load_vector(cpu);
+ addr = arm_v7m_load_vector(cpu, targets_secure);
env->regs[15] = addr & 0xfffffffe;
env->thumb = addr & 1;
}
@@ -6414,7 +6519,7 @@ static void do_v7m_exception_exit(ARMCPU *cpu)
if (sfault) {
env->v7m.sfsr |= R_V7M_SFSR_INVER_MASK;
armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_SECURE, false);
- v7m_exception_taken(cpu, excret);
+ v7m_exception_taken(cpu, excret, true);
qemu_log_mask(CPU_LOG_INT, "...taking SecureFault on existing "
"stackframe: failed EXC_RETURN.ES validity check\n");
return;
@@ -6426,7 +6531,7 @@ static void do_v7m_exception_exit(ARMCPU *cpu)
*/
env->v7m.cfsr[env->v7m.secure] |= R_V7M_CFSR_INVPC_MASK;
armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE, env->v7m.secure);
- v7m_exception_taken(cpu, excret);
+ v7m_exception_taken(cpu, excret, true);
qemu_log_mask(CPU_LOG_INT, "...taking UsageFault on existing "
"stackframe: failed exception return integrity check\n");
return;
@@ -6474,7 +6579,7 @@ static void do_v7m_exception_exit(ARMCPU *cpu)
/* Take a SecureFault on the current stack */
env->v7m.sfsr |= R_V7M_SFSR_INVIS_MASK;
armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_SECURE, false);
- v7m_exception_taken(cpu, excret);
+ v7m_exception_taken(cpu, excret, true);
qemu_log_mask(CPU_LOG_INT, "...taking SecureFault on existing "
"stackframe: failed exception return integrity "
"signature check\n");
@@ -6539,7 +6644,7 @@ static void do_v7m_exception_exit(ARMCPU *cpu)
armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE,
env->v7m.secure);
env->v7m.cfsr[env->v7m.secure] |= R_V7M_CFSR_INVPC_MASK;
- v7m_exception_taken(cpu, excret);
+ v7m_exception_taken(cpu, excret, true);
qemu_log_mask(CPU_LOG_INT, "...taking UsageFault on existing "
"stackframe: failed exception return integrity "
"check\n");
@@ -6576,7 +6681,7 @@ static void do_v7m_exception_exit(ARMCPU *cpu)
armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE, false);
env->v7m.cfsr[env->v7m.secure] |= R_V7M_CFSR_INVPC_MASK;
v7m_push_stack(cpu);
- v7m_exception_taken(cpu, excret);
+ v7m_exception_taken(cpu, excret, false);
qemu_log_mask(CPU_LOG_INT, "...taking UsageFault on new stackframe: "
"failed exception return integrity check\n");
return;
@@ -6720,20 +6825,40 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs)
return; /* Never happens. Keep compiler happy. */
}
- lr = R_V7M_EXCRET_RES1_MASK |
- R_V7M_EXCRET_S_MASK |
- R_V7M_EXCRET_DCRS_MASK |
- R_V7M_EXCRET_FTYPE_MASK |
- R_V7M_EXCRET_ES_MASK;
- if (env->v7m.control[env->v7m.secure] & R_V7M_CONTROL_SPSEL_MASK) {
- lr |= R_V7M_EXCRET_SPSEL_MASK;
+ if (arm_feature(env, ARM_FEATURE_V8)) {
+ lr = R_V7M_EXCRET_RES1_MASK |
+ R_V7M_EXCRET_DCRS_MASK |
+ R_V7M_EXCRET_FTYPE_MASK;
+ /* The S bit indicates whether we should return to Secure
+ * or NonSecure (ie our current state).
+ * The ES bit indicates whether we're taking this exception
+ * to Secure or NonSecure (ie our target state). We set it
+ * later, in v7m_exception_taken().
+ * The SPSEL bit is also set in v7m_exception_taken() for v8M.
+ * This corresponds to the ARM ARM pseudocode for v8M setting
+ * some LR bits in PushStack() and some in ExceptionTaken();
+ * the distinction matters for the tailchain cases where we
+ * can take an exception without pushing the stack.
+ */
+ if (env->v7m.secure) {
+ lr |= R_V7M_EXCRET_S_MASK;
+ }
+ } else {
+ lr = R_V7M_EXCRET_RES1_MASK |
+ R_V7M_EXCRET_S_MASK |
+ R_V7M_EXCRET_DCRS_MASK |
+ R_V7M_EXCRET_FTYPE_MASK |
+ R_V7M_EXCRET_ES_MASK;
+ if (env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK) {
+ lr |= R_V7M_EXCRET_SPSEL_MASK;
+ }
}
if (!arm_v7m_is_handler_mode(env)) {
lr |= R_V7M_EXCRET_MODE_MASK;
}
v7m_push_stack(cpu);
- v7m_exception_taken(cpu, lr);
+ v7m_exception_taken(cpu, lr, false);
qemu_log_mask(CPU_LOG_INT, "... as %d\n", env->v7m.exception);
}