diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 499d2a6028..0c5b942ed0 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -935,9 +935,6 @@ struct ArchCPU {
     /* KVM steal time */
     OnOffAuto kvm_steal_time;
 
-    /* Uniprocessor system with MP extensions */
-    bool mp_is_up;
-
     /* True if we tried kvm_arm_host_cpu_features() during CPU instance_init
      * and the probe failed (so we need to report the error in realize)
      */
@@ -977,7 +974,7 @@ struct ArchCPU {
     uint64_t id_aa64afr0;
     uint64_t id_aa64afr1;
     uint64_t clidr;
-    uint64_t mp_affinity; /* MP ID without feature bits */
+    uint64_t mpidr_el1;
     /* The elements of this array are the CCSIDR values for each cache,
      * in the order L1DCache, L1ICache, L2DCache, L2ICache, etc.
      */
@@ -1041,7 +1038,7 @@ uint64_t arm_build_mp_affinity(int idx, uint8_t clustersz);
 
 static inline uint64_t arm_cpu_mp_affinity(ARMCPU *cpu)
 {
-    return cpu->mp_affinity;
+    return cpu->mpidr_el1 & ARM64_AFFINITY_MASK;
 }
 
 #ifndef CONFIG_USER_ONLY
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index a104a77165..a46fa424d3 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -1231,6 +1231,9 @@ static void arm_cpu_initfn(Object *obj)
     cpu->sme_default_vq = 2;
 # endif
 #else
+    /* To be set properly by either the board or by realize. */
+    cpu->mpidr_el1 = ARM64_AFFINITY_INVALID;
+
     /* Our inbound IRQ and FIQ lines */
     if (kvm_enabled()) {
         /* VIRQ and VFIQ are unused with KVM but we add them to maintain
@@ -1921,16 +1924,6 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
         return;
     }
 
-    /* This cpu-id-to-MPIDR affinity is used only for TCG; KVM will override it.
-     * We don't support setting cluster ID ([16..23]) (known as Aff2
-     * in later ARM ARM versions), or any of the higher affinity level fields,
-     * so these bits always RAZ.
-     */
-    if (cpu->mp_affinity == ARM64_AFFINITY_INVALID) {
-        cpu->mp_affinity = arm_build_mp_affinity(cs->cpu_index,
-                                                 ARM_DEFAULT_CPUS_PER_CLUSTER);
-    }
-
     if (cpu->reset_hivecs) {
             cpu->reset_sctlr |= (1 << 13);
     }
@@ -2116,7 +2109,27 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
     if (cpu->core_count == -1) {
         cpu->core_count = smp_cpus;
     }
-#endif
+
+    /*
+     * Provide a default cpu-id-to-MPIDR affinity; we don't support setting
+     * Aff2 or Aff3.  This has already set by KVM and by some board models,
+     * which will have cleared our internal invalid bit.
+     */
+    if (cpu->mpidr_el1 == ARM64_AFFINITY_INVALID) {
+        assert(!kvm_enabled());
+        assert(cs->cpu_index < 256 * ARM_DEFAULT_CPUS_PER_CLUSTER);
+        cpu->mpidr_el1 = arm_build_mp_affinity(cs->cpu_index,
+                                               ARM_DEFAULT_CPUS_PER_CLUSTER);
+    }
+#endif /* !CONFIG_USER_ONLY */
+
+    /* Linux exposes M to userland, so still need to set it for user-only. */
+    if (arm_feature(env, ARM_FEATURE_V7MP)) {
+        cpu->mpidr_el1 |= (1u << 31);   /* M */
+        if (cpu->core_count == 1) {
+            cpu->mpidr_el1 |= 1 << 30;  /* U */
+        }
+    }
 
     if (tcg_enabled()) {
         int dcz_blocklen = 4 << cpu->dcz_blocksize;
@@ -2176,10 +2189,47 @@ static ObjectClass *arm_cpu_class_by_name(const char *cpu_model)
     return oc;
 }
 
+static void cpu_arm_set_mp_affinity(Object *obj, Visitor *v, const char *name,
+                                    void *opaque, Error **errp)
+{
+    ARMCPU *cpu = ARM_CPU(obj);
+    CPUARMState *env = &cpu->env;
+    uint64_t value;
+
+    if (!visit_type_uint64(v, name, &value, errp)) {
+        return;
+    }
+
+    if (arm_feature(env, ARM_FEATURE_AARCH64)) {
+        value &= ARM64_AFFINITY_MASK;
+    } else {
+        value &= ARM32_AFFINITY_MASK;
+    }
+    if (cpu->mpidr_el1 == ARM64_AFFINITY_INVALID) {
+        cpu->mpidr_el1 = value;
+    } else {
+        cpu->mpidr_el1 &= ~ARM64_AFFINITY_MASK;
+        cpu->mpidr_el1 |= value;
+    }
+}
+
+static void cpu_arm_get_mp_affinity(Object *obj, Visitor *v, const char *name,
+                                    void *opaque, Error **errp)
+{
+    ARMCPU *cpu = ARM_CPU(obj);
+    uint64_t value;
+
+    /*
+     * Note that the arm64 mask is a superset of the arm32 mask,
+     * and we will have limited the value upon setting.
+     * Here we simply want to return the Aff[0-3] fields.
+     */
+    value = cpu->mpidr_el1 & ARM64_AFFINITY_MASK;
+    visit_type_uint64(v, name, &value, errp);
+}
+
 static Property arm_cpu_properties[] = {
     DEFINE_PROP_UINT64("midr", ARMCPU, midr, 0),
-    DEFINE_PROP_UINT64("mp-affinity", ARMCPU,
-                        mp_affinity, ARM64_AFFINITY_INVALID),
     DEFINE_PROP_INT32("node-id", ARMCPU, node_id, CPU_UNSET_NUMA_NODE_ID),
     DEFINE_PROP_INT32("core-count", ARMCPU, core_count, -1),
     DEFINE_PROP_END_OF_LIST()
@@ -2244,6 +2294,10 @@ static void arm_cpu_class_init(ObjectClass *oc, void *data)
 
     device_class_set_props(dc, arm_cpu_properties);
 
+    object_class_property_add(oc, "mp-affinity", "uint64",
+                              cpu_arm_get_mp_affinity,
+                              cpu_arm_set_mp_affinity, NULL, NULL);
+
     resettable_class_set_parent_phases(rc, NULL, arm_cpu_reset_hold, NULL,
                                        &acc->parent_phases);
 
diff --git a/target/arm/cpu_tcg.c b/target/arm/cpu_tcg.c
index d566a815d3..7514065d5b 100644
--- a/target/arm/cpu_tcg.c
+++ b/target/arm/cpu_tcg.c
@@ -848,7 +848,6 @@ static void cortex_r5_initfn(Object *obj)
     cpu->isar.id_isar4 = 0x0010142;
     cpu->isar.id_isar5 = 0x0;
     cpu->isar.id_isar6 = 0x0;
-    cpu->mp_is_up = true;
     cpu->pmsav7_dregion = 16;
     cpu->isar.reset_pmcr_el0 = 0x41151800;
     define_arm_cp_regs(cpu, cortexr5_cp_reginfo);
diff --git a/target/arm/helper.c b/target/arm/helper.c
index bac2ea62c4..8f5097f995 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -4087,24 +4087,6 @@ static uint64_t midr_read(CPUARMState *env, const ARMCPRegInfo *ri)
     return raw_read(env, ri);
 }
 
-static uint64_t mpidr_read_val(CPUARMState *env)
-{
-    ARMCPU *cpu = env_archcpu(env);
-    uint64_t mpidr = cpu->mp_affinity;
-
-    if (arm_feature(env, ARM_FEATURE_V7MP)) {
-        mpidr |= (1U << 31);
-        /* Cores which are uniprocessor (non-coherent)
-         * but still implement the MP extensions set
-         * bit 30. (For instance, Cortex-R5).
-         */
-        if (cpu->mp_is_up) {
-            mpidr |= (1u << 30);
-        }
-    }
-    return mpidr;
-}
-
 static uint64_t mpidr_read(CPUARMState *env, const ARMCPRegInfo *ri)
 {
     unsigned int cur_el = arm_current_el(env);
@@ -4112,7 +4094,7 @@ static uint64_t mpidr_read(CPUARMState *env, const ARMCPRegInfo *ri)
     if (arm_is_el2_enabled(env) && cur_el == 1) {
         return env->cp15.vmpidr_el2;
     }
-    return mpidr_read_val(env);
+    return env_archcpu(env)->mpidr_el1;
 }
 
 static const ARMCPRegInfo lpae_cp_reginfo[] = {
@@ -7940,7 +7922,6 @@ void register_cp_regs_for_features(ARMCPU *cpu)
     if (arm_feature(env, ARM_FEATURE_EL2)
         || (arm_feature(env, ARM_FEATURE_EL3)
             && arm_feature(env, ARM_FEATURE_V8))) {
-        uint64_t vmpidr_def = mpidr_read_val(env);
         ARMCPRegInfo vpidr_regs[] = {
             { .name = "VPIDR", .state = ARM_CP_STATE_AA32,
               .cp = 15, .opc1 = 4, .crn = 0, .crm = 0, .opc2 = 0,
@@ -7956,12 +7937,12 @@ void register_cp_regs_for_features(ARMCPU *cpu)
             { .name = "VMPIDR", .state = ARM_CP_STATE_AA32,
               .cp = 15, .opc1 = 4, .crn = 0, .crm = 0, .opc2 = 5,
               .access = PL2_RW, .accessfn = access_el3_aa32ns,
-              .resetvalue = vmpidr_def,
+              .resetvalue = cpu->mpidr_el1,
               .type = ARM_CP_ALIAS | ARM_CP_EL3_NO_EL2_C_NZ,
               .fieldoffset = offsetoflow32(CPUARMState, cp15.vmpidr_el2) },
             { .name = "VMPIDR_EL2", .state = ARM_CP_STATE_AA64,
               .opc0 = 3, .opc1 = 4, .crn = 0, .crm = 0, .opc2 = 5,
-              .access = PL2_RW, .resetvalue = vmpidr_def,
+              .access = PL2_RW, .resetvalue = cpu->mpidr_el1,
               .type = ARM_CP_EL3_NO_EL2_C_NZ,
               .fieldoffset = offsetof(CPUARMState, cp15.vmpidr_el2) },
         };
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index e0ba91f5c6..278a4b2ede 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -607,7 +607,7 @@ int hvf_arch_init_vcpu(CPUState *cpu)
     assert_hvf_ok(ret);
 
     ret = hv_vcpu_set_sys_reg(cpu->hvf->fd, HV_SYS_REG_MPIDR_EL1,
-                              arm_cpu->mp_affinity);
+                              arm_cpu->mpidr_el1);
     assert_hvf_ok(ret);
 
     ret = hv_vcpu_get_sys_reg(cpu->hvf->fd, HV_SYS_REG_ID_AA64PFR0_EL1, &pfr);
diff --git a/target/arm/kvm64.c b/target/arm/kvm64.c
index 1197253d12..2cdd7517b8 100644
--- a/target/arm/kvm64.c
+++ b/target/arm/kvm64.c
@@ -914,11 +914,11 @@ int kvm_arch_init_vcpu(CPUState *cs)
      * Currently KVM has its own idea about MPIDR assignment, so we
      * override our defaults with what we get from KVM.
      */
-    ret = kvm_get_one_reg(cs, ARM64_SYS_REG(ARM_CPU_ID_MPIDR), &mpidr);
+    ret = kvm_get_one_reg(cs, ARM64_SYS_REG(ARM_CPU_ID_MPIDR),
+                          &cpu->mpidr_el1);
     if (ret) {
         return ret;
     }
-    cpu->mp_affinity = mpidr & ARM64_AFFINITY_MASK;
 
     kvm_arm_init_debug(cs);
 
