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

