Hi Mark,

On Thu, 9 Jul 2026 at 19:41, Mark Brown <[email protected]> wrote:
>
> If the guest has SME state we need to context switch that state, provide
> support for that for normal guests.
>
> SME has three sets of registers, ZA, ZT (only present for SME2) and also
> streaming SVE which replaces the standard floating point registers when
> active. The first two are fairly straightforward, they are accessible only
> when PSTATE.ZA is set and we can reuse the assembly from the host to save
> and load them from a single contiguous buffer. When PSTATE.ZA is not set
> then these registers are inaccessible, when the guest enables PSTATE.ZA
> all bits will be set to 0 by that and nothing is required on restore.
>
> Streaming mode is slightly more complicated, when enabled via PSTATE.SM it
> provides a version of the SVE registers using the SME vector length and may
> optionally omit the FFR register. SME may also be present without SVE. The
> register state is stored in sve_state as for non-streaming SVE mode, we
> make an initial selection of registers to update based on the guest SVE
> support and then override this when loading SVCR if streaming mode is
> enabled.
>
> A further complication is that when the hardware is in streaming mode
> guest operations that are invalid in in streaming mode will generate SME
> exceptions. There are also subfeature exceptions for SME2 controlled via
> SMCR which generate distinct exception codes. In many situations these
> exceptions are routed directly to the lower ELs with no opportunity for
> the hypervisor to intercept. So that guests do not see unexpected
> exception types due to the actual hardware configuration not being what
> the guest configured we update the SMCRs and SVCR even if the guest does
> not own the registers.
>
> Since in order to avoid duplication with SME we now restore the register
> state outside of the SVE specific restore function we need to move the
> restore of the effective VL for nested guests to a separate restore
> function run after loading the floating point register state, along with
> the similar handling required for SME.
>
> Signed-off-by: Mark Brown <[email protected]>
> ---
>  arch/arm64/include/asm/fpsimd.h         |  10 ++
>  arch/arm64/include/asm/kvm_host.h       |  25 +++--
>  arch/arm64/kvm/fpsimd.c                 |  26 +++--
>  arch/arm64/kvm/hyp/include/hyp/switch.h | 173 
> ++++++++++++++++++++++++++++++--
>  arch/arm64/kvm/hyp/nvhe/hyp-main.c      | 127 +++++++++++++++++++----
>  5 files changed, 319 insertions(+), 42 deletions(-)
>
> diff --git a/arch/arm64/include/asm/fpsimd.h b/arch/arm64/include/asm/fpsimd.h
> index c3da16fdb158..9806b75e5e4c 100644
> --- a/arch/arm64/include/asm/fpsimd.h
> +++ b/arch/arm64/include/asm/fpsimd.h
> @@ -431,6 +431,15 @@ static inline void sve_user_enable(void)
>                         write_sysreg_s(__new, (reg));   \
>         } while (0)
>
> +#define sme_cond_update_smcr_vq(val, reg)              \
> +       do {                                            \
> +               u64 __smcr = read_sysreg_s((reg));      \
> +               u64 __new = __smcr & ~SMCR_ELx_LEN_MASK;        \
> +               __new |= (val) & SMCR_ELx_LEN_MASK;     \
> +               if (__smcr != __new)                    \
> +                       write_sysreg_s(__new, (reg));   \
> +       } while (0)
> +
>  /*
>   * Probing and setup functions.
>   * Calls to these functions must be serialised with one another.
> @@ -549,6 +558,7 @@ static inline void sve_user_disable(void) { BUILD_BUG(); }
>  static inline void sve_user_enable(void) { BUILD_BUG(); }
>
>  #define sve_cond_update_zcr_vq(val, reg) do { } while (0)
> +#define sme_cond_update_smcr_vq(val, reg) do { } while (0)
>
>  static inline void vec_init_vq_map(enum vec_type t) { }
>  static inline void vec_update_vq_map(enum vec_type t) { }
> diff --git a/arch/arm64/include/asm/kvm_host.h 
> b/arch/arm64/include/asm/kvm_host.h
> index b78c039cb5ec..26ad8962b65d 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -1126,6 +1126,9 @@ struct kvm_vcpu_arch {
>  #define vcpu_sve_zcr_elx(vcpu)                                         \
>         (unlikely(is_hyp_ctxt(vcpu)) ? ZCR_EL2 : ZCR_EL1)
>
> +#define vcpu_sme_smcr_elx(vcpu)                                              
>   \
> +       (unlikely(is_hyp_ctxt(vcpu)) ? SMCR_EL2 : SMCR_EL1)
> +
>  #define sve_state_size_from_vl(sve_max_vl) ({                          \
>         size_t __size_ret;                                              \
>         unsigned int __vq;                                              \
> @@ -1140,10 +1143,20 @@ struct kvm_vcpu_arch {
>         __size_ret;                                                     \
>  })
>
> -#define vcpu_sve_state_size(vcpu) 
> sve_state_size_from_vl(vcpu_sve_max_vl(vcpu))
> +#define vcpu_sve_state_size(vcpu) sve_state_size_from_vl(vcpu_max_vl(vcpu))
>
>  #define vcpu_sme_state(vcpu) (kern_hyp_va((vcpu)->arch.sme_state))
>
> +/*
> + * Always provide space for ZT0 to avoid ordering requirements with ID
> + * register writes and vector finalization.
> + */
> +#define vcpu_sme_state_size(vcpu) ({                                   \
> +       unsigned long __vl;                                             \
> +       __vl = (vcpu)->arch.max_vl[ARM64_VEC_SME];                      \
> +       sme_state_size_from_vl(__vl, system_supports_sme2());           \
> +})
> +
>  #define sme_state_size_from_vl(vl, sme2) ({                            \
>         size_t __size_ret;                                              \
>         unsigned int __vq;                                              \
> @@ -1160,16 +1173,6 @@ struct kvm_vcpu_arch {
>         __size_ret;                                                     \
>  })
>
> -/*
> - * Always provide space for ZT0 to avoid ordering requirements with ID
> - * register writes and vector finalization.
> - */
> -#define vcpu_sme_state_size(vcpu) ({                                   \
> -       unsigned long __vl;                                             \
> -       __vl = (vcpu)->arch.max_vl[ARM64_VEC_SME];                      \
> -       sme_state_size_from_vl(__vl, system_supports_sme2());           \
> -})
> -
>  #define KVM_GUESTDBG_VALID_MASK (KVM_GUESTDBG_ENABLE | \
>                                  KVM_GUESTDBG_USE_SW_BP | \
>                                  KVM_GUESTDBG_USE_HW | \
> diff --git a/arch/arm64/kvm/fpsimd.c b/arch/arm64/kvm/fpsimd.c
> index 567dd43970c5..bb0bf8d81522 100644
> --- a/arch/arm64/kvm/fpsimd.c
> +++ b/arch/arm64/kvm/fpsimd.c
> @@ -83,19 +83,24 @@ void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu)
>         WARN_ON_ONCE(!irqs_disabled());
>
>         if (guest_owns_fp_regs()) {
> -               /*
> -                * Currently we do not support SME guests so SVCR is
> -                * always 0 and we just need a variable to point to.
> -                */
>                 fp_state.st = &vcpu->arch.ctxt.fp_regs;
>                 fp_state.sve_state = vcpu->arch.sve_state;
>                 fp_state.zcr = vcpu_sve_max_vq(vcpu) - 1;
> -               fp_state.smcr = 0;
> -               fp_state.sme_state = NULL;
> +               fp_state.smcr = vcpu_sme_max_vq(vcpu) - 1;
> +               fp_state.sme_state = vcpu->arch.sme_state;
>                 fp_state.svcr = __ctxt_sys_reg(&vcpu->arch.ctxt, SVCR);
>                 fp_state.fpmr = __ctxt_sys_reg(&vcpu->arch.ctxt, FPMR);
>                 fp_state.fp_type = &vcpu->arch.fp_type;
>
> +               if (kvm_has_fa64(vcpu->kvm))
> +                       fp_state.smcr |= SMCR_ELx_FA64;
> +               if (kvm_has_sme2(vcpu->kvm))
> +                       fp_state.smcr |= SMCR_ELx_EZT0;
> +
> +               /*
> +                * For SME only guests fpsimd_save() will override the
> +                * state selection if we are in streaming mode.
> +                */
>                 if (vcpu_has_sve(vcpu))
>                         fp_state.to_save = FP_STATE_SVE;
>                 else
> @@ -104,6 +109,15 @@ void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu)
>                 fpsimd_bind_state_to_cpu(&fp_state);
>
>                 clear_thread_flag(TIF_FOREIGN_FPSTATE);
> +       } else {
> +               /*
> +                * We might have enabled SME to configure traps but
> +                * insist the host doesn't run the hypervisor with SME
> +                * enabled, ensure it's disabled again.
> +                */
> +               if (system_supports_sme()) {
> +                       sme_smstop();
> +               }
>         }
>  }
>
> diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h 
> b/arch/arm64/kvm/hyp/include/hyp/switch.h
> index e444f0a94dcf..7c913da9babb 100644
> --- a/arch/arm64/kvm/hyp/include/hyp/switch.h
> +++ b/arch/arm64/kvm/hyp/include/hyp/switch.h
> @@ -462,6 +462,28 @@ static inline bool kvm_hyp_handle_mops(struct kvm_vcpu 
> *vcpu, u64 *exit_code)
>         return true;
>  }
>
> +static inline void __hyp_sme_restore_guest(struct kvm_vcpu *vcpu,
> +                                          bool *restore_sve,
> +                                          bool *restore_ffr)
> +{
> +       /*
> +        * The vCPU's saved SVE state layout always matches the max VL of the
> +        * vCPU. Start off with the max VL so we can load the SVE state.
> +        */
> +       sme_cond_update_smcr_vq(vcpu_sme_max_vq(vcpu) - 1, SYS_SMCR_EL2);
> +
> +       if (vcpu_in_streaming_mode(vcpu)) {
> +               *restore_sve = true;
> +               *restore_ffr = vcpu_has_fa64(vcpu);
> +       }
> +
> +       if (vcpu_za_enabled(vcpu))
> +               sme_load_state(kern_hyp_va(vcpu->arch.sme_state),
> +                              vcpu_has_sme2(vcpu));
> +
> +       write_sysreg_el1(__vcpu_sys_reg(vcpu, vcpu_sme_smcr_elx(vcpu)), 
> SYS_SMCR);
> +}
> +
>  static inline void __hyp_sve_restore_guest(struct kvm_vcpu *vcpu)
>  {
>         u64 zcr_el2 = vcpu_sve_max_vq(vcpu) - 1;
> @@ -471,20 +493,34 @@ static inline void __hyp_sve_restore_guest(struct 
> kvm_vcpu *vcpu)
>          * vCPU. Start off with the max VL so we can load the SVE state.
>          */
>         sve_cond_update_zcr_vq(zcr_el2, SYS_ZCR_EL2);
> -       sve_load_state(kern_hyp_va(vcpu->arch.sve_state), true);
> -       fpsimd_load_common(&vcpu->arch.ctxt.fp_regs);
> +       write_sysreg_el1(__vcpu_sys_reg(vcpu, vcpu_sve_zcr_elx(vcpu)), 
> SYS_ZCR);
> +}
> +
> +static inline void __hyp_nv_restore_guest_vls(struct kvm_vcpu *vcpu)
> +{
> +       if (!is_nested_ctxt(vcpu))
> +               return;
>
>         /*
>          * The effective VL for a VM could differ from the max VL when 
> running a
>          * nested guest, as the guest hypervisor could select a smaller VL. 
> Slap
>          * that into hardware before wrapping up.
>          */
> -       if (is_nested_ctxt(vcpu)) {
> +
> +       if (vcpu_has_sve(vcpu)) {
> +               u64 zcr_el2 = vcpu_sve_max_vq(vcpu) - 1;
> +
>                 zcr_el2 = min(zcr_el2, __vcpu_sys_reg(vcpu, ZCR_EL2));
>                 sve_cond_update_zcr_vq(zcr_el2, SYS_ZCR_EL2);
>         }
>
> -       write_sysreg_el1(__vcpu_sys_reg(vcpu, vcpu_sve_zcr_elx(vcpu)), 
> SYS_ZCR);
> +       if (vcpu_has_sme(vcpu)) {
> +               u64 max_len = vcpu_sme_max_vq(vcpu) - 1;
> +               u64 smcr_len = SYS_FIELD_GET(SMCR_ELx, LEN,
> +                                            __vcpu_sys_reg(vcpu, SMCR_EL2));
> +
> +               sme_cond_update_smcr_vq(min(smcr_len, max_len), SYS_SMCR_EL2);
> +       }
>  }
>
>  static inline void __hyp_sve_save_host(void)
> @@ -498,10 +534,48 @@ static inline void __hyp_sve_save_host(void)
>         fpsimd_save_common(&hctxt->fp_regs);
>  }
>
> +static inline void kvm_sme_configure_traps(struct kvm_vcpu *vcpu)
> +{
> +       u64 smcr_el1, smcr_el2, guest_el2_len, svcr;
> +
> +       if (!vcpu_has_sme(vcpu))
> +               return;
> +
> +       smcr_el2 = vcpu_sme_max_vq(vcpu) - 1;
> +
> +       /* A guest hypervisor may restrict the effective max VL. */
> +       if (is_nested_ctxt(vcpu)) {
> +               guest_el2_len = SYS_FIELD_GET(SMCR_ELx, LEN,
> +                                             __vcpu_sys_reg(vcpu, SMCR_EL2));
> +               smcr_el2 = min(smcr_el2, guest_el2_len);
> +       }
> +
> +       if (vcpu_has_fa64(vcpu))
> +               smcr_el2 |= SMCR_ELx_FA64;
> +       if (vcpu_has_sme2(vcpu))
> +               smcr_el2 |= SMCR_ELx_EZT0;

I ran Sashiko locally (my PR wasn't applied, so it still didn't run).
It flagged this:

For a nested guest this folds in the LEN restriction from the guest
hypervisor's SMCR_EL2 just above, but not FA64/EZT0. vcpu_has_fa64()
and vcpu_has_sme2() are VM-level capabilities (kvm_has_fa64/sme2 on
vcpu->kvm), so an L1 that clears SMCR_EL2.FA64 or .EZT0 to deny them to
L2 is overridden here: the physical SMCR_EL2 gets the bits set from the
VM cap regardless.

FA64 (IsFullA64Enabled()) and EZT0 (CheckSMEZT0Enabled()) are both
hierarchical AND-enables that gate EL1/EL0, so the effective SMCR_EL2
for L2 needs L1's SMCR_EL2.{FA64,EZT0} ANDed in, the same way LEN is
min()'d. Something like gating each OR on the guest's SMCR_EL2 bit when
is_nested_ctxt(vcpu).

> +
> +       write_sysreg_el2(smcr_el2, SYS_SMCR);
> +
> +       smcr_el1 = __vcpu_sys_reg(vcpu, vcpu_sme_smcr_elx(vcpu));
> +       write_sysreg_el1(smcr_el1, SYS_SMCR);
> +
> +       svcr = __vcpu_sys_reg(vcpu, SVCR);
> +       write_sysreg_s(svcr, SYS_SVCR);
> +}
> +
>  static inline void fpsimd_lazy_switch_to_guest(struct kvm_vcpu *vcpu)
>  {
>         u64 zcr_el1, zcr_el2;
>
> +       /*
> +        * We always load the SME control registers that affect traps
> +        * since if they are not configured as expected by the guest
> +        * then it may have exceptions that it does not expect
> +        * directly delivered.
> +        */
> +       kvm_sme_configure_traps(vcpu);
> +
>         if (!guest_owns_fp_regs())
>                 return;
>
> @@ -555,8 +629,57 @@ static inline void sve_lazy_switch_to_host(struct 
> kvm_vcpu *vcpu)
>         }
>  }
>
> +static inline void sme_lazy_switch_to_host(struct kvm_vcpu *vcpu)
> +{
> +       u64 smcr_el1, smcr_el2;
> +
> +       if (!vcpu_has_sme(vcpu))
> +               return;
> +
> +       /*
> +        * __deactivate_cptr_traps() disabled traps, but there hasn't
> +        * necessarily been a context synchronization event yet.
> +        */
> +       isb();
> +
> +       smcr_el1 = read_sysreg_el1(SYS_SMCR);
> +       __vcpu_assign_sys_reg(vcpu, vcpu_sme_smcr_elx(vcpu), smcr_el1);
> +
> +       smcr_el2 = 0;
> +       if (system_supports_fa64())
> +               smcr_el2 |= SMCR_ELx_FA64;
> +       if (system_supports_sme2())
> +               smcr_el2 |= SMCR_ELx_EZT0;
> +
> +       /*
> +        * The guest's state is always saved using the guest's max VL.
> +        * Ensure that the host has the guest's max VL active such
> +        * that the host can save the guest's state lazily, but don't
> +        * artificially restrict the host to the guest's max VL.
> +        */
> +       if (has_vhe()) {
> +               smcr_el2 |= vcpu_sme_max_vq(vcpu) - 1;
> +               write_sysreg_el2(smcr_el2, SYS_SMCR);
> +       } else {
> +               smcr_el1 = smcr_el2;
> +               smcr_el2 |= sve_vq_from_vl(kvm_host_max_vl[ARM64_VEC_SME]) - 
> 1;
> +               write_sysreg_el2(smcr_el2, SYS_SMCR);
> +
> +               smcr_el1 |= vcpu_sme_max_vq(vcpu) - 1;
> +               write_sysreg_el1(smcr_el1, SYS_SMCR);
> +       }
> +
> +       __vcpu_assign_sys_reg(vcpu, SVCR, read_sysreg_s(SYS_SVCR));
> +}
> +
>  static inline void fpsimd_lazy_switch_to_host(struct kvm_vcpu *vcpu)
>  {
> +       /*
> +        * We always load the control registers for the guest so we
> +        * always restore state for the host.
> +        */
> +       sme_lazy_switch_to_host(vcpu);
> +
>         if (!guest_owns_fp_regs())
>                 return;
>
> @@ -567,6 +690,16 @@ static void kvm_hyp_save_fpsimd_host(struct kvm_vcpu 
> *vcpu)
>  {
>         struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
>
> +       /*
> +        * The hypervisor refuses to run if streaming mode or ZA is
> +        * enabled, we only need to save SMCR_EL1 for SME.  For pKVM
> +        * we will restore this, reset SMCR_EL2 to a fixed value and
> +        * disable streaming mode and ZA to avoid any state being
> +        * leaked.
> +        */
> +       if (system_supports_sme())
> +               ctxt_sys_reg(hctxt, SMCR_EL1) = read_sysreg_el1(SYS_SMCR);
> +
>         /*
>          * Non-protected kvm relies on the host restoring its sve state.
>          * Protected kvm restores the host's sve state as not to reveal that
> @@ -591,14 +724,17 @@ static void kvm_hyp_save_fpsimd_host(struct kvm_vcpu 
> *vcpu)
>   */
>  static inline bool kvm_hyp_handle_fpsimd(struct kvm_vcpu *vcpu, u64 
> *exit_code)
>  {
> -       bool sve_guest;
> -       u8 esr_ec;
> +       bool restore_sve, restore_ffr;
> +       bool sve_guest, sme_guest;
> +       u8 esr_ec, esr_iss_smtc;
>
>         if (!system_supports_fpsimd())
>                 return false;
>
>         sve_guest = vcpu_has_sve(vcpu);
> +       sme_guest = vcpu_has_sme(vcpu);
>         esr_ec = kvm_vcpu_trap_get_class(vcpu);
> +       esr_iss_smtc = ESR_ELx_SME_ISS_SMTC((kvm_vcpu_get_esr(vcpu)));
>
>         /* Only handle traps the vCPU can support here: */
>         switch (esr_ec) {
> @@ -617,6 +753,15 @@ static inline bool kvm_hyp_handle_fpsimd(struct kvm_vcpu 
> *vcpu, u64 *exit_code)
>                 if (guest_hyp_sve_traps_enabled(vcpu))
>                         return false;
>                 break;
> +       case ESR_ELx_EC_SME:
> +               if (!sme_guest)
> +                       return false;
> +               if (guest_hyp_sme_traps_enabled(vcpu))
> +                       return false;
> +               if (!kvm_has_sme2(kern_hyp_va(vcpu->kvm)) &&
> +                   (esr_iss_smtc == ESR_ELx_SME_ISS_SMTC_ZT_DISABLED))
> +                       return false;
> +               break;
>         default:
>                 return false;
>         }
> @@ -632,10 +777,22 @@ static inline bool kvm_hyp_handle_fpsimd(struct 
> kvm_vcpu *vcpu, u64 *exit_code)
>                 kvm_hyp_save_fpsimd_host(vcpu);
>
>         /* Restore the guest state */
> +
> +       /* These may be overridden for a SME guest */
> +       restore_sve = sve_guest;
> +       restore_ffr = sve_guest;
> +
>         if (sve_guest)
>                 __hyp_sve_restore_guest(vcpu);
> -       else
> +       if (sme_guest)
> +               __hyp_sme_restore_guest(vcpu, &restore_sve, &restore_ffr);
> +
> +       if (restore_sve) {
> +               sve_load_state(kern_hyp_va(vcpu->arch.sve_state), 
> restore_ffr);
> +               fpsimd_load_common(&vcpu->arch.ctxt.fp_regs);
> +       } else {
>                 fpsimd_load_state(&vcpu->arch.ctxt.fp_regs);
> +       }
>
>         if (kvm_has_fpmr(kern_hyp_va(vcpu->kvm)))
>                 write_sysreg_s(__vcpu_sys_reg(vcpu, FPMR), SYS_FPMR);
> @@ -644,6 +801,8 @@ static inline bool kvm_hyp_handle_fpsimd(struct kvm_vcpu 
> *vcpu, u64 *exit_code)
>         if (!(read_sysreg(hcr_el2) & HCR_RW))
>                 write_sysreg(__vcpu_sys_reg(vcpu, FPEXC32_EL2), fpexc32_el2);
>
> +       __hyp_nv_restore_guest_vls(vcpu);
> +
>         *host_data_ptr(fp_owner) = FP_STATE_GUEST_OWNED;
>
>         /*
> diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c 
> b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> index 14e24e257dcc..0f6e3479a7e4 100644
> --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> @@ -31,16 +31,28 @@ void __kvm_hyp_host_forward_smc(struct kvm_cpu_context 
> *host_ctxt);
>
>  static void __hyp_sve_save_guest(struct kvm_vcpu *vcpu)
>  {
> -       __vcpu_assign_sys_reg(vcpu, ZCR_EL1, read_sysreg_el1(SYS_ZCR));
> -       /*
> -        * On saving/restoring guest sve state, always use the maximum VL for
> -        * the guest. The layout of the data when saving the sve state depends
> -        * on the VL, so use a consistent (i.e., the maximum) guest VL.
> -        */
> -       sve_cond_update_zcr_vq(vcpu_sve_max_vq(vcpu) - 1, SYS_ZCR_EL2);
> -       sve_save_state(kern_hyp_va(vcpu->arch.sve_state), true);
> +       bool save_ffr = !vcpu_in_streaming_mode(vcpu) || vcpu_has_fa64(vcpu);
> +
> +       if (vcpu_has_sve(vcpu)) {
> +               __vcpu_assign_sys_reg(vcpu, ZCR_EL1, 
> read_sysreg_el1(SYS_ZCR));
> +
> +               /*
> +                * On saving/restoring guest sve state, always use the
> +                * maximum VL for the guest. The layout of the data
> +                * when saving the sve state depends on the VL, so use
> +                * a consistent (i.e., the maximum) guest VL.
> +                */
> +               sve_cond_update_zcr_vq(vcpu_sve_max_vq(vcpu) - 1, 
> SYS_ZCR_EL2);
> +       }
> +
> +       /* Ensure ZCR/SMCR updates for VL are seen */
> +       isb();
> +       sve_save_state(kern_hyp_va(vcpu->arch.sve_state), save_ffr);
>         fpsimd_save_common(&vcpu->arch.ctxt.fp_regs);
> -       write_sysreg_s(sve_vq_from_vl(kvm_host_max_vl[ARM64_VEC_SVE]) - 1, 
> SYS_ZCR_EL2);
> +
> +       if (system_supports_sve())
> +               write_sysreg_s(sve_vq_from_vl(kvm_host_max_vl[ARM64_VEC_SVE]) 
> - 1,
> +                              SYS_ZCR_EL2);
>  }
>
>  static void __hyp_sve_restore_host(void)
> @@ -63,9 +75,76 @@ static void __hyp_sve_restore_host(void)
>         write_sysreg_el1(ctxt_sys_reg(hctxt, ZCR_EL1), SYS_ZCR);
>  }
>
> -static void fpsimd_sve_flush(void)
> +static void __hyp_sme_save_guest(struct kvm_vcpu *vcpu)
>  {
> -       *host_data_ptr(fp_owner) = FP_STATE_HOST_OWNED;
> +       unsigned long smcr_el2;
> +
> +       __vcpu_assign_sys_reg(vcpu, SMCR_EL1, read_sysreg_el1(SYS_SMCR));
> +       __vcpu_assign_sys_reg(vcpu, SVCR, read_sysreg_s(SYS_SVCR));
> +
> +       /*
> +        * On saving/restoring guest sve state, always use the maximum VL for
> +        * the guest. The layout of the data when saving the sve state depends
> +        * on the VL, so use a consistent (i.e., the maximum) guest VL.
> +        *
> +        * We restore the FA64 and SME2 enables for the host since we
> +        * will always restore the host configuration so if host and
> +        * guest VLs are the same we might suppress an update.
> +        */
> +       smcr_el2 = vcpu_sme_max_vq(vcpu) - 1;
> +       if (system_supports_fa64())
> +               smcr_el2 |= SMCR_ELx_FA64;
> +       if (system_supports_sme2())
> +               smcr_el2 |= SMCR_ELx_EZT0;
> +       sysreg_cond_update_s(SYS_SMCR_EL2, smcr_el2);
> +
> +       if (vcpu_za_enabled(vcpu)) {
> +               isb();
> +               sme_save_state(vcpu_sme_state(vcpu), vcpu_has_sme2(vcpu));
> +       }
> +}
> +
> +static void __hyp_sme_restore_host(void)
> +{
> +       struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
> +       u64 smcr_el2;
> +
> +       /*
> +        * The hypervisor refuses to run if we are in streaming mode
> +        * or have ZA enabled so there is no SME specific state to
> +        * restore other than the system registers.
> +        *
> +        * Note that this constrains the PE to the maximum shared VL
> +        * that was discovered, if we wish to use larger VLs this will
> +        * need to be revisited.
> +        */
> +       smcr_el2 = sve_vq_from_vl(kvm_host_max_vl[ARM64_VEC_SME]) - 1;
> +       if (system_supports_fa64())
> +               smcr_el2 |= SMCR_ELx_FA64;
> +       if (system_supports_sme2())
> +               smcr_el2 |= SMCR_ELx_EZT0;
> +       sysreg_cond_update_s(SYS_SMCR_EL2, smcr_el2);
> +
> +       write_sysreg_el1(ctxt_sys_reg(hctxt, SMCR_EL1), SYS_SMCR);
> +       sme_smstop();
> +}
> +
> +static void fpsimd_sve_flush(struct kvm_vcpu *vcpu)
> +{
> +       /*
> +        * If the guest has SME then we need to restore the trap
> +        * controls in SMCR and mode in SVCR in order to ensure that
> +        * traps generated directly to EL1 have the correct types,
> +        * otherwise we can defer until we load the guest state.
> +        */
> +       if (vcpu_has_sme(vcpu)) {
> +               kvm_hyp_save_fpsimd_host(vcpu);
> +               kvm_sme_configure_traps(vcpu);
> +
> +               *host_data_ptr(fp_owner) = FP_STATE_FREE;
> +       } else {
> +               *host_data_ptr(fp_owner) = FP_STATE_HOST_OWNED;
> +       }
>  }
>
>  static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
> @@ -73,8 +152,15 @@ static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
>         struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
>         bool has_fpmr;
>
> -       if (!guest_owns_fp_regs())
> +       if (!guest_owns_fp_regs()) {
> +               /*
> +                * We always at least partially configure SME for the
> +                * guest due to traps.
> +                */
> +               if (system_supports_sme())
> +                       __hyp_sme_restore_host();
>                 return;
> +       }
>
>         /*
>          * Traps have been disabled by __deactivate_cptr_traps(), but there
> @@ -82,7 +168,10 @@ static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
>          */
>         isb();
>
> -       if (vcpu_has_sve(vcpu))
> +       if (vcpu_has_sme(vcpu))
> +               __hyp_sme_save_guest(vcpu);
> +
> +       if (vcpu_has_sve(vcpu) || vcpu_in_streaming_mode(vcpu))
>                 __hyp_sve_save_guest(vcpu);
>         else
>                 fpsimd_save_state(&vcpu->arch.ctxt.fp_regs);
> @@ -91,6 +180,9 @@ static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
>         if (has_fpmr)
>                 __vcpu_assign_sys_reg(vcpu, FPMR, read_sysreg_s(SYS_FPMR));
>
> +       if (system_supports_sme())
> +               __hyp_sme_restore_host();
> +
>         if (system_supports_sve())
>                 __hyp_sve_restore_host();
>         else
> @@ -128,7 +220,7 @@ static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
>  {
>         struct kvm_vcpu *host_vcpu = hyp_vcpu->host_vcpu;
>
> -       fpsimd_sve_flush();
> +       fpsimd_sve_flush(host_vcpu);

sync_hyp_vcpu() runs fpsimd_sve_sync() on &hyp_vcpu->vcpu, but flush
passes host_vcpu, so the two sides operate on different vCPUs. That
asymmetry alone looks wrong: flush and sync should cover the same
state.

Concretely it means kvm_sme_configure_traps() programs a protected
guest's SVCR/SMCR from host-owned values, so the host can e.g. set
SVCR.SM and fault the guest. Keying flush off &hyp_vcpu->vcpu like
sync fixes both.

Cheers,
/fuad


>         flush_debug_state(hyp_vcpu);
>
>         hyp_vcpu->vcpu.arch.ctxt        = host_vcpu->arch.ctxt;
> @@ -223,10 +315,9 @@ static void handle___kvm_vcpu_run(struct kvm_cpu_context 
> *host_ctxt)
>                 struct pkvm_hyp_vcpu *hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
>
>                 /*
> -                * KVM (and pKVM) doesn't support SME guests for now, and
> -                * ensures that SME features aren't enabled in pstate when
> -                * loading a vcpu. Therefore, if SME features enabled the host
> -                * is misbehaving.
> +                * KVM (and pKVM) refuses to run if PSTATE.{SM,ZA} are
> +                * enabled. Therefore, if SME features enabled the
> +                * host is misbehaving.
>                  */
>                 if (unlikely(system_supports_sme() && 
> read_sysreg_s(SYS_SVCR))) {
>                         ret = -EINVAL;
>
> --
> 2.47.3
>

Reply via email to