armv7m: Check exception return consistency
Implement the exception return consistency checks described in the v7M pseudocode ExceptionReturn(). Inspired by a patch from Michael Davidsaver's series, but this is a reimplementation from scratch based on the ARM ARM pseudocode. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
This commit is contained in:
parent
39ae2474e3
commit
aa488fe3bb
@ -441,10 +441,11 @@ void armv7m_nvic_acknowledge_irq(void *opaque)
|
|||||||
nvic_irq_update(s);
|
nvic_irq_update(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void armv7m_nvic_complete_irq(void *opaque, int irq)
|
int armv7m_nvic_complete_irq(void *opaque, int irq)
|
||||||
{
|
{
|
||||||
NVICState *s = (NVICState *)opaque;
|
NVICState *s = (NVICState *)opaque;
|
||||||
VecInfo *vec;
|
VecInfo *vec;
|
||||||
|
int ret;
|
||||||
|
|
||||||
assert(irq > ARMV7M_EXCP_RESET && irq < s->num_irq);
|
assert(irq > ARMV7M_EXCP_RESET && irq < s->num_irq);
|
||||||
|
|
||||||
@ -452,6 +453,13 @@ void armv7m_nvic_complete_irq(void *opaque, int irq)
|
|||||||
|
|
||||||
trace_nvic_complete_irq(irq);
|
trace_nvic_complete_irq(irq);
|
||||||
|
|
||||||
|
if (!vec->active) {
|
||||||
|
/* Tell the caller this was an illegal exception return */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = nvic_rettobase(s);
|
||||||
|
|
||||||
vec->active = 0;
|
vec->active = 0;
|
||||||
if (vec->level) {
|
if (vec->level) {
|
||||||
/* Re-pend the exception if it's still held high; only
|
/* Re-pend the exception if it's still held high; only
|
||||||
@ -462,6 +470,8 @@ void armv7m_nvic_complete_irq(void *opaque, int irq)
|
|||||||
}
|
}
|
||||||
|
|
||||||
nvic_irq_update(s);
|
nvic_irq_update(s);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* callback when external interrupt line is changed */
|
/* callback when external interrupt line is changed */
|
||||||
|
@ -1366,7 +1366,17 @@ static inline bool armv7m_nvic_can_take_pending_exception(void *opaque)
|
|||||||
#endif
|
#endif
|
||||||
void armv7m_nvic_set_pending(void *opaque, int irq);
|
void armv7m_nvic_set_pending(void *opaque, int irq);
|
||||||
void armv7m_nvic_acknowledge_irq(void *opaque);
|
void armv7m_nvic_acknowledge_irq(void *opaque);
|
||||||
void armv7m_nvic_complete_irq(void *opaque, int irq);
|
/**
|
||||||
|
* armv7m_nvic_complete_irq: complete specified interrupt or exception
|
||||||
|
* @opaque: the NVIC
|
||||||
|
* @irq: the exception number to complete
|
||||||
|
*
|
||||||
|
* Returns: -1 if the irq was not active
|
||||||
|
* 1 if completing this irq brought us back to base (no active irqs)
|
||||||
|
* 0 if there is still an irq active after this one was completed
|
||||||
|
* (Ignoring -1, this is the same as the RETTOBASE value before completion.)
|
||||||
|
*/
|
||||||
|
int armv7m_nvic_complete_irq(void *opaque, int irq);
|
||||||
|
|
||||||
/* Interface for defining coprocessor registers.
|
/* Interface for defining coprocessor registers.
|
||||||
* Registers are defined in tables of arm_cp_reginfo structs
|
* Registers are defined in tables of arm_cp_reginfo structs
|
||||||
|
@ -6068,22 +6068,99 @@ static void v7m_push_stack(ARMCPU *cpu)
|
|||||||
v7m_push(env, env->regs[0]);
|
v7m_push(env, env->regs[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_v7m_exception_exit(CPUARMState *env)
|
static void do_v7m_exception_exit(ARMCPU *cpu)
|
||||||
{
|
{
|
||||||
|
CPUARMState *env = &cpu->env;
|
||||||
uint32_t type;
|
uint32_t type;
|
||||||
uint32_t xpsr;
|
uint32_t xpsr;
|
||||||
|
bool ufault = false;
|
||||||
|
bool return_to_sp_process = false;
|
||||||
|
bool return_to_handler = false;
|
||||||
|
bool rettobase = false;
|
||||||
|
|
||||||
|
/* We can only get here from an EXCP_EXCEPTION_EXIT, and
|
||||||
|
* arm_v7m_do_unassigned_access() enforces the architectural rule
|
||||||
|
* that jumps to magic addresses don't have magic behaviour unless
|
||||||
|
* we're in Handler mode (compare pseudocode BXWritePC()).
|
||||||
|
*/
|
||||||
|
assert(env->v7m.exception != 0);
|
||||||
|
|
||||||
|
/* In the spec pseudocode ExceptionReturn() is called directly
|
||||||
|
* from BXWritePC() and gets the full target PC value including
|
||||||
|
* bit zero. In QEMU's implementation we treat it as a normal
|
||||||
|
* jump-to-register (which is then caught later on), and so split
|
||||||
|
* the target value up between env->regs[15] and env->thumb in
|
||||||
|
* gen_bx(). Reconstitute it.
|
||||||
|
*/
|
||||||
type = env->regs[15];
|
type = env->regs[15];
|
||||||
|
if (env->thumb) {
|
||||||
|
type |= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
qemu_log_mask(CPU_LOG_INT, "Exception return: magic PC %" PRIx32
|
||||||
|
" previous exception %d\n",
|
||||||
|
type, env->v7m.exception);
|
||||||
|
|
||||||
|
if (extract32(type, 5, 23) != extract32(-1, 5, 23)) {
|
||||||
|
qemu_log_mask(LOG_GUEST_ERROR, "M profile: zero high bits in exception "
|
||||||
|
"exit PC value 0x%" PRIx32 " are UNPREDICTABLE\n", type);
|
||||||
|
}
|
||||||
|
|
||||||
if (env->v7m.exception != ARMV7M_EXCP_NMI) {
|
if (env->v7m.exception != ARMV7M_EXCP_NMI) {
|
||||||
/* Auto-clear FAULTMASK on return from other than NMI */
|
/* Auto-clear FAULTMASK on return from other than NMI */
|
||||||
env->daif &= ~PSTATE_F;
|
env->daif &= ~PSTATE_F;
|
||||||
}
|
}
|
||||||
if (env->v7m.exception != 0) {
|
|
||||||
armv7m_nvic_complete_irq(env->nvic, env->v7m.exception);
|
switch (armv7m_nvic_complete_irq(env->nvic, env->v7m.exception)) {
|
||||||
|
case -1:
|
||||||
|
/* attempt to exit an exception that isn't active */
|
||||||
|
ufault = true;
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
/* still an irq active now */
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
/* we returned to base exception level, no nesting.
|
||||||
|
* (In the pseudocode this is written using "NestedActivation != 1"
|
||||||
|
* where we have 'rettobase == false'.)
|
||||||
|
*/
|
||||||
|
rettobase = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type & 0xf) {
|
||||||
|
case 1: /* Return to Handler */
|
||||||
|
return_to_handler = true;
|
||||||
|
break;
|
||||||
|
case 13: /* Return to Thread using Process stack */
|
||||||
|
return_to_sp_process = true;
|
||||||
|
/* fall through */
|
||||||
|
case 9: /* Return to Thread using Main stack */
|
||||||
|
if (!rettobase &&
|
||||||
|
!(env->v7m.ccr & R_V7M_CCR_NONBASETHRDENA_MASK)) {
|
||||||
|
ufault = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ufault = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ufault) {
|
||||||
|
/* Bad exception return: instead of popping the exception
|
||||||
|
* stack, directly take a usage fault on the current stack.
|
||||||
|
*/
|
||||||
|
env->v7m.cfsr |= R_V7M_CFSR_INVPC_MASK;
|
||||||
|
armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE);
|
||||||
|
v7m_exception_taken(cpu, type | 0xf0000000);
|
||||||
|
qemu_log_mask(CPU_LOG_INT, "...taking UsageFault on existing "
|
||||||
|
"stackframe: failed exception return integrity check\n");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Switch to the target stack. */
|
/* Switch to the target stack. */
|
||||||
switch_v7m_sp(env, (type & 4) != 0);
|
switch_v7m_sp(env, return_to_sp_process);
|
||||||
/* Pop registers. */
|
/* Pop registers. */
|
||||||
env->regs[0] = v7m_pop(env);
|
env->regs[0] = v7m_pop(env);
|
||||||
env->regs[1] = v7m_pop(env);
|
env->regs[1] = v7m_pop(env);
|
||||||
@ -6107,11 +6184,24 @@ static void do_v7m_exception_exit(CPUARMState *env)
|
|||||||
/* Undo stack alignment. */
|
/* Undo stack alignment. */
|
||||||
if (xpsr & 0x200)
|
if (xpsr & 0x200)
|
||||||
env->regs[13] |= 4;
|
env->regs[13] |= 4;
|
||||||
/* ??? The exception return type specifies Thread/Handler mode. However
|
|
||||||
this is also implied by the xPSR value. Not sure what to do
|
/* The restored xPSR exception field will be zero if we're
|
||||||
if there is a mismatch. */
|
* resuming in Thread mode. If that doesn't match what the
|
||||||
/* ??? Likewise for mismatches between the CONTROL register and the stack
|
* exception return type specified then this is a UsageFault.
|
||||||
pointer. */
|
*/
|
||||||
|
if (return_to_handler == (env->v7m.exception == 0)) {
|
||||||
|
/* Take an INVPC UsageFault by pushing the stack again. */
|
||||||
|
armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE);
|
||||||
|
env->v7m.cfsr |= R_V7M_CFSR_INVPC_MASK;
|
||||||
|
v7m_push_stack(cpu);
|
||||||
|
v7m_exception_taken(cpu, type | 0xf0000000);
|
||||||
|
qemu_log_mask(CPU_LOG_INT, "...taking UsageFault on new stackframe: "
|
||||||
|
"failed exception return integrity check\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, we have a successful exception exit. */
|
||||||
|
qemu_log_mask(CPU_LOG_INT, "...successful exception return\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void arm_log_exception(int idx)
|
static void arm_log_exception(int idx)
|
||||||
@ -6184,7 +6274,7 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs)
|
|||||||
case EXCP_IRQ:
|
case EXCP_IRQ:
|
||||||
break;
|
break;
|
||||||
case EXCP_EXCEPTION_EXIT:
|
case EXCP_EXCEPTION_EXIT:
|
||||||
do_v7m_exception_exit(env);
|
do_v7m_exception_exit(cpu);
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
cpu_abort(cs, "Unhandled exception 0x%x\n", cs->exception_index);
|
cpu_abort(cs, "Unhandled exception 0x%x\n", cs->exception_index);
|
||||||
|
Loading…
Reference in New Issue
Block a user