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
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
>
© 2016 - 2026 Red Hat, Inc.