target/arm: Trap sysreg accesses for FEAT_NV
For FEAT_NV, accesses to system registers and instructions from EL1 which would normally UNDEF there but which work in EL2 need to instead be trapped to EL2. Detect this both for "we know this will UNDEF at translate time" and "we found this UNDEFs at runtime", and make the affected registers trap to EL2 instead. The Arm ARM defines the set of registers that should trap in terms of their names; for our implementation this would be both awkward and inefficent as a test, so we instead trap based on the opc1 field of the sysreg. The regularity of the architectural choice of encodings for sysregs means that in practice this captures exactly the correct set of registers. Regardless of how we try to define the registers this trapping applies to, there's going to be a certain possibility of breakage if new architectural features introduce new registers that don't follow the current rules (FEAT_MEC is one example already visible in the released sysreg XML, though not yet in the Arm ARM). This approach seems to me to be straightforward and likely to require a minimum of manual overrides. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Tested-by: Miguel Luis <miguel.luis@oracle.com>
This commit is contained in:
parent
44572fc984
commit
67d10fc473
@ -1080,4 +1080,38 @@ void define_cortex_a72_a57_a53_cp_reginfo(ARMCPU *cpu);
|
||||
|
||||
CPAccessResult access_tvm_trvm(CPUARMState *, const ARMCPRegInfo *, bool);
|
||||
|
||||
/**
|
||||
* arm_cpreg_trap_in_nv: Return true if cpreg traps in nested virtualization
|
||||
*
|
||||
* Return true if this cpreg is one which should be trapped to EL2 if
|
||||
* it is executed at EL1 when nested virtualization is enabled via HCR_EL2.NV.
|
||||
*/
|
||||
static inline bool arm_cpreg_traps_in_nv(const ARMCPRegInfo *ri)
|
||||
{
|
||||
/*
|
||||
* The Arm ARM defines the registers to be trapped in terms of
|
||||
* their names (I_TZTZL). However the underlying principle is "if
|
||||
* it would UNDEF at EL1 but work at EL2 then it should trap", and
|
||||
* the way the encoding of sysregs and system instructions is done
|
||||
* means that the right set of registers is exactly those where
|
||||
* the opc1 field is 4 or 5. (You can see this also in the assert
|
||||
* we do that the opc1 field and the permissions mask line up in
|
||||
* define_one_arm_cp_reg_with_opaque().)
|
||||
* Checking the opc1 field is easier for us and avoids the problem
|
||||
* that we do not consistently use the right architectural names
|
||||
* for all sysregs, since we treat the name field as largely for debug.
|
||||
*
|
||||
* However we do this check, it is going to be at least potentially
|
||||
* fragile to future new sysregs, but this seems the least likely
|
||||
* to break.
|
||||
*
|
||||
* In particular, note that the released sysreg XML defines that
|
||||
* the FEAT_MEC sysregs and instructions do not follow this FEAT_NV
|
||||
* trapping rule, so we will need to add an ARM_CP_* flag to indicate
|
||||
* "register does not trap on NV" to handle those if/when we implement
|
||||
* FEAT_MEC.
|
||||
*/
|
||||
return ri->opc1 == 4 || ri->opc1 == 5;
|
||||
}
|
||||
|
||||
#endif /* TARGET_ARM_CPREGS_H */
|
||||
|
@ -3235,6 +3235,7 @@ FIELD(TBFLAG_A64, SME_TRAP_NONSTREAMING, 28, 1)
|
||||
FIELD(TBFLAG_A64, TRAP_ERET, 29, 1)
|
||||
FIELD(TBFLAG_A64, NAA, 30, 1)
|
||||
FIELD(TBFLAG_A64, ATA0, 31, 1)
|
||||
FIELD(TBFLAG_A64, NV, 32, 1)
|
||||
|
||||
/*
|
||||
* Helpers for using the above. Note that only the A64 accessors use
|
||||
|
@ -299,6 +299,7 @@ static CPUARMTBFlags rebuild_hflags_a64(CPUARMState *env, int el, int fp_el,
|
||||
*/
|
||||
if (el == 1 && (hcr & HCR_NV)) {
|
||||
DP_TBFLAG_A64(flags, TRAP_ERET, 1);
|
||||
DP_TBFLAG_A64(flags, NV, 1);
|
||||
}
|
||||
|
||||
if (cpu_isar_feature(aa64_mte, env_archcpu(env))) {
|
||||
|
@ -2132,16 +2132,17 @@ static void handle_sys(DisasContext *s, bool isread,
|
||||
crn, crm, op0, op1, op2);
|
||||
const ARMCPRegInfo *ri = get_arm_cp_reginfo(s->cp_regs, key);
|
||||
bool need_exit_tb = false;
|
||||
bool nv_trap_to_el2 = false;
|
||||
bool skip_fp_access_checks = false;
|
||||
TCGv_ptr tcg_ri = NULL;
|
||||
TCGv_i64 tcg_rt;
|
||||
uint32_t syndrome;
|
||||
uint32_t syndrome = syn_aa64_sysregtrap(op0, op1, op2, crn, crm, rt, isread);
|
||||
|
||||
if (crn == 11 || crn == 15) {
|
||||
/*
|
||||
* Check for TIDCP trap, which must take precedence over
|
||||
* the UNDEF for "no such register" etc.
|
||||
*/
|
||||
syndrome = syn_aa64_sysregtrap(op0, op1, op2, crn, crm, rt, isread);
|
||||
switch (s->current_el) {
|
||||
case 0:
|
||||
if (dc_isar_feature(aa64_tidcp1, s)) {
|
||||
@ -2167,15 +2168,35 @@ static void handle_sys(DisasContext *s, bool isread,
|
||||
|
||||
/* Check access permissions */
|
||||
if (!cp_access_ok(s->current_el, ri, isread)) {
|
||||
gen_sysreg_undef(s, isread, op0, op1, op2, crn, crm, rt);
|
||||
return;
|
||||
/*
|
||||
* FEAT_NV/NV2 handling does not do the usual FP access checks
|
||||
* for registers only accessible at EL2 (though it *does* do them
|
||||
* for registers accessible at EL1).
|
||||
*/
|
||||
skip_fp_access_checks = true;
|
||||
if (s->nv && arm_cpreg_traps_in_nv(ri)) {
|
||||
/*
|
||||
* This register / instruction exists and is an EL2 register, so
|
||||
* we must trap to EL2 if accessed in nested virtualization EL1
|
||||
* instead of UNDEFing. We'll do that after the usual access checks.
|
||||
* (This makes a difference only for a couple of registers like
|
||||
* VSTTBR_EL2 where the "UNDEF if NonSecure" should take priority
|
||||
* over the trap-to-EL2. Most trapped-by-FEAT_NV registers have
|
||||
* an accessfn which does nothing when called from EL1, because
|
||||
* the trap-to-EL3 controls which would apply to that register
|
||||
* at EL2 don't take priority over the FEAT_NV trap-to-EL2.)
|
||||
*/
|
||||
nv_trap_to_el2 = true;
|
||||
} else {
|
||||
gen_sysreg_undef(s, isread, op0, op1, op2, crn, crm, rt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ri->accessfn || (ri->fgt && s->fgt_active)) {
|
||||
/* Emit code to perform further access permissions checks at
|
||||
* runtime; this may result in an exception.
|
||||
*/
|
||||
syndrome = syn_aa64_sysregtrap(op0, op1, op2, crn, crm, rt, isread);
|
||||
gen_a64_update_pc(s, 0);
|
||||
tcg_ri = tcg_temp_new_ptr();
|
||||
gen_helper_access_check_cp_reg(tcg_ri, tcg_env,
|
||||
@ -2190,11 +2211,18 @@ static void handle_sys(DisasContext *s, bool isread,
|
||||
gen_a64_update_pc(s, 0);
|
||||
}
|
||||
|
||||
if ((ri->type & ARM_CP_FPU) && !fp_access_check_only(s)) {
|
||||
return;
|
||||
} else if ((ri->type & ARM_CP_SVE) && !sve_access_check(s)) {
|
||||
return;
|
||||
} else if ((ri->type & ARM_CP_SME) && !sme_access_check(s)) {
|
||||
if (!skip_fp_access_checks) {
|
||||
if ((ri->type & ARM_CP_FPU) && !fp_access_check_only(s)) {
|
||||
return;
|
||||
} else if ((ri->type & ARM_CP_SVE) && !sve_access_check(s)) {
|
||||
return;
|
||||
} else if ((ri->type & ARM_CP_SME) && !sme_access_check(s)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (nv_trap_to_el2) {
|
||||
gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -13998,6 +14026,7 @@ static void aarch64_tr_init_disas_context(DisasContextBase *dcbase,
|
||||
dc->pstate_za = EX_TBFLAG_A64(tb_flags, PSTATE_ZA);
|
||||
dc->sme_trap_nonstreaming = EX_TBFLAG_A64(tb_flags, SME_TRAP_NONSTREAMING);
|
||||
dc->naa = EX_TBFLAG_A64(tb_flags, NAA);
|
||||
dc->nv = EX_TBFLAG_A64(tb_flags, NV);
|
||||
dc->vec_len = 0;
|
||||
dc->vec_stride = 0;
|
||||
dc->cp_regs = arm_cpu->cp_regs;
|
||||
|
@ -144,6 +144,8 @@ typedef struct DisasContext {
|
||||
bool trap_eret;
|
||||
/* True if FEAT_LSE2 SCTLR_ELx.nAA is set */
|
||||
bool naa;
|
||||
/* True if FEAT_NV HCR_EL2.NV is enabled */
|
||||
bool nv;
|
||||
/*
|
||||
* >= 0, a copy of PSTATE.BTYPE, which will be 0 without v8.5-BTI.
|
||||
* < 0, set by the current instruction.
|
||||
|
Loading…
Reference in New Issue
Block a user