[RFC PATCH 34/55] KVM: planes: Share XCR0 across plane twins
From: David Woodhouse
Date: Sat Oct 17 2026 - 08:30:13 EST
From: David Woodhouse <dwmw@xxxxxxxxxxxx>
XCR0/XFEM is shared between VTLs: the TLFS banks no vector state and
the reference host keeps a single XCR0 (its store_vtl/load_vtl carry
no XSAVE state). The planes series shares the FPU payload between
twins but keeps arch.xcr0 per-vCPU, so an XSETBV in one VTL was
invisible in the other: VTL1 read a stale XFEM, and state enabled by
VTL0 (AVX, for the Windows secure kernel) silently vanished across a
VTL switch.
Propagate the validated value to every plane twin in __kvm_set_xcr(),
covering both guest XSETBV and the KVM_SET_XCRS ioctl path. The CPUID
dynamic-bits dirty marker lives in the shared common state, so the
existing single set covers the twins.
Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
Assisted-by: LLM
---
arch/x86/kvm/x86.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 491a0360c403..0f8a149ba0eb 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -1361,6 +1361,28 @@ int __kvm_set_xcr(struct kvm_vcpu *vcpu, u32 index, u64 xcr)
vcpu->arch.xcr0 = xcr0;
+ /*
+ * XCR0/XFEM is shared across VTLs (TLFS 15.4; the reference host
+ * banks no vector state): propagate the new value to every plane
+ * twin so the other VTLs observe it. This covers both guest
+ * XSETBV and the KVM_SET_XCRS ioctl path. The CPUID dirty marker
+ * below lives in the shared common state, so one set covers the
+ * twins too.
+ *
+ * Skip the fan-out until this vCPU is published in
+ * common->vcpus[]: the architectural reset of a twin still being
+ * created must not clobber a live sibling's XCR0 (the new twin
+ * inherits the shared value instead, in kvm_arch_vcpu_create()).
+ */
+ if (vcpu->common &&
+ vcpu->common->vcpus[vcpu->plane_level] == vcpu) {
+ struct kvm_vcpu *twin;
+ int i;
+
+ vcpu_for_each_plane(vcpu->common, i, twin)
+ twin->arch.xcr0 = xcr0;
+ }
+
if ((xcr0 ^ old_xcr0) & XFEATURE_MASK_EXTEND)
cpuid_set_dirty(vcpu);
return 0;
@@ -13549,6 +13571,14 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
kvm_vcpu_after_set_cpuid(vcpu);
kvm_set_tsc_khz(vcpu, vcpu->kvm->arch.default_tsc_khz);
kvm_vcpu_reset(vcpu, false);
+ /*
+ * XCR0 is shared across planes. A twin created after its plane-0
+ * sibling has run (Hyper-V VSM enables VTL1 at runtime) inherits
+ * the current shared value; the architectural reset value only
+ * stands when no sibling exists yet.
+ */
+ if (vcpu->plane_level && vcpu->common->vcpus[0])
+ vcpu->arch.xcr0 = vcpu->common->vcpus[0]->arch.xcr0;
kvm_init_mmu(vcpu);
vcpu_put(vcpu);
return 0;
--
2.50.1