target/arm: Implement M-profile lazy FP state preservation

The M-profile architecture floating point system supports
lazy FP state preservation, where FP registers are not
pushed to the stack when an exception occurs but are instead
only saved if and when the first FP instruction in the exception
handler is executed. Implement this in QEMU, corresponding
to the check of LSPACT in the pseudocode ExecuteFPCheck().

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20190416125744.27770-24-peter.maydell@linaro.org
This commit is contained in:
Peter Maydell 2019-04-29 17:36:02 +01:00
parent a356dacf64
commit e33cf0f8d8
5 changed files with 140 additions and 0 deletions

View file

@ -7378,6 +7378,12 @@ void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
g_assert_not_reached();
}
void HELPER(v7m_preserve_fp_state)(CPUARMState *env)
{
/* translate.c should never generate calls here in user-only mode */
g_assert_not_reached();
}
uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
{
/* The TT instructions can be used by unprivileged code, but in
@ -7737,6 +7743,97 @@ pend_fault:
return false;
}
void HELPER(v7m_preserve_fp_state)(CPUARMState *env)
{
/*
* Preserve FP state (because LSPACT was set and we are about
* to execute an FP instruction). This corresponds to the
* PreserveFPState() pseudocode.
* We may throw an exception if the stacking fails.
*/
ARMCPU *cpu = arm_env_get_cpu(env);
bool is_secure = env->v7m.fpccr[M_REG_S] & R_V7M_FPCCR_S_MASK;
bool negpri = !(env->v7m.fpccr[M_REG_S] & R_V7M_FPCCR_HFRDY_MASK);
bool is_priv = !(env->v7m.fpccr[is_secure] & R_V7M_FPCCR_USER_MASK);
bool splimviol = env->v7m.fpccr[is_secure] & R_V7M_FPCCR_SPLIMVIOL_MASK;
uint32_t fpcar = env->v7m.fpcar[is_secure];
bool stacked_ok = true;
bool ts = is_secure && (env->v7m.fpccr[M_REG_S] & R_V7M_FPCCR_TS_MASK);
bool take_exception;
/* Take the iothread lock as we are going to touch the NVIC */
qemu_mutex_lock_iothread();
/* Check the background context had access to the FPU */
if (!v7m_cpacr_pass(env, is_secure, is_priv)) {
armv7m_nvic_set_pending_lazyfp(env->nvic, ARMV7M_EXCP_USAGE, is_secure);
env->v7m.cfsr[is_secure] |= R_V7M_CFSR_NOCP_MASK;
stacked_ok = false;
} else if (!is_secure && !extract32(env->v7m.nsacr, 10, 1)) {
armv7m_nvic_set_pending_lazyfp(env->nvic, ARMV7M_EXCP_USAGE, M_REG_S);
env->v7m.cfsr[M_REG_S] |= R_V7M_CFSR_NOCP_MASK;
stacked_ok = false;
}
if (!splimviol && stacked_ok) {
/* We only stack if the stack limit wasn't violated */
int i;
ARMMMUIdx mmu_idx;
mmu_idx = arm_v7m_mmu_idx_all(env, is_secure, is_priv, negpri);
for (i = 0; i < (ts ? 32 : 16); i += 2) {
uint64_t dn = *aa32_vfp_dreg(env, i / 2);
uint32_t faddr = fpcar + 4 * i;
uint32_t slo = extract64(dn, 0, 32);
uint32_t shi = extract64(dn, 32, 32);
if (i >= 16) {
faddr += 8; /* skip the slot for the FPSCR */
}
stacked_ok = stacked_ok &&
v7m_stack_write(cpu, faddr, slo, mmu_idx, STACK_LAZYFP) &&
v7m_stack_write(cpu, faddr + 4, shi, mmu_idx, STACK_LAZYFP);
}
stacked_ok = stacked_ok &&
v7m_stack_write(cpu, fpcar + 0x40,
vfp_get_fpscr(env), mmu_idx, STACK_LAZYFP);
}
/*
* We definitely pended an exception, but it's possible that it
* might not be able to be taken now. If its priority permits us
* to take it now, then we must not update the LSPACT or FP regs,
* but instead jump out to take the exception immediately.
* If it's just pending and won't be taken until the current
* handler exits, then we do update LSPACT and the FP regs.
*/
take_exception = !stacked_ok &&
armv7m_nvic_can_take_pending_exception(env->nvic);
qemu_mutex_unlock_iothread();
if (take_exception) {
raise_exception_ra(env, EXCP_LAZYFP, 0, 1, GETPC());
}
env->v7m.fpccr[is_secure] &= ~R_V7M_FPCCR_LSPACT_MASK;
if (ts) {
/* Clear s0 to s31 and the FPSCR */
int i;
for (i = 0; i < 32; i += 2) {
*aa32_vfp_dreg(env, i / 2) = 0;
}
vfp_set_fpscr(env, 0);
}
/*
* Otherwise s0 to s15 and FPSCR are UNKNOWN; we choose to leave them
* unchanged.
*/
}
/* Write to v7M CONTROL.SPSEL bit for the specified security bank.
* This may change the current stack pointer between Main and Process
* stack pointers if it is done for the CONTROL register for the current
@ -9062,6 +9159,7 @@ static void arm_log_exception(int idx)
[EXCP_NOCP] = "v7M NOCP UsageFault",
[EXCP_INVSTATE] = "v7M INVSTATE UsageFault",
[EXCP_STKOF] = "v8M STKOF UsageFault",
[EXCP_LAZYFP] = "v7M exception during lazy FP stacking",
};
if (idx >= 0 && idx < ARRAY_SIZE(excnames)) {
@ -9355,6 +9453,12 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs)
return;
}
break;
case EXCP_LAZYFP:
/*
* We already pended the specific exception in the NVIC in the
* v7m_preserve_fp_state() helper function.
*/
break;
default:
cpu_abort(cs, "Unhandled exception 0x%x\n", cs->exception_index);
return; /* Never happens. Keep compiler happy. */
@ -13483,6 +13587,14 @@ void cpu_get_tb_cpu_state(CPUARMState *env, target_ulong *pc,
flags = FIELD_DP32(flags, TBFLAG_A32, NEW_FP_CTXT_NEEDED, 1);
}
if (arm_feature(env, ARM_FEATURE_M)) {
bool is_secure = env->v7m.fpccr[M_REG_S] & R_V7M_FPCCR_S_MASK;
if (env->v7m.fpccr[is_secure] & R_V7M_FPCCR_LSPACT_MASK) {
flags = FIELD_DP32(flags, TBFLAG_A32, LSPACT, 1);
}
}
*pflags = flags;
*cs_base = 0;
}