[PATCH v4 1/2] hvf/arm: handle FEAT_SME2 migration

Manos Pitsidianakis posted 2 patches 3 days ago
Maintainers: Alexander Graf <agraf@csgraf.de>, Peter Maydell <peter.maydell@linaro.org>
There is a newer version of this series
[PATCH v4 1/2] hvf/arm: handle FEAT_SME2 migration
Posted by Manos Pitsidianakis 3 days ago
SME2 support adds the following state for HVF guests:

- Vector registers Z0, ... , Z31 (introduced by FEAT_SVE but HVF does
  not support it)
- Predicate registers P0, .., P15 (also FEAT_SVE)
- ZA register
- ZT0 register
- PSTATE.{SM,ZA} bits (SVCR pseudo-register)
- SMPRI_EL1 which handles the PE's priority in the SMCU
- TPIDR2_EL0 the thread local ID register for SME

Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
 target/arm/hvf/hvf.c           | 290 ++++++++++++++++++++++++++++++++++++++++-
 target/arm/hvf/hvf_sme_stubs.h | 158 ++++++++++++++++++++++
 target/arm/hvf/sysreg.c.inc    |   8 ++
 target/arm/hvf_arm.h           |  41 ++++++
 target/arm/machine.c           |   2 +-
 5 files changed, 496 insertions(+), 3 deletions(-)

diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index d79469ca27f794139b8073e25fc16d470b1942d5..ce62f8c97876e3fd948fad96df631f1b53db5e67 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -395,6 +395,60 @@ static const struct hvf_reg_match hvf_fpreg_match[] = {
     { HV_SIMD_FP_REG_Q31, offsetof(CPUARMState, vfp.zregs[31]) },
 };
 
+static const struct hvf_reg_match hvf_sme2_zreg_match[] = {
+    { HV_SME_Z_REG_0, offsetof(CPUARMState, vfp.zregs[0]) },
+    { HV_SME_Z_REG_1, offsetof(CPUARMState, vfp.zregs[1]) },
+    { HV_SME_Z_REG_2, offsetof(CPUARMState, vfp.zregs[2]) },
+    { HV_SME_Z_REG_3, offsetof(CPUARMState, vfp.zregs[3]) },
+    { HV_SME_Z_REG_4, offsetof(CPUARMState, vfp.zregs[4]) },
+    { HV_SME_Z_REG_5, offsetof(CPUARMState, vfp.zregs[5]) },
+    { HV_SME_Z_REG_6, offsetof(CPUARMState, vfp.zregs[6]) },
+    { HV_SME_Z_REG_7, offsetof(CPUARMState, vfp.zregs[7]) },
+    { HV_SME_Z_REG_8, offsetof(CPUARMState, vfp.zregs[8]) },
+    { HV_SME_Z_REG_9, offsetof(CPUARMState, vfp.zregs[9]) },
+    { HV_SME_Z_REG_10, offsetof(CPUARMState, vfp.zregs[10]) },
+    { HV_SME_Z_REG_11, offsetof(CPUARMState, vfp.zregs[11]) },
+    { HV_SME_Z_REG_12, offsetof(CPUARMState, vfp.zregs[12]) },
+    { HV_SME_Z_REG_13, offsetof(CPUARMState, vfp.zregs[13]) },
+    { HV_SME_Z_REG_14, offsetof(CPUARMState, vfp.zregs[14]) },
+    { HV_SME_Z_REG_15, offsetof(CPUARMState, vfp.zregs[15]) },
+    { HV_SME_Z_REG_16, offsetof(CPUARMState, vfp.zregs[16]) },
+    { HV_SME_Z_REG_17, offsetof(CPUARMState, vfp.zregs[17]) },
+    { HV_SME_Z_REG_18, offsetof(CPUARMState, vfp.zregs[18]) },
+    { HV_SME_Z_REG_19, offsetof(CPUARMState, vfp.zregs[19]) },
+    { HV_SME_Z_REG_20, offsetof(CPUARMState, vfp.zregs[20]) },
+    { HV_SME_Z_REG_21, offsetof(CPUARMState, vfp.zregs[21]) },
+    { HV_SME_Z_REG_22, offsetof(CPUARMState, vfp.zregs[22]) },
+    { HV_SME_Z_REG_23, offsetof(CPUARMState, vfp.zregs[23]) },
+    { HV_SME_Z_REG_24, offsetof(CPUARMState, vfp.zregs[24]) },
+    { HV_SME_Z_REG_25, offsetof(CPUARMState, vfp.zregs[25]) },
+    { HV_SME_Z_REG_26, offsetof(CPUARMState, vfp.zregs[26]) },
+    { HV_SME_Z_REG_27, offsetof(CPUARMState, vfp.zregs[27]) },
+    { HV_SME_Z_REG_28, offsetof(CPUARMState, vfp.zregs[28]) },
+    { HV_SME_Z_REG_29, offsetof(CPUARMState, vfp.zregs[29]) },
+    { HV_SME_Z_REG_30, offsetof(CPUARMState, vfp.zregs[30]) },
+    { HV_SME_Z_REG_31, offsetof(CPUARMState, vfp.zregs[31]) },
+};
+
+static const struct hvf_reg_match hvf_sme2_preg_match[] = {
+    { HV_SME_P_REG_0, offsetof(CPUARMState, vfp.pregs[0]) },
+    { HV_SME_P_REG_1, offsetof(CPUARMState, vfp.pregs[1]) },
+    { HV_SME_P_REG_2, offsetof(CPUARMState, vfp.pregs[2]) },
+    { HV_SME_P_REG_3, offsetof(CPUARMState, vfp.pregs[3]) },
+    { HV_SME_P_REG_4, offsetof(CPUARMState, vfp.pregs[4]) },
+    { HV_SME_P_REG_5, offsetof(CPUARMState, vfp.pregs[5]) },
+    { HV_SME_P_REG_6, offsetof(CPUARMState, vfp.pregs[6]) },
+    { HV_SME_P_REG_7, offsetof(CPUARMState, vfp.pregs[7]) },
+    { HV_SME_P_REG_8, offsetof(CPUARMState, vfp.pregs[8]) },
+    { HV_SME_P_REG_9, offsetof(CPUARMState, vfp.pregs[9]) },
+    { HV_SME_P_REG_10, offsetof(CPUARMState, vfp.pregs[10]) },
+    { HV_SME_P_REG_11, offsetof(CPUARMState, vfp.pregs[11]) },
+    { HV_SME_P_REG_12, offsetof(CPUARMState, vfp.pregs[12]) },
+    { HV_SME_P_REG_13, offsetof(CPUARMState, vfp.pregs[13]) },
+    { HV_SME_P_REG_14, offsetof(CPUARMState, vfp.pregs[14]) },
+    { HV_SME_P_REG_15, offsetof(CPUARMState, vfp.pregs[15]) },
+};
+
 /*
  * QEMU uses KVM system register ids in the migration format.
  * Conveniently, HVF uses the same encoding of the op* and cr* parameters
@@ -406,22 +460,201 @@ static const struct hvf_reg_match hvf_fpreg_match[] = {
 #define HVF_TO_KVMID(HVF)  \
     (CP_REG_ARM64 | CP_REG_SIZE_U64 | CP_REG_ARM64_SYSREG | (HVF))
 
-/* Verify this at compile-time. */
+/*
+ * Verify this at compile-time.
+ *
+ * SME2 registers are guarded by a runtime availability attribute instead of a
+ * compile-time def, so verify those at runtime in hvf_arch_init_vcpu() below.
+ */
 
 #define DEF_SYSREG(HVF_ID, ...) \
   QEMU_BUILD_BUG_ON(HVF_ID != KVMID_TO_HVF(KVMID_AA64_SYS_REG64(__VA_ARGS__)));
