Add MECID checks. The MECID registers were introduced before and this commit uses them to perform the proper MECID checks, which is part of FEAT_MEC implementation.
On MECID mismatches (wrong MECID) we return a substitute encrypted page instead of the real encrypted page, which doesn't hurt performance on memory accesses and behaves like FEAT_MEC design on the physical hardware. Signed-off-by: Gustavo Romero <[email protected]> --- target/arm/internals.h | 2 + target/arm/ptw.c | 215 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 216 insertions(+), 1 deletion(-) diff --git a/target/arm/internals.h b/target/arm/internals.h index 8ec2750847..d668948949 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -1473,6 +1473,8 @@ typedef struct GetPhysAddrResult { * with the stage1 protection. */ int s2prot; + /* FEAT_MEC AMEC found in Block or Page descriptor. */ + bool amec; } GetPhysAddrResult; /** diff --git a/target/arm/ptw.c b/target/arm/ptw.c index 8b8dc09e72..aacc32ba33 100644 --- a/target/arm/ptw.c +++ b/target/arm/ptw.c @@ -2090,6 +2090,17 @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw, goto do_fault; } + /* + * XXX(gromero): Add checks for NS bit and AMEC0/1 bits, generating the due + * fault in case AMEC0/1 = 0 and AMEC bit is 1. So far we just get the AMEC + * bit directly in case of a Block or Page descriptor. + * + */ + if (((level == 1 || level == 2) && (extract64(descriptor, 0, 2) == 1)) || + level == 3) { + result->amec = extract64(descriptor, 63, 1); + } + if ((descriptor & 2) && (level < 3)) { /* * Table entry. The top five bits are attributes which may @@ -3766,6 +3777,208 @@ static bool get_phys_addr_nogpc(CPUARMState *env, S1Translate *ptw, } } +static uint32_t *get_mecid_ptr(CPUARMState *env, hwaddr pa) +{ + MemoryRegion *mr; + AddressSpace *mec_as; + hwaddr mec_paddr, xlat; + MemTxAttrs memattrs = { 0x0 }; + + /* Find out the page number to use it as an offset in mec AS. */ + mec_paddr = pa >> TARGET_PAGE_BITS; + mec_paddr *= 4; /* Assume MECID maximum size is 4 bytes. */ + /* MECIDs are kept in their own Address Space. */ + mec_as = cpu_get_address_space(env_cpu(env), ARMASIdx_MEC); + mr = address_space_translate(mec_as, mec_paddr, &xlat, NULL, true, memattrs); + + /* + * Return pointer in the mec AS associated to physical address 'pa', which + * is used to store the MECID. + */ + return memory_region_get_ram_ptr(mr) + xlat; +} + +static uint32_t get_mecid(CPUARMState *env, hwaddr pa) +{ + return *get_mecid_ptr(env, pa); +} + +static void set_mecid(CPUARMState *env, hwaddr pa, uint32_t mecid) +{ + *get_mecid_ptr(env, pa) = mecid; +} + +/* + * Returns 'false' on MECID mismatch and 'true' on MECID match (success). + */ +static bool mecid_check(CPUARMState *env, S1Translate *ptw, hwaddr va, + MMUAccessType access_type, GetPhysAddrResult *result, + ARMMMUIdx s1_mmu_idx) +{ + ARMSecuritySpace ss = ptw->out_space; + /* Final physical address after translation. */ + hwaddr pa = result->f.phys_addr; + + switch (s1_mmu_idx) { + case ARMMMUIdx_Phys_S: + case ARMMMUIdx_Phys_NS: + case ARMMMUIdx_Phys_Root: + case ARMMMUIdx_Phys_Realm: + case ARMMMUIdx_Stage2: + case ARMMMUIdx_Stage2_S: + /* In the middle of a translation, so result->f.phys_addr has no PA yet. */ + return true; + break; + default: + /* Pass */ + break; + } + + /* Find out which EL controls EMEC for Stage 1 translations. */ + uint32_t el = regime_el(s1_mmu_idx) < 3 ? 2 : 3; + + /* XXX(gromero): Do we need to check for SCR_EL3.SCTLR2En when el == 2? */ + if (!(cpu_isar_feature(aa64_mec, env_archcpu(env)) && + (env->cp15.sctlr2_el[el] & SCTLR2_EMEC))) { + /* FEAT_MEC is disabled. */ + return true; + } + + if (ss != ARMSS_Realm) { + /* + * GPT checks are already done, so if SS is Root here MECIDs are + * irrelevants for EL3 accesses. If SS is Secure or NonSecure that's not + * pertinent to FEAT_MEC. Hence, only proceed with MECID checks if SS is + * Realm. + */ + return true; + } + + bool amec = result->amec; + bool varange_lower = extract64(va, 55, 1) ? false : true; + /* MECID in register set given a translation regime. */ + uint32_t mecid; + + /* ARMMMUIdx after full ptw (all stages). */ + ARMMMUIdx ptw_mmu_idx = ptw->in_mmu_idx; + + switch (ptw_mmu_idx) { + case ARMMMUIdx_Stage2: + case ARMMMUIdx_Stage2_S: + /* In the middle of a two stage translation, so result->f.phys_addr has no PA yet. */ + return true; + break; + default: + /* Pass */ + break; + } + + /* + * NB: No MECID is used for the ptw itself, i.e., used for the level + * lookups, so AArch64.S1TTWalkMECID() and AArch64.S2TTWalkMECID() are not + * relevant for our FEAT_MEC implementation. + */ + bool is_pa_from_s2 = regime_is_stage2(ptw_mmu_idx); + bool is_mmu_disabled = regime_translation_disabled(env, ptw_mmu_idx, ss); + if (is_pa_from_s2) { /* PA from Stage 2. */ + /* As per AArch64.S2OutputMECID(). */ + mecid = amec ? env->cp15.vmecid_a_el2 : env->cp15.vmecid_p_el2; + + } else { /* PA from Stage 1. */ + if (is_mmu_disabled) { /* PA from Stage 1 and MMU is disabled. */ + /* As per AArch64.S1DisabledOutputMECID(). */ + switch (s1_mmu_idx) { + case ARMMMUIdx_E3: + /* MECID = 0 (default), so return without any check. */ + return true; + break; + case ARMMMUIdx_E2: + case ARMMMUIdx_E20_0: + case ARMMMUIdx_E20_2: + case ARMMMUIdx_E20_2_PAN: + mecid = env->cp15.mecid_p0_el2; + break; + case ARMMMUIdx_E10_0: + case ARMMMUIdx_E10_1: + mecid = env->cp15.vmecid_p_el2; + break; + default: + g_assert_not_reached(); + } + + } else { /* PA from Stage 1 and MMU is enabled. */ + /* As per AArch64.S1OutputMECID(). */ + switch (s1_mmu_idx) { + case ARMMMUIdx_E3: + mecid = env->cp15.mecid_rl_a_el3; + break; + case ARMMMUIdx_E2: + mecid = amec ? env->cp15.mecid_a0_el2 : env->cp15.mecid_p0_el2; + break; + case ARMMMUIdx_E20_0: + case ARMMMUIdx_E20_2: + case ARMMMUIdx_E20_2_PAN: + if (varange_lower) { + mecid = amec ? env->cp15.mecid_a0_el2 : env->cp15.mecid_p0_el2; + } else { + mecid = amec ? env->cp15.mecid_a1_el2 : env->cp15.mecid_p1_el2; + } + break; + case ARMMMUIdx_E10_0: + case ARMMMUIdx_E10_1: + mecid = env->cp15.vmecid_p_el2; + break; + default: + g_assert_not_reached(); + } + } + } + + /* + * MECID is updated on data store and checked on other access types. + */ + if (access_type == MMU_DATA_STORE) { + /* Store MECID for physical address 'pa'. */ + set_mecid(env, pa, mecid); + return true; + } else { + uint32_t stored_mecid; + /* Load the MECID stored in memory for physical address 'pa'. */ + stored_mecid = get_mecid(env, pa); + if (stored_mecid == mecid) { + /* MECID is correct. */ + return true; + } else { + /* MECID is incorrect, so return the substitute encrypted page. */ + result->f.phys_addr = 0x0; /* Start of the page. */ + result->f.attrs.encrypted = true; /* Substitute encrypted page. */ + return false; + } + } +} + +static bool get_phys_addr_mec(CPUARMState *env, S1Translate *ptw, + vaddr address, + MMUAccessType access_type, MemOp memop, + GetPhysAddrResult *result, + ARMMMUFaultInfo *fi) + +{ + /* + * After 'address' is resolved by get_phys_addr_nogpc(), ptw->in_mmu_idx can + * change depending on the translation stages, hence stash it. + */ + ARMMMUIdx s1_mmu_idx = ptw->in_mmu_idx; + + if (get_phys_addr_gpc(env, ptw, address, access_type, memop, result, fi)) { + return true; /* Translation fault. */ + } + if (!mecid_check(env, ptw, address, access_type, result, s1_mmu_idx)) { + return true; /* MECID mismatch. */ + } + return false; +} + static bool get_phys_addr_gpc(CPUARMState *env, S1Translate *ptw, vaddr address, MMUAccessType access_type, MemOp memop, @@ -3907,7 +4120,7 @@ bool get_phys_addr(CPUARMState *env, vaddr address, .in_prot_check = 1 << access_type, }; - return get_phys_addr_gpc(env, &ptw, address, access_type, + return get_phys_addr_mec(env, &ptw, address, access_type, memop, result, fi); } -- 2.34.1
