From a976d7642a58ee8b8d9bde3f206c166f95e12c44 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Tue, 30 Apr 2019 11:11:22 -0400 Subject: [PATCH] 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(). Backports commit e33cf0f8d8c9998a7616684f9d6aa0d181b88803 from qemu --- qemu/aarch64.h | 1 + qemu/aarch64eb.h | 1 + qemu/arm.h | 1 + qemu/armeb.h | 1 + qemu/header_gen.py | 1 + qemu/m68k.h | 1 + qemu/mips.h | 1 + qemu/mips64.h | 1 + qemu/mips64el.h | 1 + qemu/mipsel.h | 1 + qemu/powerpc.h | 1 + qemu/riscv32.h | 1 + qemu/riscv64.h | 1 + qemu/sparc.h | 1 + qemu/sparc64.h | 1 + qemu/target/arm/cpu.h | 3 + qemu/target/arm/helper.c | 114 ++++++++++++++++++++++++++++++++++++ qemu/target/arm/helper.h | 2 + qemu/target/arm/translate.c | 26 ++++++++ qemu/target/arm/translate.h | 1 + qemu/x86_64.h | 1 + 21 files changed, 162 insertions(+) diff --git a/qemu/aarch64.h b/qemu/aarch64.h index cb89f218..9cd8aa60 100644 --- a/qemu/aarch64.h +++ b/qemu/aarch64.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_aarch64 #define helper_v7m_blxns helper_v7m_blxns_aarch64 #define helper_v7m_bxns helper_v7m_bxns_aarch64 +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_aarch64 #define helper_v7m_mrs helper_v7m_mrs_aarch64 #define helper_v7m_msr helper_v7m_msr_aarch64 #define helper_v7m_tt helper_v7m_tt_aarch64 diff --git a/qemu/aarch64eb.h b/qemu/aarch64eb.h index 053dc28b..a42b3b7d 100644 --- a/qemu/aarch64eb.h +++ b/qemu/aarch64eb.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_aarch64eb #define helper_v7m_blxns helper_v7m_blxns_aarch64eb #define helper_v7m_bxns helper_v7m_bxns_aarch64eb +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_aarch64eb #define helper_v7m_mrs helper_v7m_mrs_aarch64eb #define helper_v7m_msr helper_v7m_msr_aarch64eb #define helper_v7m_tt helper_v7m_tt_aarch64eb diff --git a/qemu/arm.h b/qemu/arm.h index cefb7ead..d6b1a3d9 100644 --- a/qemu/arm.h +++ b/qemu/arm.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_arm #define helper_v7m_blxns helper_v7m_blxns_arm #define helper_v7m_bxns helper_v7m_bxns_arm +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_arm #define helper_v7m_mrs helper_v7m_mrs_arm #define helper_v7m_msr helper_v7m_msr_arm #define helper_v7m_tt helper_v7m_tt_arm diff --git a/qemu/armeb.h b/qemu/armeb.h index 46efb048..c379db2e 100644 --- a/qemu/armeb.h +++ b/qemu/armeb.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_armeb #define helper_v7m_blxns helper_v7m_blxns_armeb #define helper_v7m_bxns helper_v7m_bxns_armeb +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_armeb #define helper_v7m_mrs helper_v7m_mrs_armeb #define helper_v7m_msr helper_v7m_msr_armeb #define helper_v7m_tt helper_v7m_tt_armeb diff --git a/qemu/header_gen.py b/qemu/header_gen.py index 06cdefad..34a1436f 100644 --- a/qemu/header_gen.py +++ b/qemu/header_gen.py @@ -1751,6 +1751,7 @@ symbols = ( 'helper_uxtb16', 'helper_v7m_blxns', 'helper_v7m_bxns', + 'helper_v7m_preserve_fp_state', 'helper_v7m_mrs', 'helper_v7m_msr', 'helper_v7m_tt', diff --git a/qemu/m68k.h b/qemu/m68k.h index 9111abba..37c27aef 100644 --- a/qemu/m68k.h +++ b/qemu/m68k.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_m68k #define helper_v7m_blxns helper_v7m_blxns_m68k #define helper_v7m_bxns helper_v7m_bxns_m68k +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_m68k #define helper_v7m_mrs helper_v7m_mrs_m68k #define helper_v7m_msr helper_v7m_msr_m68k #define helper_v7m_tt helper_v7m_tt_m68k diff --git a/qemu/mips.h b/qemu/mips.h index 69471ac4..58bcea30 100644 --- a/qemu/mips.h +++ b/qemu/mips.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_mips #define helper_v7m_blxns helper_v7m_blxns_mips #define helper_v7m_bxns helper_v7m_bxns_mips +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_mips #define helper_v7m_mrs helper_v7m_mrs_mips #define helper_v7m_msr helper_v7m_msr_mips #define helper_v7m_tt helper_v7m_tt_mips diff --git a/qemu/mips64.h b/qemu/mips64.h index ab28888b..3536a7f8 100644 --- a/qemu/mips64.h +++ b/qemu/mips64.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_mips64 #define helper_v7m_blxns helper_v7m_blxns_mips64 #define helper_v7m_bxns helper_v7m_bxns_mips64 +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_mips64 #define helper_v7m_mrs helper_v7m_mrs_mips64 #define helper_v7m_msr helper_v7m_msr_mips64 #define helper_v7m_tt helper_v7m_tt_mips64 diff --git a/qemu/mips64el.h b/qemu/mips64el.h index 5c8a7563..293898ef 100644 --- a/qemu/mips64el.h +++ b/qemu/mips64el.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_mips64el #define helper_v7m_blxns helper_v7m_blxns_mips64el #define helper_v7m_bxns helper_v7m_bxns_mips64el +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_mips64el #define helper_v7m_mrs helper_v7m_mrs_mips64el #define helper_v7m_msr helper_v7m_msr_mips64el #define helper_v7m_tt helper_v7m_tt_mips64el diff --git a/qemu/mipsel.h b/qemu/mipsel.h index b3952ce3..85057964 100644 --- a/qemu/mipsel.h +++ b/qemu/mipsel.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_mipsel #define helper_v7m_blxns helper_v7m_blxns_mipsel #define helper_v7m_bxns helper_v7m_bxns_mipsel +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_mipsel #define helper_v7m_mrs helper_v7m_mrs_mipsel #define helper_v7m_msr helper_v7m_msr_mipsel #define helper_v7m_tt helper_v7m_tt_mipsel diff --git a/qemu/powerpc.h b/qemu/powerpc.h index cf3c43e8..612b9187 100644 --- a/qemu/powerpc.h +++ b/qemu/powerpc.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_powerpc #define helper_v7m_blxns helper_v7m_blxns_powerpc #define helper_v7m_bxns helper_v7m_bxns_powerpc +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_powerpc #define helper_v7m_mrs helper_v7m_mrs_powerpc #define helper_v7m_msr helper_v7m_msr_powerpc #define helper_v7m_tt helper_v7m_tt_powerpc diff --git a/qemu/riscv32.h b/qemu/riscv32.h index ad6f292c..90899bdf 100644 --- a/qemu/riscv32.h +++ b/qemu/riscv32.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_riscv32 #define helper_v7m_blxns helper_v7m_blxns_riscv32 #define helper_v7m_bxns helper_v7m_bxns_riscv32 +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_riscv32 #define helper_v7m_mrs helper_v7m_mrs_riscv32 #define helper_v7m_msr helper_v7m_msr_riscv32 #define helper_v7m_tt helper_v7m_tt_riscv32 diff --git a/qemu/riscv64.h b/qemu/riscv64.h index ec3082b0..f27c36e3 100644 --- a/qemu/riscv64.h +++ b/qemu/riscv64.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_riscv64 #define helper_v7m_blxns helper_v7m_blxns_riscv64 #define helper_v7m_bxns helper_v7m_bxns_riscv64 +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_riscv64 #define helper_v7m_mrs helper_v7m_mrs_riscv64 #define helper_v7m_msr helper_v7m_msr_riscv64 #define helper_v7m_tt helper_v7m_tt_riscv64 diff --git a/qemu/sparc.h b/qemu/sparc.h index d291ddf8..a8677d42 100644 --- a/qemu/sparc.h +++ b/qemu/sparc.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_sparc #define helper_v7m_blxns helper_v7m_blxns_sparc #define helper_v7m_bxns helper_v7m_bxns_sparc +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_sparc #define helper_v7m_mrs helper_v7m_mrs_sparc #define helper_v7m_msr helper_v7m_msr_sparc #define helper_v7m_tt helper_v7m_tt_sparc diff --git a/qemu/sparc64.h b/qemu/sparc64.h index 017c4491..abbcd555 100644 --- a/qemu/sparc64.h +++ b/qemu/sparc64.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_sparc64 #define helper_v7m_blxns helper_v7m_blxns_sparc64 #define helper_v7m_bxns helper_v7m_bxns_sparc64 +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_sparc64 #define helper_v7m_mrs helper_v7m_mrs_sparc64 #define helper_v7m_msr helper_v7m_msr_sparc64 #define helper_v7m_tt helper_v7m_tt_sparc64 diff --git a/qemu/target/arm/cpu.h b/qemu/target/arm/cpu.h index 9261982b..6efcd8e4 100644 --- a/qemu/target/arm/cpu.h +++ b/qemu/target/arm/cpu.h @@ -59,6 +59,7 @@ #define EXCP_NOCP 17 /* v7M NOCP UsageFault */ #define EXCP_INVSTATE 18 /* v7M INVSTATE UsageFault */ #define EXCP_STKOF 19 /* v8M STKOF UsageFault */ +#define EXCP_LAZYFP 20 /* v7M fault during lazy FP stacking */ /* NB: add new EXCP_ defines to the array in arm_log_exception() too */ #define ARMV7M_EXCP_RESET 1 @@ -3132,6 +3133,8 @@ FIELD(TBFLAG_A32, NS, 6, 1) FIELD(TBFLAG_A32, VFPEN, 7, 1) FIELD(TBFLAG_A32, CONDEXEC, 8, 8) FIELD(TBFLAG_A32, SCTLR_B, 16, 1) +/* For M profile only, set if FPCCR.LSPACT is set */ +FIELD(TBFLAG_A32, LSPACT, 18, 1) /* For M profile only, set if we must create a new FP context */ FIELD(TBFLAG_A32, NEW_FP_CTXT_NEEDED, 19, 1) /* For M profile only, set if FPCCR.S does not match current security state */ diff --git a/qemu/target/arm/helper.c b/qemu/target/arm/helper.c index 8ce2482c..6ca9133f 100644 --- a/qemu/target/arm/helper.c +++ b/qemu/target/arm/helper.c @@ -7180,6 +7180,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 @@ -7541,6 +7547,99 @@ 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 */ + // Unicorn: commented out + //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); + + // Unicorn: commented out + //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 @@ -8878,6 +8977,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)) { @@ -9180,6 +9280,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. */ @@ -13276,6 +13382,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; } diff --git a/qemu/target/arm/helper.h b/qemu/target/arm/helper.h index d4d10451..de0b935b 100644 --- a/qemu/target/arm/helper.h +++ b/qemu/target/arm/helper.h @@ -71,6 +71,8 @@ DEF_HELPER_2(v7m_blxns, void, env, i32) DEF_HELPER_3(v7m_tt, i32, env, i32, i32) +DEF_HELPER_1(v7m_preserve_fp_state, void, env) + DEF_HELPER_2(v8m_stackcheck, void, env, i32) DEF_HELPER_4(access_check_cp_reg, void, env, ptr, i32, i32) diff --git a/qemu/target/arm/translate.c b/qemu/target/arm/translate.c index 481f29fe..30287616 100644 --- a/qemu/target/arm/translate.c +++ b/qemu/target/arm/translate.c @@ -3531,6 +3531,29 @@ static int disas_vfp_insn(DisasContext *s, uint32_t insn) if (arm_dc_feature(s, ARM_FEATURE_M)) { /* Handle M-profile lazy FP state mechanics */ + /* Trigger lazy-state preservation if necessary */ + if (s->v7m_lspact) { + /* + * Lazy state saving affects external memory and also the NVIC, + * so we must mark it as an IO operation for icount. + */ + if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { + // Unicorn: commented out + //gen_io_start(); + } + gen_helper_v7m_preserve_fp_state(tcg_ctx, tcg_ctx->cpu_env); + if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { + // Unicorn: commented out + //gen_io_end(); + } + /* + * If the preserve_fp_state helper doesn't throw an exception + * then it will clear LSPACT; we don't need to repeat this for + * any further FP insns in this TB. + */ + s->v7m_lspact = false; + } + /* Update ownership of FP context: set FPCCR.S to match current state */ if (s->v8m_fpccr_s_wrong) { TCGv_i32 tmp; @@ -13567,6 +13590,9 @@ static void arm_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs) regime_is_secure(env, dc->mmu_idx); dc->v8m_stackcheck = FIELD_EX32(tb_flags, TBFLAG_A32, STACKCHECK); dc->v8m_fpccr_s_wrong = FIELD_EX32(tb_flags, TBFLAG_A32, FPCCR_S_WRONG); + dc->v7m_new_fp_ctxt_needed = + FIELD_EX32(tb_flags, TBFLAG_A32, NEW_FP_CTXT_NEEDED); + dc->v7m_lspact = FIELD_EX32(tb_flags, TBFLAG_A32, LSPACT); dc->cp_regs = cpu->cp_regs; dc->features = env->features; diff --git a/qemu/target/arm/translate.h b/qemu/target/arm/translate.h index f9529766..f762e186 100644 --- a/qemu/target/arm/translate.h +++ b/qemu/target/arm/translate.h @@ -41,6 +41,7 @@ typedef struct DisasContext { bool v8m_stackcheck; /* true if we need to perform v8M stack limit checks */ bool v8m_fpccr_s_wrong; /* true if v8M FPCCR.S != v8m_secure */ bool v7m_new_fp_ctxt_needed; /* ASPEN set but no active FP context */ + bool v7m_lspact; /* FPCCR.LSPACT set */ /* Immediate value in AArch32 SVC insn; must be set if is_jmp == DISAS_SWI * so that top level loop can generate correct syndrome information. */ diff --git a/qemu/x86_64.h b/qemu/x86_64.h index ff2b7826..123aea59 100644 --- a/qemu/x86_64.h +++ b/qemu/x86_64.h @@ -1745,6 +1745,7 @@ #define helper_uxtb16 helper_uxtb16_x86_64 #define helper_v7m_blxns helper_v7m_blxns_x86_64 #define helper_v7m_bxns helper_v7m_bxns_x86_64 +#define helper_v7m_preserve_fp_state helper_v7m_preserve_fp_state_x86_64 #define helper_v7m_mrs helper_v7m_mrs_x86_64 #define helper_v7m_msr helper_v7m_msr_x86_64 #define helper_v7m_tt helper_v7m_tt_x86_64