On Tue, 23 Dec 2025 at 01:23, Mark Brown <[email protected]> wrote:
>
> Since SME requires configuration of a vector length in order to know the
> size of both the streaming mode SVE state and ZA array we implement a
> capability for it and require that it be enabled and finalized before
> the SME specific state can be accessed, similarly to SVE.
>
> Due to the overlap with sizing the SVE state we finalise both SVE and
> SME with a single finalization, preventing any further changes to the
> SVE and SME configuration once KVM_ARM_VCPU_VEC (an alias for _VCPU_SVE)
> has been finalised. This is not a thing of great elegance but it ensures

With KVM_ARM_VCPU_VEC being an alias for KVM_ARM_VCPU_SVE, wouldn't
kvm_arm_vcpu_finalize() fail for guests that have only SME enabled but
not SVE?

Cheers,
/fuad


> that we never have a state where one of SVE or SME is finalised and the
> other not, avoiding complexity.
>
> SME is supported for normal and protected guests.
>
> Signed-off-by: Mark Brown <[email protected]>
> ---
>  arch/arm64/include/asm/kvm_host.h  |  12 +++-
>  arch/arm64/include/uapi/asm/kvm.h  |   1 +
>  arch/arm64/kvm/arm.c               |  10 ++++
>  arch/arm64/kvm/hyp/nvhe/pkvm.c     |  76 +++++++++++++++++++-----
>  arch/arm64/kvm/hyp/nvhe/sys_regs.c |   6 ++
>  arch/arm64/kvm/reset.c             | 116 
> +++++++++++++++++++++++++++++++------
>  include/uapi/linux/kvm.h           |   1 +
>  7 files changed, 189 insertions(+), 33 deletions(-)
>
> diff --git a/arch/arm64/include/asm/kvm_host.h 
> b/arch/arm64/include/asm/kvm_host.h
> index bceaf0608d75..011debfc1afd 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -39,7 +39,7 @@
>
>  #define KVM_MAX_VCPUS VGIC_V3_MAX_CPUS
>
> -#define KVM_VCPU_MAX_FEATURES 9
> +#define KVM_VCPU_MAX_FEATURES 10
>  #define KVM_VCPU_VALID_FEATURES        (BIT(KVM_VCPU_MAX_FEATURES) - 1)
>
>  #define KVM_REQ_SLEEP \
> @@ -82,6 +82,7 @@ extern unsigned int __ro_after_init 
> kvm_host_max_vl[ARM64_VEC_MAX];
>  DECLARE_STATIC_KEY_FALSE(userspace_irqchip_in_use);
>
>  int __init kvm_arm_init_sve(void);
> +int __init kvm_arm_init_sme(void);
>
>  u32 __attribute_const__ kvm_target_cpu(void);
>  void kvm_reset_vcpu(struct kvm_vcpu *vcpu);
> @@ -1149,7 +1150,14 @@ struct kvm_vcpu_arch {
>         __size_ret;                                                     \
>  })
>
> -#define vcpu_sve_state_size(vcpu) 
> sve_state_size_from_vl((vcpu)->arch.max_vl[ARM64_VEC_SVE])
> +#define vcpu_sve_state_size(vcpu) ({                                   \
> +       unsigned int __max_vl;                                          \
> +                                                                       \
> +       __max_vl = max((vcpu)->arch.max_vl[ARM64_VEC_SVE],              \
> +                      (vcpu)->arch.max_vl[ARM64_VEC_SME]);             \
> +                                                                       \
> +       sve_state_size_from_vl(__max_vl);                               \
> +})
>
>  #define vcpu_sme_state(vcpu) (kern_hyp_va((vcpu)->arch.sme_state))
>
> diff --git a/arch/arm64/include/uapi/asm/kvm.h 
> b/arch/arm64/include/uapi/asm/kvm.h
> index 9a19cc58d227..b4be424e4230 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -106,6 +106,7 @@ struct kvm_regs {
>  #define KVM_ARM_VCPU_PTRAUTH_GENERIC   6 /* VCPU uses generic authentication 
> */
>  #define KVM_ARM_VCPU_HAS_EL2           7 /* Support nested virtualization */
>  #define KVM_ARM_VCPU_HAS_EL2_E2H0      8 /* Limit NV support to E2H RES0 */
> +#define KVM_ARM_VCPU_SME               9 /* enable SME for this CPU */
>
>  /*
>   * An alias for _SVE since we finalize VL configuration for both SVE and SME
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 4f80da0c0d1d..7de7b497f74f 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -402,6 +402,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long 
> ext)
>         case KVM_CAP_ARM_SVE:
>                 r = system_supports_sve();
>                 break;
> +       case KVM_CAP_ARM_SME:
> +               r = system_supports_sme();
> +               break;
>         case KVM_CAP_ARM_PTRAUTH_ADDRESS:
>         case KVM_CAP_ARM_PTRAUTH_GENERIC:
>                 r = kvm_has_full_ptr_auth();
> @@ -1456,6 +1459,9 @@ static unsigned long 
> system_supported_vcpu_features(void)
>         if (!system_supports_sve())
>                 clear_bit(KVM_ARM_VCPU_SVE, &features);
>
> +       if (!system_supports_sme())
> +               clear_bit(KVM_ARM_VCPU_SME, &features);
> +
>         if (!kvm_has_full_ptr_auth()) {
>                 clear_bit(KVM_ARM_VCPU_PTRAUTH_ADDRESS, &features);
>                 clear_bit(KVM_ARM_VCPU_PTRAUTH_GENERIC, &features);
> @@ -2878,6 +2884,10 @@ static __init int kvm_arm_init(void)
>         if (err)
>                 return err;
>
> +       err = kvm_arm_init_sme();
> +       if (err)
> +               return err;
> +
>         err = kvm_arm_vmid_alloc_init();
>         if (err) {
>                 kvm_err("Failed to initialize VMID allocator.\n");
> diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
> index b656449dff69..30ee9f371b0d 100644
> --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
> +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
> @@ -148,10 +148,6 @@ static int pkvm_check_pvm_cpu_features(struct kvm_vcpu 
> *vcpu)
>             !kvm_has_feat(kvm, ID_AA64PFR0_EL1, AdvSIMD, IMP))
>                 return -EINVAL;
>
> -       /* No SME support in KVM right now. Check to catch if it changes. */
> -       if (kvm_has_feat(kvm, ID_AA64PFR1_EL1, SME, IMP))
> -               return -EINVAL;
> -
>         return 0;
>  }
>
> @@ -377,6 +373,11 @@ static void pkvm_init_features_from_host(struct 
> pkvm_hyp_vm *hyp_vm, const struc
>                 kvm->arch.flags |= host_arch_flags & 
> BIT(KVM_ARCH_FLAG_GUEST_HAS_SVE);
>         }
>
> +       if (kvm_pvm_ext_allowed(KVM_CAP_ARM_SME)) {
> +               set_bit(KVM_ARM_VCPU_SME, allowed_features);
> +               kvm->arch.flags |= host_arch_flags & 
> BIT(KVM_ARCH_FLAG_GUEST_HAS_SME);
> +       }
> +
>         bitmap_and(kvm->arch.vcpu_features, host_kvm->arch.vcpu_features,
>                    allowed_features, KVM_VCPU_MAX_FEATURES);
>  }
> @@ -399,6 +400,18 @@ static void unpin_host_sve_state(struct pkvm_hyp_vcpu 
> *hyp_vcpu)
>                              sve_state + 
> vcpu_sve_state_size(&hyp_vcpu->vcpu));
>  }
>
> +static void unpin_host_sme_state(struct pkvm_hyp_vcpu *hyp_vcpu)
> +{
> +       void *sme_state;
> +
> +       if (!vcpu_has_feature(&hyp_vcpu->vcpu, KVM_ARM_VCPU_SME))
> +               return;
> +
> +       sme_state = kern_hyp_va(hyp_vcpu->vcpu.arch.sme_state);
> +       hyp_unpin_shared_mem(sme_state,
> +                            sme_state + 
> vcpu_sme_state_size(&hyp_vcpu->vcpu));
> +}
> +
>  static void unpin_host_vcpus(struct pkvm_hyp_vcpu *hyp_vcpus[],
>                              unsigned int nr_vcpus)
>  {
> @@ -412,6 +425,7 @@ static void unpin_host_vcpus(struct pkvm_hyp_vcpu 
> *hyp_vcpus[],
>
>                 unpin_host_vcpu(hyp_vcpu->host_vcpu);
>                 unpin_host_sve_state(hyp_vcpu);
> +               unpin_host_sme_state(hyp_vcpu);
>         }
>  }
>
> @@ -438,23 +452,35 @@ static void init_pkvm_hyp_vm(struct kvm *host_kvm, 
> struct pkvm_hyp_vm *hyp_vm,
>         mmu->pgt = &hyp_vm->pgt;
>  }
>
> -static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct 
> kvm_vcpu *host_vcpu)
> +static int pkvm_vcpu_init_vec(struct pkvm_hyp_vcpu *hyp_vcpu, struct 
> kvm_vcpu *host_vcpu)
>  {
>         struct kvm_vcpu *vcpu = &hyp_vcpu->vcpu;
> -       unsigned int sve_max_vl;
> -       size_t sve_state_size;
> -       void *sve_state;
> +       unsigned int sve_max_vl, sme_max_vl;
> +       size_t sve_state_size, sme_state_size;
> +       void *sve_state, *sme_state;
>         int ret = 0;
>
> -       if (!vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE)) {
> +       if (!vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE) &&
> +           !vcpu_has_feature(vcpu, KVM_ARM_VCPU_SME)) {
>                 vcpu_clear_flag(vcpu, VCPU_VEC_FINALIZED);
>                 return 0;
>         }
>
>         /* Limit guest vector length to the maximum supported by the host. */
> -       sve_max_vl = min(READ_ONCE(host_vcpu->arch.max_vl[ARM64_VEC_SVE]),
> -                        kvm_host_max_vl[ARM64_VEC_SVE]);
> -       sve_state_size = sve_state_size_from_vl(sve_max_vl);
> +       if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE))
> +               sve_max_vl = 
> min(READ_ONCE(host_vcpu->arch.max_vl[ARM64_VEC_SVE]),
> +                                kvm_host_max_vl[ARM64_VEC_SVE]);
> +       else
> +               sve_max_vl = 0;
> +
> +       if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SME))
> +               sme_max_vl = 
> min(READ_ONCE(host_vcpu->arch.max_vl[ARM64_VEC_SME]),
> +                                kvm_host_max_vl[ARM64_VEC_SME]);
> +       else
> +               sme_max_vl = 0;
> +
> +       /* We need SVE storage for the larger of normal or streaming mode */
> +       sve_state_size = sve_state_size_from_vl(max(sve_max_vl, sme_max_vl));
>         sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state));
>
>         if (!sve_state || !sve_state_size) {
> @@ -466,12 +492,36 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu 
> *hyp_vcpu, struct kvm_vcpu *h
>         if (ret)
>                 goto err;
>
> +       if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SME)) {
> +               sme_state_size = sme_state_size_from_vl(sme_max_vl,
> +                                                       vcpu_has_sme2(vcpu));
> +               sme_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sme_state));
> +
> +               if (!sme_state || !sme_state_size) {
> +                       ret = -EINVAL;
> +                       goto err_sve_mapped;
> +               }
> +
> +               ret = hyp_pin_shared_mem(sme_state, sme_state + 
> sme_state_size);
> +               if (ret)
> +                       goto err_sve_mapped;
> +       } else {
> +               sme_state = 0;
> +       }
> +
>         vcpu->arch.sve_state = sve_state;
>         vcpu->arch.max_vl[ARM64_VEC_SVE] = sve_max_vl;
>
> +       vcpu->arch.sme_state = sme_state;
> +       vcpu->arch.max_vl[ARM64_VEC_SME] = sme_max_vl;
> +
>         return 0;
> +
> +err_sve_mapped:
> +       hyp_unpin_shared_mem(sve_state, sve_state + sve_state_size);
>  err:
>         clear_bit(KVM_ARM_VCPU_SVE, vcpu->kvm->arch.vcpu_features);
> +       clear_bit(KVM_ARM_VCPU_SME, vcpu->kvm->arch.vcpu_features);
>         return ret;
>  }
>
> @@ -501,7 +551,7 @@ static int init_pkvm_hyp_vcpu(struct pkvm_hyp_vcpu 
> *hyp_vcpu,
>         if (ret)
>                 goto done;
>
> -       ret = pkvm_vcpu_init_sve(hyp_vcpu, host_vcpu);
> +       ret = pkvm_vcpu_init_vec(hyp_vcpu, host_vcpu);
>  done:
>         if (ret)
>                 unpin_host_vcpu(host_vcpu);
> diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c 
> b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
> index 3108b5185c20..40127ba86335 100644
> --- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
> +++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
> @@ -66,6 +66,11 @@ static bool vm_has_ptrauth(const struct kvm *kvm)
>                 kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_PTRAUTH_GENERIC);
>  }
>
> +static bool vm_has_sme(const struct kvm *kvm)
> +{
> +       return system_supports_sme() && kvm_vcpu_has_feature(kvm, 
> KVM_ARM_VCPU_SME);
> +}
> +
>  static bool vm_has_sve(const struct kvm *kvm)
>  {
>         return system_supports_sve() && kvm_vcpu_has_feature(kvm, 
> KVM_ARM_VCPU_SVE);
> @@ -102,6 +107,7 @@ static const struct pvm_ftr_bits pvmid_aa64pfr0[] = {
>  };
>
>  static const struct pvm_ftr_bits pvmid_aa64pfr1[] = {
> +       MAX_FEAT_FUNC(ID_AA64PFR1_EL1, SME, SME2, vm_has_sme),
>         MAX_FEAT(ID_AA64PFR1_EL1, BT, IMP),
>         MAX_FEAT(ID_AA64PFR1_EL1, SSBS, SSBS2),
>         MAX_FEAT_ENUM(ID_AA64PFR1_EL1, MTE_frac, NI),
> diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c
> index a8684a1346ec..e6dc04267cbb 100644
> --- a/arch/arm64/kvm/reset.c
> +++ b/arch/arm64/kvm/reset.c
> @@ -76,6 +76,34 @@ int __init kvm_arm_init_sve(void)
>         return 0;
>  }
>
> +int __init kvm_arm_init_sme(void)
> +{
> +       if (system_supports_sme()) {
> +               kvm_max_vl[ARM64_VEC_SME] = sme_max_virtualisable_vl();
> +               kvm_host_max_vl[ARM64_VEC_SME] = sme_max_vl();
> +               kvm_nvhe_sym(kvm_host_max_vl[ARM64_VEC_SME]) = 
> kvm_host_max_vl[ARM64_VEC_SME];
> +
> +               /*
> +                * The get_sve_reg()/set_sve_reg() ioctl interface will need
> +                * to be extended with multiple register slice support in
> +                * order to support vector lengths greater than
> +                * VL_ARCH_MAX:
> +                */
> +               if (WARN_ON(kvm_max_vl[ARM64_VEC_SME] > VL_ARCH_MAX))
> +                       kvm_max_vl[ARM64_VEC_SME] = VL_ARCH_MAX;
> +
> +               /*
> +                * Don't even try to make use of vector lengths that
> +                * aren't available on all CPUs, for now:
> +                */
> +               if (kvm_max_vl[ARM64_VEC_SME] < sme_max_vl())
> +                       pr_warn("KVM: SME vector length for guests limited to 
> %u bytes\n",
> +                               kvm_max_vl[ARM64_VEC_SME]);
> +       }
> +
> +       return 0;
> +}
> +
>  static void kvm_vcpu_enable_sve(struct kvm_vcpu *vcpu)
>  {
>         vcpu->arch.max_vl[ARM64_VEC_SVE] = kvm_max_vl[ARM64_VEC_SVE];
> @@ -88,42 +116,86 @@ static void kvm_vcpu_enable_sve(struct kvm_vcpu *vcpu)
>         set_bit(KVM_ARCH_FLAG_GUEST_HAS_SVE, &vcpu->kvm->arch.flags);
>  }
>
> +static void kvm_vcpu_enable_sme(struct kvm_vcpu *vcpu)
> +{
> +       vcpu->arch.max_vl[ARM64_VEC_SME] = kvm_max_vl[ARM64_VEC_SME];
> +
> +       /*
> +        * Userspace can still customize the vector lengths by writing
> +        * KVM_REG_ARM64_SME_VLS.  Allocation is deferred until
> +        * kvm_arm_vcpu_finalize(), which freezes the configuration.
> +        */
> +       set_bit(KVM_ARCH_FLAG_GUEST_HAS_SME, &vcpu->kvm->arch.flags);
> +}
> +
>  /*
> - * Finalize vcpu's maximum SVE vector length, allocating
> - * vcpu->arch.sve_state as necessary.
> + * Finalize vcpu's maximum vector lengths, allocating
> + * vcpu->arch.sve_state and vcpu->arch.sme_state as necessary.
>   */
>  static int kvm_vcpu_finalize_vec(struct kvm_vcpu *vcpu)
>  {
> -       void *buf;
> +       void *sve_state, *sme_state;
>         unsigned int vl;
> -       size_t reg_sz;
>         int ret;
>
> -       vl = vcpu->arch.max_vl[ARM64_VEC_SVE];
> -
>         /*
>          * Responsibility for these properties is shared between
>          * kvm_arm_init_sve(), kvm_vcpu_enable_sve() and
>          * set_sve_vls().  Double-check here just to be sure:
>          */
> -       if (WARN_ON(!sve_vl_valid(vl) || vl > sve_max_virtualisable_vl() ||
> -                   vl > VL_ARCH_MAX))
> -               return -EIO;
> +       if (vcpu_has_sve(vcpu)) {
> +               vl = vcpu->arch.max_vl[ARM64_VEC_SVE];
> +               if (WARN_ON(!sve_vl_valid(vl) ||
> +                           vl > sve_max_virtualisable_vl() ||
> +                           vl > VL_ARCH_MAX))
> +                       return -EIO;
> +       }
>
> -       reg_sz = vcpu_sve_state_size(vcpu);
> -       buf = kzalloc(reg_sz, GFP_KERNEL_ACCOUNT);
> -       if (!buf)
> +       /* Similarly for SME */
> +       if (vcpu_has_sme(vcpu)) {
> +               vl = vcpu->arch.max_vl[ARM64_VEC_SME];
> +               if (WARN_ON(!sve_vl_valid(vl) ||
> +                           vl > sme_max_virtualisable_vl() ||
> +                           vl > VL_ARCH_MAX))
> +                       return -EIO;
> +       }
> +
> +       sve_state = kzalloc(vcpu_sve_state_size(vcpu), GFP_KERNEL_ACCOUNT);
> +       if (!sve_state)
>                 return -ENOMEM;
>
> -       ret = kvm_share_hyp(buf, buf + reg_sz);
> -       if (ret) {
> -               kfree(buf);
> -               return ret;
> +       ret = kvm_share_hyp(sve_state, sve_state + vcpu_sve_state_size(vcpu));
> +       if (ret)
> +               goto err_sve_alloc;
> +
> +       if (vcpu_has_sme(vcpu)) {
> +               sme_state = kzalloc(vcpu_sme_state_size(vcpu),
> +                                   GFP_KERNEL_ACCOUNT);
> +               if (!sme_state) {
> +                       ret = -ENOMEM;
> +                       goto err_sve_map;
> +               }
> +
> +               ret = kvm_share_hyp(sme_state,
> +                                   sme_state + vcpu_sme_state_size(vcpu));
> +               if (ret)
> +                       goto err_sme_alloc;
> +       } else {
> +               sme_state = NULL;
>         }
> -
> -       vcpu->arch.sve_state = buf;
> +
> +       vcpu->arch.sve_state = sve_state;
> +       vcpu->arch.sme_state = sme_state;
>         vcpu_set_flag(vcpu, VCPU_VEC_FINALIZED);
>         return 0;
> +
> +err_sme_alloc:
> +       kfree(sme_state);
> +err_sve_map:
> +       kvm_unshare_hyp(sve_state, sve_state + vcpu_sve_state_size(vcpu));
> +err_sve_alloc:
> +       kfree(sve_state);
> +       return ret;
>  }
>
>  int kvm_arm_vcpu_finalize(struct kvm_vcpu *vcpu, int feature)
> @@ -153,12 +225,16 @@ bool kvm_arm_vcpu_is_finalized(struct kvm_vcpu *vcpu)
>  void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu)
>  {
>         void *sve_state = vcpu->arch.sve_state;
> +       void *sme_state = vcpu->arch.sme_state;
>
>         kvm_unshare_hyp(vcpu, vcpu + 1);
>         if (sve_state)
>                 kvm_unshare_hyp(sve_state, sve_state + 
> vcpu_sve_state_size(vcpu));
>         kfree(sve_state);
>         free_page((unsigned long)vcpu->arch.ctxt.vncr_array);
> +       if (sme_state)
> +               kvm_unshare_hyp(sme_state, sme_state + 
> vcpu_sme_state_size(vcpu));
> +       kfree(sme_state);
>         kfree(vcpu->arch.vncr_tlb);
>         kfree(vcpu->arch.ccsidr);
>  }
> @@ -167,6 +243,8 @@ static void kvm_vcpu_reset_vec(struct kvm_vcpu *vcpu)
>  {
>         if (vcpu_has_sve(vcpu))
>                 memset(vcpu->arch.sve_state, 0, vcpu_sve_state_size(vcpu));
> +       if (vcpu_has_sme(vcpu))
> +               memset(vcpu->arch.sme_state, 0, vcpu_sme_state_size(vcpu));
>  }
>
>  /**
> @@ -206,6 +284,8 @@ void kvm_reset_vcpu(struct kvm_vcpu *vcpu)
>         if (!kvm_arm_vcpu_vec_finalized(vcpu)) {
>                 if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE))
>                         kvm_vcpu_enable_sve(vcpu);
> +               if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SME))
> +                       kvm_vcpu_enable_sme(vcpu);
>         } else {
>                 kvm_vcpu_reset_vec(vcpu);
>         }
> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> index dddb781b0507..d9e068db3b73 100644
> --- a/include/uapi/linux/kvm.h
> +++ b/include/uapi/linux/kvm.h
> @@ -974,6 +974,7 @@ struct kvm_enable_cap {
>  #define KVM_CAP_GUEST_MEMFD_FLAGS 244
>  #define KVM_CAP_ARM_SEA_TO_USER 245
>  #define KVM_CAP_S390_USER_OPEREXEC 246
> +#define KVM_CAP_ARM_SME 247
>
>  struct kvm_irq_routing_irqchip {
>         __u32 irqchip;
>
> --
> 2.47.3
>

Reply via email to