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
This commit is contained in:
Peter Maydell 2017-10-06 16:46:49 +01:00
parent 907bedb3f3
commit d3392718e1
1 changed files with 145 additions and 20 deletions

View File

@ -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;
armv7m_nvic_acknowledge_irq(env->nvic);
targets_secure = 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);
}