+#define DEF_SYSREG_15_02(...)
 
 #include "sysreg.c.inc"
 
 #undef DEF_SYSREG
+#undef DEF_SYSREG_15_02
 
 #define DEF_SYSREG(HVF_ID, op0, op1, crn, crm, op2)  HVF_ID,
+#define DEF_SYSREG_15_02(...)
 
 static const hv_sys_reg_t hvf_sreg_list[] = {
 #include "sysreg.c.inc"
 };
 
 #undef DEF_SYSREG
+#undef DEF_SYSREG_15_02
+
+#define DEF_SYSREG(...)
+#define DEF_SYSREG_15_02(HVF_ID, op0, op1, crn, crm, op2) HVF_ID,
+
+API_AVAILABLE(macos(15.2))
+static const hv_sys_reg_t hvf_sreg_list_sme2[] = {
+#include "sysreg.c.inc"
+};
+
+#undef DEF_SYSREG
+#undef DEF_SYSREG_15_02
+
+/*
+ * For FEAT_SME2 migration, we need to store PSTATE.{SM,ZA} bits which are
+ * accessible with the SVCR pseudo-register. However, in the HVF API this is
+ * not exposed as a system-register (i.e. HVF_SYS_REG_SVCR) but a custom
+ * struct, hv_vcpu_sme_state_t. So we need to define our own KVMID in order to
+ * store it in cpreg_values and make it migrateable.
+ */
+#define SVCR KVMID_AA64_SYS_REG64(3, 3, 4, 2, 2)
+
+API_AVAILABLE(macos(15.2))
+static void hvf_arch_put_sme(CPUState *cpu)
+{
+    ARMCPU *arm_cpu = ARM_CPU(cpu);
+    CPUARMState *env = &arm_cpu->env;
+    const size_t svl_bytes = hvf_arm_sme2_get_svl();
+    const size_t z_size = svl_bytes;
+    const size_t preg_size = DIV_ROUND_UP(z_size, 8);
+    const size_t za_size = svl_bytes * svl_bytes;
+    hv_vcpu_sme_state_t sme_state = { 0 };
+    hv_return_t ret;
+    uint64_t svcr;
+    int n;
+
+    /*
+     * Set PSTATE.{SM,ZA} bits
+     */
+    svcr = arm_cpu->cpreg_values[arm_cpu->cpreg_array_len - 1];
+    env->svcr = svcr;
+
+    /*
+     * Construct SVCR (PSTATE.{SM,ZA}) state to pass to HVF:
+     */
+    sme_state.streaming_sve_mode_enabled = FIELD_EX64(env->svcr, SVCR, SM) > 0;
+    sme_state.za_storage_enabled = FIELD_EX64(env->svcr, SVCR, ZA) > 0;
+    ret = hv_vcpu_set_sme_state(cpu->accel->fd, &sme_state);
+    assert_hvf_ok(ret);
+
+    /*
+     * We only care about Z/P registers if we're in streaming SVE mode, i.e.
+     * PSTATE.SM is set, because only then can instructions that access them be
+     * used. We don't care about the register values otherwise. This is because
+     * when the processing unit exits/enters this mode, it zeroes out those
+     * registers.
+     */
+    if (sme_state.streaming_sve_mode_enabled) {
+        for (n = 0; n < ARRAY_SIZE(hvf_sme2_zreg_match); ++n) {
+            ret = hv_vcpu_set_sme_z_reg(cpu->accel->fd,
+                                        hvf_sme2_zreg_match[n].reg,
+                                        (uint8_t *)&env->vfp.zregs[n].d[0],
+                                        z_size);
+            assert_hvf_ok(ret);
+        }
+
+        for (n = 0; n < ARRAY_SIZE(hvf_sme2_preg_match); ++n) {
+            ret = hv_vcpu_set_sme_p_reg(cpu->accel->fd,
+                                        hvf_sme2_preg_match[n].reg,
+                                        (uint8_t *)&env->vfp.pregs[n].p[0],
+                                        preg_size);
+            assert_hvf_ok(ret);
+        }
+    }
+
+    /*
+     * If PSTATE.ZA bit is set then ZA and ZT0 are valid, otherwise they are
+     * zeroed out.
+     */
+    if (sme_state.za_storage_enabled) {
+        hv_sme_zt0_uchar64_t tmp = { 0 };
+
+        memcpy(&tmp, &env->za_state.zt0, 64);
+        ret = hv_vcpu_set_sme_zt0_reg(cpu->accel->fd, &tmp);
+        assert_hvf_ok(ret);
+
+        ret = hv_vcpu_set_sme_za_reg(cpu->accel->fd,
+                                     (uint8_t *)&env->za_state.za,
+                                     za_size);
+        assert_hvf_ok(ret);
+    }
+
+    return;
+}
+
+API_AVAILABLE(macos(15.2))
+static void hvf_arch_get_sme(CPUState *cpu)
+{
+    ARMCPU *arm_cpu = ARM_CPU(cpu);
+    CPUARMState *env = &arm_cpu->env;
+    const size_t svl_bytes = hvf_arm_sme2_get_svl();
+    const size_t z_size = svl_bytes;
+    const size_t preg_size = DIV_ROUND_UP(z_size, 8);
+    const size_t za_size = svl_bytes * svl_bytes;
+    hv_vcpu_sme_state_t sme_state = { 0 };
+    hv_return_t ret;
+    uint64_t svcr;
+    int n;
+
+    /*
+     * Get SVCR (PSTATE.{SM,ZA}) state from HVF:
+     */
+    ret = hv_vcpu_get_sme_state(cpu->accel->fd, &sme_state);
+    assert_hvf_ok(ret);
+
+    /*
+     * Set SVCR first because changing it will zero out Z/P regs
+     */
+    svcr =
+        (sme_state.za_storage_enabled ? R_SVCR_ZA_MASK : 0)
+        | (sme_state.streaming_sve_mode_enabled ? R_SVCR_SM_MASK : 0);
+
+    aarch64_set_svcr(env, svcr, R_SVCR_ZA_MASK | R_SVCR_SM_MASK);
+    arm_cpu->cpreg_values[arm_cpu->cpreg_array_len - 1] = svcr;
+
+    /*
+     * We only care about Z/P registers if we're in streaming SVE mode, i.e.
+     * PSTATE.SM is set, because only then can instructions that access them be
+     * used. We don't care about the register values otherwise. This is because
+     * when the processing unit exits/enters this mode, it zeroes out those
+     * registers.
+     */
+    if (sme_state.streaming_sve_mode_enabled) {
+        for (n = 0; n < ARRAY_SIZE(hvf_sme2_zreg_match); ++n) {
+            ret = hv_vcpu_get_sme_z_reg(cpu->accel->fd,
+                                        hvf_sme2_zreg_match[n].reg,
+                                        (uint8_t *)&env->vfp.zregs[n].d[0],
+                                        z_size);
+            assert_hvf_ok(ret);
+        }
+
+        for (n = 0; n < ARRAY_SIZE(hvf_sme2_preg_match); ++n) {
+            ret = hv_vcpu_get_sme_p_reg(cpu->accel->fd,
+                                        hvf_sme2_preg_match[n].reg,
+                                        (uint8_t *)&env->vfp.pregs[n].p[0],
+                                        preg_size);
+            assert_hvf_ok(ret);
+        }
+    }
+
+    /*
+     * If PSTATE.ZA bit is set then ZA and ZT0 are valid, otherwise they are
+     * zeroed out.
+     */
+    if (sme_state.za_storage_enabled) {
+        hv_sme_zt0_uchar64_t tmp = { 0 };
+
+        /* Get ZT0 in a tmp vector, and then copy it to env.za_state.zt0 */
+        ret = hv_vcpu_get_sme_zt0_reg(cpu->accel->fd, &tmp);
+        assert_hvf_ok(ret);
+
+        memcpy(&env->za_state.zt0, &tmp, 64);
+        ret = hv_vcpu_get_sme_za_reg(cpu->accel->fd,
+                                     (uint8_t *)&env->za_state.za,
+                                     za_size);
+        assert_hvf_ok(ret);
+
+    }
+
+    return;
+}
 
 static uint32_t hvf_reg2cp_reg(uint32_t reg)
 {
@@ -534,6 +767,10 @@ int hvf_arch_get_registers(CPUState *cpu)
         uint64_t kvm_id = arm_cpu->cpreg_indexes[i];
         int hvf_id = KVMID_TO_HVF(kvm_id);
 
+        if (kvm_id == HVF_TO_KVMID(SVCR)) {
+            continue;
+        }
+
         if (cpu->accel->guest_debug_enabled) {
             /* Handle debug registers */
             switch (hvf_id) {
@@ -627,6 +864,13 @@ int hvf_arch_get_registers(CPUState *cpu)
 
         arm_cpu->cpreg_values[i] = val;
     }
+    if (cpu_isar_feature(aa64_sme, arm_cpu)) {
+        if (__builtin_available(macOS 15.2, *)) {
+            hvf_arch_get_sme(cpu);
+        } else {
+            g_assert_not_reached();
+        }
+    }
     assert(write_list_to_cpustate(arm_cpu));
 
     aarch64_restore_sp(env, arm_current_el(env));
@@ -672,6 +916,10 @@ int hvf_arch_put_registers(CPUState *cpu)
         uint64_t kvm_id = arm_cpu->cpreg_indexes[i];
         int hvf_id = KVMID_TO_HVF(kvm_id);
 
+        if (kvm_id == HVF_TO_KVMID(SVCR)) {
+            continue;
+        }
+
         if (cpu->accel->guest_debug_enabled) {
             /* Handle debug registers */
             switch (hvf_id) {
@@ -756,6 +1004,14 @@ int hvf_arch_put_registers(CPUState *cpu)
     ret = hv_vcpu_set_vtimer_offset(cpu->accel->fd, hvf_state->vtimer_offset);
     assert_hvf_ok(ret);
 
+    if (cpu_isar_feature(aa64_sme, arm_cpu)) {
+        if (__builtin_available(macOS 15.2, *)) {
+            hvf_arch_put_sme(cpu);
+        } else {
+            g_assert_not_reached();
+        }
+    }
+
     return 0;
 }
 
@@ -985,6 +1241,18 @@ int hvf_arch_init_vcpu(CPUState *cpu)
     hv_return_t ret;
     int i;
 
+    if (__builtin_available(macOS 15.2, *)) {
+        sregs_match_len += ARRAY_SIZE(hvf_sreg_list_sme2) + 1;
+
+#define DEF_SYSREG_15_02(HVF_ID, ...) \
+        g_assert(HVF_ID == KVMID_TO_HVF(KVMID_AA64_SYS_REG64(__VA_ARGS__)));
+#define DEF_SYSREG(...)
+
+#include "sysreg.c.inc"
+
+#undef DEF_SYSREG
+#undef DEF_SYSREG_15_02
+    }
     env->aarch64 = true;
 
     /* system count frequency sanity check */
@@ -1005,7 +1273,7 @@ int hvf_arch_init_vcpu(CPUState *cpu)
     memset(arm_cpu->cpreg_values, 0, sregs_match_len * sizeof(uint64_t));
 
     /* Populate cp list for all known sysregs */
-    for (i = 0; i < sregs_match_len; i++) {
+    for (i = 0; i < ARRAY_SIZE(hvf_sreg_list); i++) {
         hv_sys_reg_t hvf_id = hvf_sreg_list[i];
         uint64_t kvm_id = HVF_TO_KVMID(hvf_id);
         uint32_t key = kvm_to_cpreg_id(kvm_id);
@@ -1016,6 +1284,24 @@ int hvf_arch_init_vcpu(CPUState *cpu)
             arm_cpu->cpreg_indexes[sregs_cnt++] = kvm_id;
         }
     }
+    if (__builtin_available(macOS 15.2, *)) {
+        for (i = 0; i < ARRAY_SIZE(hvf_sreg_list_sme2); i++) {
+            hv_sys_reg_t hvf_id = hvf_sreg_list_sme2[i];
+            uint64_t kvm_id = HVF_TO_KVMID(hvf_id);
+            uint32_t key = kvm_to_cpreg_id(kvm_id);
+            const ARMCPRegInfo *ri = get_arm_cp_reginfo(arm_cpu->cp_regs, key);
+
+            if (ri) {
+                assert(!(ri->type & ARM_CP_NO_RAW));
+                arm_cpu->cpreg_indexes[sregs_cnt++] = kvm_id;
+            }
+        }
+        /*
+         * Add SVCR last. It is elsewhere assumed its index is after
+         * hvf_sreg_list and hvf_sreg_list_sme2.
+         */
+        arm_cpu->cpreg_indexes[sregs_cnt++] = HVF_TO_KVMID(SVCR);
+    }
     arm_cpu->cpreg_array_len = sregs_cnt;
     arm_cpu->cpreg_vmstate_array_len = sregs_cnt;
 
diff --git a/target/arm/hvf/hvf_sme_stubs.h b/target/arm/hvf/hvf_sme_stubs.h
new file mode 100644
index 0000000000000000000000000000000000000000..9c679b711017448681e532b88ce10a07ebfd5122
--- /dev/null
+++ b/target/arm/hvf/hvf_sme_stubs.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+typedef int32_t hv_return_t;
+typedef uint64_t hv_vcpu_t;
+
+static inline bool hvf_arm_sme2_supported(void)
+{
+    return false;
+}
+
+static inline uint32_t hvf_arm_sme2_get_svl(void)
+{
+    g_assert_not_reached();
+}
+
+typedef enum hv_sme_p_reg_t {
+    HV_SME_P_REG_0,
+    HV_SME_P_REG_1,
+    HV_SME_P_REG_2,
+    HV_SME_P_REG_3,
+    HV_SME_P_REG_4,
+    HV_SME_P_REG_5,
+    HV_SME_P_REG_6,
+    HV_SME_P_REG_7,
+    HV_SME_P_REG_8,
+    HV_SME_P_REG_9,
+    HV_SME_P_REG_10,
+    HV_SME_P_REG_11,
+    HV_SME_P_REG_12,
+    HV_SME_P_REG_13,
+    HV_SME_P_REG_14,
+    HV_SME_P_REG_15,
+} hv_sme_p_reg_t;
+
+typedef __attribute__((ext_vector_type(64))) uint8_t hv_sme_zt0_uchar64_t;
+
+typedef enum hv_sme_z_reg_t {
+    HV_SME_Z_REG_0,
+    HV_SME_Z_REG_1,
+    HV_SME_Z_REG_2,
+    HV_SME_Z_REG_3,
+    HV_SME_Z_REG_4,
+    HV_SME_Z_REG_5,
+    HV_SME_Z_REG_6,
+    HV_SME_Z_REG_7,
+    HV_SME_Z_REG_8,
+    HV_SME_Z_REG_9,
+    HV_SME_Z_REG_10,
+    HV_SME_Z_REG_11,
+    HV_SME_Z_REG_12,
+    HV_SME_Z_REG_13,
+    HV_SME_Z_REG_14,
+    HV_SME_Z_REG_15,
+    HV_SME_Z_REG_16,
+    HV_SME_Z_REG_17,
+    HV_SME_Z_REG_18,
+    HV_SME_Z_REG_19,
+    HV_SME_Z_REG_20,
+    HV_SME_Z_REG_21,
+    HV_SME_Z_REG_22,
+    HV_SME_Z_REG_23,
+    HV_SME_Z_REG_24,
+    HV_SME_Z_REG_25,
+    HV_SME_Z_REG_26,
+    HV_SME_Z_REG_27,
+    HV_SME_Z_REG_28,
+    HV_SME_Z_REG_29,
+    HV_SME_Z_REG_30,
+    HV_SME_Z_REG_31,
+} hv_sme_z_reg_t;
+
+enum {
+  HV_SYS_REG_SMCR_EL1,
+  HV_SYS_REG_SMPRI_EL1,
+  HV_SYS_REG_TPIDR2_EL0,
+  HV_SYS_REG_ID_AA64ZFR0_EL1,
+  HV_SYS_REG_ID_AA64SMFR0_EL1,
+};
+
+typedef struct {
+    bool streaming_sve_mode_enabled;
+    bool za_storage_enabled;
+} hv_vcpu_sme_state_t;
+
+static inline hv_return_t hv_sme_config_get_max_svl_bytes(size_t *value)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_get_sme_state(hv_vcpu_t vcpu,
+                                                hv_vcpu_sme_state_t *sme_state)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_set_sme_state(hv_vcpu_t vcpu,
+                                                const hv_vcpu_sme_state_t *sme_state)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_get_sme_z_reg(hv_vcpu_t vcpu,
+                                                hv_sme_z_reg_t reg,
+                                                uint8_t *value,
+                                                size_t length)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_set_sme_z_reg(hv_vcpu_t vcpu,
+                                                hv_sme_z_reg_t reg,
+                                                const uint8_t *value,
+                                                size_t length)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_get_sme_p_reg(hv_vcpu_t vcpu,
+                                                hv_sme_p_reg_t reg,
+                                                uint8_t *value,
+                                                size_t length)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_set_sme_p_reg(hv_vcpu_t vcpu,
+                                                hv_sme_p_reg_t reg,
+                                                const uint8_t *value,
+                                                size_t length)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_get_sme_za_reg(hv_vcpu_t vcpu,
+                                                 uint8_t *value,
+                                                 size_t length)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_set_sme_za_reg(hv_vcpu_t vcpu,
+                                                 const uint8_t *value,
+                                                 size_t length)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_get_sme_zt0_reg(hv_vcpu_t vcpu,
+                                                  hv_sme_zt0_uchar64_t *value)
+{
+    g_assert_not_reached();
+}
+
+static inline hv_return_t hv_vcpu_set_sme_zt0_reg(hv_vcpu_t vcpu,
+                                                  const hv_sme_zt0_uchar64_t *value)
+{
+    g_assert_not_reached();
+}
diff --git a/target/arm/hvf/sysreg.c.inc b/target/arm/hvf/sysreg.c.inc
index 067a8603fa785593ed0879cea26d036b0ec2823e..7a2f880f784b7610b14a6eb91fec1817e98bfd2e 100644
--- a/target/arm/hvf/sysreg.c.inc
+++ b/target/arm/hvf/sysreg.c.inc
@@ -145,3 +145,11 @@ DEF_SYSREG(HV_SYS_REG_TPIDRRO_EL0, 3, 3, 13, 0, 3)
 DEF_SYSREG(HV_SYS_REG_CNTV_CTL_EL0, 3, 3, 14, 3, 1)
 DEF_SYSREG(HV_SYS_REG_CNTV_CVAL_EL0, 3, 3, 14, 3, 2)
 DEF_SYSREG(HV_SYS_REG_SP_EL1, 3, 4, 4, 1, 0)
+
+DEF_SYSREG_15_02(HV_SYS_REG_SMCR_EL1, 3, 0, 1, 2, 6)
+DEF_SYSREG_15_02(HV_SYS_REG_SMPRI_EL1, 3, 0, 1, 2, 4)
+DEF_SYSREG_15_02(HV_SYS_REG_TPIDR2_EL0, 3, 3, 13, 0, 5)
+DEF_SYSREG_15_02(HV_SYS_REG_ID_AA64ZFR0_EL1, 3, 0, 0, 4, 4)
+DEF_SYSREG_15_02(HV_SYS_REG_ID_AA64SMFR0_EL1, 3, 0, 0, 4, 5)
+DEF_SYSREG_15_02(HV_SYS_REG_SMPRI_EL1, 3, 0, 1, 2, 4)
+DEF_SYSREG_15_02(HV_SYS_REG_SMCR_EL1, 3, 0, 1, 2, 6)
diff --git a/target/arm/hvf_arm.h b/target/arm/hvf_arm.h
index 5d19d82e5ded66bc02da10523c0f7138840ecff3..6b1c3b9792dfd7d81ef747d8a6676a23fd212d84 100644
--- a/target/arm/hvf_arm.h
+++ b/target/arm/hvf_arm.h
@@ -22,4 +22,45 @@ void hvf_arm_init_debug(void);
 
 void hvf_arm_set_cpu_features_from_host(ARMCPU *cpu);
 
+/*
+ * We need access to types from macOS SDK >=15.2, so expose stubs if the
+ * headers are not available until we raise our minimum macOS version.
+ */
+#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
+  #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200)
+    #include "system/hvf_int.h"
+
+    static inline bool hvf_arm_sme2_supported(void)
+    {
+        if (__builtin_available(macOS 15.2, *)) {
+            size_t svl_bytes;
+            hv_return_t result = hv_sme_config_get_max_svl_bytes(&svl_bytes);
+            if (result == HV_UNSUPPORTED) {
+                return false;
+            }
+            assert_hvf_ok(result);
+            return svl_bytes > 0;
+        } else {
+            return false;
+        }
+    }
+
+    static inline uint32_t hvf_arm_sme2_get_svl(void)
+    {
+        if (__builtin_available(macOS 15.2, *)) {
+            size_t svl_bytes;
+            hv_return_t result = hv_sme_config_get_max_svl_bytes(&svl_bytes);
+            assert_hvf_ok(result);
+            return svl_bytes;
+        } else {
+            abort();
+        }
+    }
+  #else /* (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200) */
+      #include "hvf/hvf_sme_stubs.h"
+  #endif /* (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200) */
+#else /* ifdef __MAC_OS_X_VERSION_MAX_ALLOWED */
+  #include "hvf/hvf_sme_stubs.h"
+#endif /* ifdef __MAC_OS_X_VERSION_MAX_ALLOWED */
+
 #endif
diff --git a/target/arm/machine.c b/target/arm/machine.c
index bbaae3444928985813b7dcc91888784d06cfe305..81a506ce4cc09493dd7faed88652d8d246e23187 100644
--- a/target/arm/machine.c
+++ b/target/arm/machine.c
@@ -231,7 +231,7 @@ static bool sve_needed(void *opaque)
 {
     ARMCPU *cpu = opaque;
 
-    return cpu_isar_feature(aa64_sve, cpu);
+    return cpu_isar_feature(aa64_sve, cpu) || cpu_isar_feature(aa64_sme, cpu);
 }
 
 /* The first two words of each Zreg is stored in VFP state.  */

-- 
2.47.3
Re: [PATCH v4 1/2] hvf/arm: handle FEAT_SME2 migration
Posted by Manos Pitsidianakis 2 days, 23 hours ago
On Fri, Feb 27, 2026 at 10:01 AM Manos Pitsidianakis
<manos.pitsidianakis@linaro.org> wrote:
>
> SME2 support adds the following state for HVF guests:
>
> - Vector registers Z0, ... , Z31 (introduced by FEAT_SVE but HVF does
>   not support it)
> - Predicate registers P0, .., P15 (also FEAT_SVE)
> - ZA register
> - ZT0 register
> - PSTATE.{SM,ZA} bits (SVCR pseudo-register)
> - SMPRI_EL1 which handles the PE's priority in the SMCU
> - TPIDR2_EL0 the thread local ID register for SME
>
> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> ---
>  target/arm/hvf/hvf.c           | 290 ++++++++++++++++++++++++++++++++++++++++-
>  target/arm/hvf/hvf_sme_stubs.h | 158 ++++++++++++++++++++++
>  target/arm/hvf/sysreg.c.inc    |   8 ++
>  target/arm/hvf_arm.h           |  41 ++++++
>  target/arm/machine.c           |   2 +-
>  5 files changed, 496 insertions(+), 3 deletions(-)
>
> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
> index d79469ca27f794139b8073e25fc16d470b1942d5..ce62f8c97876e3fd948fad96df631f1b53db5e67 100644
> --- a/target/arm/hvf/hvf.c
> +++ b/target/arm/hvf/hvf.c
> @@ -395,6 +395,60 @@ static const struct hvf_reg_match hvf_fpreg_match[] = {
>      { HV_SIMD_FP_REG_Q31, offsetof(CPUARMState, vfp.zregs[31]) },
>  };
>
> +static const struct hvf_reg_match hvf_sme2_zreg_match[] = {
> +    { HV_SME_Z_REG_0, offsetof(CPUARMState, vfp.zregs[0]) },
> +    { HV_SME_Z_REG_1, offsetof(CPUARMState, vfp.zregs[1]) },
> +    { HV_SME_Z_REG_2, offsetof(CPUARMState, vfp.zregs[2]) },
> +    { HV_SME_Z_REG_3, offsetof(CPUARMState, vfp.zregs[3]) },
> +    { HV_SME_Z_REG_4, offsetof(CPUARMState, vfp.zregs[4]) },
> +    { HV_SME_Z_REG_5, offsetof(CPUARMState, vfp.zregs[5]) },
> +    { HV_SME_Z_REG_6, offsetof(CPUARMState, vfp.zregs[6]) },
> +    { HV_SME_Z_REG_7, offsetof(CPUARMState, vfp.zregs[7]) },
> +    { HV_SME_Z_REG_8, offsetof(CPUARMState, vfp.zregs[8]) },
> +    { HV_SME_Z_REG_9, offsetof(CPUARMState, vfp.zregs[9]) },
> +    { HV_SME_Z_REG_10, offsetof(CPUARMState, vfp.zregs[10]) },
> +    { HV_SME_Z_REG_11, offsetof(CPUARMState, vfp.zregs[11]) },
> +    { HV_SME_Z_REG_12, offsetof(CPUARMState, vfp.zregs[12]) },
> +    { HV_SME_Z_REG_13, offsetof(CPUARMState, vfp.zregs[13]) },
> +    { HV_SME_Z_REG_14, offsetof(CPUARMState, vfp.zregs[14]) },
> +    { HV_SME_Z_REG_15, offsetof(CPUARMState, vfp.zregs[15]) },
> +    { HV_SME_Z_REG_16, offsetof(CPUARMState, vfp.zregs[16]) },
> +    { HV_SME_Z_REG_17, offsetof(CPUARMState, vfp.zregs[17]) },
> +    { HV_SME_Z_REG_18, offsetof(CPUARMState, vfp.zregs[18]) },
> +    { HV_SME_Z_REG_19, offsetof(CPUARMState, vfp.zregs[19]) },
> +    { HV_SME_Z_REG_20, offsetof(CPUARMState, vfp.zregs[20]) },
> +    { HV_SME_Z_REG_21, offsetof(CPUARMState, vfp.zregs[21]) },
> +    { HV_SME_Z_REG_22, offsetof(CPUARMState, vfp.zregs[22]) },
> +    { HV_SME_Z_REG_23, offsetof(CPUARMState, vfp.zregs[23]) },
> +    { HV_SME_Z_REG_24, offsetof(CPUARMState, vfp.zregs[24]) },
> +    { HV_SME_Z_REG_25, offsetof(CPUARMState, vfp.zregs[25]) },
> +    { HV_SME_Z_REG_26, offsetof(CPUARMState, vfp.zregs[26]) },
> +    { HV_SME_Z_REG_27, offsetof(CPUARMState, vfp.zregs[27]) },
> +    { HV_SME_Z_REG_28, offsetof(CPUARMState, vfp.zregs[28]) },
> +    { HV_SME_Z_REG_29, offsetof(CPUARMState, vfp.zregs[29]) },
> +    { HV_SME_Z_REG_30, offsetof(CPUARMState, vfp.zregs[30]) },
> +    { HV_SME_Z_REG_31, offsetof(CPUARMState, vfp.zregs[31]) },
> +};
> +
> +static const struct hvf_reg_match hvf_sme2_preg_match[] = {
> +    { HV_SME_P_REG_0, offsetof(CPUARMState, vfp.pregs[0]) },
> +    { HV_SME_P_REG_1, offsetof(CPUARMState, vfp.pregs[1]) },
> +    { HV_SME_P_REG_2, offsetof(CPUARMState, vfp.pregs[2]) },
> +    { HV_SME_P_REG_3, offsetof(CPUARMState, vfp.pregs[3]) },
> +    { HV_SME_P_REG_4, offsetof(CPUARMState, vfp.pregs[4]) },
> +    { HV_SME_P_REG_5, offsetof(CPUARMState, vfp.pregs[5]) },
> +    { HV_SME_P_REG_6, offsetof(CPUARMState, vfp.pregs[6]) },
> +    { HV_SME_P_REG_7, offsetof(CPUARMState, vfp.pregs[7]) },
> +    { HV_SME_P_REG_8, offsetof(CPUARMState, vfp.pregs[8]) },
> +    { HV_SME_P_REG_9, offsetof(CPUARMState, vfp.pregs[9]) },
> +    { HV_SME_P_REG_10, offsetof(CPUARMState, vfp.pregs[10]) },
> +    { HV_SME_P_REG_11, offsetof(CPUARMState, vfp.pregs[11]) },
> +    { HV_SME_P_REG_12, offsetof(CPUARMState, vfp.pregs[12]) },
> +    { HV_SME_P_REG_13, offsetof(CPUARMState, vfp.pregs[13]) },
> +    { HV_SME_P_REG_14, offsetof(CPUARMState, vfp.pregs[14]) },
> +    { HV_SME_P_REG_15, offsetof(CPUARMState, vfp.pregs[15]) },
> +};
> +
>  /*
>   * QEMU uses KVM system register ids in the migration format.
>   * Conveniently, HVF uses the same encoding of the op* and cr* parameters
> @@ -406,22 +460,201 @@ static const struct hvf_reg_match hvf_fpreg_match[] = {
>  #define HVF_TO_KVMID(HVF)  \
>      (CP_REG_ARM64 | CP_REG_SIZE_U64 | CP_REG_ARM64_SYSREG | (HVF))
>
> -/* Verify this at compile-time. */
> +/*
> + * Verify this at compile-time.
> + *
> + * SME2 registers are guarded by a runtime availability attribute instead of a
> + * compile-time def, so verify those at runtime in hvf_arch_init_vcpu() below.
> + */
>
>  #define DEF_SYSREG(HVF_ID, ...) \
>    QEMU_BUILD_BUG_ON(HVF_ID != KVMID_TO_HVF(KVMID_AA64_SYS_REG64(__VA_ARGS__)));
> +#define DEF_SYSREG_15_02(...)
>
>  #include "sysreg.c.inc"
>
>  #undef DEF_SYSREG
> +#undef DEF_SYSREG_15_02
>
>  #define DEF_SYSREG(HVF_ID, op0, op1, crn, crm, op2)  HVF_ID,
> +#define DEF_SYSREG_15_02(...)
>
>  static const hv_sys_reg_t hvf_sreg_list[] = {
>  #include "sysreg.c.inc"
>  };
>
>  #undef DEF_SYSREG
> +#undef DEF_SYSREG_15_02
> +
> +#define DEF_SYSREG(...)
> +#define DEF_SYSREG_15_02(HVF_ID, op0, op1, crn, crm, op2) HVF_ID,
> +
> +API_AVAILABLE(macos(15.2))
> +static const hv_sys_reg_t hvf_sreg_list_sme2[] = {
> +#include "sysreg.c.inc"
> +};
> +
> +#undef DEF_SYSREG
> +#undef DEF_SYSREG_15_02
> +
> +/*
> + * For FEAT_SME2 migration, we need to store PSTATE.{SM,ZA} bits which are
> + * accessible with the SVCR pseudo-register. However, in the HVF API this is
> + * not exposed as a system-register (i.e. HVF_SYS_REG_SVCR) but a custom
> + * struct, hv_vcpu_sme_state_t. So we need to define our own KVMID in order to
> + * store it in cpreg_values and make it migrateable.
> + */
> +#define SVCR KVMID_AA64_SYS_REG64(3, 3, 4, 2, 2)
> +
> +API_AVAILABLE(macos(15.2))
> +static void hvf_arch_put_sme(CPUState *cpu)
> +{
> +    ARMCPU *arm_cpu = ARM_CPU(cpu);
> +    CPUARMState *env = &arm_cpu->env;
> +    const size_t svl_bytes = hvf_arm_sme2_get_svl();
> +    const size_t z_size = svl_bytes;
> +    const size_t preg_size = DIV_ROUND_UP(z_size, 8);
> +    const size_t za_size = svl_bytes * svl_bytes;
> +    hv_vcpu_sme_state_t sme_state = { 0 };
> +    hv_return_t ret;
> +    uint64_t svcr;
> +    int n;
> +
> +    /*
> +     * Set PSTATE.{SM,ZA} bits
> +     */
> +    svcr = arm_cpu->cpreg_values[arm_cpu->cpreg_array_len - 1];
> +    env->svcr = svcr;
> +
> +    /*
> +     * Construct SVCR (PSTATE.{SM,ZA}) state to pass to HVF:
> +     */
> +    sme_state.streaming_sve_mode_enabled = FIELD_EX64(env->svcr, SVCR, SM) > 0;
> +    sme_state.za_storage_enabled = FIELD_EX64(env->svcr, SVCR, ZA) > 0;
> +    ret = hv_vcpu_set_sme_state(cpu->accel->fd, &sme_state);
> +    assert_hvf_ok(ret);
> +
> +    /*
> +     * We only care about Z/P registers if we're in streaming SVE mode, i.e.
> +     * PSTATE.SM is set, because only then can instructions that access them be
> +     * used. We don't care about the register values otherwise. This is because
> +     * when the processing unit exits/enters this mode, it zeroes out those
> +     * registers.
> +     */
> +    if (sme_state.streaming_sve_mode_enabled) {
> +        for (n = 0; n < ARRAY_SIZE(hvf_sme2_zreg_match); ++n) {
> +            ret = hv_vcpu_set_sme_z_reg(cpu->accel->fd,
> +                                        hvf_sme2_zreg_match[n].reg,
> +                                        (uint8_t *)&env->vfp.zregs[n].d[0],
> +                                        z_size);
> +            assert_hvf_ok(ret);
> +        }
> +
> +        for (n = 0; n < ARRAY_SIZE(hvf_sme2_preg_match); ++n) {
> +            ret = hv_vcpu_set_sme_p_reg(cpu->accel->fd,
> +                                        hvf_sme2_preg_match[n].reg,
> +                                        (uint8_t *)&env->vfp.pregs[n].p[0],
> +                                        preg_size);
> +            assert_hvf_ok(ret);
> +        }
> +    }
> +
> +    /*
> +     * If PSTATE.ZA bit is set then ZA and ZT0 are valid, otherwise they are
> +     * zeroed out.
> +     */
> +    if (sme_state.za_storage_enabled) {
> +        hv_sme_zt0_uchar64_t tmp = { 0 };
> +
> +        memcpy(&tmp, &env->za_state.zt0, 64);
> +        ret = hv_vcpu_set_sme_zt0_reg(cpu->accel->fd, &tmp);
> +        assert_hvf_ok(ret);
> +
> +        ret = hv_vcpu_set_sme_za_reg(cpu->accel->fd,
> +                                     (uint8_t *)&env->za_state.za,
> +                                     za_size);
> +        assert_hvf_ok(ret);
> +    }
> +
> +    return;
> +}
> +
> +API_AVAILABLE(macos(15.2))
> +static void hvf_arch_get_sme(CPUState *cpu)
> +{
> +    ARMCPU *arm_cpu = ARM_CPU(cpu);
> +    CPUARMState *env = &arm_cpu->env;
> +    const size_t svl_bytes = hvf_arm_sme2_get_svl();
> +    const size_t z_size = svl_bytes;
> +    const size_t preg_size = DIV_ROUND_UP(z_size, 8);
> +    const size_t za_size = svl_bytes * svl_bytes;
> +    hv_vcpu_sme_state_t sme_state = { 0 };
> +    hv_return_t ret;
> +    uint64_t svcr;
> +    int n;
> +
> +    /*
> +     * Get SVCR (PSTATE.{SM,ZA}) state from HVF:
> +     */
> +    ret = hv_vcpu_get_sme_state(cpu->accel->fd, &sme_state);
> +    assert_hvf_ok(ret);
> +
> +    /*
> +     * Set SVCR first because changing it will zero out Z/P regs
> +     */
> +    svcr =
> +        (sme_state.za_storage_enabled ? R_SVCR_ZA_MASK : 0)
> +        | (sme_state.streaming_sve_mode_enabled ? R_SVCR_SM_MASK : 0);
> +
> +    aarch64_set_svcr(env, svcr, R_SVCR_ZA_MASK | R_SVCR_SM_MASK);
> +    arm_cpu->cpreg_values[arm_cpu->cpreg_array_len - 1] = svcr;
> +
> +    /*
> +     * We only care about Z/P registers if we're in streaming SVE mode, i.e.
> +     * PSTATE.SM is set, because only then can instructions that access them be
> +     * used. We don't care about the register values otherwise. This is because
> +     * when the processing unit exits/enters this mode, it zeroes out those
> +     * registers.
> +     */
> +    if (sme_state.streaming_sve_mode_enabled) {
> +        for (n = 0; n < ARRAY_SIZE(hvf_sme2_zreg_match); ++n) {
> +            ret = hv_vcpu_get_sme_z_reg(cpu->accel->fd,
> +                                        hvf_sme2_zreg_match[n].reg,
> +                                        (uint8_t *)&env->vfp.zregs[n].d[0],
> +                                        z_size);
> +            assert_hvf_ok(ret);
> +        }
> +
> +        for (n = 0; n < ARRAY_SIZE(hvf_sme2_preg_match); ++n) {
> +            ret = hv_vcpu_get_sme_p_reg(cpu->accel->fd,
> +                                        hvf_sme2_preg_match[n].reg,
> +                                        (uint8_t *)&env->vfp.pregs[n].p[0],
> +                                        preg_size);
> +            assert_hvf_ok(ret);
> +        }
> +    }
> +
> +    /*
> +     * If PSTATE.ZA bit is set then ZA and ZT0 are valid, otherwise they are
> +     * zeroed out.
> +     */
> +    if (sme_state.za_storage_enabled) {
> +        hv_sme_zt0_uchar64_t tmp = { 0 };
> +
> +        /* Get ZT0 in a tmp vector, and then copy it to env.za_state.zt0 */
> +        ret = hv_vcpu_get_sme_zt0_reg(cpu->accel->fd, &tmp);
> +        assert_hvf_ok(ret);
> +
> +        memcpy(&env->za_state.zt0, &tmp, 64);
> +        ret = hv_vcpu_get_sme_za_reg(cpu->accel->fd,
> +                                     (uint8_t *)&env->za_state.za,
> +                                     za_size);
> +        assert_hvf_ok(ret);
> +
> +    }
> +
> +    return;
> +}
>
>  static uint32_t hvf_reg2cp_reg(uint32_t reg)
>  {
> @@ -534,6 +767,10 @@ int hvf_arch_get_registers(CPUState *cpu)
>          uint64_t kvm_id = arm_cpu->cpreg_indexes[i];
>          int hvf_id = KVMID_TO_HVF(kvm_id);
>
> +        if (kvm_id == HVF_TO_KVMID(SVCR)) {
> +            continue;
> +        }
> +
>          if (cpu->accel->guest_debug_enabled) {
>              /* Handle debug registers */
>              switch (hvf_id) {
> @@ -627,6 +864,13 @@ int hvf_arch_get_registers(CPUState *cpu)
>
>          arm_cpu->cpreg_values[i] = val;
>      }
> +    if (cpu_isar_feature(aa64_sme, arm_cpu)) {
> +        if (__builtin_available(macOS 15.2, *)) {
> +            hvf_arch_get_sme(cpu);
> +        } else {
> +            g_assert_not_reached();
> +        }
> +    }
>      assert(write_list_to_cpustate(arm_cpu));
>
>      aarch64_restore_sp(env, arm_current_el(env));
> @@ -672,6 +916,10 @@ int hvf_arch_put_registers(CPUState *cpu)
>          uint64_t kvm_id = arm_cpu->cpreg_indexes[i];
>          int hvf_id = KVMID_TO_HVF(kvm_id);
>
> +        if (kvm_id == HVF_TO_KVMID(SVCR)) {
> +            continue;
> +        }
> +
>          if (cpu->accel->guest_debug_enabled) {
>              /* Handle debug registers */
>              switch (hvf_id) {
> @@ -756,6 +1004,14 @@ int hvf_arch_put_registers(CPUState *cpu)
>      ret = hv_vcpu_set_vtimer_offset(cpu->accel->fd, hvf_state->vtimer_offset);
>      assert_hvf_ok(ret);
>
> +    if (cpu_isar_feature(aa64_sme, arm_cpu)) {
> +        if (__builtin_available(macOS 15.2, *)) {
> +            hvf_arch_put_sme(cpu);
> +        } else {
> +            g_assert_not_reached();
> +        }
> +    }

This will reset the NEON vector registers we set up earlier in
hvf_arch_put_registers() if transitioning from streaming mode enabled
to disabled. I had tested disabled->{dis,en}abled, enabled->enabled
but not enabled -> disabled. Will send a new revision.

> +
>      return 0;
>  }
>
> @@ -985,6 +1241,18 @@ int hvf_arch_init_vcpu(CPUState *cpu)
>      hv_return_t ret;
>      int i;
>
> +    if (__builtin_available(macOS 15.2, *)) {
> +        sregs_match_len += ARRAY_SIZE(hvf_sreg_list_sme2) + 1;
> +
> +#define DEF_SYSREG_15_02(HVF_ID, ...) \
> +        g_assert(HVF_ID == KVMID_TO_HVF(KVMID_AA64_SYS_REG64(__VA_ARGS__)));
> +#define DEF_SYSREG(...)
> +
> +#include "sysreg.c.inc"
> +
> +#undef DEF_SYSREG
> +#undef DEF_SYSREG_15_02
> +    }
>      env->aarch64 = true;
>
>      /* system count frequency sanity check */
> @@ -1005,7 +1273,7 @@ int hvf_arch_init_vcpu(CPUState *cpu)
>      memset(arm_cpu->cpreg_values, 0, sregs_match_len * sizeof(uint64_t));
>
>      /* Populate cp list for all known sysregs */
> -    for (i = 0; i < sregs_match_len; i++) {
> +    for (i = 0; i < ARRAY_SIZE(hvf_sreg_list); i++) {
>          hv_sys_reg_t hvf_id = hvf_sreg_list[i];
>          uint64_t kvm_id = HVF_TO_KVMID(hvf_id);
>          uint32_t key = kvm_to_cpreg_id(kvm_id);
> @@ -1016,6 +1284,24 @@ int hvf_arch_init_vcpu(CPUState *cpu)
>              arm_cpu->cpreg_indexes[sregs_cnt++] = kvm_id;
>          }
>      }
> +    if (__builtin_available(macOS 15.2, *)) {
> +        for (i = 0; i < ARRAY_SIZE(hvf_sreg_list_sme2); i++) {
> +            hv_sys_reg_t hvf_id = hvf_sreg_list_sme2[i];
> +            uint64_t kvm_id = HVF_TO_KVMID(hvf_id);
> +            uint32_t key = kvm_to_cpreg_id(kvm_id);
> +            const ARMCPRegInfo *ri = get_arm_cp_reginfo(arm_cpu->cp_regs, key);
> +
> +            if (ri) {
> +                assert(!(ri->type & ARM_CP_NO_RAW));
> +                arm_cpu->cpreg_indexes[sregs_cnt++] = kvm_id;
> +            }
> +        }
> +        /*
> +         * Add SVCR last. It is elsewhere assumed its index is after
> +         * hvf_sreg_list and hvf_sreg_list_sme2.
> +         */
> +        arm_cpu->cpreg_indexes[sregs_cnt++] = HVF_TO_KVMID(SVCR);
> +    }
>      arm_cpu->cpreg_array_len = sregs_cnt;
>      arm_cpu->cpreg_vmstate_array_len = sregs_cnt;
>
> diff --git a/target/arm/hvf/hvf_sme_stubs.h b/target/arm/hvf/hvf_sme_stubs.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..9c679b711017448681e532b88ce10a07ebfd5122
> --- /dev/null
> +++ b/target/arm/hvf/hvf_sme_stubs.h
> @@ -0,0 +1,158 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +typedef int32_t hv_return_t;
> +typedef uint64_t hv_vcpu_t;
> +
> +static inline bool hvf_arm_sme2_supported(void)
> +{
> +    return false;
> +}
> +
> +static inline uint32_t hvf_arm_sme2_get_svl(void)
> +{
> +    g_assert_not_reached();
> +}
> +
> +typedef enum hv_sme_p_reg_t {
> +    HV_SME_P_REG_0,
> +    HV_SME_P_REG_1,
> +    HV_SME_P_REG_2,
> +    HV_SME_P_REG_3,
> +    HV_SME_P_REG_4,
> +    HV_SME_P_REG_5,
> +    HV_SME_P_REG_6,
> +    HV_SME_P_REG_7,
> +    HV_SME_P_REG_8,
> +    HV_SME_P_REG_9,
> +    HV_SME_P_REG_10,
> +    HV_SME_P_REG_11,
> +    HV_SME_P_REG_12,
> +    HV_SME_P_REG_13,
> +    HV_SME_P_REG_14,
> +    HV_SME_P_REG_15,
> +} hv_sme_p_reg_t;
> +
> +typedef __attribute__((ext_vector_type(64))) uint8_t hv_sme_zt0_uchar64_t;
> +
> +typedef enum hv_sme_z_reg_t {
> +    HV_SME_Z_REG_0,
> +    HV_SME_Z_REG_1,
> +    HV_SME_Z_REG_2,
> +    HV_SME_Z_REG_3,
> +    HV_SME_Z_REG_4,
> +    HV_SME_Z_REG_5,
> +    HV_SME_Z_REG_6,
> +    HV_SME_Z_REG_7,
> +    HV_SME_Z_REG_8,
> +    HV_SME_Z_REG_9,
> +    HV_SME_Z_REG_10,
> +    HV_SME_Z_REG_11,
> +    HV_SME_Z_REG_12,
> +    HV_SME_Z_REG_13,
> +    HV_SME_Z_REG_14,
> +    HV_SME_Z_REG_15,
> +    HV_SME_Z_REG_16,
> +    HV_SME_Z_REG_17,
> +    HV_SME_Z_REG_18,
> +    HV_SME_Z_REG_19,
> +    HV_SME_Z_REG_20,
> +    HV_SME_Z_REG_21,
> +    HV_SME_Z_REG_22,
> +    HV_SME_Z_REG_23,
> +    HV_SME_Z_REG_24,
> +    HV_SME_Z_REG_25,
> +    HV_SME_Z_REG_26,
> +    HV_SME_Z_REG_27,
> +    HV_SME_Z_REG_28,
> +    HV_SME_Z_REG_29,
> +    HV_SME_Z_REG_30,
> +    HV_SME_Z_REG_31,
> +} hv_sme_z_reg_t;
> +
> +enum {
> +  HV_SYS_REG_SMCR_EL1,
> +  HV_SYS_REG_SMPRI_EL1,
> +  HV_SYS_REG_TPIDR2_EL0,
> +  HV_SYS_REG_ID_AA64ZFR0_EL1,
> +  HV_SYS_REG_ID_AA64SMFR0_EL1,
> +};
> +
> +typedef struct {
> +    bool streaming_sve_mode_enabled;
> +    bool za_storage_enabled;
> +} hv_vcpu_sme_state_t;
> +
> +static inline hv_return_t hv_sme_config_get_max_svl_bytes(size_t *value)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_get_sme_state(hv_vcpu_t vcpu,
> +                                                hv_vcpu_sme_state_t *sme_state)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_set_sme_state(hv_vcpu_t vcpu,
> +                                                const hv_vcpu_sme_state_t *sme_state)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_get_sme_z_reg(hv_vcpu_t vcpu,
> +                                                hv_sme_z_reg_t reg,
> +                                                uint8_t *value,
> +                                                size_t length)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_set_sme_z_reg(hv_vcpu_t vcpu,
> +                                                hv_sme_z_reg_t reg,
> +                                                const uint8_t *value,
> +                                                size_t length)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_get_sme_p_reg(hv_vcpu_t vcpu,
> +                                                hv_sme_p_reg_t reg,
> +                                                uint8_t *value,
> +                                                size_t length)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_set_sme_p_reg(hv_vcpu_t vcpu,
> +                                                hv_sme_p_reg_t reg,
> +                                                const uint8_t *value,
> +                                                size_t length)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_get_sme_za_reg(hv_vcpu_t vcpu,
> +                                                 uint8_t *value,
> +                                                 size_t length)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_set_sme_za_reg(hv_vcpu_t vcpu,
> +                                                 const uint8_t *value,
> +                                                 size_t length)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_get_sme_zt0_reg(hv_vcpu_t vcpu,
> +                                                  hv_sme_zt0_uchar64_t *value)
> +{
> +    g_assert_not_reached();
> +}
> +
> +static inline hv_return_t hv_vcpu_set_sme_zt0_reg(hv_vcpu_t vcpu,
> +                                                  const hv_sme_zt0_uchar64_t *value)
> +{
> +    g_assert_not_reached();
> +}
> diff --git a/target/arm/hvf/sysreg.c.inc b/target/arm/hvf/sysreg.c.inc
> index 067a8603fa785593ed0879cea26d036b0ec2823e..7a2f880f784b7610b14a6eb91fec1817e98bfd2e 100644
> --- a/target/arm/hvf/sysreg.c.inc
> +++ b/target/arm/hvf/sysreg.c.inc
> @@ -145,3 +145,11 @@ DEF_SYSREG(HV_SYS_REG_TPIDRRO_EL0, 3, 3, 13, 0, 3)
>  DEF_SYSREG(HV_SYS_REG_CNTV_CTL_EL0, 3, 3, 14, 3, 1)
>  DEF_SYSREG(HV_SYS_REG_CNTV_CVAL_EL0, 3, 3, 14, 3, 2)
>  DEF_SYSREG(HV_SYS_REG_SP_EL1, 3, 4, 4, 1, 0)
> +
> +DEF_SYSREG_15_02(HV_SYS_REG_SMCR_EL1, 3, 0, 1, 2, 6)
> +DEF_SYSREG_15_02(HV_SYS_REG_SMPRI_EL1, 3, 0, 1, 2, 4)
> +DEF_SYSREG_15_02(HV_SYS_REG_TPIDR2_EL0, 3, 3, 13, 0, 5)
> +DEF_SYSREG_15_02(HV_SYS_REG_ID_AA64ZFR0_EL1, 3, 0, 0, 4, 4)
> +DEF_SYSREG_15_02(HV_SYS_REG_ID_AA64SMFR0_EL1, 3, 0, 0, 4, 5)
> +DEF_SYSREG_15_02(HV_SYS_REG_SMPRI_EL1, 3, 0, 1, 2, 4)
> +DEF_SYSREG_15_02(HV_SYS_REG_SMCR_EL1, 3, 0, 1, 2, 6)
> diff --git a/target/arm/hvf_arm.h b/target/arm/hvf_arm.h
> index 5d19d82e5ded66bc02da10523c0f7138840ecff3..6b1c3b9792dfd7d81ef747d8a6676a23fd212d84 100644
> --- a/target/arm/hvf_arm.h
> +++ b/target/arm/hvf_arm.h
> @@ -22,4 +22,45 @@ void hvf_arm_init_debug(void);
>
>  void hvf_arm_set_cpu_features_from_host(ARMCPU *cpu);
>
> +/*
> + * We need access to types from macOS SDK >=15.2, so expose stubs if the
> + * headers are not available until we raise our minimum macOS version.
> + */
> +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
> +  #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200)
> +    #include "system/hvf_int.h"
> +
> +    static inline bool hvf_arm_sme2_supported(void)
> +    {
> +        if (__builtin_available(macOS 15.2, *)) {
> +            size_t svl_bytes;
> +            hv_return_t result = hv_sme_config_get_max_svl_bytes(&svl_bytes);
> +            if (result == HV_UNSUPPORTED) {
> +                return false;
> +            }
> +            assert_hvf_ok(result);
> +            return svl_bytes > 0;
> +        } else {
> +            return false;
> +        }
> +    }
> +
> +    static inline uint32_t hvf_arm_sme2_get_svl(void)
> +    {
> +        if (__builtin_available(macOS 15.2, *)) {
> +            size_t svl_bytes;
> +            hv_return_t result = hv_sme_config_get_max_svl_bytes(&svl_bytes);
> +            assert_hvf_ok(result);
> +            return svl_bytes;
> +        } else {
> +            abort();
> +        }
> +    }
> +  #else /* (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200) */
> +      #include "hvf/hvf_sme_stubs.h"
> +  #endif /* (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200) */
> +#else /* ifdef __MAC_OS_X_VERSION_MAX_ALLOWED */
> +  #include "hvf/hvf_sme_stubs.h"
> +#endif /* ifdef __MAC_OS_X_VERSION_MAX_ALLOWED */
> +
>  #endif
> diff --git a/target/arm/machine.c b/target/arm/machine.c
> index bbaae3444928985813b7dcc91888784d06cfe305..81a506ce4cc09493dd7faed88652d8d246e23187 100644
> --- a/target/arm/machine.c
> +++ b/target/arm/machine.c
> @@ -231,7 +231,7 @@ static bool sve_needed(void *opaque)
>  {
>      ARMCPU *cpu = opaque;
>
> -    return cpu_isar_feature(aa64_sve, cpu);
> +    return cpu_isar_feature(aa64_sve, cpu) || cpu_isar_feature(aa64_sme, cpu);
>  }
>
>  /* The first two words of each Zreg is stored in VFP state.  */
>
> --
> 2.47.3
>