target/arm: Take exceptions on ATS instructions when needed

The translation table walk for an ATS instruction can result in
various faults.  In general these are just reported back via the
PAR_EL1 fault status fields, but in some cases the architecture
requires that the fault is turned into an exception:
 * synchronous stage 2 faults of any kind during AT S1E0* and
   AT S1E1* instructions executed from NS EL1 fault to EL2 or EL3
 * synchronous external aborts are taken as Data Abort exceptions

(This is documented in the v8A Arm ARM DDI0487A.e D5.2.11 and
G5.13.4.)

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Tested-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Message-id: 20190816125802.25877-3-peter.maydell@linaro.org
This commit is contained in:
Peter Maydell 2019-08-16 13:58:02 +01:00
parent 37ff584c15
commit 0710b2fa84
1 changed files with 92 additions and 15 deletions

View File

@ -2946,6 +2946,73 @@ static uint64_t do_ats_write(CPUARMState *env, uint64_t value,
ret = get_phys_addr(env, value, access_type, mmu_idx, &phys_addr, &attrs,
&prot, &page_size, &fi, &cacheattrs);
if (ret) {
/*
* Some kinds of translation fault must cause exceptions rather
* than being reported in the PAR.
*/
int current_el = arm_current_el(env);
int target_el;
uint32_t syn, fsr, fsc;
bool take_exc = false;
if (fi.s1ptw && current_el == 1 && !arm_is_secure(env)
&& (mmu_idx == ARMMMUIdx_S1NSE1 || mmu_idx == ARMMMUIdx_S1NSE0)) {
/*
* Synchronous stage 2 fault on an access made as part of the
* translation table walk for AT S1E0* or AT S1E1* insn
* executed from NS EL1. If this is a synchronous external abort
* and SCR_EL3.EA == 1, then we take a synchronous external abort
* to EL3. Otherwise the fault is taken as an exception to EL2,
* and HPFAR_EL2 holds the faulting IPA.
*/
if (fi.type == ARMFault_SyncExternalOnWalk &&
(env->cp15.scr_el3 & SCR_EA)) {
target_el = 3;
} else {
env->cp15.hpfar_el2 = extract64(fi.s2addr, 12, 47) << 4;
target_el = 2;
}
take_exc = true;
} else if (fi.type == ARMFault_SyncExternalOnWalk) {
/*
* Synchronous external aborts during a translation table walk
* are taken as Data Abort exceptions.
*/
if (fi.stage2) {
if (current_el == 3) {
target_el = 3;
} else {
target_el = 2;
}
} else {
target_el = exception_target_el(env);
}
take_exc = true;
}
if (take_exc) {
/* Construct FSR and FSC using same logic as arm_deliver_fault() */
if (target_el == 2 || arm_el_is_aa64(env, target_el) ||
arm_s1_regime_using_lpae_format(env, mmu_idx)) {
fsr = arm_fi_to_lfsc(&fi);
fsc = extract32(fsr, 0, 6);
} else {
fsr = arm_fi_to_sfsc(&fi);
fsc = 0x3f;
}
/*
* Report exception with ESR indicating a fault due to a
* translation table walk for a cache maintenance instruction.
*/
syn = syn_data_abort_no_iss(current_el == target_el,
fi.ea, 1, fi.s1ptw, 1, fsc);
env->exception.vaddress = value;
env->exception.fsr = fsr;
raise_exception(env, EXCP_DATA_ABORT, syn, target_el);
}
}
if (is_a64(env)) {
format64 = true;
} else if (arm_feature(env, ARM_FEATURE_LPAE)) {
@ -3150,7 +3217,7 @@ static const ARMCPRegInfo vapa_cp_reginfo[] = {
/* This underdecoding is safe because the reginfo is NO_RAW. */
{ .name = "ATS", .cp = 15, .crn = 7, .crm = 8, .opc1 = 0, .opc2 = CP_ANY,
.access = PL1_W, .accessfn = ats_access,
.writefn = ats_write, .type = ARM_CP_NO_RAW },
.writefn = ats_write, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC },
#endif
REGINFO_SENTINEL
};
@ -4283,35 +4350,45 @@ static const ARMCPRegInfo v8_cp_reginfo[] = {
/* 64 bit address translation operations */
{ .name = "AT_S1E1R", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 0,
.access = PL1_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "AT_S1E1W", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 1,
.access = PL1_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "AT_S1E0R", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 2,
.access = PL1_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "AT_S1E0W", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 3,
.access = PL1_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "AT_S12E1R", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 4,
.access = PL2_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL2_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "AT_S12E1W", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 5,
.access = PL2_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL2_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "AT_S12E0R", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 6,
.access = PL2_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL2_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "AT_S12E0W", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 7,
.access = PL2_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL2_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
/* AT S1E2* are elsewhere as they UNDEF from EL3 if EL2 is not present */
{ .name = "AT_S1E3R", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 6, .crn = 7, .crm = 8, .opc2 = 0,
.access = PL3_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL3_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "AT_S1E3W", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 6, .crn = 7, .crm = 8, .opc2 = 1,
.access = PL3_W, .type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.access = PL3_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.writefn = ats_write64 },
{ .name = "PAR_EL1", .state = ARM_CP_STATE_AA64,
.type = ARM_CP_ALIAS,
.opc0 = 3, .opc1 = 0, .crn = 7, .crm = 4, .opc2 = 0,
@ -4893,11 +4970,11 @@ static const ARMCPRegInfo el2_cp_reginfo[] = {
{ .name = "AT_S1E2R", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 0,
.access = PL2_W, .accessfn = at_s1e2_access,
.type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC, .writefn = ats_write64 },
{ .name = "AT_S1E2W", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 1,
.access = PL2_W, .accessfn = at_s1e2_access,
.type = ARM_CP_NO_RAW, .writefn = ats_write64 },
.type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC, .writefn = ats_write64 },
/* The AArch32 ATS1H* operations are CONSTRAINED UNPREDICTABLE
* if EL2 is not implemented; we choose to UNDEF. Behaviour at EL3
* with SCR.NS == 0 outside Monitor mode is UNPREDICTABLE; we choose
@ -4905,10 +4982,10 @@ static const ARMCPRegInfo el2_cp_reginfo[] = {
*/
{ .name = "ATS1HR", .cp = 15, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 0,
.access = PL2_W,
.writefn = ats1h_write, .type = ARM_CP_NO_RAW },
.writefn = ats1h_write, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC },
{ .name = "ATS1HW", .cp = 15, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 1,
.access = PL2_W,
.writefn = ats1h_write, .type = ARM_CP_NO_RAW },
.writefn = ats1h_write, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC },
{ .name = "CNTHCTL_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 14, .crm = 1, .opc2 = 0,
/* ARMv7 requires bit 0 and 1 to reset to 1. ARMv8 defines the