[PATCH 2/4] KVM: arm64: Allocate a protected vCPU's SVE state at EL2
From: Fuad Tabba
Date: Thu Nov 05 2026 - 08:11:21 EST
For a vCPU with SVE, EL2 pins the buffer the host allocated at
KVM_ARM_VCPU_FINALIZE and saves the guest's SVE registers there. The
host can still read and write that buffer, so it can't hold a protected
vCPU's state.
Allocate a protected vCPU's SVE state from the hyp heap instead, and
free it with the vCPU. When EL2 resets a protected vCPU, zero its SVE
state and ZCR_EL1, as KVM does for a non-protected one. The host still
allocates its own buffer, which KVM_{GET,SET}_ONE_REG access before the
first run, but EL2 doesn't use it.
For a protected vCPU, the host's sve_max_vl now sizes an EL2 allocation
and is the guest's maximum vector length on every CPU. Give EL2 a copy
of kvm_sve_max_vl, the largest length all CPUs can give a guest, and
check every vCPU's sve_max_vl against it rather than clamping it to the
host's own maximum. set_sve_vls() already keeps the host's value within
that bound.
Signed-off-by: Fuad Tabba <fuad.tabba@xxxxxxxxx>
---
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/hyp/nvhe/pkvm.c | 41 ++++++++++++++++++++----------
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 1 +
arch/arm64/kvm/reset.c | 2 ++
4 files changed, 31 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index b0e42be3d9eef..8cf42f87526dd 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -157,6 +157,7 @@ extern unsigned int kvm_nvhe_sym(kvm_arm_vmid_bits);
extern enum mitigation_state kvm_nvhe_sym(spectre_v2_state);
extern enum mitigation_state kvm_nvhe_sym(spectre_v4_state);
extern enum mitigation_state kvm_nvhe_sym(spectre_bhb_state);
+extern unsigned int kvm_nvhe_sym(kvm_sve_max_vl);
extern unsigned int kvm_nvhe_sym(kvm_host_sve_max_vl);
extern unsigned long kvm_nvhe_sym(hyp_nr_cpus);
extern unsigned int kvm_nvhe_sym(hyp_gicv3_nr_lr);
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index fc0475f0877d1..6500acbd84969 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -30,6 +30,7 @@ unsigned long __icache_flags;
/* Used by kvm_get_vttbr(). */
unsigned int kvm_arm_vmid_bits;
+unsigned int kvm_sve_max_vl;
unsigned int kvm_host_sve_max_vl;
/*
@@ -476,7 +477,7 @@ static void unpin_host_vcpu(struct kvm_vcpu *host_vcpu)
hyp_unpin_shared_mem(host_vcpu, host_vcpu + 1);
}
-static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu)
+static void teardown_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu)
{
void *sve_state;
@@ -484,8 +485,10 @@ static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu)
if (!sve_state)
return;
- hyp_unpin_shared_mem(sve_state,
- sve_state + vcpu_sve_state_size(&hyp_vcpu->vcpu));
+ if (pkvm_hyp_vcpu_is_protected(hyp_vcpu))
+ hyp_free(sve_state);
+ else
+ hyp_unpin_shared_mem(sve_state, sve_state + vcpu_sve_state_size(&hyp_vcpu->vcpu));
}
static void unpin_host_vcpus(struct pkvm_hyp_vcpu *hyp_vcpus[],
@@ -500,7 +503,7 @@ static void unpin_host_vcpus(struct pkvm_hyp_vcpu *hyp_vcpus[],
continue;
unpin_host_vcpu(hyp_vcpu->host_vcpu);
- unpin_host_sve_state(hyp_vcpu);
+ teardown_sve_state(hyp_vcpu);
}
}
@@ -543,18 +546,25 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h
return 0;
}
- /* Limit guest vector length to the maximum supported by the host. */
- sve_max_vl = min(READ_ONCE(host_vcpu->arch.sve_max_vl), kvm_host_sve_max_vl);
- sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state));
-
- if (!sve_vl_valid(sve_max_vl) || !sve_state)
+ sve_max_vl = READ_ONCE(host_vcpu->arch.sve_max_vl);
+ if (!sve_vl_valid(sve_max_vl) || sve_max_vl > kvm_sve_max_vl)
return -EINVAL;
sve_state_size = sve_state_size_from_vl(sve_max_vl);
- ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size);
- if (ret)
- return ret;
+ if (pkvm_hyp_vcpu_is_protected(hyp_vcpu)) {
+ sve_state = hyp_alloc(sve_state_size);
+ if (!sve_state)
+ return hyp_alloc_errno();
+ } else {
+ sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state));
+ if (!sve_state)
+ return -EINVAL;
+
+ ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size);
+ if (ret)
+ return ret;
+ }
vcpu->arch.sve_state = sve_state;
vcpu->arch.sve_max_vl = sve_max_vl;
@@ -643,7 +653,7 @@ static int init_pkvm_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu,
done:
if (ret) {
unpin_host_vcpu(host_vcpu);
- unpin_host_sve_state(hyp_vcpu);
+ teardown_sve_state(hyp_vcpu);
}
return ret;
}
@@ -1015,7 +1025,7 @@ int __pkvm_init_vcpu(pkvm_handle_t handle, struct kvm_vcpu *host_vcpu)
ret = register_hyp_vcpu(hyp_vm, hyp_vcpu);
if (ret) {
unpin_host_vcpu(host_vcpu);
- unpin_host_sve_state(hyp_vcpu);
+ teardown_sve_state(hyp_vcpu);
goto unclaim;
}
goto unlock;
@@ -1243,6 +1253,9 @@ int pkvm_reset_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
PSCI_0_2_AFFINITY_LEVEL_ON) != PSCI_0_2_AFFINITY_LEVEL_ON_PENDING)
return -ECANCELED;
+ if (vcpu_has_sve(&hyp_vcpu->vcpu))
+ memset(hyp_vcpu->vcpu.arch.sve_state, 0, vcpu_sve_state_size(&hyp_vcpu->vcpu));
+
kvm_reset_vcpu_core(&hyp_vcpu->vcpu);
kvm_reset_pvm_sys_regs(&hyp_vcpu->vcpu);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 32e82653ebf7e..06ea91415ad8b 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -570,6 +570,7 @@ static const struct sys_reg_desc_reset pvm_sys_reg_reset_vals[] = {
RESET_UNKNOWN(MAIR_EL1),
RESET_ZERO(AMAIR_EL1),
RESET_ZERO(MDSCR_EL1),
+ RESET_ZERO(ZCR_EL1),
RESET_ZERO(CNTV_CTL_EL0),
RESET_UNKNOWN(TTBR0_EL1),
RESET_UNKNOWN(TTBR1_EL1),
diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c
index 10eb7249aa9e8..aaaa1b8634ba4 100644
--- a/arch/arm64/kvm/reset.c
+++ b/arch/arm64/kvm/reset.c
@@ -52,6 +52,8 @@ int __init kvm_arm_init_sve(void)
if (WARN_ON(kvm_sve_max_vl > VL_ARCH_MAX))
kvm_sve_max_vl = VL_ARCH_MAX;
+ kvm_nvhe_sym(kvm_sve_max_vl) = kvm_sve_max_vl;
+
/*
* Don't even try to make use of vector lengths that
* aren't available on all CPUs, for now:
--
2.47.3