From: David Woodhouse <[email protected]>

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: it shares the common CPUID, and
kvm_arch_vcpu_create() syncs it through kvm_vcpu_after_set_cpuid(),
so that walk cannot cover it and the twin hits the same WARN with an
empty cpuid_cache. Allocate the context in kvm_vcpu_after_set_cpuid()
when the CPUID has the Hyper-V signature and the vCPU doesn't have
one yet. The allocation is idempotent, so the kvm_set_cpuid() path is
unaffected; if it fails, treat Hyper-V as disabled for the vCPU
exactly as a failing kvm_set_cpuid() would have.

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.

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: LLM
---
 arch/x86/kvm/cpuid.c | 18 +++++++++++++++---
 arch/x86/kvm/cpuid.h |  1 +
 arch/x86/kvm/x86.c   | 20 +++++++++++++++++++-
 3 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index fab075bb6fdc..fe8a793b1643 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -324,7 +324,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;
@@ -380,6 +380,7 @@ void kvm_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu)
        struct kvm_cpuid_entry2 *best;
        struct kvm_cpuid_entry2 *entry;
        bool allow_gbpages;
+       bool hyperv_enabled;
        int i;
 
        memset(common->arch.cpu_caps, 0, sizeof(common->arch.cpu_caps));
@@ -456,7 +457,18 @@ void kvm_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu)
                                         __cr4_reserved_bits(guest_cpu_cap_has, 
vcpu);
 #undef __kvm_cpu_cap_has
 
-       kvm_hv_set_cpuid(vcpu, kvm_cpuid_has_hyperv(vcpu));
+       hyperv_enabled = kvm_cpuid_has_hyperv(vcpu);
+
+       /*
+        * A plane twin created after KVM_SET_CPUID shares the common CPUID
+        * without passing through kvm_set_cpuid(), which is what allocates
+        * the Hyper-V context for the planes that exist at set time;
+        * allocate it here for a late-created twin.
+        */
+       if (hyperv_enabled && kvm_hv_vcpu_init(vcpu))
+               hyperv_enabled = false;
+
+       kvm_hv_set_cpuid(vcpu, hyperv_enabled);
 
        /* Invoke the vendor callback only after the above state is updated. */
        kvm_x86_call(vcpu_after_set_cpuid)(vcpu);
@@ -565,7 +577,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 75abf447eabf..873377404899 100644
--- a/arch/x86/kvm/cpuid.h
+++ b/arch/x86/kvm/cpuid.h
@@ -19,6 +19,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/x86.c b/arch/x86/kvm/x86.c
index 2c41c53e4d4f..0828889db149 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -6286,6 +6286,7 @@ static int kvm_get_reg_list(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:
@@ -13135,9 +13136,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);
@@ -13148,6 +13164,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


Reply via email to