* [PATCH 0/4] KVM: arm64: Support SVE in protected VMs
@ 2026-11-05 13:04 Fuad Tabba
2026-11-05 13:04 ` [PATCH 1/4] KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync Fuad Tabba
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-11-05 13:04 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Hi folks,
This series adds SVE support for protected VMs. EL2 allocates a
protected vCPU's SVE state from its own heap rather than pinning the
host's buffer, so the host never sees the guest's vector registers. It
then advertises ID_AA64ZFR0_EL1 to protected VMs, and routes the SVE
trap to the lazy FP switch. The first patch is a small tidy-up of the
pKVM FP sync code that saves a guest's SVE state.
v2 of the pKVM selftests series [1] will add a selftest for this.
Based on Linux 7.4-rc1 (6628e7d4c6b25).
Cheers,
/fuad
[1] https://lore.kernel.org/all/20261104084842.132944-1-fuad.tabba@linux.dev/
Fuad Tabba (4):
KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync
KVM: arm64: Allocate a protected vCPU's SVE state at EL2
KVM: arm64: Advertise ID_AA64ZFR0_EL1 to protected VMs
KVM: arm64: Allow protected VMs to use SVE
Documentation/virt/kvm/arm/pkvm.rst | 6 ++---
arch/arm64/include/asm/kvm_hyp.h | 2 ++
arch/arm64/include/asm/kvm_pkvm.h | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 4 +--
arch/arm64/kvm/hyp/nvhe/pkvm.c | 41 +++++++++++++++++++----------
arch/arm64/kvm/hyp/nvhe/switch.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 22 ++++++++++++++--
arch/arm64/kvm/reset.c | 2 ++
9 files changed, 59 insertions(+), 21 deletions(-)
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/4] KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync
2026-11-05 13:04 [PATCH 0/4] KVM: arm64: Support SVE in protected VMs Fuad Tabba
@ 2026-11-05 13:04 ` Fuad Tabba
2026-11-05 13:04 ` [PATCH 2/4] KVM: arm64: Allocate a protected vCPU's SVE state at EL2 Fuad Tabba
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-11-05 13:04 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
With pKVM, EL2 saves a guest's FP and SVE state on exit into the hyp
vCPU, whose kvm is the hyp VM's and whose sve_state is already a hyp
address. kern_hyp_va() returns both unchanged.
Remove the two calls, in fpsimd_sve_sync() and __hyp_sve_save_guest().
The one in __hyp_sve_restore_guest() stays, as nVHE without pKVM reaches
it with the host's pointer.
No functional change intended.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 179d7c8277b88..ea762788e325e 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -482,7 +482,7 @@ static void __hyp_sve_save_guest(struct kvm_vcpu *vcpu)
* 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);
+ sve_save_state(vcpu->arch.sve_state, true);
fpsimd_save_common(&vcpu->arch.ctxt.fp_regs);
write_sysreg_s(sve_vq_from_vl(kvm_host_sve_max_vl) - 1, SYS_ZCR_EL2);
}
@@ -531,7 +531,7 @@ static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
else
fpsimd_save_state(&vcpu->arch.ctxt.fp_regs);
- has_fpmr = kvm_has_fpmr(kern_hyp_va(vcpu->kvm));
+ has_fpmr = kvm_has_fpmr(vcpu->kvm);
if (has_fpmr)
__vcpu_assign_sys_reg(vcpu, FPMR, read_sysreg_s(SYS_FPMR));
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/4] KVM: arm64: Allocate a protected vCPU's SVE state at EL2
2026-11-05 13:04 [PATCH 0/4] KVM: arm64: Support SVE in protected VMs Fuad Tabba
2026-11-05 13:04 ` [PATCH 1/4] KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync Fuad Tabba
@ 2026-11-05 13:04 ` Fuad Tabba
2026-11-05 13:04 ` [PATCH 3/4] KVM: arm64: Advertise ID_AA64ZFR0_EL1 to protected VMs Fuad Tabba
2026-11-05 13:04 ` [PATCH 4/4] KVM: arm64: Allow protected VMs to use SVE Fuad Tabba
3 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-11-05 13:04 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
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@linux.dev>
---
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
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 3/4] KVM: arm64: Advertise ID_AA64ZFR0_EL1 to protected VMs
2026-11-05 13:04 [PATCH 0/4] KVM: arm64: Support SVE in protected VMs Fuad Tabba
2026-11-05 13:04 ` [PATCH 1/4] KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync Fuad Tabba
2026-11-05 13:04 ` [PATCH 2/4] KVM: arm64: Allocate a protected vCPU's SVE state at EL2 Fuad Tabba
@ 2026-11-05 13:04 ` Fuad Tabba
2026-11-05 13:04 ` [PATCH 4/4] KVM: arm64: Allow protected VMs to use SVE Fuad Tabba
3 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-11-05 13:04 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
EL2 builds a protected guest's ID registers from the host's sanitised
values, most of them capped per field. It has no case for
ID_AA64ZFR0_EL1, so the register reads as 0, and a protected guest with
SVE wouldn't see any of the SVE extensions the CPUs implement.
Keep a copy of the host's sanitised ID_AA64ZFR0_EL1 at EL2, and
advertise it when the guest has SVE, as KVM does for a non-protected
guest. Cap each field at the highest value the sysreg file defines.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 19 +++++++++++++++++++
3 files changed, 21 insertions(+)
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 8cf42f87526dd..b9455ff8d7bdb 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -144,6 +144,7 @@ void __noreturn __host_enter(struct kvm_cpu_context *host_ctxt);
extern u64 kvm_nvhe_sym(id_aa64pfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64pfr1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64pfr2_el1_sys_val);
+extern u64 kvm_nvhe_sym(id_aa64zfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar2_el1_sys_val);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 157bf4b812020..be5127ce388a8 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2827,6 +2827,7 @@ static void kvm_hyp_init_symbols(void)
kvm_nvhe_sym(id_aa64pfr0_el1_sys_val) = get_hyp_id_aa64pfr0_el1();
kvm_nvhe_sym(id_aa64pfr1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1);
kvm_nvhe_sym(id_aa64pfr2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR2_EL1);
+ kvm_nvhe_sym(id_aa64zfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ZFR0_EL1);
kvm_nvhe_sym(id_aa64isar0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
kvm_nvhe_sym(id_aa64isar1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR1_EL1);
kvm_nvhe_sym(id_aa64isar2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR2_EL1);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 06ea91415ad8b..e6bbc897cd7b1 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -21,6 +21,7 @@
u64 id_aa64pfr0_el1_sys_val;
u64 id_aa64pfr1_el1_sys_val;
u64 id_aa64pfr2_el1_sys_val;
+u64 id_aa64zfr0_el1_sys_val;
u64 id_aa64isar0_el1_sys_val;
u64 id_aa64isar1_el1_sys_val;
u64 id_aa64isar2_el1_sys_val;
@@ -114,6 +115,22 @@ static const struct pvm_ftr_bits pvmid_aa64pfr2[] = {
FEAT_END
};
+static const struct pvm_ftr_bits pvmid_aa64zfr0[] = {
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, SVEver, SVE2p3, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, AES, AES2, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, EltPerm, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, BitPerm, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, BF16, EBF16, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, B16B16, B16MM, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, SHA3, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, SM4, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, I8MM, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, F16MM, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, F32MM, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, F64MM, IMP, vm_has_sve),
+ FEAT_END
+};
+
static const struct pvm_ftr_bits pvmid_aa64mmfr0[] = {
MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, PARANGE, 40),
MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, ASIDBITS, 16),
@@ -232,6 +249,8 @@ static u64 pvm_calc_id_reg(const struct kvm_vcpu *vcpu, u32 id)
return get_restricted_features(vcpu, id_aa64pfr1_el1_sys_val, pvmid_aa64pfr1);
case SYS_ID_AA64PFR2_EL1:
return get_restricted_features(vcpu, id_aa64pfr2_el1_sys_val, pvmid_aa64pfr2);
+ case SYS_ID_AA64ZFR0_EL1:
+ return get_restricted_features(vcpu, id_aa64zfr0_el1_sys_val, pvmid_aa64zfr0);
case SYS_ID_AA64ISAR0_EL1:
return id_aa64isar0_el1_sys_val;
case SYS_ID_AA64ISAR1_EL1:
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 4/4] KVM: arm64: Allow protected VMs to use SVE
2026-11-05 13:04 [PATCH 0/4] KVM: arm64: Support SVE in protected VMs Fuad Tabba
` (2 preceding siblings ...)
2026-11-05 13:04 ` [PATCH 3/4] KVM: arm64: Advertise ID_AA64ZFR0_EL1 to protected VMs Fuad Tabba
@ 2026-11-05 13:04 ` Fuad Tabba
3 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-11-05 13:04 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Now that EL2 owns a protected vCPU's SVE state and advertises
ID_AA64ZFR0_EL1, allow KVM_CAP_ARM_SVE for a protected VM, which lets
KVM_ARM_VCPU_INIT take KVM_ARM_VCPU_SVE.
A protected guest's SVE access trap injects an UNDEF. An SVE
instruction takes the SVE trap rather than the FP one, so a guest whose
first vector instruction is an SVE one would get an UNDEF instead of
the lazy FP switch. Handle the SVE trap as the FP trap is:
kvm_handle_pvm_fpsimd() switches the state for a vCPU with SVE and
still injects the UNDEF for one without.
Update the documentation to match.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
Documentation/virt/kvm/arm/pkvm.rst | 6 +++---
arch/arm64/include/asm/kvm_pkvm.h | 1 +
arch/arm64/kvm/hyp/nvhe/switch.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 2 --
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Documentation/virt/kvm/arm/pkvm.rst b/Documentation/virt/kvm/arm/pkvm.rst
index b75bb8ad6f26d..163aa01ac5067 100644
--- a/Documentation/virt/kvm/arm/pkvm.rst
+++ b/Documentation/virt/kvm/arm/pkvm.rst
@@ -134,9 +134,9 @@ A protected VM boots from a single primary vCPU. Before the first
* Set the primary vCPU's boot state with ``KVM_SET_ONE_REG``: the kernel
entry address in ``PC`` and the DTB pointer in ``x0``.
-``PC`` and ``x0`` are the only registers EL2 takes from the host. Other
-pre-run writes are accepted, but the guest starts from the state the
-hypervisor resets it to.
+``PC``, ``x0`` and the SVE vector lengths (``KVM_REG_ARM64_SVE_VLS``) are
+the only registers EL2 takes from the host. Other pre-run writes are
+accepted, but the guest starts from the state the hypervisor resets it to.
Secondary vCPUs are started by the guest itself through PSCI ``CPU_ON``
(see `Power state`_), which supplies their entry point and context ID.
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index fd042d2b54624..5f1b3308b42e1 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -40,6 +40,7 @@ static inline bool kvm_pkvm_ext_allowed(struct kvm *kvm, long ext)
case KVM_CAP_MAX_VCPU_ID:
case KVM_CAP_MSI_DEVID:
case KVM_CAP_ARM_VM_IPA_SIZE:
+ case KVM_CAP_ARM_SVE:
case KVM_CAP_ARM_PTRAUTH_ADDRESS:
case KVM_CAP_ARM_PTRAUTH_GENERIC:
case KVM_CAP_ONE_REG:
diff --git a/arch/arm64/kvm/hyp/nvhe/switch.c b/arch/arm64/kvm/hyp/nvhe/switch.c
index 89fb9dd351ad5..0626f4203ba6d 100644
--- a/arch/arm64/kvm/hyp/nvhe/switch.c
+++ b/arch/arm64/kvm/hyp/nvhe/switch.c
@@ -230,6 +230,7 @@ static const exit_handler_fn pvm_exit_handlers[] = {
[ESR_ELx_EC_WFx] = kvm_handle_pvm_wfx,
[ESR_ELx_EC_HVC64] = kvm_handle_pvm_hvc64,
[ESR_ELx_EC_SYS64] = kvm_handle_pvm_sys64,
+ [ESR_ELx_EC_SVE] = kvm_handle_pvm_fpsimd,
[ESR_ELx_EC_FP_ASIMD] = kvm_handle_pvm_fpsimd,
[ESR_ELx_EC_IABT_LOW] = kvm_hyp_handle_iabt_low,
[ESR_ELx_EC_DABT_LOW] = kvm_hyp_handle_dabt_low,
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index e6bbc897cd7b1..0ddddf957f5ab 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -502,8 +502,6 @@ static const struct sys_reg_desc pvm_sys_reg_descs[] = {
ID_UNALLOCATED(7,6),
ID_UNALLOCATED(7,7),
- /* Scalable Vector Registers are restricted. */
-
HOST_HANDLED(SYS_ICC_PMR_EL1),
RAZ_WI(SYS_ERRIDR_EL1),
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-05 13:05 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 13:04 [PATCH 0/4] KVM: arm64: Support SVE in protected VMs Fuad Tabba
2026-11-05 13:04 ` [PATCH 1/4] KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync Fuad Tabba
2026-11-05 13:04 ` [PATCH 2/4] KVM: arm64: Allocate a protected vCPU's SVE state at EL2 Fuad Tabba
2026-11-05 13:04 ` [PATCH 3/4] KVM: arm64: Advertise ID_AA64ZFR0_EL1 to protected VMs Fuad Tabba
2026-11-05 13:04 ` [PATCH 4/4] KVM: arm64: Allow protected VMs to use SVE Fuad Tabba
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®