According to the Hypervisor Extension in RISC-V spec: - VMIDMAX is 7 bits for RV32 and 14 bits for RV64 - the fields of hgatp are WARL in the normal way - the lowest two bits of the physical page number (PPN) in hgatp always read as zeros
These behaviors differ from those of the satp CSR, so hgatp cannot reuse legalize_xatp(). Add a dedicated legalize_hgatp() helper to implement the hgatp-specific WARL semantics. Co-authored-by: Tianze Wu <[email protected]> Signed-off-by: Tianze Wu <[email protected]> Signed-off-by: Shunchao Hu <[email protected]> --- Changes in v3: - Keep the hgatp masks and legalization helpers in 64-bit arithmetic, fixing the riscv32 build where the RV64 field masks do not fit in target_ulong. No functional change. - Link to v2: https://lore.kernel.org/qemu-devel/[email protected] Changes in v2: - Reword the comment to clarify that retaining the previous MODE is QEMU's WARL legalization choice, not a requirement of the spec. - Link to v1: https://lore.kernel.org/qemu-devel/[email protected] --- target/riscv/cpu_bits.h | 8 ++++++++ target/riscv/tcg/csr.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index c01050ce2b..c4a914411b 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -711,6 +711,14 @@ typedef enum { #define SATP64_ASID 0x0FFFF00000000000ULL #define SATP64_PPN 0x00000FFFFFFFFFFFULL +/* hgatp CSR field masks */ +#define HGATP32_MODE SATP32_MODE +#define HGATP32_VMID 0x1FC00000 +#define HGATP32_PPN SATP32_PPN +#define HGATP64_MODE SATP64_MODE +#define HGATP64_VMID 0x03FFF00000000000ULL +#define HGATP64_PPN SATP64_PPN + /* RNMI mnstatus CSR mask */ #define MNSTATUS_NMIE 0x00000008 #define MNSTATUS_MNPV 0x00000080 diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c index 061bc9db77..0e92a78c1f 100644 --- a/target/riscv/tcg/csr.c +++ b/target/riscv/tcg/csr.c @@ -5050,17 +5050,63 @@ static RISCVException read_hgeip(CPURISCVState *env, int csrno, return RISCV_EXCP_NONE; } +/* + * Masks must be 64-bit: env->hgatp is a uint64_t and the RV64 field + * masks do not fit in target_ulong when built for riscv32. + */ +static uint64_t hgatp_mask(CPURISCVState *env) +{ + uint64_t mask; + + if (riscv_cpu_mxl(env) == MXL_RV32) { + mask = HGATP32_MODE | HGATP32_VMID | HGATP32_PPN; + } else { + mask = HGATP64_MODE | HGATP64_VMID | HGATP64_PPN; + } + + /* G-stage x4 root page tables are always 16 KiB aligned. */ + return mask & ~(uint64_t)3; +} + +static uint64_t legalize_hgatp(CPURISCVState *env, + uint64_t old_hgatp, + uint64_t val) +{ + uint64_t mode_mask = riscv_cpu_mxl(env) == MXL_RV32 ? + HGATP32_MODE : HGATP64_MODE; + uint64_t hgatp = val & hgatp_mask(env); + uint64_t mode = get_field(hgatp, mode_mask); + + /* + * hgatp.MODE is a WARL field, so an unsupported value must be + * legalized rather than causing the entire write to be ignored. + * The specification does not require a particular legal value, + * QEMU here chooses to keep the previous mode while accepting + * writes to the remaining WARL fields. + */ + if (!validate_vm(env, mode)) { + hgatp = set_field(hgatp, mode_mask, + get_field(old_hgatp, mode_mask)); + } + + if (hgatp != old_hgatp) { + tlb_flush(env_cpu(env)); + } + + return hgatp; +} + static RISCVException read_hgatp(CPURISCVState *env, int csrno, target_ulong *val) { - *val = env->hgatp; + *val = env->hgatp & hgatp_mask(env); return RISCV_EXCP_NONE; } static RISCVException write_hgatp(CPURISCVState *env, int csrno, target_ulong val, uintptr_t ra) { - env->hgatp = legalize_xatp(env, env->hgatp, val); + env->hgatp = legalize_hgatp(env, env->hgatp, val); return RISCV_EXCP_NONE; } --- base-commit: c1c18d1e640b64292859ce9f30f3c344edfb0294 change-id: 20260920-riscv-hgatp-warl-1e48fd321188 Best regards, -- Shunchao Hu <[email protected]>
