target/arm: Implement the granule protection check

Place the check at the end of get_phys_addr_with_struct,
so that we check all physical results.

Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20230620124418.805717-20-richard.henderson@linaro.org
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Richard Henderson 2023-06-23 11:15:48 +01:00 committed by Peter Maydell
parent 11b76fda0a
commit 46f38c975f
1 changed files with 232 additions and 17 deletions

View File

@ -39,11 +39,17 @@ typedef struct S1Translate {
void *out_host;
} S1Translate;
static bool get_phys_addr_with_struct(CPUARMState *env, S1Translate *ptw,
target_ulong address,
MMUAccessType access_type,
GetPhysAddrResult *result,
ARMMMUFaultInfo *fi);
static bool get_phys_addr_nogpc(CPUARMState *env, S1Translate *ptw,
target_ulong address,
MMUAccessType access_type,
GetPhysAddrResult *result,
ARMMMUFaultInfo *fi);
static bool get_phys_addr_gpc(CPUARMState *env, S1Translate *ptw,
target_ulong address,
MMUAccessType access_type,
GetPhysAddrResult *result,
ARMMMUFaultInfo *fi);
/* This mapping is common between ID_AA64MMFR0.PARANGE and TCR_ELx.{I}PS. */
static const uint8_t pamax_map[] = {
@ -230,6 +236,197 @@ static bool regime_translation_disabled(CPUARMState *env, ARMMMUIdx mmu_idx,
return (regime_sctlr(env, mmu_idx) & SCTLR_M) == 0;
}
static bool granule_protection_check(CPUARMState *env, uint64_t paddress,
ARMSecuritySpace pspace,
ARMMMUFaultInfo *fi)
{
MemTxAttrs attrs = {
.secure = true,
.space = ARMSS_Root,
};
ARMCPU *cpu = env_archcpu(env);
uint64_t gpccr = env->cp15.gpccr_el3;
unsigned pps, pgs, l0gptsz, level = 0;
uint64_t tableaddr, pps_mask, align, entry, index;
AddressSpace *as;
MemTxResult result;
int gpi;
if (!FIELD_EX64(gpccr, GPCCR, GPC)) {
return true;
}
/*
* GPC Priority 1 (R_GMGRR):
* R_JWCSM: If the configuration of GPCCR_EL3 is invalid,
* the access fails as GPT walk fault at level 0.
*/
/*
* Configuration of PPS to a value exceeding the implemented
* physical address size is invalid.
*/
pps = FIELD_EX64(gpccr, GPCCR, PPS);
if (pps > FIELD_EX64(cpu->isar.id_aa64mmfr0, ID_AA64MMFR0, PARANGE)) {
goto fault_walk;
}
pps = pamax_map[pps];
pps_mask = MAKE_64BIT_MASK(0, pps);
switch (FIELD_EX64(gpccr, GPCCR, SH)) {
case 0b10: /* outer shareable */
break;
case 0b00: /* non-shareable */
case 0b11: /* inner shareable */
/* Inner and Outer non-cacheable requires Outer shareable. */
if (FIELD_EX64(gpccr, GPCCR, ORGN) == 0 &&
FIELD_EX64(gpccr, GPCCR, IRGN) == 0) {
goto fault_walk;
}
break;
default: /* reserved */
goto fault_walk;
}
switch (FIELD_EX64(gpccr, GPCCR, PGS)) {
case 0b00: /* 4KB */
pgs = 12;
break;
case 0b01: /* 64KB */
pgs = 16;
break;
case 0b10: /* 16KB */
pgs = 14;
break;
default: /* reserved */
goto fault_walk;
}
/* Note this field is read-only and fixed at reset. */
l0gptsz = 30 + FIELD_EX64(gpccr, GPCCR, L0GPTSZ);
/*
* GPC Priority 2: Secure, Realm or Root address exceeds PPS.
* R_CPDSB: A NonSecure physical address input exceeding PPS
* does not experience any fault.
*/
if (paddress & ~pps_mask) {
if (pspace == ARMSS_NonSecure) {
return true;
}
goto fault_size;
}
/* GPC Priority 3: the base address of GPTBR_EL3 exceeds PPS. */
tableaddr = env->cp15.gptbr_el3 << 12;
if (tableaddr & ~pps_mask) {
goto fault_size;
}
/*
* BADDR is aligned per a function of PPS and L0GPTSZ.
* These bits of GPTBR_EL3 are RES0, but are not a configuration error,
* unlike the RES0 bits of the GPT entries (R_XNKFZ).
*/
align = MAX(pps - l0gptsz + 3, 12);
align = MAKE_64BIT_MASK(0, align);
tableaddr &= ~align;
as = arm_addressspace(env_cpu(env), attrs);
/* Level 0 lookup. */
index = extract64(paddress, l0gptsz, pps - l0gptsz);
tableaddr += index * 8;
entry = address_space_ldq_le(as, tableaddr, attrs, &result);
if (result != MEMTX_OK) {
goto fault_eabt;
}
switch (extract32(entry, 0, 4)) {
case 1: /* block descriptor */
if (entry >> 8) {
goto fault_walk; /* RES0 bits not 0 */
}
gpi = extract32(entry, 4, 4);
goto found;
case 3: /* table descriptor */
tableaddr = entry & ~0xf;
align = MAX(l0gptsz - pgs - 1, 12);
align = MAKE_64BIT_MASK(0, align);
if (tableaddr & (~pps_mask | align)) {
goto fault_walk; /* RES0 bits not 0 */
}
break;
default: /* invalid */
goto fault_walk;
}
/* Level 1 lookup */
level = 1;
index = extract64(paddress, pgs + 4, l0gptsz - pgs - 4);
tableaddr += index * 8;
entry = address_space_ldq_le(as, tableaddr, attrs, &result);
if (result != MEMTX_OK) {
goto fault_eabt;
}
switch (extract32(entry, 0, 4)) {
case 1: /* contiguous descriptor */
if (entry >> 10) {
goto fault_walk; /* RES0 bits not 0 */
}
/*
* Because the softmmu tlb only works on units of TARGET_PAGE_SIZE,
* and because we cannot invalidate by pa, and thus will always
* flush entire tlbs, we don't actually care about the range here
* and can simply extract the GPI as the result.
*/
if (extract32(entry, 8, 2) == 0) {
goto fault_walk; /* reserved contig */
}
gpi = extract32(entry, 4, 4);
break;
default:
index = extract64(paddress, pgs, 4);
gpi = extract64(entry, index * 4, 4);
break;
}
found:
switch (gpi) {
case 0b0000: /* no access */
break;
case 0b1111: /* all access */
return true;
case 0b1000:
case 0b1001:
case 0b1010:
case 0b1011:
if (pspace == (gpi & 3)) {
return true;
}
break;
default:
goto fault_walk; /* reserved */
}
fi->gpcf = GPCF_Fail;
goto fault_common;
fault_eabt:
fi->gpcf = GPCF_EABT;
goto fault_common;
fault_size:
fi->gpcf = GPCF_AddressSize;
goto fault_common;
fault_walk:
fi->gpcf = GPCF_Walk;
fault_common:
fi->level = level;
fi->paddr = paddress;
fi->paddr_space = pspace;
return false;
}
static bool S2_attrs_are_device(uint64_t hcr, uint8_t attrs)
{
/*
@ -276,10 +473,10 @@ static bool S1_ptw_translate(CPUARMState *env, S1Translate *ptw,
};
GetPhysAddrResult s2 = { };
if (get_phys_addr_with_struct(env, &s2ptw, addr,
MMU_DATA_LOAD, &s2, fi)) {
if (get_phys_addr_gpc(env, &s2ptw, addr, MMU_DATA_LOAD, &s2, fi)) {
goto fail;
}
ptw->out_phys = s2.f.phys_addr;
pte_attrs = s2.cacheattrs.attrs;
ptw->out_host = NULL;
@ -332,6 +529,9 @@ static bool S1_ptw_translate(CPUARMState *env, S1Translate *ptw,
fail:
assert(fi->type != ARMFault_None);
if (fi->type == ARMFault_GPCFOnOutput) {
fi->type = ARMFault_GPCFOnWalk;
}
fi->s2addr = addr;
fi->stage2 = true;
fi->s1ptw = true;
@ -2769,7 +2969,7 @@ static bool get_phys_addr_disabled(CPUARMState *env, target_ulong address,
ARMMMUFaultInfo *fi)
{
uint8_t memattr = 0x00; /* Device nGnRnE */
uint8_t shareability = 0; /* non-sharable */
uint8_t shareability = 0; /* non-shareable */
int r_el;
switch (mmu_idx) {
@ -2828,7 +3028,7 @@ static bool get_phys_addr_disabled(CPUARMState *env, target_ulong address,
} else {
memattr = 0x44; /* Normal, NC, No */
}
shareability = 2; /* outer sharable */
shareability = 2; /* outer shareable */
}
result->cacheattrs.is_s2_format = false;
break;
@ -2856,7 +3056,7 @@ static bool get_phys_addr_twostage(CPUARMState *env, S1Translate *ptw,
ARMSecuritySpace ipa_space;
uint64_t hcr;
ret = get_phys_addr_with_struct(env, ptw, address, access_type, result, fi);
ret = get_phys_addr_nogpc(env, ptw, address, access_type, result, fi);
/* If S1 fails, return early. */
if (ret) {
@ -2882,7 +3082,7 @@ static bool get_phys_addr_twostage(CPUARMState *env, S1Translate *ptw,
cacheattrs1 = result->cacheattrs;
memset(result, 0, sizeof(*result));
ret = get_phys_addr_with_struct(env, ptw, ipa, access_type, result, fi);
ret = get_phys_addr_nogpc(env, ptw, ipa, access_type, result, fi);
fi->s2addr = ipa;
/* Combine the S1 and S2 perms. */
@ -2942,7 +3142,7 @@ static bool get_phys_addr_twostage(CPUARMState *env, S1Translate *ptw,
return false;
}
static bool get_phys_addr_with_struct(CPUARMState *env, S1Translate *ptw,
static bool get_phys_addr_nogpc(CPUARMState *env, S1Translate *ptw,
target_ulong address,
MMUAccessType access_type,
GetPhysAddrResult *result,
@ -3076,6 +3276,23 @@ static bool get_phys_addr_with_struct(CPUARMState *env, S1Translate *ptw,
}
}
static bool get_phys_addr_gpc(CPUARMState *env, S1Translate *ptw,
target_ulong address,
MMUAccessType access_type,
GetPhysAddrResult *result,
ARMMMUFaultInfo *fi)
{
if (get_phys_addr_nogpc(env, ptw, address, access_type, result, fi)) {
return true;
}
if (!granule_protection_check(env, result->f.phys_addr,
result->f.attrs.space, fi)) {
fi->type = ARMFault_GPCFOnOutput;
return true;
}
return false;
}
bool get_phys_addr_with_secure(CPUARMState *env, target_ulong address,
MMUAccessType access_type, ARMMMUIdx mmu_idx,
bool is_secure, GetPhysAddrResult *result,
@ -3086,8 +3303,7 @@ bool get_phys_addr_with_secure(CPUARMState *env, target_ulong address,
.in_secure = is_secure,
.in_space = arm_secure_to_space(is_secure),
};
return get_phys_addr_with_struct(env, &ptw, address, access_type,
result, fi);
return get_phys_addr_gpc(env, &ptw, address, access_type, result, fi);
}
bool get_phys_addr(CPUARMState *env, target_ulong address,
@ -3157,8 +3373,7 @@ bool get_phys_addr(CPUARMState *env, target_ulong address,
ptw.in_space = ss;
ptw.in_secure = arm_space_is_secure(ss);
return get_phys_addr_with_struct(env, &ptw, address, access_type,
result, fi);
return get_phys_addr_gpc(env, &ptw, address, access_type, result, fi);
}
hwaddr arm_cpu_get_phys_page_attrs_debug(CPUState *cs, vaddr addr,
@ -3178,7 +3393,7 @@ hwaddr arm_cpu_get_phys_page_attrs_debug(CPUState *cs, vaddr addr,
ARMMMUFaultInfo fi = {};
bool ret;
ret = get_phys_addr_with_struct(env, &ptw, addr, MMU_DATA_LOAD, &res, &fi);
ret = get_phys_addr_gpc(env, &ptw, addr, MMU_DATA_LOAD, &res, &fi);
*attrs = res.f.attrs;
if (ret) {