:p
atchew
Login
This patch set introduces support for the RISC-V Smsdid and Smmpt (Supervisor Domain Identifier and Memory Protection Table) extensions v0.4.9(https://github.com/riscv/riscv-smmtt/releases/tag/v0.4.9). Smmpt provides a hardware mechanism for fine-grained memory protection, checked after address translation, which is particularly useful for supervisor-level sandboxing and security monitoring. The rfc patch set: https://mail.gnu.org/archive/html/qemu-riscv/2025-09/msg00216.html v6->v7: The implementation is updated from the v0.3.4 specification to v0.4.9. The main changes are: 1. Update the mmpt CSR number to 0x382 and msdcfg to 0x74E. 2. Update the mmpt register layout: the SDID and PPN field positions changed and the RV64 PPN is now 44 bits. 3. Rework the MPTE format: the N (NAPOT) bit now lives at bit 2, the non-leaf NAPOT form is removed, NAPOT leaf entries encode a single XWR tuple plus a G granularity field, and non-NAPOT leaf entries hold per-page XWR tuples. 4. Treat XWR=000 as "no access" and a non-leaf entry with N=1 as a fault. 5. Enforce the Smmpt64 root-table PPN alignment (low 3 bits zero). 6. Rename the fence instructions mfence.spa/minval.spa to mfence.pa/minval.pa. 7. Drop stale Reviewed-by tags on the substantially reworked patches. 8. Introduce the configurable "smmpt-sdidlen" CPU property. 9. Add disassembly support for the Smmpt. 10. Add bare-metal M-mode system tests. 11. Rebase to master. v5->v6: 1. Use explicitly bit fields extract instead of mpte_union_t. 2. Use the same exception behavior for MPT valiation as PMP valiation. 3. Use PAGE_* instead of MPT_ACCESS_* as they have same value. 4. Use address_space_*_le instead of address_space_* for SMMPT. 5. Only print SMMPT address check log when SMMPT is enabled. 6. Add implied rule for SMMPT as it depends on SMSDID. 7. Rebase to master. v4->v5: 1. Rebase to master. v3->v4: 1. Add missing review tags. v2->v3: 1. Fix build error in patch 2. 2. Rebase to master. rfc->v2: 1. When ext_smmpt is false or BARE mode, make other fields in mmpt CSR zero. 2. Add patch 5 to fix smrnmi ISA string order. 3. Fix patch 6 smmpt and smsdid ISA string order. 4. Make smmpt and smsdid experiment extensions. 5. Add review tags. LIU Zhiwei (11): target/riscv: Add basic definitions and CSRs for SMMPT target/riscv: Add smmpt-sdidlen property for the mmpt SDID field target/riscv: Implement core SMMPT lookup logic target/riscv: Integrate SMMPT checks into MMU and TLB fill target/riscv: Implement SMMPT fence instructions target/riscv: Fix smrnmi isa alphabetical order target/riscv: Add disassembly for Smmpt instructions and CSRs target/riscv: Enable SMMPT extension target/riscv: Add system test for SMMPT extension target/riscv: Add system tests for Smmpt52 and Smmpt64 target/riscv: Add system test for Smmpt34 disas/riscv.c | 9 +- target/riscv/cpu.c | 60 +++- target/riscv/cpu.h | 8 + target/riscv/cpu_bits.h | 27 ++ target/riscv/cpu_cfg_fields.h.inc | 3 + target/riscv/insn32.decode | 2 + target/riscv/meson.build | 1 + target/riscv/riscv_smmpt.c | 339 ++++++++++++++++++ target/riscv/riscv_smmpt.h | 26 ++ target/riscv/tcg/cpu_helper.c | 119 +++++- target/riscv/tcg/csr.c | 106 ++++++ .../tcg/insn_trans/trans_privileged.c.inc | 30 ++ target/riscv/tcg/pmp.h | 3 + tests/tcg/riscv64/Makefile.softmmu-target | 12 + tests/tcg/riscv64/smmpt-common.S | 256 +++++++++++++ tests/tcg/riscv64/test-smmpt.S | 85 +++++ tests/tcg/riscv64/test-smmpt34.S | 97 +++++ tests/tcg/riscv64/test-smmpt52.S | 91 +++++ tests/tcg/riscv64/test-smmpt64.S | 98 +++++ 19 files changed, 1353 insertions(+), 19 deletions(-) create mode 100644 target/riscv/riscv_smmpt.c create mode 100644 target/riscv/riscv_smmpt.h create mode 100644 tests/tcg/riscv64/smmpt-common.S create mode 100644 tests/tcg/riscv64/test-smmpt.S create mode 100644 tests/tcg/riscv64/test-smmpt34.S create mode 100644 tests/tcg/riscv64/test-smmpt52.S create mode 100644 tests/tcg/riscv64/test-smmpt64.S base-commit: 499039798cdad7d86b787fec0eaf1da4151c0f05 -- 2.43.0
This patch lays the groundwork for the SMMPT (Supervisor Domains Access Protection) extension by introducing its fundamental components. It adds: - New CPU configuration flags, `ext_smmpt` and `ext_smsdid`, to enable the extension. - Bit-field definitions for the `mmpt` CSR in `cpu_bits.h`. - The `mmpt` and `msdcfg` CSR numbers and their read/write handlers in `csr.c`. - New fields in `CPUArchState` to store the state of these new CSRs. - A new translation failure reason `TRANSLATE_MPT_FAIL`. This provides the necessary infrastructure for the core MPT logic and MMU integration that will follow. Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- target/riscv/cpu.h | 8 +++ target/riscv/cpu_bits.h | 24 ++++++++ target/riscv/cpu_cfg_fields.h.inc | 2 + target/riscv/riscv_smmpt.h | 21 +++++++ target/riscv/tcg/csr.c | 99 +++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+) create mode 100644 target/riscv/riscv_smmpt.h diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -XXX,XX +XXX,XX @@ enum { TRANSLATE_PMP_FAIL, TRANSLATE_G_STAGE_FAIL, TRANSLATE_PMA_FAIL, + TRANSLATE_MPT_FAIL }; /* Extension context status */ @@ -XXX,XX +XXX,XX @@ extern RISCVCPUImpliedExtsRule *riscv_multi_ext_implied_rules[]; #if !defined(CONFIG_USER_ONLY) #include "tcg/pmp.h" +#include "riscv_smmpt.h" #endif #define RV_VLEN_MAX 1024 @@ -XXX,XX +XXX,XX @@ struct CPUArchState { uint64_t rnmip; uint64_t rnmi_irqvec; uint64_t rnmi_excpvec; + + /* Smsdid */ + uint32_t mptmode; + uint32_t sdid; + uint64_t mptppn; + uint32_t msdcfg; #endif /* Fields from here on are preserved across CPU reset. */ diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -XXX,XX +XXX,XX @@ typedef enum CTRType { #define MCONTEXT64 0x0000000000001FFFULL #define MCONTEXT32_HCONTEXT 0x0000007F #define MCONTEXT64_HCONTEXT 0x0000000000003FFFULL + +/* Smsdid */ +#define CSR_MMPT 0x382 +#define CSR_MSDCFG 0x74E + +/* + * MMPT register layout for MXLEN=32: + * MODE[31:30], 0[29:28], SDID[27:22], PPN[21:0] + */ +#define MMPT_MODE_MASK_32 0xC0000000 +#define MMPT_MODE_SHIFT_32 30 +#define MMPT_SDID_MASK_32 0x0FC00000 +#define MMPT_SDID_SHIFT_32 22 +#define MMPT_PPN_MASK_32 0x003FFFFF + +/* + * MMPT register layout for MXLEN=64: + * MODE[63:60], 0[59:58], SDID[57:52], 0[51:44], PPN[43:0] + */ +#define MMPT_MODE_MASK_64 0xF000000000000000ULL +#define MMPT_MODE_SHIFT_64 60 +#define MMPT_SDID_MASK_64 0x03F0000000000000ULL +#define MMPT_SDID_SHIFT_64 52 +#define MMPT_PPN_MASK_64 0x00000FFFFFFFFFFFULL #endif diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -XXX,XX +XXX,XX @@ BOOL_FIELD(ext_smpmpmt) BOOL_FIELD(ext_svrsw60t59b) BOOL_FIELD(ext_svvptc) BOOL_FIELD(ext_svukte) +BOOL_FIELD(ext_smmpt) +BOOL_FIELD(ext_smsdid) BOOL_FIELD(ext_zdinx) BOOL_FIELD(ext_zaamo) BOOL_FIELD(ext_zacas) diff --git a/target/riscv/riscv_smmpt.h b/target/riscv/riscv_smmpt.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/target/riscv/riscv_smmpt.h @@ -XXX,XX +XXX,XX @@ +/* + * QEMU RISC-V Smmpt (Memory Protection Table) + * + * Copyright (c) 2024 Alibaba Group. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef RISCV_SMMPT_H +#define RISCV_SMMPT_H + +typedef enum { + SMMPTBARE = 0, + SMMPT34 = 1, + SMMPT43 = 2, + SMMPT52 = 3, + SMMPT64 = 4, + SMMPTMAX +} mpt_mode_t; + +#endif diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/csr.c +++ b/target/riscv/tcg/csr.c @@ -XXX,XX +XXX,XX @@ static RISCVException rnmi(CPURISCVState *env, int csrno) return RISCV_EXCP_ILLEGAL_INST; } + +static RISCVException smsdid(CPURISCVState *env, int csrno) +{ + if (riscv_cpu_cfg(env)->ext_smsdid) { + return RISCV_EXCP_NONE; + } + + return RISCV_EXCP_ILLEGAL_INST; +} #endif static RISCVException seed(CPURISCVState *env, int csrno) @@ -XXX,XX +XXX,XX @@ static RISCVException write_mnstatus(CPURISCVState *env, int csrno, return RISCV_EXCP_NONE; } +static RISCVException read_mmpt(CPURISCVState *env, int csrno, + target_ulong *val) +{ + if (riscv_cpu_xlen(env) == 32) { + uint32_t value = 0; + value |= env->mptmode << MMPT_MODE_SHIFT_32; + value |= (env->sdid << MMPT_SDID_SHIFT_32) & MMPT_SDID_MASK_32; + value |= env->mptppn & MMPT_PPN_MASK_32; + *val = value; + } else if (riscv_cpu_xlen(env) == 64) { + uint64_t value_64 = 0; + uint32_t mode_value = env->mptmode; + /* mpt_mode_t convert to mmpt.mode value */ + if (mode_value) { + mode_value -= SMMPT43 - SMMPT34; + } + value_64 |= (uint64_t)mode_value << MMPT_MODE_SHIFT_64; + value_64 |= ((uint64_t)env->sdid << MMPT_SDID_SHIFT_64) + & MMPT_SDID_MASK_64; + value_64 |= (uint64_t)env->mptppn & MMPT_PPN_MASK_64; + *val = value_64; + } else { + return RISCV_EXCP_ILLEGAL_INST; + } + return RISCV_EXCP_NONE; +} + +static RISCVException write_mmpt(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + uint32_t mode_value = 0; + if (!riscv_cpu_cfg(env)->ext_smmpt) { + goto set_remaining_fields_zero; + } + + if (riscv_cpu_xlen(env) == 32) { + mode_value = (val & MMPT_MODE_MASK_32) >> MMPT_MODE_SHIFT_32; + /* If mode is bare, the remaining fields in mmpt must be zero */ + if (mode_value == SMMPTBARE) { + goto set_remaining_fields_zero; + } else if (mode_value <= SMMPT34) { + /* Only write the legal value */ + env->mptmode = mode_value; + } + env->sdid = (val & MMPT_SDID_MASK_32) >> MMPT_SDID_SHIFT_32; + env->mptppn = val & MMPT_PPN_MASK_32; + } else if (riscv_cpu_xlen(env) == 64) { + mode_value = (val & MMPT_MODE_MASK_64) >> MMPT_MODE_SHIFT_64; + if (mode_value == SMMPTBARE) { + goto set_remaining_fields_zero; + } else if (mode_value < SMMPTMAX) { + /* convert to mpt_mode_t */ + mode_value += SMMPT43 - SMMPT34; + env->mptmode = mode_value; + } + env->sdid = (val & MMPT_SDID_MASK_64) >> MMPT_SDID_SHIFT_64; + env->mptppn = val & MMPT_PPN_MASK_64; + /* Smmpt64: root table PPN bits 2:0 must be zero (32KiB alignment) */ + if (env->mptmode == SMMPT64) { + env->mptppn &= ~(uint64_t)0x7; + } + } else { + return RISCV_EXCP_ILLEGAL_INST; + } + return RISCV_EXCP_NONE; + +set_remaining_fields_zero: + env->sdid = 0; + env->mptmode = SMMPTBARE; + env->mptppn = 0; + return RISCV_EXCP_NONE; +} + +static RISCVException read_msdcfg(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = env->msdcfg; + return RISCV_EXCP_NONE; +} + +static RISCVException write_msdcfg(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + env->msdcfg = val; + return RISCV_EXCP_NONE; +} + #endif /* Crypto Extension */ @@ -XXX,XX +XXX,XX @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = { write_mhpmcounterh }, [CSR_SCOUNTOVF] = { "scountovf", sscofpmf, read_scountovf, .min_priv_ver = PRIV_VERSION_1_12_0 }, + /* Supervisor Domain Identifier and Protection Registers */ + [CSR_MMPT] = { "mmpt", smsdid, read_mmpt, write_mmpt }, + [CSR_MSDCFG] = { "msdcfg", smsdid, read_msdcfg, write_msdcfg }, #endif /* !CONFIG_USER_ONLY */ }; -- 2.43.0
The number of implemented SDID bits is UNSPECIFIED and may be zero. The number of implemented SDID bits, termed SDIDLEN, may be determined by writing one to every bit position in the SDID field, then reading back the value in mmpt to see which bit positions in the SDID field hold a one. The least-significant bits of SDID are implemented first: that is, if SDIDLEN > 0, SDID[SDIDLEN-1:0] is writable. The maximal value of SDIDLEN, termed SDIDMAX, is 6. Model this by adding a configurable "smmpt-sdidlen" CPU property. The value defaults to SDIDMAX (6) to preserve the current behaviour. The mmpt write handler now keeps only the low SDIDLEN bits of the SDID field, making the SDIDLEN discovery sequence work as specified. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- target/riscv/cpu.c | 45 +++++++++++++++++++++++++++++++ target/riscv/cpu_bits.h | 3 +++ target/riscv/cpu_cfg_fields.h.inc | 1 + target/riscv/tcg/csr.c | 11 ++++++-- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -XXX,XX +XXX,XX @@ static void riscv_cpu_init(Object *obj) cpu->cfg.pmu_mask = MAKE_64BIT_MASK(3, 16); cpu->cfg.vlenb = 128 >> 3; cpu->cfg.elen = 64; + cpu->cfg.sdidlen = MMPT_SDIDLEN_MAX; cpu->cfg.cbom_blocksize = 64; cpu->cfg.cbop_blocksize = 64; cpu->cfg.cboz_blocksize = 64; @@ -XXX,XX +XXX,XX @@ static const PropertyInfo prop_vlen = { .set = prop_vlen_set, }; +static void prop_sdidlen_set(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + RISCVCPU *cpu = RISCV_CPU(obj); + uint8_t value; + + if (!visit_type_uint8(v, name, &value, errp)) { + return; + } + + if (value > MMPT_SDIDLEN_MAX) { + error_setg(errp, "smmpt-sdidlen must be between 0 and %d", + MMPT_SDIDLEN_MAX); + return; + } + + if (value != cpu->cfg.sdidlen && riscv_cpu_is_vendor(obj)) { + cpu_set_prop_err(cpu, name, errp); + error_append_hint(errp, "Current '%s' val: %u\n", + name, cpu->cfg.sdidlen); + return; + } + + cpu_option_add_user_setting(cpu, name); + cpu->cfg.sdidlen = value; +} + +static void prop_sdidlen_get(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + uint8_t value = RISCV_CPU(obj)->cfg.sdidlen; + + visit_type_uint8(v, name, &value, errp); +} + +static const PropertyInfo prop_sdidlen = { + .type = "uint8", + .description = "smmpt-sdidlen", + .get = prop_sdidlen_get, + .set = prop_sdidlen_set, +}; + static void prop_elen_set(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) { @@ -XXX,XX +XXX,XX @@ static const Property riscv_cpu_properties[] = { {.name = "vlen", .info = &prop_vlen}, {.name = "elen", .info = &prop_elen}, + {.name = "smmpt-sdidlen", .info = &prop_sdidlen}, + {.name = "cbom_blocksize", .info = &prop_cbom_blksize}, {.name = "cbop_blocksize", .info = &prop_cbop_blksize}, {.name = "cboz_blocksize", .info = &prop_cboz_blksize}, diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -XXX,XX +XXX,XX @@ typedef enum CTRType { #define CSR_MMPT 0x382 #define CSR_MSDCFG 0x74E +/* Maximal number of implemented SDID bits (SDIDMAX) */ +#define MMPT_SDIDLEN_MAX 6 + /* * MMPT register layout for MXLEN=32: * MODE[31:30], 0[29:28], SDID[27:22], PPN[21:0] diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -XXX,XX +XXX,XX @@ TYPED_FIELD(uint64_t, mimpid, 0) TYPED_FIELD(uint32_t, pmu_mask, 0) TYPED_FIELD(uint16_t, vlenb, 0) TYPED_FIELD(uint16_t, elen, 0) +TYPED_FIELD(uint8_t, sdidlen, 0) TYPED_FIELD(uint16_t, cbom_blocksize, 0) TYPED_FIELD(uint16_t, cbop_blocksize, 0) TYPED_FIELD(uint16_t, cboz_blocksize, 0) diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/csr.c +++ b/target/riscv/tcg/csr.c @@ -XXX,XX +XXX,XX @@ static RISCVException write_mmpt(CPURISCVState *env, int csrno, target_ulong val, uintptr_t ra) { uint32_t mode_value = 0; + /* + * Only the least-significant SDIDLEN bits of the SDID field are + * writable; the remaining bits are WARL and read as zero. + */ + uint64_t sdid_mask = MAKE_64BIT_MASK(0, riscv_cpu_cfg(env)->sdidlen); if (!riscv_cpu_cfg(env)->ext_smmpt) { goto set_remaining_fields_zero; } @@ -XXX,XX +XXX,XX @@ static RISCVException write_mmpt(CPURISCVState *env, int csrno, /* Only write the legal value */ env->mptmode = mode_value; } - env->sdid = (val & MMPT_SDID_MASK_32) >> MMPT_SDID_SHIFT_32; + env->sdid = ((val & MMPT_SDID_MASK_32) >> MMPT_SDID_SHIFT_32) + & sdid_mask; env->mptppn = val & MMPT_PPN_MASK_32; } else if (riscv_cpu_xlen(env) == 64) { mode_value = (val & MMPT_MODE_MASK_64) >> MMPT_MODE_SHIFT_64; @@ -XXX,XX +XXX,XX @@ static RISCVException write_mmpt(CPURISCVState *env, int csrno, mode_value += SMMPT43 - SMMPT34; env->mptmode = mode_value; } - env->sdid = (val & MMPT_SDID_MASK_64) >> MMPT_SDID_SHIFT_64; + env->sdid = (((uint64_t)val & MMPT_SDID_MASK_64) >> MMPT_SDID_SHIFT_64) + & sdid_mask; env->mptppn = val & MMPT_PPN_MASK_64; /* Smmpt64: root table PPN bits 2:0 must be zero (32KiB alignment) */ if (env->mptmode == SMMPT64) { -- 2.43.0
This patch introduces the core implementation for the Memory Protection Table (MPT) walk, which is the central mechanism of the SMMPT extension. A new file, `riscv_smmpt.c`, is added to encapsulate the MPT logic. It implements the `smmpt_lookup()` function, which performs a multi-level page table-like walk starting from the physical address specified in the `mptppn` CSR field. This walk determines the access permissions (read, write, execute) for a given physical address. The implementation supports various SMMPT modes (SMMPT34, SMMPT43, etc.) and correctly handles leaf and non-leaf entries, including reserved bit checks. Both non-NAPOT leaf entries (per-page XWR tuples) and NAPOT leaf entries (a single XWR tuple with a G granularity field) are supported, as defined by the v0.4.9 MPTE format. Helper functions for parsing MPT entries and converting access permissions are included in the new `riscv_smmpt.h` header. Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- target/riscv/meson.build | 1 + target/riscv/riscv_smmpt.c | 339 ++++++++++++++++++++++++++++++++++ target/riscv/riscv_smmpt.h | 5 + target/riscv/tcg/cpu_helper.c | 6 +- target/riscv/tcg/pmp.h | 3 + 5 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 target/riscv/riscv_smmpt.c diff --git a/target/riscv/meson.build b/target/riscv/meson.build index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/meson.build +++ b/target/riscv/meson.build @@ -XXX,XX +XXX,XX @@ riscv_ss.add(files( riscv_system_ss = ss.source_set() riscv_system_ss.add(files( 'arch_dump.c', + 'riscv_smmpt.c', 'monitor.c', 'machine.c', 'time_helper.c', diff --git a/target/riscv/riscv_smmpt.c b/target/riscv/riscv_smmpt.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/target/riscv/riscv_smmpt.c @@ -XXX,XX +XXX,XX @@ +/* + * QEMU RISC-V Smmpt (Memory Protection Table) + * + * Copyright (c) 2024 Alibaba Group. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Implements the MPT lookup algorithm per SMMTT specification v0.4.9. + * + * MPTE format (v0.4.9): + * + * 32-bit non-leaf: V[0], L=0[1], Reserved[9:2], PPN[31:10] + * 32-bit non-NAPOT leaf: V[0], L=1[1], N=0[2], Reserved[7:3], XWR[31:8] + * 32-bit NAPOT leaf: V[0], L=1[1], N=1[2], Reserved[7:3], XWR[10:8], 0[11], + * G[15:12], Reserved[31:16] + * + * 64-bit non-leaf: V[0], L=0[1], Reserved[9:2], PPN[53:10], Reserved[63:54] + * 64-bit non-NAPOT leaf: V[0], L=1[1], N=0[2], Reserved[7:3], XWR[55:8], + * Reserved[63:56] + * 64-bit NAPOT leaf: V[0], L=1[1], N=1[2], Reserved[7:3], XWR[10:8], 0[11], + * G[15:12], Reserved[63:16] + */ + +#include "qemu/osdep.h" +#include "riscv_smmpt.h" +#include "tcg/pmp.h" +#include "exec/page-protection.h" +#include "system/memory.h" + +typedef uint64_t load_entry_fn(AddressSpace *, hwaddr, + MemTxAttrs, MemTxResult *); + +static uint64_t load_entry_32(AddressSpace *as, hwaddr addr, + MemTxAttrs attrs, MemTxResult *result) +{ + return address_space_ldl_le(as, addr, attrs, result); +} + +static uint64_t load_entry_64(AddressSpace *as, hwaddr addr, + MemTxAttrs attrs, MemTxResult *result) +{ + return address_space_ldq_le(as, addr, attrs, result); +} + +static inline bool mpte_is_valid(uint64_t mpte) +{ + return mpte & 0x1; +} + +static inline bool mpte_is_leaf(uint64_t mpte) +{ + return mpte & 0x2; +} + +static inline bool mpte_get_n(uint64_t mpte) +{ + return (mpte >> 2) & 0x1; +} + +/* + * Get reserved bits from MPTE. Returns non-zero if any reserved bit is set. + */ +static uint64_t mpte_get_rsv(CPURISCVState *env, uint64_t mpte) +{ + RISCVMXL mxl = riscv_cpu_mxl(env); + bool leaf = mpte_is_leaf(mpte); + bool napot = mpte_get_n(mpte); + + if (mxl == MXL_RV32) { + if (!leaf) { + /* non-leaf32: Reserved = bits[9:2] */ + return extract32(mpte, 2, 8); + } + if (!napot) { + /* non-NAPOT leaf32: Reserved = bits[7:3] (XWR fills [31:8]) */ + return extract32(mpte, 3, 5); + } + /* NAPOT leaf32: Reserved = bits[7:3] | bit[11] (mbz) | bits[31:16] */ + return extract32(mpte, 3, 5) | extract32(mpte, 11, 1) | + extract32(mpte, 16, 16); + } + + /* RV64 */ + if (!leaf) { + /* non-leaf64: Reserved = bits[9:2] | bits[63:54] */ + return extract64(mpte, 2, 8) | extract64(mpte, 54, 10); + } + if (!napot) { + /* non-NAPOT leaf64: Reserved = bits[7:3] | bits[63:56] */ + return extract64(mpte, 3, 5) | extract64(mpte, 56, 8); + } + /* NAPOT leaf64: Reserved = bits[7:3] | bit[11] (mbz) | bits[63:16] */ + return extract64(mpte, 3, 5) | extract64(mpte, 11, 1) | + extract64(mpte, 16, 48); +} + +/* + * Get PPN from a non-leaf MPTE. + * RV32 non-leaf: PPN = bits[31:10] (22 bits) + * RV64 non-leaf: PPN = bits[53:10] (44 bits) + */ +static uint64_t mpte_get_ppn(CPURISCVState *env, uint64_t mpte) +{ + RISCVMXL mxl = riscv_cpu_mxl(env); + + if (mxl == MXL_RV32) { + return extract32(mpte, 10, 22); + } + return extract64(mpte, 10, 44); +} + +/* + * Get XWR permission for a specific page index from a non-NAPOT leaf. + * The XWR base bit is 8 for both RV32 and RV64; only the number of + * entries differs (pi in 0..7 for RV32, 0..15 for RV64). + */ +static uint32_t mpte_get_xwr(CPURISCVState *env, uint64_t mpte, int pi) +{ + return extract64(mpte, 8 + pi * 3, 3); +} + +/* + * Get single XWR from a NAPOT leaf. + * The NAPOT XWR base bit is 8 for both RV32 and RV64 (bits[10:8]). + */ +static uint32_t mpte_get_napot_xwr(CPURISCVState *env, uint64_t mpte) +{ + return extract64(mpte, 8, 3); +} + +/* + * Get G field from a NAPOT leaf. + * The G base bit is 12 for both RV32 and RV64 (bits[15:12]). + */ +static uint32_t mpte_get_g(CPURISCVState *env, uint64_t mpte) +{ + return extract64(mpte, 12, 4); +} + +/* + * Validate the G encoding for the given MPT mode. + * Smmpt34: only G=6 is valid + * Smmpt43/52/64: only G=4 is valid + */ +static bool mpte_validate_g(uint32_t g, mpt_mode_t mode) +{ + switch (mode) { + case SMMPT34: + return g == 6; + case SMMPT43: + case SMMPT52: + case SMMPT64: + return g == 4; + default: + return false; + } +} + +/* + * Get page number index pn[i] from the supervisor physical address. + * + * Smmpt34 (34-bit SPA): + * SPA layout: range_offset[14:0], pn[0][24:15] (10 bits), + * pn[1][33:25] (9 bits) + * + * Smmpt43/52/64 (RV64 MPT): + * SPA layout: range_offset[15:0], pn[0][24:16] (9 bits), ... + * For Smmpt64, pn[4] (top level) is 12 bits. + */ +static int mpt_get_pn(hwaddr addr, int i, mpt_mode_t mode) +{ + if (mode == SMMPT34) { + return i == 0 + ? extract64(addr, 15, 10) + : extract64(addr, 25, 9); + } else { + int offset = 16 + i * 9; + if ((mode == SMMPT64) && (i == 4)) { + return extract64(addr, offset, 12); + } else { + return extract64(addr, offset, 9); + } + } +} + +/* + * Get the page index within a leaf MPTE. + * + * Smmpt34: pi = SPA[14:12] (3 bits) for level 0, SPA[24:22] for level 1 + * Smmpt43/52/64: pi = SPA[offset-4 +: 4] (4 bits) + */ +static int mpt_get_pi(hwaddr addr, int i, mpt_mode_t mode) +{ + if (mode == SMMPT34) { + return i == 0 + ? extract64(addr, 12, 3) + : extract64(addr, 22, 3); + } else { + int offset = 16 + i * 9; + return extract64(addr, offset - 4, 4); + } +} + +/* + * Check XWR permission bits against the access type. + * Returns true if access is allowed. + * + * The 3-bit XWR field uses the same bit order as RISC-V PTE permissions + * (bit0 = R, bit1 = W, bit2 = X), matching QEMU's PAGE_READ / PAGE_WRITE / + * PAGE_EXEC. As with PTEs, writable-but-not-readable encodings are reserved. + * + * XWR encoding (bit2=X, bit1=W, bit0=R): + * 000 = No access + * 001 = Read only + * 010 = Reserved (fault) + * 011 = Read + Write + * 100 = Execute only + * 101 = Read + Execute + * 110 = Reserved (fault) + * 111 = Read + Write + Execute + */ +static bool mpt_check_xwr(uint32_t xwr, int *prot, MMUAccessType access_type) +{ + switch (xwr) { + case 0: /* No access */ + return false; + case PAGE_EXEC: /* 100: Execute only */ + *prot = PAGE_EXEC; + return access_type == MMU_INST_FETCH; + case PAGE_READ | PAGE_EXEC: /* 101: Read + Execute */ + *prot = PAGE_READ | PAGE_EXEC; + return (access_type == MMU_DATA_LOAD || + access_type == MMU_INST_FETCH); + case PAGE_READ: /* 001: Read only */ + *prot = PAGE_READ; + return access_type == MMU_DATA_LOAD; + case PAGE_READ | PAGE_WRITE: /* 011: Read + Write */ + *prot = PAGE_READ | PAGE_WRITE; + return (access_type == MMU_DATA_LOAD || + access_type == MMU_DATA_STORE); + case PAGE_READ | PAGE_WRITE | PAGE_EXEC: /* 111: R+W+X */ + *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return true; + default: /* 010, 110: Reserved - fault */ + return false; + } +} + +static bool smmpt_lookup(CPURISCVState *env, hwaddr addr, mpt_mode_t mode, + int *prot, MMUAccessType access_type) +{ + MemTxResult res; + MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED; + CPUState *cs = env_cpu(env); + hwaddr mpte_addr, base = (hwaddr)env->mptppn << PGSHIFT; + load_entry_fn *load_entry; + uint32_t mptesize, levels, xwr, g; + int pn, pi, pmp_prot, pmp_ret; + uint64_t mpte; + + switch (mode) { + case SMMPT34: + load_entry = &load_entry_32; levels = 2; mptesize = 4; break; + case SMMPT43: + load_entry = &load_entry_64; levels = 3; mptesize = 8; break; + case SMMPT52: + load_entry = &load_entry_64; levels = 4; mptesize = 8; break; + case SMMPT64: + load_entry = &load_entry_64; levels = 5; mptesize = 8; break; + case SMMPTBARE: + *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return true; + default: + g_assert_not_reached(); + break; + } + + for (int i = levels - 1; i >= 0; i--) { + /* Step 1: Get pn[i] as the MPT index */ + pn = mpt_get_pn(addr, i, mode); + + /* Step 2: Load MPTE from memory */ + mpte_addr = base + pn * mptesize; + pmp_ret = get_physical_address_pmp(env, &pmp_prot, mpte_addr, + mptesize, MMU_DATA_LOAD, PRV_M); + if (pmp_ret != TRANSLATE_SUCCESS) { + return false; + } + mpte = load_entry(cs->as, mpte_addr, attrs, &res); + if (res != MEMTX_OK) { + return false; + } + + /* Step 3: Check valid bit and reserved bits */ + if (!mpte_is_valid(mpte) || mpte_get_rsv(env, mpte)) { + return false; + } + + /* Step 3 (cont): non-leaf with N=1 is a fault */ + if (!mpte_is_leaf(mpte) && mpte_get_n(mpte)) { + return false; + } + + /* Step 4: Process non-leaf node */ + if (!mpte_is_leaf(mpte)) { + if (i == 0) { + return false; + } + base = mpte_get_ppn(env, mpte) << PGSHIFT; + continue; + } + + /* Step 5 & 6: Process leaf node */ + if (!mpte_get_n(mpte)) { + /* Step 5: Non-NAPOT leaf - get XWR[pi] */ + pi = mpt_get_pi(addr, i, mode); + xwr = mpte_get_xwr(env, mpte, pi); + } else { + /* Step 6: NAPOT leaf - validate G, get single XWR */ + g = mpte_get_g(env, mpte); + if (!mpte_validate_g(g, mode)) { + return false; + } + xwr = mpte_get_napot_xwr(env, mpte); + } + + /* Step 7: Check permission */ + return mpt_check_xwr(xwr, prot, access_type); + } + return false; +} + +bool smmpt_check_access(CPURISCVState *env, hwaddr addr, + int *prot, MMUAccessType access_type) +{ + mpt_mode_t mode = env->mptmode; + + return smmpt_lookup(env, addr, mode, prot, access_type); +} diff --git a/target/riscv/riscv_smmpt.h b/target/riscv/riscv_smmpt.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/riscv_smmpt.h +++ b/target/riscv/riscv_smmpt.h @@ -XXX,XX +XXX,XX @@ #ifndef RISCV_SMMPT_H #define RISCV_SMMPT_H +#include "cpu.h" +#include "exec/mmu-access-type.h" + typedef enum { SMMPTBARE = 0, SMMPT34 = 1, @@ -XXX,XX +XXX,XX @@ typedef enum { SMMPTMAX } mpt_mode_t; +bool smmpt_check_access(CPURISCVState *env, hwaddr addr, + int *prot, MMUAccessType access_type); #endif diff --git a/target/riscv/tcg/cpu_helper.c b/target/riscv/tcg/cpu_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/cpu_helper.c +++ b/target/riscv/tcg/cpu_helper.c @@ -XXX,XX +XXX,XX @@ void riscv_cpu_set_mode(CPURISCVState *env, privilege_mode_t newpriv, * @access_type: The type of MMU access * @mode: Indicates current privilege level. */ -static int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr, - int size, MMUAccessType access_type, - privilege_mode_t mode) +int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr, + int size, MMUAccessType access_type, + privilege_mode_t mode) { pmp_priv_t pmp_priv; bool pmp_has_privs; diff --git a/target/riscv/tcg/pmp.h b/target/riscv/tcg/pmp.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/pmp.h +++ b/target/riscv/tcg/pmp.h @@ -XXX,XX +XXX,XX @@ void pmp_update_rule_nums(CPURISCVState *env); uint32_t pmp_get_num_rules(CPURISCVState *env); int pmp_priv_to_page_prot(pmp_priv_t pmp_priv); void pmp_unlock_entries(CPURISCVState *env); +int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr, + int size, MMUAccessType access_type, + privilege_mode_t mode); #define MSECCFG_MML_ISSET(env) get_field(env->mseccfg, MSECCFG_MML) #define MSECCFG_MMWP_ISSET(env) get_field(env->mseccfg, MSECCFG_MMWP) -- 2.43.0
With the core MPT lookup logic in place, this patch integrates the permission checks into QEMU's main MMU processing functions. A new helper, `get_physical_address_mpt`, is introduced to check the permissions for a given physical address against the MPT. This helper is then called at two critical points: 1. During page table walks (`get_physical_address`): The physical address of the Page Table Entry (PTE) itself is checked to ensure the supervisor has permission to read it. 2. After successful address translation (`riscv_cpu_tlb_fill`): The final guest-physical address is checked against the MPT before the access is allowed to proceed. This ensures that SMMPT protection is enforced for both the translation process and the final memory access, as required by the specification. Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> --- target/riscv/tcg/cpu_helper.c | 113 ++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/target/riscv/tcg/cpu_helper.c b/target/riscv/tcg/cpu_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/cpu_helper.c +++ b/target/riscv/tcg/cpu_helper.c @@ -XXX,XX +XXX,XX @@ static bool check_svukte_addr(CPURISCVState *env, vaddr addr) return !high_bit; } +/* + * get_physical_address_mpt - check mpt permission for this physical address + * + * Lookup the Memory Protection Table and check permission for this + * physical address. Returns 0 if the permission checking was successful + * + * @env: CPURISCVState + * @prot: The returned protection attributes + * @addr: The physical address to be checked permission + * @access_type: The type of MMU access + * @mode: Indicates current privilege level. + */ +static int get_physical_address_mpt(CPURISCVState *env, int *prot, hwaddr addr, + MMUAccessType access_type, int mode) +{ + /* + * If the extension is not supported or the mmpt.mode is Bare, + * there is no protection, return success. + */ + if (!riscv_cpu_cfg(env)->ext_smmpt || env->mptmode == SMMPTBARE) { + *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return TRANSLATE_SUCCESS; + } + + /* + * MPT is checked for all accesses to physical memory, unless the + * effective privilege mode is M. + * + * Data accesses in M-mode when the MPRV bit in mstatus is set and + * the MPP field in mstatus contains S or U are subject to MPT checks. + * + * In riscv_env_mmu_index, The MPRV and MPP bits are already checked and + * encoded to mmu_idx, So we do not need to check it here. + */ + if (mode == PRV_M) { + *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return TRANSLATE_SUCCESS; + } + + if (!smmpt_check_access(env, addr, prot, access_type)) { + *prot = 0; + return TRANSLATE_MPT_FAIL; + } + + return TRANSLATE_SUCCESS; +} + /* * get_physical_address - get the physical address for this virtual address * @@ -XXX,XX +XXX,XX @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, pte_addr = base + idx * ptesize; } + int mpt_prot; + int mpt_ret = get_physical_address_mpt(env, &mpt_prot, pte_addr, + MMU_DATA_LOAD, PRV_S); + if (mpt_ret != TRANSLATE_SUCCESS) { + return mpt_ret; + } + int pmp_prot; int pmp_ret = get_physical_address_pmp(env, &pmp_prot, pte_addr, sxlen_bytes, @@ -XXX,XX +XXX,XX @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, CPURISCVState *env = &cpu->env; vaddr im_address; hwaddr pa = 0; - int prot, prot2, prot_pmp; + int prot, prot2, prot_pmp, mpt_prot; bool pmp_pma_violation = false; bool first_stage_error = true; bool two_stage_lookup = mmuidx_2stage(mmu_idx); @@ -XXX,XX +XXX,XX @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, prot &= prot2; if (ret == TRANSLATE_SUCCESS) { - ret = get_physical_address_pmp(env, &prot_pmp, pa, - size, access_type, mode); - tlb_size = pmp_get_tlb_size(env, pa); + ret = get_physical_address_mpt(env, &mpt_prot, pa, + access_type, mode); + if (riscv_cpu_cfg(env)->ext_smmpt) { + qemu_log_mask(CPU_LOG_MMU, + "%s MPT address=" HWADDR_FMT_plx " ret %d" + " prot %d\n", + __func__, pa, ret, mpt_prot); + } + prot &= mpt_prot; - qemu_log_mask(CPU_LOG_MMU, - "%s PMP address=" HWADDR_FMT_plx " ret %d prot" - " %d tlb_size %" HWADDR_PRIu "\n", - __func__, pa, ret, prot_pmp, tlb_size); + if (ret == TRANSLATE_SUCCESS) { + ret = get_physical_address_pmp(env, &prot_pmp, pa, + size, access_type, mode); + tlb_size = pmp_get_tlb_size(env, pa); + + qemu_log_mask(CPU_LOG_MMU, + "%s PMP address=" HWADDR_FMT_plx + " ret %d prot %d tlb_size %" + HWADDR_PRIu "\n", + __func__, pa, ret, prot_pmp, + tlb_size); - prot &= prot_pmp; + prot &= prot_pmp; + } } else { /* * Guest physical address translation failed, this is a HS * level exception */ first_stage_error = false; - if (ret != TRANSLATE_PMP_FAIL) { + if (ret != TRANSLATE_PMP_FAIL && + ret != TRANSLATE_MPT_FAIL) { env->guest_phys_fault_addr = (im_address | (address & - (TARGET_PAGE_SIZE - 1))) >> 2; + (TARGET_PAGE_SIZE - 1))) + >> 2; } } } @@ -XXX,XX +XXX,XX @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, HWADDR_FMT_plx " prot %d\n", __func__, address, ret, pa, prot); + if (ret == TRANSLATE_SUCCESS) { + ret = get_physical_address_mpt(env, &mpt_prot, pa, + access_type, mode); + if (riscv_cpu_cfg(env)->ext_smmpt) { + qemu_log_mask(CPU_LOG_MMU, + "%s MPT address=" HWADDR_FMT_plx " ret %d" + " prot %d\n", + __func__, pa, ret, mpt_prot); + } + prot &= mpt_prot; + } + if (ret == TRANSLATE_SUCCESS) { ret = get_physical_address_pmp(env, &prot_pmp, pa, size, access_type, mode); @@ -XXX,XX +XXX,XX @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, prot &= prot_pmp; } } - - if (ret == TRANSLATE_PMP_FAIL || ret == TRANSLATE_PMA_FAIL) { + /* + * Both MPT (Machine-level Memory Protection Table, Smmpt extension) and + * PMP (Physical Memory Protection) follow the same exception reporting + * rule when an access violation is detected + */ + if (ret == TRANSLATE_PMP_FAIL || ret == TRANSLATE_PMA_FAIL || + ret == TRANSLATE_MPT_FAIL) { pmp_pma_violation = true; } -- 2.43.0
This patch completes the SMMPT implementation by adding support for the new fence instructions: `mfence.pa` and `minval.pa`. According to the specification, these instructions act as memory ordering fences for MPT updates. In QEMU's TCG model, this is conservatively implemented by flushing the entire TLB, which ensures that any subsequent memory accesses will re-evaluate permissions and see the effects of any prior MPT modifications. The instructions are privileged and will cause an illegal instruction exception if executed outside of M-mode. Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- target/riscv/insn32.decode | 2 ++ .../tcg/insn_trans/trans_privileged.c.inc | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/target/riscv/insn32.decode b/target/riscv/insn32.decode index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/insn32.decode +++ b/target/riscv/insn32.decode @@ -XXX,XX +XXX,XX @@ sret 0001000 00010 00000 000 00000 1110011 mret 0011000 00010 00000 000 00000 1110011 wfi 0001000 00101 00000 000 00000 1110011 sfence_vma 0001001 ..... ..... 000 00000 1110011 @sfence_vma +mfence_pa 1000011 ..... ..... 000 00000 1110011 @sfence_vma +minval_pa 0000011 ..... ..... 000 00000 1110011 @sfence_vma # *** NMI *** mnret 0111000 00010 00000 000 00000 1110011 diff --git a/target/riscv/tcg/insn_trans/trans_privileged.c.inc b/target/riscv/tcg/insn_trans/trans_privileged.c.inc index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/insn_trans/trans_privileged.c.inc +++ b/target/riscv/tcg/insn_trans/trans_privileged.c.inc @@ -XXX,XX +XXX,XX @@ static bool trans_sfence_vma(DisasContext *ctx, arg_sfence_vma *a) #endif return false; } + +#define REQUIRE_SMSDID(ctx) do { \ + if (!ctx->cfg_ptr->ext_smsdid) { \ + return false; \ + } \ +} while (0) + +static bool do_mfence_pa(DisasContext *ctx) +{ +#ifndef CONFIG_USER_ONLY + REQUIRE_SMSDID(ctx); + if (ctx->priv != PRV_M) { + return false; + } + decode_save_opc(ctx, 0); + gen_helper_tlb_flush_all(tcg_env); + return true; +#endif + return false; +} + +static bool trans_mfence_pa(DisasContext *ctx, arg_mfence_pa *a) +{ + return do_mfence_pa(ctx); +} + +static bool trans_minval_pa(DisasContext *ctx, arg_minval_pa *a) +{ + return do_mfence_pa(ctx); +} -- 2.43.0
Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> Suggested-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Reviewed-by: Frank Chang <frank.chang@sifive.com> Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> --- target/riscv/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -XXX,XX +XXX,XX @@ const RISCVIsaExtData isa_edata_arr[] = { ISA_EXT_DATA_ENTRY(smdbltrp, PRIV_VERSION_1_13_0, ext_smdbltrp), ISA_EXT_DATA_ENTRY(smepmp, PRIV_VERSION_1_12_0, ext_smepmp), ISA_EXT_DATA_ENTRY(smpmpmt, PRIV_VERSION_1_12_0, ext_smpmpmt), - ISA_EXT_DATA_ENTRY(smrnmi, PRIV_VERSION_1_12_0, ext_smrnmi), ISA_EXT_DATA_ENTRY(smmpm, PRIV_VERSION_1_13_0, ext_smmpm), ISA_EXT_DATA_ENTRY(smnpm, PRIV_VERSION_1_13_0, ext_smnpm), + ISA_EXT_DATA_ENTRY(smrnmi, PRIV_VERSION_1_12_0, ext_smrnmi), ISA_EXT_DATA_ENTRY(smstateen, PRIV_VERSION_1_12_0, ext_smstateen), ISA_EXT_DATA_ENTRY(ssaia, PRIV_VERSION_1_12_0, ext_ssaia), ISA_EXT_DATA_ENTRY(ssccfg, PRIV_VERSION_1_13_0, ext_ssccfg), -- 2.43.0
Add disassembly support to the RISC-V disassembler for the machine CSRs and fence instructions introduced by the Smmpt/Smsdid series: - CSRs: mmpt (0x382), msdcfg (0x74e) - Instructions: mfence.pa, minval.pa Note that CSR 0x382 was previously mapped to the obsolete "mibase" register from the removed base-and-bound draft; it is now reused by Smmpt for mmpt. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- disas/riscv.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/disas/riscv.c b/disas/riscv.c index XXXXXXX..XXXXXXX 100644 --- a/disas/riscv.c +++ b/disas/riscv.c @@ -XXX,XX +XXX,XX @@ typedef enum { rv_op_cbo_flush = 958, rv_op_cbo_zero = 959, rv_op_mnret = 960, + rv_op_mfence_pa = 961, + rv_op_minval_pa = 962, } rv_op; /* register names */ @@ -XXX,XX +XXX,XX @@ const rv_opcode_data rvi_opcode_data[] = { { "cbo.flush", rv_codec_r, rv_fmt_rs1, NULL, 0, 0, 0 }, { "cbo.zero", rv_codec_r, rv_fmt_rs1, NULL, 0, 0, 0 }, { "mnret", rv_codec_none, rv_fmt_none, NULL, 0, 0, 0 }, + { "mfence.pa", rv_codec_r, rv_fmt_rs1_rs2, NULL, 0, 0, 0 }, + { "minval.pa", rv_codec_r, rv_fmt_rs1_rs2, NULL, 0, 0, 0 }, }; /* CSR names */ @@ -XXX,XX +XXX,XX @@ static const char *csr_name(int csrno) case 0x0344: return "mip"; case 0x0380: return "mbase"; case 0x0381: return "mbound"; - case 0x0382: return "mibase"; + case 0x0382: return "mmpt"; case 0x0383: return "mibound"; case 0x0384: return "mdbase"; case 0x0385: return "mdbound"; @@ -XXX,XX +XXX,XX @@ static const char *csr_name(int csrno) case 0x03ed: return "pmpaddr61"; case 0x03ee: return "pmpaddr62"; case 0x03ef: return "pmpaddr63"; + case 0x074e: return "msdcfg"; case 0x0780: return "mtohost"; case 0x0781: return "mfromhost"; case 0x0782: return "mreset"; @@ -XXX,XX +XXX,XX @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) case 928: op = rv_op_wrs_sto; break; } break; + case 96: op = rv_op_minval_pa; break; case 256: switch ((inst >> 20) & 0b11111) { case 2: @@ -XXX,XX +XXX,XX @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) case 576: op = rv_op_dret; break; } break; + case 2144: op = rv_op_mfence_pa; break; } break; case 1: op = rv_op_csrrw; break; -- 2.43.0
This patch implements v0.4.9 SMMPT specification(https://github.com/riscv/riscv-smmtt/releases/tag/v0.4.9). Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> Reviewed-by: Frank Chang <frank.chang@sifive.com> --- target/riscv/cpu.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -XXX,XX +XXX,XX @@ const RISCVIsaExtData isa_edata_arr[] = { ISA_EXT_DATA_ENTRY(smepmp, PRIV_VERSION_1_12_0, ext_smepmp), ISA_EXT_DATA_ENTRY(smpmpmt, PRIV_VERSION_1_12_0, ext_smpmpmt), ISA_EXT_DATA_ENTRY(smmpm, PRIV_VERSION_1_13_0, ext_smmpm), + ISA_EXPERIMENTAL_EXT_DATA_ENTRY(smmpt, PRIV_VERSION_1_13_0, ext_smmpt), ISA_EXT_DATA_ENTRY(smnpm, PRIV_VERSION_1_13_0, ext_smnpm), ISA_EXT_DATA_ENTRY(smrnmi, PRIV_VERSION_1_12_0, ext_smrnmi), + ISA_EXPERIMENTAL_EXT_DATA_ENTRY(smsdid, PRIV_VERSION_1_13_0, ext_smsdid), ISA_EXT_DATA_ENTRY(smstateen, PRIV_VERSION_1_12_0, ext_smstateen), ISA_EXT_DATA_ENTRY(ssaia, PRIV_VERSION_1_12_0, ext_ssaia), ISA_EXT_DATA_ENTRY(ssccfg, PRIV_VERSION_1_13_0, ext_ssccfg), @@ -XXX,XX +XXX,XX @@ static RISCVCPUImpliedExtsRule ZVFBFA_IMPLIED = { }, }; +static RISCVCPUImpliedExtsRule SMMPT_IMPLIED = { + .ext = CPU_CFG_OFFSET(ext_smmpt), + .implied_multi_exts = { + CPU_CFG_OFFSET(ext_smsdid), + + RISCV_IMPLIED_EXTS_RULE_END + }, +}; + RISCVCPUImpliedExtsRule *riscv_misa_ext_implied_rules[] = { &RVA_IMPLIED, &RVD_IMPLIED, &RVF_IMPLIED, &RVM_IMPLIED, &RVV_IMPLIED, &RVG_IMPLIED, @@ -XXX,XX +XXX,XX @@ RISCVCPUImpliedExtsRule *riscv_multi_ext_implied_rules[] = { &ZVKS_IMPLIED, &ZVKSC_IMPLIED, &ZVKSG_IMPLIED, &SHA_IMPLIED, &SSCFG_IMPLIED, &SUPM_IMPLIED, &SSPM_IMPLIED, &SMCTR_IMPLIED, &SSCTR_IMPLIED, &SSSTATEEN_IMPLIED, - NULL + &SMMPT_IMPLIED, NULL }; static const Property riscv_cpu_properties[] = { -- 2.43.0
Add a bare-metal M-mode assembly test (test-smmpt.S) that validates the Smmpt43 MPT lookup and permission enforcement against SMMTT v0.4.9. The test builds a 3-level Smmpt43 MPT in RAM and uses the MPRV trick (mstatus.MPRV=1, MPP=S, satp=Bare) so that data accesses are subject to MPT checks while instruction fetches remain in M-mode (which bypasses the MPT). PMP is configured to grant all permissions so that only the MPT gates the accesses. Coverage: mfence.pa in M-mode; every XWR encoding (RW, no-access, R-only, R+X, RWX, the reserved 010/110 encodings and X-only) checked for both load and store, verifying the correct exception cause (load access fault = 5, store/AMO access fault = 7); an invalid (V=0) leaf; a leaf with a reserved bit set; and NAPOT leaves with a valid and a reserved G field. The mode-independent harness (MPRV helpers, per-access check/verify subroutines, the M-mode trap handler and the semihosting exit) lives in a shared smmpt-common.S so the Smmpt52/Smmpt64/Smmpt34 tests can reuse it. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- tests/tcg/riscv64/Makefile.softmmu-target | 4 + tests/tcg/riscv64/smmpt-common.S | 256 ++++++++++++++++++++++ tests/tcg/riscv64/test-smmpt.S | 85 +++++++ 3 files changed, 345 insertions(+) create mode 100644 tests/tcg/riscv64/smmpt-common.S create mode 100644 tests/tcg/riscv64/test-smmpt.S diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target index XXXXXXX..XXXXXXX 100644 --- a/tests/tcg/riscv64/Makefile.softmmu-target +++ b/tests/tcg/riscv64/Makefile.softmmu-target @@ -XXX,XX +XXX,XX @@ comma:= , run-test-crc32: test-crc32 $(call run-test, $<, $(QEMU) -cpu rv64$(comma)xlrbr=true $(QEMU_OPTS)$<) +EXTRA_RUNS += run-test-smmpt +run-test-smmpt: test-smmpt + $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<) + # We don't currently support the multiarch system tests undefine MULTIARCH_TESTS diff --git a/tests/tcg/riscv64/smmpt-common.S b/tests/tcg/riscv64/smmpt-common.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/smmpt-common.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Shared harness for the Smmpt (Supervisor Memory Protection Table) system + * tests, per SMMTT specification v0.4.9. + * + * This header is meant to be #included by the per-mode Smmpt test programs + * (Smmpt34 on RV32, Smmpt43/52/64 on RV64). All the mode-independent logic + * lives here so each test only has to build its mode-specific MPT and invoke + * RUN_LEAF_CHECKS. + * + * The tests run in M-mode and use the MPRV trick (mstatus.MPRV=1, + * mstatus.MPP=S, satp=Bare so PA==VA) to subject explicit data accesses to + * MPT checks while the instruction stream keeps running in M-mode (M-mode + * fetches bypass the MPT). The data-access width is irrelevant to the MPT + * check, so the harness uses lw/sw and works unmodified on RV32 and RV64. + * + * The MPT XWR field uses the standard bit order R=bit0, W=bit1, X=bit2 + * (matching QEMU PAGE_READ/PAGE_WRITE/PAGE_EXEC), so Read+Write is 0b011=3. + * Per the spec the exception is reported for the original access type: a load + * violation raises "load access fault" (cause 5) and a store violation raises + * "store/AMO access fault" (cause 7). + * + * Execute permission is not exercised: the MPRV trick only redirects data + * accesses, while instruction fetch stays in M-mode and bypasses the MPT. + * X-bearing encodings are therefore checked only for their load/store + * behaviour (e.g. X-only denies loads, R+X denies stores). + * + * The tests expect an MPT laid out so that these supervisor physical + * addresses resolve as follows (identical across all modes): + * + * 0x8050_0000..0x8050_7000 non-NAPOT leaf, XWR[pi] tuples (LEAF_ALL_XWR): + * pi0 011 (RW) pi1 000 (no access) + * pi2 001 (R) pi3 101 (R+X) + * pi4 111 (RWX) pi5 010 (reserved) + * pi6 110 (rsvd) pi7 100 (X only) + * 0x8051_0000 invalid leaf (V=0) + * 0x8052_0000 leaf with a reserved bit set + * 0x8060_0000 NAPOT leaf with a valid G + * 0x8080_0000 NAPOT leaf with a reserved G + * + * On any mismatch the test exits (via semihosting) with a non-zero code that + * identifies the failing check; a successful run exits with 0. + */ + + /* mstatus bits (same positions on RV32 and RV64) */ + .equ MSTATUS_MPP_S, (1 << 11) /* MPP = 01 (Supervisor) */ + .equ MSTATUS_MPP_M, (3 << 11) /* MPP mask */ + .equ MSTATUS_MPRV, (1 << 17) + + /* Expected fault causes */ + .equ CAUSE_LOAD_ACCESS_FAULT, 5 + .equ CAUSE_STORE_ACCESS_FAULT, 7 + + .equ EXPECT_OK, 0 + .equ EXPECT_FAULT, 1 + + /* + * Non-NAPOT leaf value carrying all eight XWR encodings in pi0..pi7: + * V|L | pi0=011 pi1=000 pi2=001 pi3=101 pi4=111 pi5=010 pi6=110 pi7=100 + * The XWR tuples occupy bits [31:8], valid for both the 4-byte (RV32) + * and 8-byte (RV64) leaf formats. + */ + .equ LEAF_ALL_XWR, 0x997A4303 + + /* + * Switch the effective privilege of data accesses to S-mode so that + * they are subject to MPT checks (MPP=S, MPRV=1). Uses t0 only. + */ + .macro ENTER_MPRV + li t0, MSTATUS_MPP_M + csrc mstatus, t0 /* clear MPP */ + li t0, MSTATUS_MPP_S + csrs mstatus, t0 /* MPP = S */ + li t0, MSTATUS_MPRV + csrs mstatus, t0 /* enable MPRV */ + .endm + + .macro EXIT_MPRV + li t0, MSTATUS_MPRV + csrc mstatus, t0 /* disable MPRV */ + .endm + + /* + * Perform an MPT-checked load/store from \addr and verify the outcome. + * \eflt : EXPECT_OK or EXPECT_FAULT + * \ecause : expected mcause when a fault is expected + * \code : exit code reported if the check fails + */ + .macro TEST_LOAD addr, eflt, ecause, code + li a3, \addr + jal do_load + li a4, \eflt + li a5, \ecause + li a6, \code + jal check + .endm + + .macro TEST_STORE addr, eflt, ecause, code + li a3, \addr + li t3, 0x1234 + jal do_store + li a4, \eflt + li a5, \ecause + li a6, \code + jal check + .endm + + /* + * MFENCE.PA (funct7=0b1000011) must not fault in M-mode. Reuses the + * generic check subroutine (expects no fault). + */ + .macro TEST_MFENCE_PA code + li s0, 0 + .insn r 0x73, 0, 0x43, x0, x0, x0 + li a4, EXPECT_OK + li a6, \code + jal check + .endm + + /* + * Run the full set of MPT permission and structural checks against the + * shared address layout described above. + */ + .macro RUN_LEAF_CHECKS + /* pi0 RW: load and store both allowed */ + TEST_LOAD 0x80500000, EXPECT_OK, 0, 2 + TEST_STORE 0x80500000, EXPECT_OK, 0, 3 + /* pi1 no-access: load faults (cause 5), store faults (cause 7) */ + TEST_LOAD 0x80501000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 4 + TEST_STORE 0x80501000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 5 + /* pi2 R-only: load allowed, store faults */ + TEST_LOAD 0x80502000, EXPECT_OK, 0, 6 + TEST_STORE 0x80502000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 7 + /* pi3 R+X: load allowed, store faults */ + TEST_LOAD 0x80503000, EXPECT_OK, 0, 8 + TEST_STORE 0x80503000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 9 + /* pi4 RWX: load and store both allowed */ + TEST_LOAD 0x80504000, EXPECT_OK, 0, 10 + TEST_STORE 0x80504000, EXPECT_OK, 0, 11 + /* pi5/pi6 reserved encodings (010/110): load faults (cause 5) */ + TEST_LOAD 0x80505000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 12 + TEST_LOAD 0x80506000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 13 + /* pi7 X-only: load faults (cause 5) */ + TEST_LOAD 0x80507000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 14 + /* invalid leaf (V=0): load faults (cause 5) */ + TEST_LOAD 0x80510000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 15 + /* leaf with a reserved bit set: load faults (cause 5) */ + TEST_LOAD 0x80520000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 16 + /* NAPOT leaf, valid G, RW: load and store both allowed */ + TEST_LOAD 0x80600000, EXPECT_OK, 0, 17 + TEST_STORE 0x80600000, EXPECT_OK, 0, 18 + /* NAPOT leaf with a reserved G: load faults (cause 5) */ + TEST_LOAD 0x80800000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 19 + .endm + + /* + * Emit the shared subroutines, trap handler, semihosting exit and data. + * Invoke once, after the test's _start code. + */ + .macro SMMPT_HARNESS + /* + * do_load / do_store: perform one MPT-checked access from a3. + * On return, s0 = 1 if the access faulted (else 0) and s1 = mcause. + * do_store uses the value in t3. lw/sw keep these width-agnostic. + */ + .balign 4 +do_load: + li s0, 0 + li s1, 0 + ENTER_MPRV + lw t4, 0(a3) /* MPT-checked load */ + EXIT_MPRV + ret + +do_store: + li s0, 0 + li s1, 0 + ENTER_MPRV + sw t3, 0(a3) /* MPT-checked store */ + EXIT_MPRV + ret + + /* + * check: verify the outcome of the last access. + * a4 = expected fault (0/1), a5 = expected cause, a6 = fail code. + * Returns on success; exits with a6 on mismatch. + */ + .balign 4 +check: + beqz a4, 1f + beqz s0, 2f /* expected a fault but none occurred */ + bne s1, a5, 2f /* faulted with the wrong cause */ + ret +1: + bnez s0, 2f /* unexpected fault */ + ret +2: + mv a0, a6 + j _exit + + /* + * M-mode trap handler. Records that a fault occurred (s0=1) and the + * mcause (s1), skips the faulting 4-byte instruction, disables MPRV + * and returns. Only clobbers t5/t6 besides s0/s1. + */ + .balign 4 +mtrap: + csrr t5, mcause + li s0, 1 + mv s1, t5 + csrr t6, mepc + addi t6, t6, 4 /* skip the faulting instruction */ + csrw mepc, t6 + li t5, MSTATUS_MPRV + csrc mstatus, t5 /* ensure MPRV is off on return */ + mret + + /* Exit via semihosting (ADP_Stopped_ApplicationExit) */ +_exit: +#if __riscv_xlen == 32 + /* + * On RV32 the SYS_EXIT_EXTENDED parameter block cannot be used: when + * this test runs on qemu-system-riscv64 with a 32-bit CPU the block + * pointer is sign-extended (0xffffffff_8xxx_xxxx) and the semihosting + * argument read faults. Use the plain SYS_EXIT, which takes the exit + * reason directly in a1: a successful run (a0==0) reports + * ADP_Stopped_ApplicationExit and any failure reports a non-zero + * reason (reported by QEMU as exit status 1). + */ + li a1, 0x20026 /* ADP_Stopped_ApplicationExit */ + beqz a0, 1f + mv a1, a0 /* non-zero reason -> exit status 1 */ +1: + li a0, 0x18 /* TARGET_SYS_EXIT */ +#else + lla a1, semiargs + li t0, 0x20026 + sd t0, 0(a1) + sd a0, 8(a1) + li a0, 0x20 /* TARGET_SYS_EXIT_EXTENDED */ +#endif + + .balign 16 + slli zero, zero, 0x1f + ebreak + srai zero, zero, 0x7 + j . + +#if __riscv_xlen != 32 + .data + .balign 8 +semiargs: + .space 16 +#endif + .endm diff --git a/tests/tcg/riscv64/test-smmpt.S b/tests/tcg/riscv64/test-smmpt.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/test-smmpt.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test for the Smmpt43 (Supervisor Memory Protection Table) extension, + * per SMMTT specification v0.4.9. + * + * The test runs in M-mode and builds a 3-level Smmpt43 MPT, then drives the + * mode-independent checks in smmpt-common.S. See that harness for the MPRV + * trick, the shared address layout and the exit-code convention. + * + * MPT layout (Smmpt43, 3 levels, 8-byte entries, 16 pages per leaf, + * pi = SPA[15:12], pn[i] 9 bits at SPA[16 + i*9 +: 9]): + * + * L2 (root) @ 0x8040_0000 : entry[0] -> non-leaf, PPN(L1)=0x80410 + * L1 @ 0x8041_0000 : entry[64] -> non-leaf, PPN(L0)=0x80420 + * L0 @ 0x8042_0000 : + * entry[80] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+ + * entry[81] -> V=0 (invalid) covering 0x8051_0000 + * entry[82] -> leaf with a reserved bit set covering 0x8052_0000 + * entry[96] -> NAPOT leaf, G=4, XWR=RW covering 0x8060_0000 + * entry[128] -> NAPOT leaf, reserved G=5 covering 0x8080_0000 + * + * mmpt = MODE(1=Smmpt43)<<60 | PPN(0x80400) + */ + +#include "smmpt-common.S" + + .option norvc + + .text + .global _start +_start: + /* Install the M-mode trap handler */ + lla t0, mtrap + csrw mtvec, t0 + csrw medeleg, zero + + /* PMP entry 0: NAPOT covering the whole space, RWX, so S-mode + * accesses pass PMP and the MPT is the only gate. */ + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x1f /* A=NAPOT(0x18) | R | W | X */ + csrw pmpcfg0, t0 + + /* Build the MPT tables (M-mode stores bypass the MPT). */ + /* L2[0] = non-leaf -> L1 (PPN 0x80410): (0x80410 << 10) | V */ + li t0, 0x80400000 + li t1, 0x20104001 + sd t1, 0(t0) + /* L1[64] = non-leaf -> L0 (PPN 0x80420): (0x80420 << 10) | V */ + li t0, 0x80410000 + li t1, 0x20108001 + sd t1, 0x200(t0) /* 64 * 8 = 0x200 */ + + li t0, 0x80420000 + /* L0[80] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */ + li t1, LEAF_ALL_XWR + sd t1, 0x280(t0) /* 80 * 8 = 0x280 */ + /* L0[81] = 0: invalid entry (V=0) */ + sd x0, 0x288(t0) /* 81 * 8 = 0x288 */ + /* L0[82] = leaf with reserved bit 3 set (V|L|rsv|XWR[pi0]=RW) */ + li t1, 0x30B + sd t1, 0x290(t0) /* 82 * 8 = 0x290 */ + /* L0[96] = NAPOT leaf: V|L|N | XWR=RW(0x300) | G=4(0x4000) */ + li t1, 0x4307 + sd t1, 0x300(t0) /* 96 * 8 = 0x300 */ + /* L0[128] = NAPOT leaf with reserved G=5: V|L|N | XWR=RW | G=5(0x5000) */ + li t1, 0x5307 + sd t1, 0x400(t0) /* 128 * 8 = 0x400 */ + + /* Program mmpt: MODE=1 (Smmpt43), PPN = 0x80400 */ + li t0, 0x1000000000080400 + csrw 0x382, t0 /* CSR_MMPT */ + + /* mfence.pa must not fault in M-mode */ + TEST_MFENCE_PA 1 + + /* Run the shared permission and structural checks */ + RUN_LEAF_CHECKS + + /* All tests passed */ + li a0, 0 + j _exit + + SMMPT_HARNESS -- 2.43.0
Extend the SMMPT system-test coverage to the two larger RV64 memory protection schemes: Smmpt52 (4-level MPT, 52-bit SPA) and Smmpt64 (5-level MPT, 64-bit SPA with a 32 KiB / 32 KiB-aligned root table). Both tests reuse the mode-independent harness in smmpt-common.S and only differ in the number of radix levels and, for Smmpt64, the root-table size and alignment. They exercise the same permission and structural checks (all XWR encodings for load and store, V=0 and reserved-bit leaves, and NAPOT leaves with a valid and a reserved G field) and are wired into the Makefile as run-test-smmpt52 and run-test-smmpt64. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- tests/tcg/riscv64/Makefile.softmmu-target | 8 ++ tests/tcg/riscv64/test-smmpt52.S | 91 +++++++++++++++++++++ tests/tcg/riscv64/test-smmpt64.S | 98 +++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 tests/tcg/riscv64/test-smmpt52.S create mode 100644 tests/tcg/riscv64/test-smmpt64.S diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target index XXXXXXX..XXXXXXX 100644 --- a/tests/tcg/riscv64/Makefile.softmmu-target +++ b/tests/tcg/riscv64/Makefile.softmmu-target @@ -XXX,XX +XXX,XX @@ EXTRA_RUNS += run-test-smmpt run-test-smmpt: test-smmpt $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<) +EXTRA_RUNS += run-test-smmpt52 +run-test-smmpt52: test-smmpt52 + $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<) + +EXTRA_RUNS += run-test-smmpt64 +run-test-smmpt64: test-smmpt64 + $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<) + # We don't currently support the multiarch system tests undefine MULTIARCH_TESTS diff --git a/tests/tcg/riscv64/test-smmpt52.S b/tests/tcg/riscv64/test-smmpt52.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/test-smmpt52.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test for the Smmpt52 (Supervisor Memory Protection Table) extension, + * per SMMTT specification v0.4.9. + * + * Smmpt52 uses a 4-level MPT with 8-byte entries (16 pages per leaf, + * pi = SPA[15:12], pn[i] 9 bits at SPA[16 + i*9 +: 9]). It shares the MPTE + * formats and the permission-lookup algorithm with Smmpt43, only adding one + * more radix level. See smmpt-common.S for the MPRV trick, the shared + * address layout and the exit-code convention. + * + * For the low (< 4 GiB) addresses exercised here pn[3] and pn[2] are 0, so + * the upper levels are single-entry chains: + * + * L3 (root) @ 0x8040_0000 : entry[0] -> non-leaf, PPN(L2)=0x80410 + * L2 @ 0x8041_0000 : entry[0] -> non-leaf, PPN(L1)=0x80420 + * L1 @ 0x8042_0000 : entry[64] -> non-leaf, PPN(L0)=0x80430 + * L0 @ 0x8043_0000 : + * entry[80] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+ + * entry[81] -> V=0 (invalid) covering 0x8051_0000 + * entry[82] -> leaf with a reserved bit set covering 0x8052_0000 + * entry[96] -> NAPOT leaf, G=4, XWR=RW covering 0x8060_0000 + * entry[128] -> NAPOT leaf, reserved G=5 covering 0x8080_0000 + * + * mmpt = MODE(2=Smmpt52)<<60 | PPN(0x80400) + */ + +#include "smmpt-common.S" + + .option norvc + + .text + .global _start +_start: + /* Install the M-mode trap handler */ + lla t0, mtrap + csrw mtvec, t0 + csrw medeleg, zero + + /* PMP entry 0: NAPOT covering the whole space, RWX. */ + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x1f + csrw pmpcfg0, t0 + + /* Build the MPT tables (M-mode stores bypass the MPT). */ + /* L3[0] = non-leaf -> L2 (PPN 0x80410) */ + li t0, 0x80400000 + li t1, 0x20104001 + sd t1, 0(t0) + /* L2[0] = non-leaf -> L1 (PPN 0x80420) */ + li t0, 0x80410000 + li t1, 0x20108001 + sd t1, 0(t0) + /* L1[64] = non-leaf -> L0 (PPN 0x80430) */ + li t0, 0x80420000 + li t1, 0x2010C001 + sd t1, 0x200(t0) /* 64 * 8 = 0x200 */ + + li t0, 0x80430000 + /* L0[80] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */ + li t1, LEAF_ALL_XWR + sd t1, 0x280(t0) /* 80 * 8 = 0x280 */ + /* L0[81] = 0: invalid entry (V=0) */ + sd x0, 0x288(t0) /* 81 * 8 = 0x288 */ + /* L0[82] = leaf with reserved bit 3 set */ + li t1, 0x30B + sd t1, 0x290(t0) /* 82 * 8 = 0x290 */ + /* L0[96] = NAPOT leaf: V|L|N | XWR=RW | G=4 */ + li t1, 0x4307 + sd t1, 0x300(t0) /* 96 * 8 = 0x300 */ + /* L0[128] = NAPOT leaf with reserved G=5 */ + li t1, 0x5307 + sd t1, 0x400(t0) /* 128 * 8 = 0x400 */ + + /* Program mmpt: MODE=2 (Smmpt52), PPN = 0x80400 */ + li t0, 0x2000000000080400 + csrw 0x382, t0 /* CSR_MMPT */ + + /* mfence.pa must not fault in M-mode */ + TEST_MFENCE_PA 1 + + /* Run the shared permission and structural checks */ + RUN_LEAF_CHECKS + + /* All tests passed */ + li a0, 0 + j _exit + + SMMPT_HARNESS diff --git a/tests/tcg/riscv64/test-smmpt64.S b/tests/tcg/riscv64/test-smmpt64.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/test-smmpt64.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test for the Smmpt64 (Supervisor Memory Protection Table) extension, + * per SMMTT specification v0.4.9. + * + * Smmpt64 uses a 5-level MPT with 8-byte entries. Unlike the other RV64 + * modes its root table is 32 KiB (2^12 entries, pn[4] = SPA[63:52], 12 bits) + * and must be aligned to a 32 KiB boundary; the non-root levels are the usual + * 4 KiB / 9-bit tables (16 pages per leaf, pi = SPA[15:12]). See + * smmpt-common.S for the MPRV trick, the shared address layout and the + * exit-code convention. + * + * For the low (< 4 GiB) addresses exercised here pn[4], pn[3] and pn[2] are + * 0, so the upper levels are single-entry chains: + * + * L4 (root) @ 0x8040_0000 : entry[0] -> non-leaf, PPN(L3)=0x80410 + * (32 KiB / 32 KiB-aligned) + * L3 @ 0x8041_0000 : entry[0] -> non-leaf, PPN(L2)=0x80420 + * L2 @ 0x8042_0000 : entry[0] -> non-leaf, PPN(L1)=0x80430 + * L1 @ 0x8043_0000 : entry[64] -> non-leaf, PPN(L0)=0x80440 + * L0 @ 0x8044_0000 : + * entry[80] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+ + * entry[81] -> V=0 (invalid) covering 0x8051_0000 + * entry[82] -> leaf with a reserved bit set covering 0x8052_0000 + * entry[96] -> NAPOT leaf, G=4, XWR=RW covering 0x8060_0000 + * entry[128] -> NAPOT leaf, reserved G=5 covering 0x8080_0000 + * + * mmpt = MODE(3=Smmpt64)<<60 | PPN(0x80400) (PPN[2:0] must be 0) + */ + +#include "smmpt-common.S" + + .option norvc + + .text + .global _start +_start: + /* Install the M-mode trap handler */ + lla t0, mtrap + csrw mtvec, t0 + csrw medeleg, zero + + /* PMP entry 0: NAPOT covering the whole space, RWX. */ + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x1f + csrw pmpcfg0, t0 + + /* Build the MPT tables (M-mode stores bypass the MPT). */ + /* L4[0] = non-leaf -> L3 (PPN 0x80410); root is 32 KiB aligned */ + li t0, 0x80400000 + li t1, 0x20104001 + sd t1, 0(t0) + /* L3[0] = non-leaf -> L2 (PPN 0x80420) */ + li t0, 0x80410000 + li t1, 0x20108001 + sd t1, 0(t0) + /* L2[0] = non-leaf -> L1 (PPN 0x80430) */ + li t0, 0x80420000 + li t1, 0x2010C001 + sd t1, 0(t0) + /* L1[64] = non-leaf -> L0 (PPN 0x80440) */ + li t0, 0x80430000 + li t1, 0x20110001 + sd t1, 0x200(t0) /* 64 * 8 = 0x200 */ + + li t0, 0x80440000 + /* L0[80] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */ + li t1, LEAF_ALL_XWR + sd t1, 0x280(t0) /* 80 * 8 = 0x280 */ + /* L0[81] = 0: invalid entry (V=0) */ + sd x0, 0x288(t0) /* 81 * 8 = 0x288 */ + /* L0[82] = leaf with reserved bit 3 set */ + li t1, 0x30B + sd t1, 0x290(t0) /* 82 * 8 = 0x290 */ + /* L0[96] = NAPOT leaf: V|L|N | XWR=RW | G=4 */ + li t1, 0x4307 + sd t1, 0x300(t0) /* 96 * 8 = 0x300 */ + /* L0[128] = NAPOT leaf with reserved G=5 */ + li t1, 0x5307 + sd t1, 0x400(t0) /* 128 * 8 = 0x400 */ + + /* Program mmpt: MODE=3 (Smmpt64), PPN = 0x80400 */ + li t0, 0x3000000000080400 + csrw 0x382, t0 /* CSR_MMPT */ + + /* mfence.pa must not fault in M-mode */ + TEST_MFENCE_PA 1 + + /* Run the shared permission and structural checks */ + RUN_LEAF_CHECKS + + /* All tests passed */ + li a0, 0 + j _exit + + SMMPT_HARNESS -- 2.43.0
Add SMMPT system-test coverage for Smmpt34, the RV32-only memory protection scheme (2-level MPT with 4-byte entries). Smmpt34 requires a 32-bit CPU, but qemu-system-riscv64 can run a 32-bit CPU (-cpu rv32), so the test lives in tests/tcg/riscv64 alongside the RV64 Smmpt tests and shares the same harness (smmpt-common.S). It only differs in the table geometry: 4-byte MPTEs, two radix levels, a 10-bit level-0 index and the RV32 mmpt CSR layout (MODE in bits[31:30]). It exercises the same permission and structural checks as the RV64 tests (all XWR encodings for load and store, V=0 and reserved-bit leaves, and NAPOT leaves with a valid and a reserved G field), using the same access addresses. It is built for RV32 and run on qemu-system-riscv64 with -cpu rv32. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- tests/tcg/riscv64/test-smmpt34.S | 97 ++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/tcg/riscv64/test-smmpt34.S diff --git a/tests/tcg/riscv64/test-smmpt34.S b/tests/tcg/riscv64/test-smmpt34.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/test-smmpt34.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test for the Smmpt34 (Supervisor Memory Protection Table) extension, + * per SMMTT specification v0.4.9. Smmpt34 is the RV32-only scheme. + * + * Although Smmpt34 requires a 32-bit CPU, the program is run on + * qemu-system-riscv64 with a 32-bit CPU (-cpu rv32), which lives in the + * same target as the RV64 Smmpt tests. It is therefore kept here in + * tests/tcg/riscv64 alongside the other Smmpt tests and shares the same + * harness (smmpt-common.S). Build it for RV32 and run it as: + * + * $CC -march=rv32ima_zicsr -mabi=ilp32 -I tests/tcg/riscv64 \ + * tests/tcg/riscv64/test-smmpt34.S -Wa,--noexecstack -c -o smmpt34.o + * $LD -m elf32lriscv -T tests/tcg/riscv64/semihost.ld smmpt34.o -o smmpt34 + * qemu-system-riscv64 -cpu rv32,x-smmpt=true -M virt -display none \ + * -semihosting -device loader,file=smmpt34 + * + * Smmpt34 uses a 2-level MPT with 4-byte entries (8 pages per leaf, + * pi = SPA[14:12]). The supervisor physical address is partitioned as + * pn[1] = SPA[33:25] (9-bit root index), pn[0] = SPA[24:15] (10-bit level-0 + * index) and range offset SPA[14:0]. For the RAM addresses used here + * (0x8xxx_xxxx, < 4 GiB) pn[1] resolves to 64. See smmpt-common.S for the + * MPRV trick, the shared address layout and the exit-code convention. + * + * This test reuses the same shared harness and the same access addresses as + * the RV64 tests; only the table geometry (4-byte entries, 2 levels, 10-bit + * level-0 index) and the RV32 mmpt layout differ. + * + * MPT layout (Smmpt34, 2 levels, 4-byte entries): + * + * L1 (root) @ 0x8040_0000 : entry[64] -> non-leaf, PPN(L0)=0x80410 + * L0 @ 0x8041_0000 : + * entry[160] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+ + * entry[162] -> V=0 (invalid) covering 0x8051_0000 + * entry[164] -> leaf with a reserved bit set covering 0x8052_0000 + * entry[192] -> NAPOT leaf, G=6, XWR=RW covering 0x8060_0000 + * entry[256] -> NAPOT leaf, reserved G=0 covering 0x8080_0000 + * + * mmpt = MODE(1=Smmpt34)<<30 | PPN(0x80400) + */ + +#include "smmpt-common.S" + + .option norvc + + .text + .global _start +_start: + /* Install the M-mode trap handler */ + lla t0, mtrap + csrw mtvec, t0 + csrw medeleg, zero + + /* PMP entry 0: NAPOT covering the whole space, RWX. */ + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x1f + csrw pmpcfg0, t0 + + /* Build the MPT tables (M-mode stores bypass the MPT). */ + /* L1[64] = non-leaf -> L0 (PPN 0x80410): (0x80410 << 10) | V */ + li t0, 0x80400000 + li t1, 0x20104001 + sw t1, 0x100(t0) /* 64 * 4 = 0x100 */ + + li t0, 0x80410000 + /* L0[160] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */ + li t1, LEAF_ALL_XWR + sw t1, 0x280(t0) /* 160 * 4 = 0x280 */ + /* L0[162] = 0: invalid entry (V=0) */ + sw x0, 0x288(t0) /* 162 * 4 = 0x288 */ + /* L0[164] = leaf with reserved bit 3 set (V|L|rsv|XWR[pi0]=RW) */ + li t1, 0x30B + sw t1, 0x290(t0) /* 164 * 4 = 0x290 */ + /* L0[192] = NAPOT leaf: V|L|N | XWR=RW(0x300) | G=6(0x6000) */ + li t1, 0x6307 + sw t1, 0x300(t0) /* 192 * 4 = 0x300 */ + /* L0[256] = NAPOT leaf with reserved G=0: V|L|N | XWR=RW | G=0 */ + li t1, 0x307 + sw t1, 0x400(t0) /* 256 * 4 = 0x400 */ + + /* Program mmpt: MODE=1 (Smmpt34) at bits[31:30], PPN = 0x80400 */ + li t0, 0x40080400 + csrw 0x382, t0 /* CSR_MMPT */ + + /* mfence.pa must not fault in M-mode */ + TEST_MFENCE_PA 1 + + /* Run the shared permission and structural checks */ + RUN_LEAF_CHECKS + + /* All tests passed */ + li a0, 0 + j _exit + + SMMPT_HARNESS -- 2.43.0
This patch set introduces support for the RISC-V Smsdid and Smmpt (Supervisor Domain Identifier and Memory Protection Table) extensions v0.4.9(https://github.com/riscv/riscv-smmtt/releases/tag/v0.4.9). Smmpt provides a hardware mechanism for fine-grained memory protection, checked after address translation, which is particularly useful for supervisor-level sandboxing and security monitoring. The rfc patch set: https://mail.gnu.org/archive/html/qemu-riscv/2025-09/msg00216.html Rahul Pathak is working on SMMPT OpenSBI support - https://lists.infradead.org/pipermail/opensbi/2026-July/010235.html based on his QEMU SMMPT branch - https://github.com/pathakraul/qemu/tree/rpathak_smmpt_v1 As we all agree - https://patchew.org/QEMU/20260715145016.17369-1-zhiwei._5Fliu@linux.alibaba.com/ , we will collaborate on this work and not duplicate efforts. Rahul will focus on OpenSBI upstream and I will update the QEMU patch set to v8. Xiangyi will review the OpenSBI SMMPT patch set and may collaborate to upstream the other parts, such as SMSDIA and IOMPT. v7->v8: 1. Update the mfence.pa and minval.pa instruction encodings to match the upstream riscv-opcodes definition (https://github.com/riscv/riscv-opcodes/pull/389/): mfence.pa now uses funct7=0b0011001 and minval.pa uses funct7=0b0011011. The decoder, disassembler and the system test are updated accordingly. v6->v7: The implementation is updated from the v0.3.4 specification to v0.4.9. The main changes are: 1. Update the mmpt CSR number to 0x382 and msdcfg to 0x74E. 2. Update the mmpt register layout: the SDID and PPN field positions changed and the RV64 PPN is now 44 bits. 3. Rework the MPTE format: the N (NAPOT) bit now lives at bit 2, the non-leaf NAPOT form is removed, NAPOT leaf entries encode a single XWR tuple plus a G granularity field, and non-NAPOT leaf entries hold per-page XWR tuples. 4. Treat XWR=000 as "no access" and a non-leaf entry with N=1 as a fault. 5. Enforce the Smmpt64 root-table PPN alignment (low 3 bits zero). 6. Rename the fence instructions mfence.spa/minval.spa to mfence.pa/minval.pa. 7. Drop stale Reviewed-by tags on the substantially reworked patches. 8. Introduce the configurable "smmpt-sdidlen" CPU property. 9. Add disassembly support for the Smmpt. 10. Add bare-metal M-mode system tests. 11. Rebase to master. v5->v6: 1. Use explicitly bit fields extract instead of mpte_union_t. 2. Use the same exception behavior for MPT valiation as PMP valiation. 3. Use PAGE_* instead of MPT_ACCESS_* as they have same value. 4. Use address_space_*_le instead of address_space_* for SMMPT. 5. Only print SMMPT address check log when SMMPT is enabled. 6. Add implied rule for SMMPT as it depends on SMSDID. 7. Rebase to master. v4->v5: 1. Rebase to master. v3->v4: 1. Add missing review tags. v2->v3: 1. Fix build error in patch 2. 2. Rebase to master. rfc->v2: 1. When ext_smmpt is false or BARE mode, make other fields in mmpt CSR zero. 2. Add patch 5 to fix smrnmi ISA string order. 3. Fix patch 6 smmpt and smsdid ISA string order. 4. Make smmpt and smsdid experiment extensions. 5. Add review tags. LIU Zhiwei (11): target/riscv: Add basic definitions and CSRs for SMMPT target/riscv: Add smmpt-sdidlen property for the mmpt SDID field target/riscv: Implement core SMMPT lookup logic target/riscv: Integrate SMMPT checks into MMU and TLB fill target/riscv: Implement SMMPT fence instructions target/riscv: Fix smrnmi isa alphabetical order target/riscv: Add disassembly for Smmpt instructions and CSRs target/riscv: Enable SMMPT extension target/riscv: Add system test for SMMPT extension target/riscv: Add system tests for Smmpt52 and Smmpt64 target/riscv: Add system test for Smmpt34 disas/riscv.c | 9 +- target/riscv/cpu.c | 60 +++- target/riscv/cpu.h | 8 + target/riscv/cpu_bits.h | 27 ++ target/riscv/cpu_cfg_fields.h.inc | 3 + target/riscv/insn32.decode | 2 + target/riscv/meson.build | 1 + target/riscv/riscv_smmpt.c | 339 ++++++++++++++++++ target/riscv/riscv_smmpt.h | 26 ++ target/riscv/tcg/cpu_helper.c | 119 +++++- target/riscv/tcg/csr.c | 106 ++++++ .../tcg/insn_trans/trans_privileged.c.inc | 30 ++ target/riscv/tcg/pmp.h | 3 + tests/tcg/riscv64/Makefile.softmmu-target | 12 + tests/tcg/riscv64/smmpt-common.S | 257 +++++++++++++ tests/tcg/riscv64/test-smmpt.S | 85 +++++ tests/tcg/riscv64/test-smmpt34.S | 97 +++++ tests/tcg/riscv64/test-smmpt52.S | 91 +++++ tests/tcg/riscv64/test-smmpt64.S | 98 +++++ 19 files changed, 1354 insertions(+), 19 deletions(-) create mode 100644 target/riscv/riscv_smmpt.c create mode 100644 target/riscv/riscv_smmpt.h create mode 100644 tests/tcg/riscv64/smmpt-common.S create mode 100644 tests/tcg/riscv64/test-smmpt.S create mode 100644 tests/tcg/riscv64/test-smmpt34.S create mode 100644 tests/tcg/riscv64/test-smmpt52.S create mode 100644 tests/tcg/riscv64/test-smmpt64.S base-commit: 499039798cdad7d86b787fec0eaf1da4151c0f05 -- 2.43.0
This patch lays the groundwork for the SMMPT (Supervisor Domains Access Protection) extension by introducing its fundamental components. It adds: - New CPU configuration flags, `ext_smmpt` and `ext_smsdid`, to enable the extension. - Bit-field definitions for the `mmpt` CSR in `cpu_bits.h`. - The `mmpt` and `msdcfg` CSR numbers and their read/write handlers in `csr.c`. - New fields in `CPUArchState` to store the state of these new CSRs. - A new translation failure reason `TRANSLATE_MPT_FAIL`. This provides the necessary infrastructure for the core MPT logic and MMU integration that will follow. Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- target/riscv/cpu.h | 8 +++ target/riscv/cpu_bits.h | 24 ++++++++ target/riscv/cpu_cfg_fields.h.inc | 2 + target/riscv/riscv_smmpt.h | 21 +++++++ target/riscv/tcg/csr.c | 99 +++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+) create mode 100644 target/riscv/riscv_smmpt.h diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -XXX,XX +XXX,XX @@ enum { TRANSLATE_PMP_FAIL, TRANSLATE_G_STAGE_FAIL, TRANSLATE_PMA_FAIL, + TRANSLATE_MPT_FAIL }; /* Extension context status */ @@ -XXX,XX +XXX,XX @@ extern RISCVCPUImpliedExtsRule *riscv_multi_ext_implied_rules[]; #if !defined(CONFIG_USER_ONLY) #include "tcg/pmp.h" +#include "riscv_smmpt.h" #endif #define RV_VLEN_MAX 1024 @@ -XXX,XX +XXX,XX @@ struct CPUArchState { uint64_t rnmip; uint64_t rnmi_irqvec; uint64_t rnmi_excpvec; + + /* Smsdid */ + uint32_t mptmode; + uint32_t sdid; + uint64_t mptppn; + uint32_t msdcfg; #endif /* Fields from here on are preserved across CPU reset. */ diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -XXX,XX +XXX,XX @@ typedef enum CTRType { #define MCONTEXT64 0x0000000000001FFFULL #define MCONTEXT32_HCONTEXT 0x0000007F #define MCONTEXT64_HCONTEXT 0x0000000000003FFFULL + +/* Smsdid */ +#define CSR_MMPT 0x382 +#define CSR_MSDCFG 0x74E + +/* + * MMPT register layout for MXLEN=32: + * MODE[31:30], 0[29:28], SDID[27:22], PPN[21:0] + */ +#define MMPT_MODE_MASK_32 0xC0000000 +#define MMPT_MODE_SHIFT_32 30 +#define MMPT_SDID_MASK_32 0x0FC00000 +#define MMPT_SDID_SHIFT_32 22 +#define MMPT_PPN_MASK_32 0x003FFFFF + +/* + * MMPT register layout for MXLEN=64: + * MODE[63:60], 0[59:58], SDID[57:52], 0[51:44], PPN[43:0] + */ +#define MMPT_MODE_MASK_64 0xF000000000000000ULL +#define MMPT_MODE_SHIFT_64 60 +#define MMPT_SDID_MASK_64 0x03F0000000000000ULL +#define MMPT_SDID_SHIFT_64 52 +#define MMPT_PPN_MASK_64 0x00000FFFFFFFFFFFULL #endif diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -XXX,XX +XXX,XX @@ BOOL_FIELD(ext_smpmpmt) BOOL_FIELD(ext_svrsw60t59b) BOOL_FIELD(ext_svvptc) BOOL_FIELD(ext_svukte) +BOOL_FIELD(ext_smmpt) +BOOL_FIELD(ext_smsdid) BOOL_FIELD(ext_zdinx) BOOL_FIELD(ext_zaamo) BOOL_FIELD(ext_zacas) diff --git a/target/riscv/riscv_smmpt.h b/target/riscv/riscv_smmpt.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/target/riscv/riscv_smmpt.h @@ -XXX,XX +XXX,XX @@ +/* + * QEMU RISC-V Smmpt (Memory Protection Table) + * + * Copyright (c) 2024 Alibaba Group. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef RISCV_SMMPT_H +#define RISCV_SMMPT_H + +typedef enum { + SMMPTBARE = 0, + SMMPT34 = 1, + SMMPT43 = 2, + SMMPT52 = 3, + SMMPT64 = 4, + SMMPTMAX +} mpt_mode_t; + +#endif diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/csr.c +++ b/target/riscv/tcg/csr.c @@ -XXX,XX +XXX,XX @@ static RISCVException rnmi(CPURISCVState *env, int csrno) return RISCV_EXCP_ILLEGAL_INST; } + +static RISCVException smsdid(CPURISCVState *env, int csrno) +{ + if (riscv_cpu_cfg(env)->ext_smsdid) { + return RISCV_EXCP_NONE; + } + + return RISCV_EXCP_ILLEGAL_INST; +} #endif static RISCVException seed(CPURISCVState *env, int csrno) @@ -XXX,XX +XXX,XX @@ static RISCVException write_mnstatus(CPURISCVState *env, int csrno, return RISCV_EXCP_NONE; } +static RISCVException read_mmpt(CPURISCVState *env, int csrno, + target_ulong *val) +{ + if (riscv_cpu_xlen(env) == 32) { + uint32_t value = 0; + value |= env->mptmode << MMPT_MODE_SHIFT_32; + value |= (env->sdid << MMPT_SDID_SHIFT_32) & MMPT_SDID_MASK_32; + value |= env->mptppn & MMPT_PPN_MASK_32; + *val = value; + } else if (riscv_cpu_xlen(env) == 64) { + uint64_t value_64 = 0; + uint32_t mode_value = env->mptmode; + /* mpt_mode_t convert to mmpt.mode value */ + if (mode_value) { + mode_value -= SMMPT43 - SMMPT34; + } + value_64 |= (uint64_t)mode_value << MMPT_MODE_SHIFT_64; + value_64 |= ((uint64_t)env->sdid << MMPT_SDID_SHIFT_64) + & MMPT_SDID_MASK_64; + value_64 |= (uint64_t)env->mptppn & MMPT_PPN_MASK_64; + *val = value_64; + } else { + return RISCV_EXCP_ILLEGAL_INST; + } + return RISCV_EXCP_NONE; +} + +static RISCVException write_mmpt(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + uint32_t mode_value = 0; + if (!riscv_cpu_cfg(env)->ext_smmpt) { + goto set_remaining_fields_zero; + } + + if (riscv_cpu_xlen(env) == 32) { + mode_value = (val & MMPT_MODE_MASK_32) >> MMPT_MODE_SHIFT_32; + /* If mode is bare, the remaining fields in mmpt must be zero */ + if (mode_value == SMMPTBARE) { + goto set_remaining_fields_zero; + } else if (mode_value <= SMMPT34) { + /* Only write the legal value */ + env->mptmode = mode_value; + } + env->sdid = (val & MMPT_SDID_MASK_32) >> MMPT_SDID_SHIFT_32; + env->mptppn = val & MMPT_PPN_MASK_32; + } else if (riscv_cpu_xlen(env) == 64) { + mode_value = (val & MMPT_MODE_MASK_64) >> MMPT_MODE_SHIFT_64; + if (mode_value == SMMPTBARE) { + goto set_remaining_fields_zero; + } else if (mode_value < SMMPTMAX) { + /* convert to mpt_mode_t */ + mode_value += SMMPT43 - SMMPT34; + env->mptmode = mode_value; + } + env->sdid = (val & MMPT_SDID_MASK_64) >> MMPT_SDID_SHIFT_64; + env->mptppn = val & MMPT_PPN_MASK_64; + /* Smmpt64: root table PPN bits 2:0 must be zero (32KiB alignment) */ + if (env->mptmode == SMMPT64) { + env->mptppn &= ~(uint64_t)0x7; + } + } else { + return RISCV_EXCP_ILLEGAL_INST; + } + return RISCV_EXCP_NONE; + +set_remaining_fields_zero: + env->sdid = 0; + env->mptmode = SMMPTBARE; + env->mptppn = 0; + return RISCV_EXCP_NONE; +} + +static RISCVException read_msdcfg(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = env->msdcfg; + return RISCV_EXCP_NONE; +} + +static RISCVException write_msdcfg(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + env->msdcfg = val; + return RISCV_EXCP_NONE; +} + #endif /* Crypto Extension */ @@ -XXX,XX +XXX,XX @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = { write_mhpmcounterh }, [CSR_SCOUNTOVF] = { "scountovf", sscofpmf, read_scountovf, .min_priv_ver = PRIV_VERSION_1_12_0 }, + /* Supervisor Domain Identifier and Protection Registers */ + [CSR_MMPT] = { "mmpt", smsdid, read_mmpt, write_mmpt }, + [CSR_MSDCFG] = { "msdcfg", smsdid, read_msdcfg, write_msdcfg }, #endif /* !CONFIG_USER_ONLY */ }; -- 2.43.0
The number of implemented SDID bits is UNSPECIFIED and may be zero. The number of implemented SDID bits, termed SDIDLEN, may be determined by writing one to every bit position in the SDID field, then reading back the value in mmpt to see which bit positions in the SDID field hold a one. The least-significant bits of SDID are implemented first: that is, if SDIDLEN > 0, SDID[SDIDLEN-1:0] is writable. The maximal value of SDIDLEN, termed SDIDMAX, is 6. Model this by adding a configurable "smmpt-sdidlen" CPU property. The value defaults to SDIDMAX (6) to preserve the current behaviour. The mmpt write handler now keeps only the low SDIDLEN bits of the SDID field, making the SDIDLEN discovery sequence work as specified. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- target/riscv/cpu.c | 45 +++++++++++++++++++++++++++++++ target/riscv/cpu_bits.h | 3 +++ target/riscv/cpu_cfg_fields.h.inc | 1 + target/riscv/tcg/csr.c | 11 ++++++-- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -XXX,XX +XXX,XX @@ static void riscv_cpu_init(Object *obj) cpu->cfg.pmu_mask = MAKE_64BIT_MASK(3, 16); cpu->cfg.vlenb = 128 >> 3; cpu->cfg.elen = 64; + cpu->cfg.sdidlen = MMPT_SDIDLEN_MAX; cpu->cfg.cbom_blocksize = 64; cpu->cfg.cbop_blocksize = 64; cpu->cfg.cboz_blocksize = 64; @@ -XXX,XX +XXX,XX @@ static const PropertyInfo prop_vlen = { .set = prop_vlen_set, }; +static void prop_sdidlen_set(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + RISCVCPU *cpu = RISCV_CPU(obj); + uint8_t value; + + if (!visit_type_uint8(v, name, &value, errp)) { + return; + } + + if (value > MMPT_SDIDLEN_MAX) { + error_setg(errp, "smmpt-sdidlen must be between 0 and %d", + MMPT_SDIDLEN_MAX); + return; + } + + if (value != cpu->cfg.sdidlen && riscv_cpu_is_vendor(obj)) { + cpu_set_prop_err(cpu, name, errp); + error_append_hint(errp, "Current '%s' val: %u\n", + name, cpu->cfg.sdidlen); + return; + } + + cpu_option_add_user_setting(cpu, name); + cpu->cfg.sdidlen = value; +} + +static void prop_sdidlen_get(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + uint8_t value = RISCV_CPU(obj)->cfg.sdidlen; + + visit_type_uint8(v, name, &value, errp); +} + +static const PropertyInfo prop_sdidlen = { + .type = "uint8", + .description = "smmpt-sdidlen", + .get = prop_sdidlen_get, + .set = prop_sdidlen_set, +}; + static void prop_elen_set(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) { @@ -XXX,XX +XXX,XX @@ static const Property riscv_cpu_properties[] = { {.name = "vlen", .info = &prop_vlen}, {.name = "elen", .info = &prop_elen}, + {.name = "smmpt-sdidlen", .info = &prop_sdidlen}, + {.name = "cbom_blocksize", .info = &prop_cbom_blksize}, {.name = "cbop_blocksize", .info = &prop_cbop_blksize}, {.name = "cboz_blocksize", .info = &prop_cboz_blksize}, diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -XXX,XX +XXX,XX @@ typedef enum CTRType { #define CSR_MMPT 0x382 #define CSR_MSDCFG 0x74E +/* Maximal number of implemented SDID bits (SDIDMAX) */ +#define MMPT_SDIDLEN_MAX 6 + /* * MMPT register layout for MXLEN=32: * MODE[31:30], 0[29:28], SDID[27:22], PPN[21:0] diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -XXX,XX +XXX,XX @@ TYPED_FIELD(uint64_t, mimpid, 0) TYPED_FIELD(uint32_t, pmu_mask, 0) TYPED_FIELD(uint16_t, vlenb, 0) TYPED_FIELD(uint16_t, elen, 0) +TYPED_FIELD(uint8_t, sdidlen, 0) TYPED_FIELD(uint16_t, cbom_blocksize, 0) TYPED_FIELD(uint16_t, cbop_blocksize, 0) TYPED_FIELD(uint16_t, cboz_blocksize, 0) diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/csr.c +++ b/target/riscv/tcg/csr.c @@ -XXX,XX +XXX,XX @@ static RISCVException write_mmpt(CPURISCVState *env, int csrno, target_ulong val, uintptr_t ra) { uint32_t mode_value = 0; + /* + * Only the least-significant SDIDLEN bits of the SDID field are + * writable; the remaining bits are WARL and read as zero. + */ + uint64_t sdid_mask = MAKE_64BIT_MASK(0, riscv_cpu_cfg(env)->sdidlen); if (!riscv_cpu_cfg(env)->ext_smmpt) { goto set_remaining_fields_zero; } @@ -XXX,XX +XXX,XX @@ static RISCVException write_mmpt(CPURISCVState *env, int csrno, /* Only write the legal value */ env->mptmode = mode_value; } - env->sdid = (val & MMPT_SDID_MASK_32) >> MMPT_SDID_SHIFT_32; + env->sdid = ((val & MMPT_SDID_MASK_32) >> MMPT_SDID_SHIFT_32) + & sdid_mask; env->mptppn = val & MMPT_PPN_MASK_32; } else if (riscv_cpu_xlen(env) == 64) { mode_value = (val & MMPT_MODE_MASK_64) >> MMPT_MODE_SHIFT_64; @@ -XXX,XX +XXX,XX @@ static RISCVException write_mmpt(CPURISCVState *env, int csrno, mode_value += SMMPT43 - SMMPT34; env->mptmode = mode_value; } - env->sdid = (val & MMPT_SDID_MASK_64) >> MMPT_SDID_SHIFT_64; + env->sdid = (((uint64_t)val & MMPT_SDID_MASK_64) >> MMPT_SDID_SHIFT_64) + & sdid_mask; env->mptppn = val & MMPT_PPN_MASK_64; /* Smmpt64: root table PPN bits 2:0 must be zero (32KiB alignment) */ if (env->mptmode == SMMPT64) { -- 2.43.0
This patch introduces the core implementation for the Memory Protection Table (MPT) walk, which is the central mechanism of the SMMPT extension. A new file, `riscv_smmpt.c`, is added to encapsulate the MPT logic. It implements the `smmpt_lookup()` function, which performs a multi-level page table-like walk starting from the physical address specified in the `mptppn` CSR field. This walk determines the access permissions (read, write, execute) for a given physical address. The implementation supports various SMMPT modes (SMMPT34, SMMPT43, etc.) and correctly handles leaf and non-leaf entries, including reserved bit checks. Both non-NAPOT leaf entries (per-page XWR tuples) and NAPOT leaf entries (a single XWR tuple with a G granularity field) are supported, as defined by the v0.4.9 MPTE format. Helper functions for parsing MPT entries and converting access permissions are included in the new `riscv_smmpt.h` header. Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- target/riscv/meson.build | 1 + target/riscv/riscv_smmpt.c | 339 ++++++++++++++++++++++++++++++++++ target/riscv/riscv_smmpt.h | 5 + target/riscv/tcg/cpu_helper.c | 6 +- target/riscv/tcg/pmp.h | 3 + 5 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 target/riscv/riscv_smmpt.c diff --git a/target/riscv/meson.build b/target/riscv/meson.build index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/meson.build +++ b/target/riscv/meson.build @@ -XXX,XX +XXX,XX @@ riscv_ss.add(files( riscv_system_ss = ss.source_set() riscv_system_ss.add(files( 'arch_dump.c', + 'riscv_smmpt.c', 'monitor.c', 'machine.c', 'time_helper.c', diff --git a/target/riscv/riscv_smmpt.c b/target/riscv/riscv_smmpt.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/target/riscv/riscv_smmpt.c @@ -XXX,XX +XXX,XX @@ +/* + * QEMU RISC-V Smmpt (Memory Protection Table) + * + * Copyright (c) 2024 Alibaba Group. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Implements the MPT lookup algorithm per SMMTT specification v0.4.9. + * + * MPTE format (v0.4.9): + * + * 32-bit non-leaf: V[0], L=0[1], Reserved[9:2], PPN[31:10] + * 32-bit non-NAPOT leaf: V[0], L=1[1], N=0[2], Reserved[7:3], XWR[31:8] + * 32-bit NAPOT leaf: V[0], L=1[1], N=1[2], Reserved[7:3], XWR[10:8], 0[11], + * G[15:12], Reserved[31:16] + * + * 64-bit non-leaf: V[0], L=0[1], Reserved[9:2], PPN[53:10], Reserved[63:54] + * 64-bit non-NAPOT leaf: V[0], L=1[1], N=0[2], Reserved[7:3], XWR[55:8], + * Reserved[63:56] + * 64-bit NAPOT leaf: V[0], L=1[1], N=1[2], Reserved[7:3], XWR[10:8], 0[11], + * G[15:12], Reserved[63:16] + */ + +#include "qemu/osdep.h" +#include "riscv_smmpt.h" +#include "tcg/pmp.h" +#include "exec/page-protection.h" +#include "system/memory.h" + +typedef uint64_t load_entry_fn(AddressSpace *, hwaddr, + MemTxAttrs, MemTxResult *); + +static uint64_t load_entry_32(AddressSpace *as, hwaddr addr, + MemTxAttrs attrs, MemTxResult *result) +{ + return address_space_ldl_le(as, addr, attrs, result); +} + +static uint64_t load_entry_64(AddressSpace *as, hwaddr addr, + MemTxAttrs attrs, MemTxResult *result) +{ + return address_space_ldq_le(as, addr, attrs, result); +} + +static inline bool mpte_is_valid(uint64_t mpte) +{ + return mpte & 0x1; +} + +static inline bool mpte_is_leaf(uint64_t mpte) +{ + return mpte & 0x2; +} + +static inline bool mpte_get_n(uint64_t mpte) +{ + return (mpte >> 2) & 0x1; +} + +/* + * Get reserved bits from MPTE. Returns non-zero if any reserved bit is set. + */ +static uint64_t mpte_get_rsv(CPURISCVState *env, uint64_t mpte) +{ + RISCVMXL mxl = riscv_cpu_mxl(env); + bool leaf = mpte_is_leaf(mpte); + bool napot = mpte_get_n(mpte); + + if (mxl == MXL_RV32) { + if (!leaf) { + /* non-leaf32: Reserved = bits[9:2] */ + return extract32(mpte, 2, 8); + } + if (!napot) { + /* non-NAPOT leaf32: Reserved = bits[7:3] (XWR fills [31:8]) */ + return extract32(mpte, 3, 5); + } + /* NAPOT leaf32: Reserved = bits[7:3] | bit[11] (mbz) | bits[31:16] */ + return extract32(mpte, 3, 5) | extract32(mpte, 11, 1) | + extract32(mpte, 16, 16); + } + + /* RV64 */ + if (!leaf) { + /* non-leaf64: Reserved = bits[9:2] | bits[63:54] */ + return extract64(mpte, 2, 8) | extract64(mpte, 54, 10); + } + if (!napot) { + /* non-NAPOT leaf64: Reserved = bits[7:3] | bits[63:56] */ + return extract64(mpte, 3, 5) | extract64(mpte, 56, 8); + } + /* NAPOT leaf64: Reserved = bits[7:3] | bit[11] (mbz) | bits[63:16] */ + return extract64(mpte, 3, 5) | extract64(mpte, 11, 1) | + extract64(mpte, 16, 48); +} + +/* + * Get PPN from a non-leaf MPTE. + * RV32 non-leaf: PPN = bits[31:10] (22 bits) + * RV64 non-leaf: PPN = bits[53:10] (44 bits) + */ +static uint64_t mpte_get_ppn(CPURISCVState *env, uint64_t mpte) +{ + RISCVMXL mxl = riscv_cpu_mxl(env); + + if (mxl == MXL_RV32) { + return extract32(mpte, 10, 22); + } + return extract64(mpte, 10, 44); +} + +/* + * Get XWR permission for a specific page index from a non-NAPOT leaf. + * The XWR base bit is 8 for both RV32 and RV64; only the number of + * entries differs (pi in 0..7 for RV32, 0..15 for RV64). + */ +static uint32_t mpte_get_xwr(CPURISCVState *env, uint64_t mpte, int pi) +{ + return extract64(mpte, 8 + pi * 3, 3); +} + +/* + * Get single XWR from a NAPOT leaf. + * The NAPOT XWR base bit is 8 for both RV32 and RV64 (bits[10:8]). + */ +static uint32_t mpte_get_napot_xwr(CPURISCVState *env, uint64_t mpte) +{ + return extract64(mpte, 8, 3); +} + +/* + * Get G field from a NAPOT leaf. + * The G base bit is 12 for both RV32 and RV64 (bits[15:12]). + */ +static uint32_t mpte_get_g(CPURISCVState *env, uint64_t mpte) +{ + return extract64(mpte, 12, 4); +} + +/* + * Validate the G encoding for the given MPT mode. + * Smmpt34: only G=6 is valid + * Smmpt43/52/64: only G=4 is valid + */ +static bool mpte_validate_g(uint32_t g, mpt_mode_t mode) +{ + switch (mode) { + case SMMPT34: + return g == 6; + case SMMPT43: + case SMMPT52: + case SMMPT64: + return g == 4; + default: + return false; + } +} + +/* + * Get page number index pn[i] from the supervisor physical address. + * + * Smmpt34 (34-bit SPA): + * SPA layout: range_offset[14:0], pn[0][24:15] (10 bits), + * pn[1][33:25] (9 bits) + * + * Smmpt43/52/64 (RV64 MPT): + * SPA layout: range_offset[15:0], pn[0][24:16] (9 bits), ... + * For Smmpt64, pn[4] (top level) is 12 bits. + */ +static int mpt_get_pn(hwaddr addr, int i, mpt_mode_t mode) +{ + if (mode == SMMPT34) { + return i == 0 + ? extract64(addr, 15, 10) + : extract64(addr, 25, 9); + } else { + int offset = 16 + i * 9; + if ((mode == SMMPT64) && (i == 4)) { + return extract64(addr, offset, 12); + } else { + return extract64(addr, offset, 9); + } + } +} + +/* + * Get the page index within a leaf MPTE. + * + * Smmpt34: pi = SPA[14:12] (3 bits) for level 0, SPA[24:22] for level 1 + * Smmpt43/52/64: pi = SPA[offset-4 +: 4] (4 bits) + */ +static int mpt_get_pi(hwaddr addr, int i, mpt_mode_t mode) +{ + if (mode == SMMPT34) { + return i == 0 + ? extract64(addr, 12, 3) + : extract64(addr, 22, 3); + } else { + int offset = 16 + i * 9; + return extract64(addr, offset - 4, 4); + } +} + +/* + * Check XWR permission bits against the access type. + * Returns true if access is allowed. + * + * The 3-bit XWR field uses the same bit order as RISC-V PTE permissions + * (bit0 = R, bit1 = W, bit2 = X), matching QEMU's PAGE_READ / PAGE_WRITE / + * PAGE_EXEC. As with PTEs, writable-but-not-readable encodings are reserved. + * + * XWR encoding (bit2=X, bit1=W, bit0=R): + * 000 = No access + * 001 = Read only + * 010 = Reserved (fault) + * 011 = Read + Write + * 100 = Execute only + * 101 = Read + Execute + * 110 = Reserved (fault) + * 111 = Read + Write + Execute + */ +static bool mpt_check_xwr(uint32_t xwr, int *prot, MMUAccessType access_type) +{ + switch (xwr) { + case 0: /* No access */ + return false; + case PAGE_EXEC: /* 100: Execute only */ + *prot = PAGE_EXEC; + return access_type == MMU_INST_FETCH; + case PAGE_READ | PAGE_EXEC: /* 101: Read + Execute */ + *prot = PAGE_READ | PAGE_EXEC; + return (access_type == MMU_DATA_LOAD || + access_type == MMU_INST_FETCH); + case PAGE_READ: /* 001: Read only */ + *prot = PAGE_READ; + return access_type == MMU_DATA_LOAD; + case PAGE_READ | PAGE_WRITE: /* 011: Read + Write */ + *prot = PAGE_READ | PAGE_WRITE; + return (access_type == MMU_DATA_LOAD || + access_type == MMU_DATA_STORE); + case PAGE_READ | PAGE_WRITE | PAGE_EXEC: /* 111: R+W+X */ + *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return true; + default: /* 010, 110: Reserved - fault */ + return false; + } +} + +static bool smmpt_lookup(CPURISCVState *env, hwaddr addr, mpt_mode_t mode, + int *prot, MMUAccessType access_type) +{ + MemTxResult res; + MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED; + CPUState *cs = env_cpu(env); + hwaddr mpte_addr, base = (hwaddr)env->mptppn << PGSHIFT; + load_entry_fn *load_entry; + uint32_t mptesize, levels, xwr, g; + int pn, pi, pmp_prot, pmp_ret; + uint64_t mpte; + + switch (mode) { + case SMMPT34: + load_entry = &load_entry_32; levels = 2; mptesize = 4; break; + case SMMPT43: + load_entry = &load_entry_64; levels = 3; mptesize = 8; break; + case SMMPT52: + load_entry = &load_entry_64; levels = 4; mptesize = 8; break; + case SMMPT64: + load_entry = &load_entry_64; levels = 5; mptesize = 8; break; + case SMMPTBARE: + *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return true; + default: + g_assert_not_reached(); + break; + } + + for (int i = levels - 1; i >= 0; i--) { + /* Step 1: Get pn[i] as the MPT index */ + pn = mpt_get_pn(addr, i, mode); + + /* Step 2: Load MPTE from memory */ + mpte_addr = base + pn * mptesize; + pmp_ret = get_physical_address_pmp(env, &pmp_prot, mpte_addr, + mptesize, MMU_DATA_LOAD, PRV_M); + if (pmp_ret != TRANSLATE_SUCCESS) { + return false; + } + mpte = load_entry(cs->as, mpte_addr, attrs, &res); + if (res != MEMTX_OK) { + return false; + } + + /* Step 3: Check valid bit and reserved bits */ + if (!mpte_is_valid(mpte) || mpte_get_rsv(env, mpte)) { + return false; + } + + /* Step 3 (cont): non-leaf with N=1 is a fault */ + if (!mpte_is_leaf(mpte) && mpte_get_n(mpte)) { + return false; + } + + /* Step 4: Process non-leaf node */ + if (!mpte_is_leaf(mpte)) { + if (i == 0) { + return false; + } + base = mpte_get_ppn(env, mpte) << PGSHIFT; + continue; + } + + /* Step 5 & 6: Process leaf node */ + if (!mpte_get_n(mpte)) { + /* Step 5: Non-NAPOT leaf - get XWR[pi] */ + pi = mpt_get_pi(addr, i, mode); + xwr = mpte_get_xwr(env, mpte, pi); + } else { + /* Step 6: NAPOT leaf - validate G, get single XWR */ + g = mpte_get_g(env, mpte); + if (!mpte_validate_g(g, mode)) { + return false; + } + xwr = mpte_get_napot_xwr(env, mpte); + } + + /* Step 7: Check permission */ + return mpt_check_xwr(xwr, prot, access_type); + } + return false; +} + +bool smmpt_check_access(CPURISCVState *env, hwaddr addr, + int *prot, MMUAccessType access_type) +{ + mpt_mode_t mode = env->mptmode; + + return smmpt_lookup(env, addr, mode, prot, access_type); +} diff --git a/target/riscv/riscv_smmpt.h b/target/riscv/riscv_smmpt.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/riscv_smmpt.h +++ b/target/riscv/riscv_smmpt.h @@ -XXX,XX +XXX,XX @@ #ifndef RISCV_SMMPT_H #define RISCV_SMMPT_H +#include "cpu.h" +#include "exec/mmu-access-type.h" + typedef enum { SMMPTBARE = 0, SMMPT34 = 1, @@ -XXX,XX +XXX,XX @@ typedef enum { SMMPTMAX } mpt_mode_t; +bool smmpt_check_access(CPURISCVState *env, hwaddr addr, + int *prot, MMUAccessType access_type); #endif diff --git a/target/riscv/tcg/cpu_helper.c b/target/riscv/tcg/cpu_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/cpu_helper.c +++ b/target/riscv/tcg/cpu_helper.c @@ -XXX,XX +XXX,XX @@ void riscv_cpu_set_mode(CPURISCVState *env, privilege_mode_t newpriv, * @access_type: The type of MMU access * @mode: Indicates current privilege level. */ -static int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr, - int size, MMUAccessType access_type, - privilege_mode_t mode) +int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr, + int size, MMUAccessType access_type, + privilege_mode_t mode) { pmp_priv_t pmp_priv; bool pmp_has_privs; diff --git a/target/riscv/tcg/pmp.h b/target/riscv/tcg/pmp.h index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/pmp.h +++ b/target/riscv/tcg/pmp.h @@ -XXX,XX +XXX,XX @@ void pmp_update_rule_nums(CPURISCVState *env); uint32_t pmp_get_num_rules(CPURISCVState *env); int pmp_priv_to_page_prot(pmp_priv_t pmp_priv); void pmp_unlock_entries(CPURISCVState *env); +int get_physical_address_pmp(CPURISCVState *env, int *prot, hwaddr addr, + int size, MMUAccessType access_type, + privilege_mode_t mode); #define MSECCFG_MML_ISSET(env) get_field(env->mseccfg, MSECCFG_MML) #define MSECCFG_MMWP_ISSET(env) get_field(env->mseccfg, MSECCFG_MMWP) -- 2.43.0
With the core MPT lookup logic in place, this patch integrates the permission checks into QEMU's main MMU processing functions. A new helper, `get_physical_address_mpt`, is introduced to check the permissions for a given physical address against the MPT. This helper is then called at two critical points: 1. During page table walks (`get_physical_address`): The physical address of the Page Table Entry (PTE) itself is checked to ensure the supervisor has permission to read it. 2. After successful address translation (`riscv_cpu_tlb_fill`): The final guest-physical address is checked against the MPT before the access is allowed to proceed. This ensures that SMMPT protection is enforced for both the translation process and the final memory access, as required by the specification. Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> --- target/riscv/tcg/cpu_helper.c | 113 ++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/target/riscv/tcg/cpu_helper.c b/target/riscv/tcg/cpu_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/cpu_helper.c +++ b/target/riscv/tcg/cpu_helper.c @@ -XXX,XX +XXX,XX @@ static bool check_svukte_addr(CPURISCVState *env, vaddr addr) return !high_bit; } +/* + * get_physical_address_mpt - check mpt permission for this physical address + * + * Lookup the Memory Protection Table and check permission for this + * physical address. Returns 0 if the permission checking was successful + * + * @env: CPURISCVState + * @prot: The returned protection attributes + * @addr: The physical address to be checked permission + * @access_type: The type of MMU access + * @mode: Indicates current privilege level. + */ +static int get_physical_address_mpt(CPURISCVState *env, int *prot, hwaddr addr, + MMUAccessType access_type, int mode) +{ + /* + * If the extension is not supported or the mmpt.mode is Bare, + * there is no protection, return success. + */ + if (!riscv_cpu_cfg(env)->ext_smmpt || env->mptmode == SMMPTBARE) { + *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return TRANSLATE_SUCCESS; + } + + /* + * MPT is checked for all accesses to physical memory, unless the + * effective privilege mode is M. + * + * Data accesses in M-mode when the MPRV bit in mstatus is set and + * the MPP field in mstatus contains S or U are subject to MPT checks. + * + * In riscv_env_mmu_index, The MPRV and MPP bits are already checked and + * encoded to mmu_idx, So we do not need to check it here. + */ + if (mode == PRV_M) { + *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return TRANSLATE_SUCCESS; + } + + if (!smmpt_check_access(env, addr, prot, access_type)) { + *prot = 0; + return TRANSLATE_MPT_FAIL; + } + + return TRANSLATE_SUCCESS; +} + /* * get_physical_address - get the physical address for this virtual address * @@ -XXX,XX +XXX,XX @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, pte_addr = base + idx * ptesize; } + int mpt_prot; + int mpt_ret = get_physical_address_mpt(env, &mpt_prot, pte_addr, + MMU_DATA_LOAD, PRV_S); + if (mpt_ret != TRANSLATE_SUCCESS) { + return mpt_ret; + } + int pmp_prot; int pmp_ret = get_physical_address_pmp(env, &pmp_prot, pte_addr, sxlen_bytes, @@ -XXX,XX +XXX,XX @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, CPURISCVState *env = &cpu->env; vaddr im_address; hwaddr pa = 0; - int prot, prot2, prot_pmp; + int prot, prot2, prot_pmp, mpt_prot; bool pmp_pma_violation = false; bool first_stage_error = true; bool two_stage_lookup = mmuidx_2stage(mmu_idx); @@ -XXX,XX +XXX,XX @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, prot &= prot2; if (ret == TRANSLATE_SUCCESS) { - ret = get_physical_address_pmp(env, &prot_pmp, pa, - size, access_type, mode); - tlb_size = pmp_get_tlb_size(env, pa); + ret = get_physical_address_mpt(env, &mpt_prot, pa, + access_type, mode); + if (riscv_cpu_cfg(env)->ext_smmpt) { + qemu_log_mask(CPU_LOG_MMU, + "%s MPT address=" HWADDR_FMT_plx " ret %d" + " prot %d\n", + __func__, pa, ret, mpt_prot); + } + prot &= mpt_prot; - qemu_log_mask(CPU_LOG_MMU, - "%s PMP address=" HWADDR_FMT_plx " ret %d prot" - " %d tlb_size %" HWADDR_PRIu "\n", - __func__, pa, ret, prot_pmp, tlb_size); + if (ret == TRANSLATE_SUCCESS) { + ret = get_physical_address_pmp(env, &prot_pmp, pa, + size, access_type, mode); + tlb_size = pmp_get_tlb_size(env, pa); + + qemu_log_mask(CPU_LOG_MMU, + "%s PMP address=" HWADDR_FMT_plx + " ret %d prot %d tlb_size %" + HWADDR_PRIu "\n", + __func__, pa, ret, prot_pmp, + tlb_size); - prot &= prot_pmp; + prot &= prot_pmp; + } } else { /* * Guest physical address translation failed, this is a HS * level exception */ first_stage_error = false; - if (ret != TRANSLATE_PMP_FAIL) { + if (ret != TRANSLATE_PMP_FAIL && + ret != TRANSLATE_MPT_FAIL) { env->guest_phys_fault_addr = (im_address | (address & - (TARGET_PAGE_SIZE - 1))) >> 2; + (TARGET_PAGE_SIZE - 1))) + >> 2; } } } @@ -XXX,XX +XXX,XX @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, HWADDR_FMT_plx " prot %d\n", __func__, address, ret, pa, prot); + if (ret == TRANSLATE_SUCCESS) { + ret = get_physical_address_mpt(env, &mpt_prot, pa, + access_type, mode); + if (riscv_cpu_cfg(env)->ext_smmpt) { + qemu_log_mask(CPU_LOG_MMU, + "%s MPT address=" HWADDR_FMT_plx " ret %d" + " prot %d\n", + __func__, pa, ret, mpt_prot); + } + prot &= mpt_prot; + } + if (ret == TRANSLATE_SUCCESS) { ret = get_physical_address_pmp(env, &prot_pmp, pa, size, access_type, mode); @@ -XXX,XX +XXX,XX @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, prot &= prot_pmp; } } - - if (ret == TRANSLATE_PMP_FAIL || ret == TRANSLATE_PMA_FAIL) { + /* + * Both MPT (Machine-level Memory Protection Table, Smmpt extension) and + * PMP (Physical Memory Protection) follow the same exception reporting + * rule when an access violation is detected + */ + if (ret == TRANSLATE_PMP_FAIL || ret == TRANSLATE_PMA_FAIL || + ret == TRANSLATE_MPT_FAIL) { pmp_pma_violation = true; } -- 2.43.0
This patch completes the SMMPT implementation by adding support for the new fence instructions: `mfence.pa` and `minval.pa`. According to the specification, these instructions act as memory ordering fences for MPT updates. In QEMU's TCG model, this is conservatively implemented by flushing the entire TLB, which ensures that any subsequent memory accesses will re-evaluate permissions and see the effects of any prior MPT modifications. The instructions are privileged and will cause an illegal instruction exception if executed outside of M-mode. Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- target/riscv/insn32.decode | 2 ++ .../tcg/insn_trans/trans_privileged.c.inc | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/target/riscv/insn32.decode b/target/riscv/insn32.decode index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/insn32.decode +++ b/target/riscv/insn32.decode @@ -XXX,XX +XXX,XX @@ sret 0001000 00010 00000 000 00000 1110011 mret 0011000 00010 00000 000 00000 1110011 wfi 0001000 00101 00000 000 00000 1110011 sfence_vma 0001001 ..... ..... 000 00000 1110011 @sfence_vma +mfence_pa 0011001 ..... ..... 000 00000 1110011 @sfence_vma +minval_pa 0011011 ..... ..... 000 00000 1110011 @sfence_vma # *** NMI *** mnret 0111000 00010 00000 000 00000 1110011 diff --git a/target/riscv/tcg/insn_trans/trans_privileged.c.inc b/target/riscv/tcg/insn_trans/trans_privileged.c.inc index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/tcg/insn_trans/trans_privileged.c.inc +++ b/target/riscv/tcg/insn_trans/trans_privileged.c.inc @@ -XXX,XX +XXX,XX @@ static bool trans_sfence_vma(DisasContext *ctx, arg_sfence_vma *a) #endif return false; } + +#define REQUIRE_SMSDID(ctx) do { \ + if (!ctx->cfg_ptr->ext_smsdid) { \ + return false; \ + } \ +} while (0) + +static bool do_mfence_pa(DisasContext *ctx) +{ +#ifndef CONFIG_USER_ONLY + REQUIRE_SMSDID(ctx); + if (ctx->priv != PRV_M) { + return false; + } + decode_save_opc(ctx, 0); + gen_helper_tlb_flush_all(tcg_env); + return true; +#endif + return false; +} + +static bool trans_mfence_pa(DisasContext *ctx, arg_mfence_pa *a) +{ + return do_mfence_pa(ctx); +} + +static bool trans_minval_pa(DisasContext *ctx, arg_minval_pa *a) +{ + return do_mfence_pa(ctx); +} -- 2.43.0
Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> Suggested-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Reviewed-by: Frank Chang <frank.chang@sifive.com> Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> --- target/riscv/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -XXX,XX +XXX,XX @@ const RISCVIsaExtData isa_edata_arr[] = { ISA_EXT_DATA_ENTRY(smdbltrp, PRIV_VERSION_1_13_0, ext_smdbltrp), ISA_EXT_DATA_ENTRY(smepmp, PRIV_VERSION_1_12_0, ext_smepmp), ISA_EXT_DATA_ENTRY(smpmpmt, PRIV_VERSION_1_12_0, ext_smpmpmt), - ISA_EXT_DATA_ENTRY(smrnmi, PRIV_VERSION_1_12_0, ext_smrnmi), ISA_EXT_DATA_ENTRY(smmpm, PRIV_VERSION_1_13_0, ext_smmpm), ISA_EXT_DATA_ENTRY(smnpm, PRIV_VERSION_1_13_0, ext_smnpm), + ISA_EXT_DATA_ENTRY(smrnmi, PRIV_VERSION_1_12_0, ext_smrnmi), ISA_EXT_DATA_ENTRY(smstateen, PRIV_VERSION_1_12_0, ext_smstateen), ISA_EXT_DATA_ENTRY(ssaia, PRIV_VERSION_1_12_0, ext_ssaia), ISA_EXT_DATA_ENTRY(ssccfg, PRIV_VERSION_1_13_0, ext_ssccfg), -- 2.43.0
Add disassembly support to the RISC-V disassembler for the machine CSRs and fence instructions introduced by the Smmpt/Smsdid series: - CSRs: mmpt (0x382), msdcfg (0x74e) - Instructions: mfence.pa, minval.pa Note that CSR 0x382 was previously mapped to the obsolete "mibase" register from the removed base-and-bound draft; it is now reused by Smmpt for mmpt. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- disas/riscv.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/disas/riscv.c b/disas/riscv.c index XXXXXXX..XXXXXXX 100644 --- a/disas/riscv.c +++ b/disas/riscv.c @@ -XXX,XX +XXX,XX @@ typedef enum { rv_op_cbo_flush = 958, rv_op_cbo_zero = 959, rv_op_mnret = 960, + rv_op_mfence_pa = 961, + rv_op_minval_pa = 962, } rv_op; /* register names */ @@ -XXX,XX +XXX,XX @@ const rv_opcode_data rvi_opcode_data[] = { { "cbo.flush", rv_codec_r, rv_fmt_rs1, NULL, 0, 0, 0 }, { "cbo.zero", rv_codec_r, rv_fmt_rs1, NULL, 0, 0, 0 }, { "mnret", rv_codec_none, rv_fmt_none, NULL, 0, 0, 0 }, + { "mfence.pa", rv_codec_r, rv_fmt_rs1_rs2, NULL, 0, 0, 0 }, + { "minval.pa", rv_codec_r, rv_fmt_rs1_rs2, NULL, 0, 0, 0 }, }; /* CSR names */ @@ -XXX,XX +XXX,XX @@ static const char *csr_name(int csrno) case 0x0344: return "mip"; case 0x0380: return "mbase"; case 0x0381: return "mbound"; - case 0x0382: return "mibase"; + case 0x0382: return "mmpt"; case 0x0383: return "mibound"; case 0x0384: return "mdbase"; case 0x0385: return "mdbound"; @@ -XXX,XX +XXX,XX @@ static const char *csr_name(int csrno) case 0x03ed: return "pmpaddr61"; case 0x03ee: return "pmpaddr62"; case 0x03ef: return "pmpaddr63"; + case 0x074e: return "msdcfg"; case 0x0780: return "mtohost"; case 0x0781: return "mfromhost"; case 0x0782: return "mreset"; @@ -XXX,XX +XXX,XX @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) case 64: op = rv_op_mret; break; } break; + case 800: op = rv_op_mfence_pa; break; + case 864: op = rv_op_minval_pa; break; case 1792: switch ((inst >> 15) & 0b1111111111) { case 64: op = rv_op_mnret; break; -- 2.43.0
This patch implements v0.4.9 SMMPT specification(https://github.com/riscv/riscv-smmtt/releases/tag/v0.4.9). Co-authored-by: Huang Tao <eric.huang@linux.alibaba.com> Co-authored-by: TANG Tiancheng <lyndra@linux.alibaba.com> Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> Reviewed-by: Frank Chang <frank.chang@sifive.com> --- target/riscv/cpu.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -XXX,XX +XXX,XX @@ const RISCVIsaExtData isa_edata_arr[] = { ISA_EXT_DATA_ENTRY(smepmp, PRIV_VERSION_1_12_0, ext_smepmp), ISA_EXT_DATA_ENTRY(smpmpmt, PRIV_VERSION_1_12_0, ext_smpmpmt), ISA_EXT_DATA_ENTRY(smmpm, PRIV_VERSION_1_13_0, ext_smmpm), + ISA_EXPERIMENTAL_EXT_DATA_ENTRY(smmpt, PRIV_VERSION_1_13_0, ext_smmpt), ISA_EXT_DATA_ENTRY(smnpm, PRIV_VERSION_1_13_0, ext_smnpm), ISA_EXT_DATA_ENTRY(smrnmi, PRIV_VERSION_1_12_0, ext_smrnmi), + ISA_EXPERIMENTAL_EXT_DATA_ENTRY(smsdid, PRIV_VERSION_1_13_0, ext_smsdid), ISA_EXT_DATA_ENTRY(smstateen, PRIV_VERSION_1_12_0, ext_smstateen), ISA_EXT_DATA_ENTRY(ssaia, PRIV_VERSION_1_12_0, ext_ssaia), ISA_EXT_DATA_ENTRY(ssccfg, PRIV_VERSION_1_13_0, ext_ssccfg), @@ -XXX,XX +XXX,XX @@ static RISCVCPUImpliedExtsRule ZVFBFA_IMPLIED = { }, }; +static RISCVCPUImpliedExtsRule SMMPT_IMPLIED = { + .ext = CPU_CFG_OFFSET(ext_smmpt), + .implied_multi_exts = { + CPU_CFG_OFFSET(ext_smsdid), + + RISCV_IMPLIED_EXTS_RULE_END + }, +}; + RISCVCPUImpliedExtsRule *riscv_misa_ext_implied_rules[] = { &RVA_IMPLIED, &RVD_IMPLIED, &RVF_IMPLIED, &RVM_IMPLIED, &RVV_IMPLIED, &RVG_IMPLIED, @@ -XXX,XX +XXX,XX @@ RISCVCPUImpliedExtsRule *riscv_multi_ext_implied_rules[] = { &ZVKS_IMPLIED, &ZVKSC_IMPLIED, &ZVKSG_IMPLIED, &SHA_IMPLIED, &SSCFG_IMPLIED, &SUPM_IMPLIED, &SSPM_IMPLIED, &SMCTR_IMPLIED, &SSCTR_IMPLIED, &SSSTATEEN_IMPLIED, - NULL + &SMMPT_IMPLIED, NULL }; static const Property riscv_cpu_properties[] = { -- 2.43.0
Add a bare-metal M-mode assembly test (test-smmpt.S) that validates the Smmpt43 MPT lookup and permission enforcement against SMMTT v0.4.9. The test builds a 3-level Smmpt43 MPT in RAM and uses the MPRV trick (mstatus.MPRV=1, MPP=S, satp=Bare) so that data accesses are subject to MPT checks while instruction fetches remain in M-mode (which bypasses the MPT). PMP is configured to grant all permissions so that only the MPT gates the accesses. Coverage: mfence.pa in M-mode; every XWR encoding (RW, no-access, R-only, R+X, RWX, the reserved 010/110 encodings and X-only) checked for both load and store, verifying the correct exception cause (load access fault = 5, store/AMO access fault = 7); an invalid (V=0) leaf; a leaf with a reserved bit set; and NAPOT leaves with a valid and a reserved G field. The mode-independent harness (MPRV helpers, per-access check/verify subroutines, the M-mode trap handler and the semihosting exit) lives in a shared smmpt-common.S so the Smmpt52/Smmpt64/Smmpt34 tests can reuse it. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- tests/tcg/riscv64/Makefile.softmmu-target | 4 + tests/tcg/riscv64/smmpt-common.S | 257 ++++++++++++++++++++++ tests/tcg/riscv64/test-smmpt.S | 85 +++++++ 3 files changed, 346 insertions(+) create mode 100644 tests/tcg/riscv64/smmpt-common.S create mode 100644 tests/tcg/riscv64/test-smmpt.S diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target index XXXXXXX..XXXXXXX 100644 --- a/tests/tcg/riscv64/Makefile.softmmu-target +++ b/tests/tcg/riscv64/Makefile.softmmu-target @@ -XXX,XX +XXX,XX @@ comma:= , run-test-crc32: test-crc32 $(call run-test, $<, $(QEMU) -cpu rv64$(comma)xlrbr=true $(QEMU_OPTS)$<) +EXTRA_RUNS += run-test-smmpt +run-test-smmpt: test-smmpt + $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<) + # We don't currently support the multiarch system tests undefine MULTIARCH_TESTS diff --git a/tests/tcg/riscv64/smmpt-common.S b/tests/tcg/riscv64/smmpt-common.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/smmpt-common.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Shared harness for the Smmpt (Supervisor Memory Protection Table) system + * tests, per SMMTT specification v0.4.9. + * + * This header is meant to be #included by the per-mode Smmpt test programs + * (Smmpt34 on RV32, Smmpt43/52/64 on RV64). All the mode-independent logic + * lives here so each test only has to build its mode-specific MPT and invoke + * RUN_LEAF_CHECKS. + * + * The tests run in M-mode and use the MPRV trick (mstatus.MPRV=1, + * mstatus.MPP=S, satp=Bare so PA==VA) to subject explicit data accesses to + * MPT checks while the instruction stream keeps running in M-mode (M-mode + * fetches bypass the MPT). The data-access width is irrelevant to the MPT + * check, so the harness uses lw/sw and works unmodified on RV32 and RV64. + * + * The MPT XWR field uses the standard bit order R=bit0, W=bit1, X=bit2 + * (matching QEMU PAGE_READ/PAGE_WRITE/PAGE_EXEC), so Read+Write is 0b011=3. + * Per the spec the exception is reported for the original access type: a load + * violation raises "load access fault" (cause 5) and a store violation raises + * "store/AMO access fault" (cause 7). + * + * Execute permission is not exercised: the MPRV trick only redirects data + * accesses, while instruction fetch stays in M-mode and bypasses the MPT. + * X-bearing encodings are therefore checked only for their load/store + * behaviour (e.g. X-only denies loads, R+X denies stores). + * + * The tests expect an MPT laid out so that these supervisor physical + * addresses resolve as follows (identical across all modes): + * + * 0x8050_0000..0x8050_7000 non-NAPOT leaf, XWR[pi] tuples (LEAF_ALL_XWR): + * pi0 011 (RW) pi1 000 (no access) + * pi2 001 (R) pi3 101 (R+X) + * pi4 111 (RWX) pi5 010 (reserved) + * pi6 110 (rsvd) pi7 100 (X only) + * 0x8051_0000 invalid leaf (V=0) + * 0x8052_0000 leaf with a reserved bit set + * 0x8060_0000 NAPOT leaf with a valid G + * 0x8080_0000 NAPOT leaf with a reserved G + * + * On any mismatch the test exits (via semihosting) with a non-zero code that + * identifies the failing check; a successful run exits with 0. + */ + + /* mstatus bits (same positions on RV32 and RV64) */ + .equ MSTATUS_MPP_S, (1 << 11) /* MPP = 01 (Supervisor) */ + .equ MSTATUS_MPP_M, (3 << 11) /* MPP mask */ + .equ MSTATUS_MPRV, (1 << 17) + + /* Expected fault causes */ + .equ CAUSE_LOAD_ACCESS_FAULT, 5 + .equ CAUSE_STORE_ACCESS_FAULT, 7 + + .equ EXPECT_OK, 0 + .equ EXPECT_FAULT, 1 + + /* + * Non-NAPOT leaf value carrying all eight XWR encodings in pi0..pi7: + * V|L | pi0=011 pi1=000 pi2=001 pi3=101 + * pi4=111 pi5=010 pi6=110 pi7=100 + * The XWR tuples occupy bits [31:8], valid for both the 4-byte (RV32) + * and 8-byte (RV64) leaf formats. + */ + .equ LEAF_ALL_XWR, 0x997A4303 + + /* + * Switch the effective privilege of data accesses to S-mode so that + * they are subject to MPT checks (MPP=S, MPRV=1). Uses t0 only. + */ + .macro ENTER_MPRV + li t0, MSTATUS_MPP_M + csrc mstatus, t0 /* clear MPP */ + li t0, MSTATUS_MPP_S + csrs mstatus, t0 /* MPP = S */ + li t0, MSTATUS_MPRV + csrs mstatus, t0 /* enable MPRV */ + .endm + + .macro EXIT_MPRV + li t0, MSTATUS_MPRV + csrc mstatus, t0 /* disable MPRV */ + .endm + + /* + * Perform an MPT-checked load/store from \addr and verify the outcome. + * \eflt : EXPECT_OK or EXPECT_FAULT + * \ecause : expected mcause when a fault is expected + * \code : exit code reported if the check fails + */ + .macro TEST_LOAD addr, eflt, ecause, code + li a3, \addr + jal do_load + li a4, \eflt + li a5, \ecause + li a6, \code + jal check + .endm + + .macro TEST_STORE addr, eflt, ecause, code + li a3, \addr + li t3, 0x1234 + jal do_store + li a4, \eflt + li a5, \ecause + li a6, \code + jal check + .endm + + /* + * MFENCE.PA (funct7=0b0011001) must not fault in M-mode. Reuses the + * generic check subroutine (expects no fault). + */ + .macro TEST_MFENCE_PA code + li s0, 0 + .insn r 0x73, 0, 0x19, x0, x0, x0 + li a4, EXPECT_OK + li a6, \code + jal check + .endm + + /* + * Run the full set of MPT permission and structural checks against the + * shared address layout described above. + */ + .macro RUN_LEAF_CHECKS + /* pi0 RW: load and store both allowed */ + TEST_LOAD 0x80500000, EXPECT_OK, 0, 2 + TEST_STORE 0x80500000, EXPECT_OK, 0, 3 + /* pi1 no-access: load faults (cause 5), store faults (cause 7) */ + TEST_LOAD 0x80501000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 4 + TEST_STORE 0x80501000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 5 + /* pi2 R-only: load allowed, store faults */ + TEST_LOAD 0x80502000, EXPECT_OK, 0, 6 + TEST_STORE 0x80502000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 7 + /* pi3 R+X: load allowed, store faults */ + TEST_LOAD 0x80503000, EXPECT_OK, 0, 8 + TEST_STORE 0x80503000, EXPECT_FAULT, CAUSE_STORE_ACCESS_FAULT, 9 + /* pi4 RWX: load and store both allowed */ + TEST_LOAD 0x80504000, EXPECT_OK, 0, 10 + TEST_STORE 0x80504000, EXPECT_OK, 0, 11 + /* pi5/pi6 reserved encodings (010/110): load faults (cause 5) */ + TEST_LOAD 0x80505000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 12 + TEST_LOAD 0x80506000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 13 + /* pi7 X-only: load faults (cause 5) */ + TEST_LOAD 0x80507000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 14 + /* invalid leaf (V=0): load faults (cause 5) */ + TEST_LOAD 0x80510000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 15 + /* leaf with a reserved bit set: load faults (cause 5) */ + TEST_LOAD 0x80520000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 16 + /* NAPOT leaf, valid G, RW: load and store both allowed */ + TEST_LOAD 0x80600000, EXPECT_OK, 0, 17 + TEST_STORE 0x80600000, EXPECT_OK, 0, 18 + /* NAPOT leaf with a reserved G: load faults (cause 5) */ + TEST_LOAD 0x80800000, EXPECT_FAULT, CAUSE_LOAD_ACCESS_FAULT, 19 + .endm + + /* + * Emit the shared subroutines, trap handler, semihosting exit and data. + * Invoke once, after the test's _start code. + */ + .macro SMMPT_HARNESS + /* + * do_load / do_store: perform one MPT-checked access from a3. + * On return, s0 = 1 if the access faulted (else 0) and s1 = mcause. + * do_store uses the value in t3. lw/sw keep these width-agnostic. + */ + .balign 4 +do_load: + li s0, 0 + li s1, 0 + ENTER_MPRV + lw t4, 0(a3) /* MPT-checked load */ + EXIT_MPRV + ret + +do_store: + li s0, 0 + li s1, 0 + ENTER_MPRV + sw t3, 0(a3) /* MPT-checked store */ + EXIT_MPRV + ret + + /* + * check: verify the outcome of the last access. + * a4 = expected fault (0/1), a5 = expected cause, a6 = fail code. + * Returns on success; exits with a6 on mismatch. + */ + .balign 4 +check: + beqz a4, 1f + beqz s0, 2f /* expected a fault but none occurred */ + bne s1, a5, 2f /* faulted with the wrong cause */ + ret +1: + bnez s0, 2f /* unexpected fault */ + ret +2: + mv a0, a6 + j _exit + + /* + * M-mode trap handler. Records that a fault occurred (s0=1) and the + * mcause (s1), skips the faulting 4-byte instruction, disables MPRV + * and returns. Only clobbers t5/t6 besides s0/s1. + */ + .balign 4 +mtrap: + csrr t5, mcause + li s0, 1 + mv s1, t5 + csrr t6, mepc + addi t6, t6, 4 /* skip the faulting instruction */ + csrw mepc, t6 + li t5, MSTATUS_MPRV + csrc mstatus, t5 /* ensure MPRV is off on return */ + mret + + /* Exit via semihosting (ADP_Stopped_ApplicationExit) */ +_exit: +#if __riscv_xlen == 32 + /* + * On RV32 the SYS_EXIT_EXTENDED parameter block cannot be used: when + * this test runs on qemu-system-riscv64 with a 32-bit CPU the block + * pointer is sign-extended (0xffffffff_8xxx_xxxx) and the semihosting + * argument read faults. Use the plain SYS_EXIT, which takes the exit + * reason directly in a1: a successful run (a0==0) reports + * ADP_Stopped_ApplicationExit and any failure reports a non-zero + * reason (reported by QEMU as exit status 1). + */ + li a1, 0x20026 /* ADP_Stopped_ApplicationExit */ + beqz a0, 1f + mv a1, a0 /* non-zero reason -> exit status 1 */ +1: + li a0, 0x18 /* TARGET_SYS_EXIT */ +#else + lla a1, semiargs + li t0, 0x20026 + sd t0, 0(a1) + sd a0, 8(a1) + li a0, 0x20 /* TARGET_SYS_EXIT_EXTENDED */ +#endif + + .balign 16 + slli zero, zero, 0x1f + ebreak + srai zero, zero, 0x7 + j . + +#if __riscv_xlen != 32 + .data + .balign 8 +semiargs: + .space 16 +#endif + .endm diff --git a/tests/tcg/riscv64/test-smmpt.S b/tests/tcg/riscv64/test-smmpt.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/test-smmpt.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test for the Smmpt43 (Supervisor Memory Protection Table) extension, + * per SMMTT specification v0.4.9. + * + * The test runs in M-mode and builds a 3-level Smmpt43 MPT, then drives the + * mode-independent checks in smmpt-common.S. See that harness for the MPRV + * trick, the shared address layout and the exit-code convention. + * + * MPT layout (Smmpt43, 3 levels, 8-byte entries, 16 pages per leaf, + * pi = SPA[15:12], pn[i] 9 bits at SPA[16 + i*9 +: 9]): + * + * L2 (root) @ 0x8040_0000 : entry[0] -> non-leaf, PPN(L1)=0x80410 + * L1 @ 0x8041_0000 : entry[64] -> non-leaf, PPN(L0)=0x80420 + * L0 @ 0x8042_0000 : + * entry[80] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+ + * entry[81] -> V=0 (invalid) covering 0x8051_0000 + * entry[82] -> leaf with a reserved bit set covering 0x8052_0000 + * entry[96] -> NAPOT leaf, G=4, XWR=RW covering 0x8060_0000 + * entry[128] -> NAPOT leaf, reserved G=5 covering 0x8080_0000 + * + * mmpt = MODE(1=Smmpt43)<<60 | PPN(0x80400) + */ + +#include "smmpt-common.S" + + .option norvc + + .text + .global _start +_start: + /* Install the M-mode trap handler */ + lla t0, mtrap + csrw mtvec, t0 + csrw medeleg, zero + + /* PMP entry 0: NAPOT covering the whole space, RWX, so S-mode + * accesses pass PMP and the MPT is the only gate. */ + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x1f /* A=NAPOT(0x18) | R | W | X */ + csrw pmpcfg0, t0 + + /* Build the MPT tables (M-mode stores bypass the MPT). */ + /* L2[0] = non-leaf -> L1 (PPN 0x80410): (0x80410 << 10) | V */ + li t0, 0x80400000 + li t1, 0x20104001 + sd t1, 0(t0) + /* L1[64] = non-leaf -> L0 (PPN 0x80420): (0x80420 << 10) | V */ + li t0, 0x80410000 + li t1, 0x20108001 + sd t1, 0x200(t0) /* 64 * 8 = 0x200 */ + + li t0, 0x80420000 + /* L0[80] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */ + li t1, LEAF_ALL_XWR + sd t1, 0x280(t0) /* 80 * 8 = 0x280 */ + /* L0[81] = 0: invalid entry (V=0) */ + sd x0, 0x288(t0) /* 81 * 8 = 0x288 */ + /* L0[82] = leaf with reserved bit 3 set (V|L|rsv|XWR[pi0]=RW) */ + li t1, 0x30B + sd t1, 0x290(t0) /* 82 * 8 = 0x290 */ + /* L0[96] = NAPOT leaf: V|L|N | XWR=RW(0x300) | G=4(0x4000) */ + li t1, 0x4307 + sd t1, 0x300(t0) /* 96 * 8 = 0x300 */ + /* L0[128] = NAPOT leaf, reserved G=5 (0x5000): V|L|N | XWR=RW */ + li t1, 0x5307 + sd t1, 0x400(t0) /* 128 * 8 = 0x400 */ + + /* Program mmpt: MODE=1 (Smmpt43), PPN = 0x80400 */ + li t0, 0x1000000000080400 + csrw 0x382, t0 /* CSR_MMPT */ + + /* mfence.pa must not fault in M-mode */ + TEST_MFENCE_PA 1 + + /* Run the shared permission and structural checks */ + RUN_LEAF_CHECKS + + /* All tests passed */ + li a0, 0 + j _exit + + SMMPT_HARNESS -- 2.43.0
Extend the SMMPT system-test coverage to the two larger RV64 memory protection schemes: Smmpt52 (4-level MPT, 52-bit SPA) and Smmpt64 (5-level MPT, 64-bit SPA with a 32 KiB / 32 KiB-aligned root table). Both tests reuse the mode-independent harness in smmpt-common.S and only differ in the number of radix levels and, for Smmpt64, the root-table size and alignment. They exercise the same permission and structural checks (all XWR encodings for load and store, V=0 and reserved-bit leaves, and NAPOT leaves with a valid and a reserved G field) and are wired into the Makefile as run-test-smmpt52 and run-test-smmpt64. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- tests/tcg/riscv64/Makefile.softmmu-target | 8 ++ tests/tcg/riscv64/test-smmpt52.S | 91 +++++++++++++++++++++ tests/tcg/riscv64/test-smmpt64.S | 98 +++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 tests/tcg/riscv64/test-smmpt52.S create mode 100644 tests/tcg/riscv64/test-smmpt64.S diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target index XXXXXXX..XXXXXXX 100644 --- a/tests/tcg/riscv64/Makefile.softmmu-target +++ b/tests/tcg/riscv64/Makefile.softmmu-target @@ -XXX,XX +XXX,XX @@ EXTRA_RUNS += run-test-smmpt run-test-smmpt: test-smmpt $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<) +EXTRA_RUNS += run-test-smmpt52 +run-test-smmpt52: test-smmpt52 + $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<) + +EXTRA_RUNS += run-test-smmpt64 +run-test-smmpt64: test-smmpt64 + $(call run-test, $<, $(QEMU) -cpu rv64$(comma)x-smmpt=true $(QEMU_OPTS)$<) + # We don't currently support the multiarch system tests undefine MULTIARCH_TESTS diff --git a/tests/tcg/riscv64/test-smmpt52.S b/tests/tcg/riscv64/test-smmpt52.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/test-smmpt52.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test for the Smmpt52 (Supervisor Memory Protection Table) extension, + * per SMMTT specification v0.4.9. + * + * Smmpt52 uses a 4-level MPT with 8-byte entries (16 pages per leaf, + * pi = SPA[15:12], pn[i] 9 bits at SPA[16 + i*9 +: 9]). It shares the MPTE + * formats and the permission-lookup algorithm with Smmpt43, only adding one + * more radix level. See smmpt-common.S for the MPRV trick, the shared + * address layout and the exit-code convention. + * + * For the low (< 4 GiB) addresses exercised here pn[3] and pn[2] are 0, so + * the upper levels are single-entry chains: + * + * L3 (root) @ 0x8040_0000 : entry[0] -> non-leaf, PPN(L2)=0x80410 + * L2 @ 0x8041_0000 : entry[0] -> non-leaf, PPN(L1)=0x80420 + * L1 @ 0x8042_0000 : entry[64] -> non-leaf, PPN(L0)=0x80430 + * L0 @ 0x8043_0000 : + * entry[80] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+ + * entry[81] -> V=0 (invalid) covering 0x8051_0000 + * entry[82] -> leaf with a reserved bit set covering 0x8052_0000 + * entry[96] -> NAPOT leaf, G=4, XWR=RW covering 0x8060_0000 + * entry[128] -> NAPOT leaf, reserved G=5 covering 0x8080_0000 + * + * mmpt = MODE(2=Smmpt52)<<60 | PPN(0x80400) + */ + +#include "smmpt-common.S" + + .option norvc + + .text + .global _start +_start: + /* Install the M-mode trap handler */ + lla t0, mtrap + csrw mtvec, t0 + csrw medeleg, zero + + /* PMP entry 0: NAPOT covering the whole space, RWX. */ + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x1f + csrw pmpcfg0, t0 + + /* Build the MPT tables (M-mode stores bypass the MPT). */ + /* L3[0] = non-leaf -> L2 (PPN 0x80410) */ + li t0, 0x80400000 + li t1, 0x20104001 + sd t1, 0(t0) + /* L2[0] = non-leaf -> L1 (PPN 0x80420) */ + li t0, 0x80410000 + li t1, 0x20108001 + sd t1, 0(t0) + /* L1[64] = non-leaf -> L0 (PPN 0x80430) */ + li t0, 0x80420000 + li t1, 0x2010C001 + sd t1, 0x200(t0) /* 64 * 8 = 0x200 */ + + li t0, 0x80430000 + /* L0[80] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */ + li t1, LEAF_ALL_XWR + sd t1, 0x280(t0) /* 80 * 8 = 0x280 */ + /* L0[81] = 0: invalid entry (V=0) */ + sd x0, 0x288(t0) /* 81 * 8 = 0x288 */ + /* L0[82] = leaf with reserved bit 3 set */ + li t1, 0x30B + sd t1, 0x290(t0) /* 82 * 8 = 0x290 */ + /* L0[96] = NAPOT leaf: V|L|N | XWR=RW | G=4 */ + li t1, 0x4307 + sd t1, 0x300(t0) /* 96 * 8 = 0x300 */ + /* L0[128] = NAPOT leaf with reserved G=5 */ + li t1, 0x5307 + sd t1, 0x400(t0) /* 128 * 8 = 0x400 */ + + /* Program mmpt: MODE=2 (Smmpt52), PPN = 0x80400 */ + li t0, 0x2000000000080400 + csrw 0x382, t0 /* CSR_MMPT */ + + /* mfence.pa must not fault in M-mode */ + TEST_MFENCE_PA 1 + + /* Run the shared permission and structural checks */ + RUN_LEAF_CHECKS + + /* All tests passed */ + li a0, 0 + j _exit + + SMMPT_HARNESS diff --git a/tests/tcg/riscv64/test-smmpt64.S b/tests/tcg/riscv64/test-smmpt64.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/test-smmpt64.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test for the Smmpt64 (Supervisor Memory Protection Table) extension, + * per SMMTT specification v0.4.9. + * + * Smmpt64 uses a 5-level MPT with 8-byte entries. Unlike the other RV64 + * modes its root table is 32 KiB (2^12 entries, pn[4] = SPA[63:52], 12 bits) + * and must be aligned to a 32 KiB boundary; the non-root levels are the usual + * 4 KiB / 9-bit tables (16 pages per leaf, pi = SPA[15:12]). See + * smmpt-common.S for the MPRV trick, the shared address layout and the + * exit-code convention. + * + * For the low (< 4 GiB) addresses exercised here pn[4], pn[3] and pn[2] are + * 0, so the upper levels are single-entry chains: + * + * L4 (root) @ 0x8040_0000 : entry[0] -> non-leaf, PPN(L3)=0x80410 + * (32 KiB / 32 KiB-aligned) + * L3 @ 0x8041_0000 : entry[0] -> non-leaf, PPN(L2)=0x80420 + * L2 @ 0x8042_0000 : entry[0] -> non-leaf, PPN(L1)=0x80430 + * L1 @ 0x8043_0000 : entry[64] -> non-leaf, PPN(L0)=0x80440 + * L0 @ 0x8044_0000 : + * entry[80] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+ + * entry[81] -> V=0 (invalid) covering 0x8051_0000 + * entry[82] -> leaf with a reserved bit set covering 0x8052_0000 + * entry[96] -> NAPOT leaf, G=4, XWR=RW covering 0x8060_0000 + * entry[128] -> NAPOT leaf, reserved G=5 covering 0x8080_0000 + * + * mmpt = MODE(3=Smmpt64)<<60 | PPN(0x80400) (PPN[2:0] must be 0) + */ + +#include "smmpt-common.S" + + .option norvc + + .text + .global _start +_start: + /* Install the M-mode trap handler */ + lla t0, mtrap + csrw mtvec, t0 + csrw medeleg, zero + + /* PMP entry 0: NAPOT covering the whole space, RWX. */ + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x1f + csrw pmpcfg0, t0 + + /* Build the MPT tables (M-mode stores bypass the MPT). */ + /* L4[0] = non-leaf -> L3 (PPN 0x80410); root is 32 KiB aligned */ + li t0, 0x80400000 + li t1, 0x20104001 + sd t1, 0(t0) + /* L3[0] = non-leaf -> L2 (PPN 0x80420) */ + li t0, 0x80410000 + li t1, 0x20108001 + sd t1, 0(t0) + /* L2[0] = non-leaf -> L1 (PPN 0x80430) */ + li t0, 0x80420000 + li t1, 0x2010C001 + sd t1, 0(t0) + /* L1[64] = non-leaf -> L0 (PPN 0x80440) */ + li t0, 0x80430000 + li t1, 0x20110001 + sd t1, 0x200(t0) /* 64 * 8 = 0x200 */ + + li t0, 0x80440000 + /* L0[80] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */ + li t1, LEAF_ALL_XWR + sd t1, 0x280(t0) /* 80 * 8 = 0x280 */ + /* L0[81] = 0: invalid entry (V=0) */ + sd x0, 0x288(t0) /* 81 * 8 = 0x288 */ + /* L0[82] = leaf with reserved bit 3 set */ + li t1, 0x30B + sd t1, 0x290(t0) /* 82 * 8 = 0x290 */ + /* L0[96] = NAPOT leaf: V|L|N | XWR=RW | G=4 */ + li t1, 0x4307 + sd t1, 0x300(t0) /* 96 * 8 = 0x300 */ + /* L0[128] = NAPOT leaf with reserved G=5 */ + li t1, 0x5307 + sd t1, 0x400(t0) /* 128 * 8 = 0x400 */ + + /* Program mmpt: MODE=3 (Smmpt64), PPN = 0x80400 */ + li t0, 0x3000000000080400 + csrw 0x382, t0 /* CSR_MMPT */ + + /* mfence.pa must not fault in M-mode */ + TEST_MFENCE_PA 1 + + /* Run the shared permission and structural checks */ + RUN_LEAF_CHECKS + + /* All tests passed */ + li a0, 0 + j _exit + + SMMPT_HARNESS -- 2.43.0
Add SMMPT system-test coverage for Smmpt34, the RV32-only memory protection scheme (2-level MPT with 4-byte entries). Smmpt34 requires a 32-bit CPU, but qemu-system-riscv64 can run a 32-bit CPU (-cpu rv32), so the test lives in tests/tcg/riscv64 alongside the RV64 Smmpt tests and shares the same harness (smmpt-common.S). It only differs in the table geometry: 4-byte MPTEs, two radix levels, a 10-bit level-0 index and the RV32 mmpt CSR layout (MODE in bits[31:30]). It exercises the same permission and structural checks as the RV64 tests (all XWR encodings for load and store, V=0 and reserved-bit leaves, and NAPOT leaves with a valid and a reserved G field), using the same access addresses. It is built for RV32 and run on qemu-system-riscv64 with -cpu rv32. Signed-off-by: LIU Zhiwei <zhiwei_liu@linux.alibaba.com> --- tests/tcg/riscv64/test-smmpt34.S | 97 ++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/tcg/riscv64/test-smmpt34.S diff --git a/tests/tcg/riscv64/test-smmpt34.S b/tests/tcg/riscv64/test-smmpt34.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/riscv64/test-smmpt34.S @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test for the Smmpt34 (Supervisor Memory Protection Table) extension, + * per SMMTT specification v0.4.9. Smmpt34 is the RV32-only scheme. + * + * Although Smmpt34 requires a 32-bit CPU, the program is run on + * qemu-system-riscv64 with a 32-bit CPU (-cpu rv32), which lives in the + * same target as the RV64 Smmpt tests. It is therefore kept here in + * tests/tcg/riscv64 alongside the other Smmpt tests and shares the same + * harness (smmpt-common.S). Build it for RV32 and run it as: + * + * $CC -march=rv32ima_zicsr -mabi=ilp32 -I tests/tcg/riscv64 \ + * tests/tcg/riscv64/test-smmpt34.S -Wa,--noexecstack -c -o smmpt34.o + * $LD -m elf32lriscv -T tests/tcg/riscv64/semihost.ld smmpt34.o -o smmpt34 + * qemu-system-riscv64 -cpu rv32,x-smmpt=true -M virt -display none \ + * -semihosting -device loader,file=smmpt34 + * + * Smmpt34 uses a 2-level MPT with 4-byte entries (8 pages per leaf, + * pi = SPA[14:12]). The supervisor physical address is partitioned as + * pn[1] = SPA[33:25] (9-bit root index), pn[0] = SPA[24:15] (10-bit level-0 + * index) and range offset SPA[14:0]. For the RAM addresses used here + * (0x8xxx_xxxx, < 4 GiB) pn[1] resolves to 64. See smmpt-common.S for the + * MPRV trick, the shared address layout and the exit-code convention. + * + * This test reuses the same shared harness and the same access addresses as + * the RV64 tests; only the table geometry (4-byte entries, 2 levels, 10-bit + * level-0 index) and the RV32 mmpt layout differ. + * + * MPT layout (Smmpt34, 2 levels, 4-byte entries): + * + * L1 (root) @ 0x8040_0000 : entry[64] -> non-leaf, PPN(L0)=0x80410 + * L0 @ 0x8041_0000 : + * entry[160] -> non-NAPOT leaf (LEAF_ALL_XWR) covering 0x8050_0000+ + * entry[162] -> V=0 (invalid) covering 0x8051_0000 + * entry[164] -> leaf with a reserved bit set covering 0x8052_0000 + * entry[192] -> NAPOT leaf, G=6, XWR=RW covering 0x8060_0000 + * entry[256] -> NAPOT leaf, reserved G=0 covering 0x8080_0000 + * + * mmpt = MODE(1=Smmpt34)<<30 | PPN(0x80400) + */ + +#include "smmpt-common.S" + + .option norvc + + .text + .global _start +_start: + /* Install the M-mode trap handler */ + lla t0, mtrap + csrw mtvec, t0 + csrw medeleg, zero + + /* PMP entry 0: NAPOT covering the whole space, RWX. */ + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x1f + csrw pmpcfg0, t0 + + /* Build the MPT tables (M-mode stores bypass the MPT). */ + /* L1[64] = non-leaf -> L0 (PPN 0x80410): (0x80410 << 10) | V */ + li t0, 0x80400000 + li t1, 0x20104001 + sw t1, 0x100(t0) /* 64 * 4 = 0x100 */ + + li t0, 0x80410000 + /* L0[160] = non-NAPOT leaf carrying all XWR encodings (pi0..pi7) */ + li t1, LEAF_ALL_XWR + sw t1, 0x280(t0) /* 160 * 4 = 0x280 */ + /* L0[162] = 0: invalid entry (V=0) */ + sw x0, 0x288(t0) /* 162 * 4 = 0x288 */ + /* L0[164] = leaf with reserved bit 3 set (V|L|rsv|XWR[pi0]=RW) */ + li t1, 0x30B + sw t1, 0x290(t0) /* 164 * 4 = 0x290 */ + /* L0[192] = NAPOT leaf: V|L|N | XWR=RW(0x300) | G=6(0x6000) */ + li t1, 0x6307 + sw t1, 0x300(t0) /* 192 * 4 = 0x300 */ + /* L0[256] = NAPOT leaf with reserved G=0: V|L|N | XWR=RW | G=0 */ + li t1, 0x307 + sw t1, 0x400(t0) /* 256 * 4 = 0x400 */ + + /* Program mmpt: MODE=1 (Smmpt34) at bits[31:30], PPN = 0x80400 */ + li t0, 0x40080400 + csrw 0x382, t0 /* CSR_MMPT */ + + /* mfence.pa must not fault in M-mode */ + TEST_MFENCE_PA 1 + + /* Run the shared permission and structural checks */ + RUN_LEAF_CHECKS + + /* All tests passed */ + li a0, 0 + j _exit + + SMMPT_HARNESS -- 2.43.0