[PATCH 05/16] KVM: x86: Initialise Hyper-V state on every plane vCPU
From: David Woodhouse
Date: Wed Nov 11 2026 - 17:34:53 EST
From: David Woodhouse <dwmw@xxxxxxxxxxxx>
When userspace sets a Hyper-V CPUID, kvm_set_cpuid() walks the vCPU's
planes to allocate the per-plane kvm_vcpu_hv, but passes the plane-0
vCPU to kvm_hv_vcpu_init() on every iteration. Non-zero plane vCPUs
are left without hv_vcpu, which trips the
WARN_ON_ONCE(vcpu->arch.hyperv_enabled) in kvm_hv_set_cpuid() and
leaves their vp_index and SynIC state uninitialised until a lazy init
path happens to run. Pass the plane vCPU being iterated.
A twin created *after* KVM_SET_CPUID never passes through
kvm_set_cpuid() at all, but shares a CPUID that already advertises
Hyper-V. Allocate its context in kvm_arch_vcpu_create(), so that
-ENOMEM fails KVM_CREATE_VCPU on the plane fd rather than leaving one
twin silently without Hyper-V.
That allocation runs before the twin's vcpu->vcpu_idx is assigned, so
kvm_hv_vcpu_init() takes the default VP index from the common, which
already holds it. With vcpu->vcpu_idx the twin would latch 0xFFFFFFFF,
and a host write of the real index to HV_X64_MSR_VP_INDEX would then
decrement num_mismatched_vp_indexes for a mismatch that was never
counted. (Assigning vcpu->vcpu_idx earlier instead would defeat the
"vcpu_idx < 0 means unreachable" test that to_hv_vcpu()'s lockdep
assertion falls back on during creation.)
Also admit KVM_ENABLE_CAP on plane vCPU fds, so userspace can enable
per-vCPU capabilities such as KVM_CAP_HYPERV_SYNIC2 on a plane vCPU
before it first runs.
Fixes the Hyper-V initialisation of plane vCPUs in "kvm: x86: Update
state for all plane VCPUs after CPUID update" and "KVM: Implement
KVM_CREATE_VCPU ioctl for planes".
Fixes: https://lore.kernel.org/all/20260608144252.351443-46-joro@xxxxxxxxxx/
Fixes: https://lore.kernel.org/all/20260608144252.351443-34-joro@xxxxxxxxxx/
Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
---
arch/x86/kvm/cpuid.c | 4 ++--
arch/x86/kvm/cpuid.h | 1 +
arch/x86/kvm/hyperv.c | 7 ++++++-
arch/x86/kvm/x86.c | 20 +++++++++++++++++++-
4 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index 7a32f658238a..54bcfedc5226 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -326,7 +326,7 @@ static void kvm_update_cpuid_runtime(struct kvm_vcpu *vcpu)
vcpu->arch.ia32_xss, true);
}
-static bool kvm_cpuid_has_hyperv(struct kvm_vcpu *vcpu)
+bool kvm_cpuid_has_hyperv(struct kvm_vcpu *vcpu)
{
#ifdef CONFIG_KVM_HYPERV
struct kvm_cpuid_entry2 *entry;
@@ -553,7 +553,7 @@ static int kvm_set_cpuid(struct kvm_vcpu *vcpu, struct kvm_cpuid_entry2 *e2,
#ifdef CONFIG_KVM_HYPERV
if (kvm_cpuid_has_hyperv(vcpu)) {
vcpu_for_each_plane(common, i, v) {
- r = kvm_hv_vcpu_init(vcpu);
+ r = kvm_hv_vcpu_init(v);
if (r)
goto err;
}
diff --git a/arch/x86/kvm/cpuid.h b/arch/x86/kvm/cpuid.h
index 2abfab4b1c33..02dbd02c8f80 100644
--- a/arch/x86/kvm/cpuid.h
+++ b/arch/x86/kvm/cpuid.h
@@ -21,6 +21,7 @@ static inline void kvm_finalize_cpu_caps(void)
}
void kvm_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu);
+bool kvm_cpuid_has_hyperv(struct kvm_vcpu *vcpu);
struct kvm_cpuid_entry2 *kvm_find_cpuid_entry2(struct kvm_cpuid_entry2 *entries,
int nent, u32 function, u64 index);
/*
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 1855b7e13e82..0b7440d09d49 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -997,7 +997,12 @@ int kvm_hv_vcpu_init(struct kvm_vcpu *vcpu)
for (i = 0; i < ARRAY_SIZE(hv_vcpu->stimer); i++)
stimer_init(&hv_vcpu->stimer[i], i);
- hv_vcpu->vp_index = vcpu->vcpu_idx;
+ /*
+ * A plane twin is initialised here from kvm_arch_vcpu_create(),
+ * before vcpu->vcpu_idx is assigned; the index it is about to
+ * get is already in the shared common.
+ */
+ hv_vcpu->vp_index = vcpu->common->vcpu_idx;
for (i = 0; i < HV_NR_TLB_FLUSH_FIFOS; i++) {
INIT_KFIFO(hv_vcpu->tlb_flush_fifo[i].entries);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 2e38eaa0b7df..25f574bfbb32 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3467,6 +3467,7 @@ static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu,
bool kvm_arch_is_vcpu_plane_ioctl(unsigned ioctl)
{
switch (ioctl) {
+ case KVM_ENABLE_CAP:
case KVM_GET_DEBUGREGS:
case KVM_SET_DEBUGREGS:
case KVM_GET_LAPIC:
@@ -9635,9 +9636,24 @@ 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 advertise Hyper-V. Allocate the Hyper-V context now so
+ * a failure fails the creation; left to kvm_vcpu_after_set_cpuid()
+ * below, an -ENOMEM silently downgrades just this twin to no
+ * Hyper-V (its MSRs fail) while the shared CPUID still advertises
+ * the Hyper-V signature, and nothing short of another full
+ * KVM_SET_CPUID repairs it.
+ */
+ if (kvm_cpuid_has_hyperv(vcpu)) {
+ r = kvm_hv_vcpu_init(vcpu);
+ if (r)
+ goto free_guest_fpu;
+ }
+
r = kvm_x86_call(vcpu_create)(vcpu);
if (r)
- goto free_guest_fpu;
+ goto free_hv;
kvm_xen_init_vcpu(vcpu);
vcpu_load(vcpu);
@@ -9648,6 +9664,8 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
vcpu_put(vcpu);
return 0;
+free_hv:
+ kvm_hv_vcpu_uninit(vcpu);
free_guest_fpu:
fpu_free_guest_fpstate(&vcpu->arch.guest_fpu);
free_emulate_ctxt:
--
2.50.1