diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index a5bc7353b4..bb7c739a74 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -228,7 +228,7 @@ static void create_fdt_socket_cpus(RISCVVirtState *s, int socket,
     int cpu;
     uint32_t cpu_phandle;
     MachineState *mc = MACHINE(s);
-    char *name, *cpu_name, *core_name, *intc_name;
+    char *name, *cpu_name, *core_name, *intc_name, *sv_name;
 
     for (cpu = s->soc[socket].num_harts - 1; cpu >= 0; cpu--) {
         cpu_phandle = (*phandle)++;
@@ -236,14 +236,12 @@ static void create_fdt_socket_cpus(RISCVVirtState *s, int socket,
         cpu_name = g_strdup_printf("/cpus/cpu@%d",
             s->soc[socket].hartid_base + cpu);
         qemu_fdt_add_subnode(mc->fdt, cpu_name);
-        if (riscv_feature(&s->soc[socket].harts[cpu].env,
-                          RISCV_FEATURE_MMU)) {
-            qemu_fdt_setprop_string(mc->fdt, cpu_name, "mmu-type",
-                                    (is_32_bit) ? "riscv,sv32" : "riscv,sv48");
-        } else {
-            qemu_fdt_setprop_string(mc->fdt, cpu_name, "mmu-type",
-                                    "riscv,none");
-        }
+
+        sv_name = g_strdup_printf("riscv,%s",
+                                  s->soc[socket].harts[cpu].cfg.satp_mode_str);
+        qemu_fdt_setprop_string(mc->fdt, cpu_name, "mmu-type", sv_name);
+        g_free(sv_name);
+
         name = riscv_isa_string(&s->soc[socket].harts[cpu]);
         qemu_fdt_setprop_string(mc->fdt, cpu_name, "riscv,isa", name);
         g_free(name);
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index d14e95c9dc..51c06ed057 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -907,6 +907,66 @@ static void riscv_cpu_realize(DeviceState *dev, Error **errp)
      }
 #endif
 
+    /*
+     * Either a cpu sets its supported satp_mode in XXX_cpu_init
+     * or the user sets this value using satp_mode property.
+     */
+    bool rv32 = riscv_cpu_mxl(&cpu->env) == MXL_RV32;
+
+    cpu->cfg.satp_mode = VM_1_10_UNDEF;
+
+    if (rv32) {
+        if (cpu->cfg.sv32 == ON_OFF_AUTO_ON) {
+            cpu->cfg.satp_mode_str = g_strdup("sv32");
+            cpu->cfg.satp_mode = VM_1_10_SV32;
+        } else if (cpu->cfg.mbare == ON_OFF_AUTO_ON) {
+            cpu->cfg.satp_mode_str = g_strdup("none");
+            cpu->cfg.satp_mode = VM_1_10_MBARE;
+        }
+    } else {
+        if (cpu->cfg.sv64 == ON_OFF_AUTO_ON) {
+            cpu->cfg.satp_mode_str = g_strdup("sv64");
+            cpu->cfg.satp_mode = VM_1_10_SV64;
+        } else if (cpu->cfg.sv57 == ON_OFF_AUTO_ON) {
+            cpu->cfg.satp_mode_str = g_strdup("sv57");
+            cpu->cfg.satp_mode = VM_1_10_SV57;
+        } else if (cpu->cfg.sv48 == ON_OFF_AUTO_ON) {
+            cpu->cfg.satp_mode_str = g_strdup("sv48");
+            cpu->cfg.satp_mode = VM_1_10_SV48;
+        } else if (cpu->cfg.sv39 == ON_OFF_AUTO_ON) {
+            cpu->cfg.satp_mode_str = g_strdup("sv39");
+            cpu->cfg.satp_mode = VM_1_10_SV39;
+        } else if (cpu->cfg.mbare == ON_OFF_AUTO_ON) {
+            cpu->cfg.satp_mode_str = g_strdup("none");
+            cpu->cfg.satp_mode = VM_1_10_MBARE;
+        }
+    }
+
+    /*
+     * If unset by both the user and the cpu, we fallback to sv32 for 32-bit
+     * or sv57 for 64-bit when a MMU is present, and bare otherwise.
+     */
+    if (cpu->cfg.satp_mode == VM_1_10_UNDEF) {
+        if (riscv_feature(&cpu->env, RISCV_FEATURE_MMU)) {
+            if (rv32) {
+                cpu->cfg.satp_mode_str = g_strdup("sv32");
+                cpu->cfg.satp_mode = VM_1_10_SV32;
+            } else {
+                cpu->cfg.satp_mode_str = g_strdup("sv57");
+                cpu->cfg.satp_mode = VM_1_10_SV57;
+            }
+        } else {
+            cpu->cfg.satp_mode_str = g_strdup("none");
+            cpu->cfg.satp_mode = VM_1_10_MBARE;
+        }
+    }
+
+    riscv_cpu_finalize_features(cpu, &local_err);
+    if (local_err != NULL) {
+        error_propagate(errp, local_err);
+        return;
+    }
+
     riscv_cpu_register_gdb_regs_for_features(cs);
 
     qemu_init_vcpu(cs);
@@ -915,6 +975,102 @@ static void riscv_cpu_realize(DeviceState *dev, Error **errp)
     mcc->parent_realize(dev, errp);
 }
 
+void riscv_cpu_satp_mode_finalize(RISCVCPU *cpu, Error **errp)
+{
+    bool rv32 = riscv_cpu_mxl(&cpu->env) == MXL_RV32;
+
+    /* First, get rid of 32-bit/64-bit incompatibilities */
+    if (rv32) {
+        if (cpu->cfg.sv39 == ON_OFF_AUTO_ON
+                || cpu->cfg.sv48 == ON_OFF_AUTO_ON
+                || cpu->cfg.sv57 == ON_OFF_AUTO_ON
+                || cpu->cfg.sv64 == ON_OFF_AUTO_ON) {
+            error_setg(errp, "cannot enable 64-bit satp modes "
+                       "(sv39/sv48/sv57/sv64)");
+            error_append_hint(errp, "cpu is in 32-bit mode, 64-bit satp modes "
+                              "can't be enabled\n");
+            return;
+        }
+    } else {
+        if (cpu->cfg.sv32 == ON_OFF_AUTO_ON) {
+            error_setg(errp, "cannot enable 32-bit satp mode (sv32)");
+            error_append_hint(errp, "cpu is in 64-bit mode, 32-bit satp mode "
+                              "can't be enabled\n");
+            return;
+        }
+    }
+
+    /*
+     * Then make sure the user did not ask for an invalid configuration as per
+     * the specification.
+     */
+    switch (cpu->cfg.satp_mode) {
+    case VM_1_10_SV32:
+        if (cpu->cfg.mbare == ON_OFF_AUTO_OFF) {
+            error_setg(errp, "cannot disable mbare satp mode");
+            error_append_hint(errp, "mbare satp mode must be enabled if sv32 "
+                              "is enabled\n");
+            return;
+        }
+
+        break;
+    case VM_1_10_SV39:
+        if (cpu->cfg.mbare == ON_OFF_AUTO_OFF) {
+            error_setg(errp, "cannot disable mbare satp mode");
+            error_append_hint(errp, "mbare satp mode must be enabled if sv39 "
+                              "is enabled\n");
+            return;
+        }
+
+        break;
+    case VM_1_10_SV48:
+        if (cpu->cfg.mbare == ON_OFF_AUTO_OFF
+                || cpu->cfg.sv39 == ON_OFF_AUTO_OFF) {
+            error_setg(errp, "cannot disable mbare/sv39 satp modes");
+            error_append_hint(errp, "mbare/sv39 satp modes must be enabled if "
+                              "sv48 is enabled\n");
+            return;
+        }
+
+        break;
+    case VM_1_10_SV57:
+        if (cpu->cfg.mbare == ON_OFF_AUTO_OFF
+                || cpu->cfg.sv39 == ON_OFF_AUTO_OFF
+                || cpu->cfg.sv48 == ON_OFF_AUTO_OFF) {
+            error_setg(errp, "cannot disable mbare/sv39/sv48 satp modes");
+            error_append_hint(errp, "mbare/sv39/sv48 satp modes must be "
+                              "enabled if sv57 is enabled\n");
+            return;
+        }
+
+        break;
+    case VM_1_10_SV64:
+        if (cpu->cfg.mbare == ON_OFF_AUTO_OFF
+                || cpu->cfg.sv39 == ON_OFF_AUTO_OFF
+                || cpu->cfg.sv48 == ON_OFF_AUTO_OFF
+                || cpu->cfg.sv57 == ON_OFF_AUTO_OFF) {
+            error_setg(errp, "cannot disable mbare/sv39/sv48/sv57 satp "
+                       "modes");
+            error_append_hint(errp, "mbare/sv39/sv48/sv57 satp modes must be "
+                              "enabled if sv57 is enabled\n");
+            return;
+        }
+
+        break;
+    }
+}
+
+void riscv_cpu_finalize_features(RISCVCPU *cpu, Error **errp)
+{
+    Error *local_err = NULL;
+
+    riscv_cpu_satp_mode_finalize(cpu, &local_err);
+    if (local_err != NULL) {
+        error_propagate(errp, local_err);
+        return;
+    }
+}
+
 #ifndef CONFIG_USER_ONLY
 static void riscv_cpu_set_irq(void *opaque, int irq, int level)
 {
@@ -1094,6 +1250,14 @@ static Property riscv_cpu_properties[] = {
 
     DEFINE_PROP_BOOL("rvv_ta_all_1s", RISCVCPU, cfg.rvv_ta_all_1s, false),
     DEFINE_PROP_BOOL("rvv_ma_all_1s", RISCVCPU, cfg.rvv_ma_all_1s, false),
+
+    DEFINE_PROP_ON_OFF_AUTO("mbare", RISCVCPU, cfg.mbare, ON_OFF_AUTO_AUTO),
+    DEFINE_PROP_ON_OFF_AUTO("sv32", RISCVCPU, cfg.sv32, ON_OFF_AUTO_AUTO),
+    DEFINE_PROP_ON_OFF_AUTO("sv39", RISCVCPU, cfg.sv39, ON_OFF_AUTO_AUTO),
+    DEFINE_PROP_ON_OFF_AUTO("sv48", RISCVCPU, cfg.sv48, ON_OFF_AUTO_AUTO),
+    DEFINE_PROP_ON_OFF_AUTO("sv57", RISCVCPU, cfg.sv57, ON_OFF_AUTO_AUTO),
+    DEFINE_PROP_ON_OFF_AUTO("sv64", RISCVCPU, cfg.sv64, ON_OFF_AUTO_AUTO),
+
     DEFINE_PROP_END_OF_LIST(),
 };
 
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 3a9e25053f..dcdde1e0b7 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -27,6 +27,7 @@
 #include "qom/object.h"
 #include "qemu/int128.h"
 #include "cpu_bits.h"
+#include "qapi/qapi-types-common.h"
 
 #define TCG_GUEST_DEFAULT_MO 0
 
@@ -480,6 +481,10 @@ struct RISCVCPUConfig {
     bool debug;
 
     bool short_isa_string;
+
+    OnOffAuto mbare, sv32, sv39, sv48, sv57, sv64;
+    uint8_t satp_mode;
+    char *satp_mode_str;
 };
 
 typedef struct RISCVCPUConfig RISCVCPUConfig;
@@ -789,4 +794,7 @@ void riscv_set_csr_ops(int csrno, riscv_csr_operations *ops);
 
 void riscv_cpu_register_gdb_regs_for_features(CPUState *cs);
 
+void riscv_cpu_satp_mode_finalize(RISCVCPU *cpu, Error **errp);
+void riscv_cpu_finalize_features(RISCVCPU *cpu, Error **errp);
+
 #endif /* RISCV_CPU_H */
diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h
index d8f5f0abed..3e67a815d5 100644
--- a/target/riscv/cpu_bits.h
+++ b/target/riscv/cpu_bits.h
@@ -590,6 +590,7 @@ typedef enum {
 #define VM_1_10_SV48        9
 #define VM_1_10_SV57        10
 #define VM_1_10_SV64        11
+#define VM_1_10_UNDEF       16
 
 /* Page table entry (PTE) fields */
 #define PTE_V               0x001 /* Valid */
diff --git a/target/riscv/csr.c b/target/riscv/csr.c
index 5c9a7ee287..d2aab1627e 100644
--- a/target/riscv/csr.c
+++ b/target/riscv/csr.c
@@ -1109,10 +1109,14 @@ static RISCVException read_mstatus(CPURISCVState *env, int csrno,
 
 static int validate_vm(CPURISCVState *env, target_ulong vm)
 {
+    vm &= 0xf;
+
     if (riscv_cpu_mxl(env) == MXL_RV32) {
-        return valid_vm_1_10_32[vm & 0xf];
+        return valid_vm_1_10_32[vm] &&
+            (vm <= RISCV_CPU(env_cpu(env))->cfg.satp_mode);
     } else {
-        return valid_vm_1_10_64[vm & 0xf];
+        return valid_vm_1_10_64[vm] &&
+            (vm <= RISCV_CPU(env_cpu(env))->cfg.satp_mode);
     }
 }
 
