Commit aa488fe3 authored by Peter Maydell's avatar Peter Maydell
Browse files

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: default avatarPeter Maydell <peter.maydell@linaro.org>
Reviewed-by: default avatarAlex Bennée <alex.bennee@linaro.org>
parent 39ae2474
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -441,10 +441,11 @@ void armv7m_nvic_acknowledge_irq(void *opaque)
    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;
    VecInfo *vec;
    int ret;

    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);

    if (!vec->active) {
        /* Tell the caller this was an illegal exception return */
        return -1;
    }

    ret = nvic_rettobase(s);

    vec->active = 0;
    if (vec->level) {
        /* 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);

    return ret;
}

/* callback when external interrupt line is changed */
+11 −1
Original line number Diff line number Diff line
@@ -1366,7 +1366,17 @@ static inline bool armv7m_nvic_can_take_pending_exception(void *opaque)
#endif
void armv7m_nvic_set_pending(void *opaque, int irq);
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.
 * Registers are defined in tables of arm_cp_reginfo structs
+101 −11
Original line number Diff line number Diff line
@@ -6068,22 +6068,99 @@ static void v7m_push_stack(ARMCPU *cpu)
    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 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];
    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) {
        /* Auto-clear FAULTMASK on return from other than NMI */
        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_v7m_sp(env, (type & 4) != 0);
    switch_v7m_sp(env, return_to_sp_process);
    /* Pop registers.  */
    env->regs[0] = v7m_pop(env);
    env->regs[1] = v7m_pop(env);
@@ -6107,11 +6184,24 @@ static void do_v7m_exception_exit(CPUARMState *env)
    /* Undo stack alignment.  */
    if (xpsr & 0x200)
        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
       if there is a mismatch.  */
    /* ??? Likewise for mismatches between the CONTROL register and the stack
       pointer.  */

    /* The restored xPSR exception field will be zero if we're
     * resuming in Thread mode. If that doesn't match what the
     * exception return type specified then this is a UsageFault.
     */
    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)
@@ -6184,7 +6274,7 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs)
    case EXCP_IRQ:
        break;
    case EXCP_EXCEPTION_EXIT:
        do_v7m_exception_exit(env);
        do_v7m_exception_exit(cpu);
        return;
    default:
        cpu_abort(cs, "Unhandled exception 0x%x\n", cs->exception_index);