During a two-stage (VS-stage + G-stage) page-table walk with hardware A/D updating enabled (Svadu / menvcfg.ADUE), a store that reaches a VS-stage leaf PTE whose accessed or dirty bit is clear triggers a hardware write-back of those bits into the PTE. That write-back is an implicit store to the PTE's guest-physical address, so it must be permitted by G-stage.
Fix this by re-running the G-stage translation of the guest PTE's address with store semantics. get_physical_address() then also checks that G-stage permits the guest PTE to be written, and raises a guest-page store fault when it does not. Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3426 Signed-off-by: imaginos <[email protected]> --- target/riscv/cpu_helper.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) v2: Reword an incomplete sentence in the code comment. Verified against Spike, with this patch QEMU's reported values match Spike. Spike (reference): QEMU, before this patch: mcause 0x17 mcause 0x17 mtval 0x80000000 mtval 0x80000000 mtval2 0x20001004 mtval2 0x20000000 mtinst 0x3020 mtinst 0x6a704073 If I've misread any of the A/D-update semantics here, I'd appreciate the correction. diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 2db07f5dfb..eb34ba19e1 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -1370,6 +1370,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, int ptshift; target_ulong pte; hwaddr pte_addr; + hwaddr pte_gpa = 0; const hwaddr base_root = base; const bool be = mo_endian_env(env) == MO_BE; int i; @@ -1407,6 +1408,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, } pte_addr = vbase + idx * ptesize; + pte_gpa = base + idx * ptesize; } else { pte_addr = base + idx * ptesize; } @@ -1661,6 +1663,28 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, return TRANSLATE_FAIL; } + /* + * The implicit store that writes updated A/D bits back to a VS-stage + * (first-stage) PTE must itself be permitted by G-stage. Re-run the + * second-stage translation of the PTE's guest-physical address with + * store semantics. If G-stage denies write, raise G-stage store fault + * against the PTE address. + */ + if (two_stage && first_stage) { + int gpa_prot; + hwaddr gpa_paddr; + int gpa_ret = get_physical_address(env, &gpa_paddr, &gpa_prot, + pte_gpa, NULL, MMU_DATA_STORE, + MMUIdx_U, false, true, + is_debug, false); + if (gpa_ret != TRANSLATE_SUCCESS) { + if (fault_pte_addr) { + *fault_pte_addr = pte_gpa >> 2; + } + return TRANSLATE_G_STAGE_FAIL; + } + } + pmp_ret = get_physical_address_pmp(env, &pmp_prot, pte_addr, sxlen_bytes, MMU_DATA_STORE, PRV_S); if (pmp_ret != TRANSLATE_SUCCESS) { @@ -2345,6 +2369,10 @@ void riscv_cpu_do_interrupt(CPUState *cs) * doing VS-stage page table walk. */ tinst = (riscv_cpu_xlen(env) == 32) ? 0x00002000 : 0x00003000; + + if (cause == RISCV_EXCP_STORE_GUEST_AMO_ACCESS_FAULT) { + tinst |= 0x20; + } } else { /* * The "Addr. Offset" field in transformed instruction is -- 2.43.0
