[RFC PATCH 08/13] KVM: planes: Enlarge every twin's fpstate for dynamic xfeatures

From: David Woodhouse

Date: Fri Nov 13 2026 - 14:13:28 EST


From: David Woodhouse <dwmw@xxxxxxxxxxxx>

Plane twins share CPUID, so a twin's guest can set the XCR0 bits for a
dynamic xfeature such as AMX's XTILEDATA with its own XSETBV. But
kvm_check_cpuid() calls fpu_enable_guest_xfd_features() only for the
vCPU that KVM_SET_CPUID was issued on, and a twin created afterwards
starts with the default fpstate. A twin that then runs AMX code loses
its tile data on every fpstate save, because the fpstate it saves into
has no room for it. On a host that exposes AMX to the guest, any twin
using AMX hits this.

Whether the XRSTORS on the next entry can also leave a previous
context's tile registers in place for that twin to read is an open
question; it has not been verified.

Enlarge the fpstate of every twin in kvm_check_cpuid(), and give a twin
created after KVM_SET_CPUID the dynamic xfeatures of its plane-0
sibling's fpstate.

Only a twin that has not run may have its fpstate enlarged:
fpstate_realloc() hands a guest fpstate that is not loaded a fresh
image and copies nothing, so a twin that has run would lose its XSAVE
state. kvm_set_cpuid() already accepts only identical CPUID once the
vCPU has run, but it checked only the vCPU the ioctl was issued on.
With plane 0 held while a higher plane runs, KVM_SET_CPUID2 on plane 0
would pass that check and resize the plane that ran. Since the twins
share CPUID, take the identical-CPUID path once any twin has run.

Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
---
arch/x86/kvm/cpuid.c | 21 +++++++++++++++++++--
arch/x86/kvm/x86.c | 17 +++++++++++++++++
2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index c5fa0551ddd5..971ebd781c6e 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -141,7 +141,9 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_find_cpuid_entry2);
static int kvm_check_cpuid(struct kvm_vcpu *vcpu)
{
struct kvm_cpuid_entry2 *best;
+ struct kvm_vcpu *v;
u64 xfeatures;
+ int i, r;

/*
* The existing code assumes virtual address is 48-bit or 57-bit in the
@@ -168,7 +170,18 @@ static int kvm_check_cpuid(struct kvm_vcpu *vcpu)
if (!xfeatures)
return 0;

- return fpu_enable_guest_xfd_features(&vcpu->arch.guest_fpu, xfeatures);
+ /*
+ * The planes share CPUID, so any twin's guest can enable these
+ * xfeatures in its XCR0: enlarge every twin's fpstate, not just
+ * this vCPU's.
+ */
+ vcpu_for_each_plane(vcpu->common, i, v) {
+ r = fpu_enable_guest_xfd_features(&v->arch.guest_fpu, xfeatures);
+ if (r)
+ return r;
+ }
+
+ return 0;
}

static u32 kvm_apply_cpuid_pv_features_quirk(struct kvm_vcpu *vcpu);
@@ -545,8 +558,12 @@ static int kvm_set_cpuid(struct kvm_vcpu *vcpu, struct kvm_cpuid_entry2 *e2,
* VMMs (e.g. QEMU) reuse vCPU fds for CPU hotplug/unplug and do
* KVM_SET_CPUID{,2} again. To support this legacy behavior, check
* whether the supplied CPUID data is equal to what's already set.
+ * Plane twins share CPUID, so this applies once any twin has run.
*/
- if (!kvm_can_set_cpuid_and_feature_msrs(vcpu)) {
+ vcpu_for_each_plane(common, i, v) {
+ if (kvm_can_set_cpuid_and_feature_msrs(v))
+ continue;
+
r = kvm_cpuid_check_equal(vcpu, e2, nent);
if (r)
goto err;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 6d3d93e85a65..af28e73e4b9b 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -9771,6 +9771,23 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
vcpu->arch.hv_root_tdp = INVALID_PAGE;
#endif

+ /*
+ * A plane twin created after KVM_SET_CPUID shares CPUID that may
+ * already allow dynamic xfeatures, which KVM_SET_CPUID enabled only
+ * in the twins that existed at the time. Give it the dynamic
+ * xfeatures of its plane-0 sibling's fpstate.
+ */
+ if (vcpu->plane_level && vcpu->common->vcpus[0]) {
+ u64 dyn = vcpu->common->vcpus[0]->arch.guest_fpu.fpstate->xfeatures &
+ XFEATURE_MASK_USER_DYNAMIC;
+
+ if (dyn) {
+ r = fpu_enable_guest_xfd_features(&vcpu->arch.guest_fpu, dyn);
+ if (r)
+ goto free_guest_fpu;
+ }
+ }
+
/*
* A plane twin created after KVM_SET_CPUID shares CPUID that may
* already advertise Hyper-V. Allocate the Hyper-V context now so
--
2.50.1