Message ID | 20221102231911.3107438-39-seanjc@google.com |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:6687:0:0:0:0:0 with SMTP id l7csp201719wru; Wed, 2 Nov 2022 16:27:12 -0700 (PDT) X-Google-Smtp-Source: AMsMyM6d2Wxny3a1DlIGamCgVbOL0ot8n9VgQXmUtM+FnDxr4v4Qpwi8/bs2ZVCI9HjNc+dGDSVM X-Received: by 2002:a05:6402:1a48:b0:461:900a:7f0d with SMTP id bf8-20020a0564021a4800b00461900a7f0dmr27336417edb.125.1667431632716; Wed, 02 Nov 2022 16:27:12 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1667431632; cv=none; d=google.com; s=arc-20160816; b=tsZczeDpSKKtZ68WBoZHPE+3Rj0PPZgtdS1G4/ekU7Ftj6x44LCfGFG60r0ZQZU81U BLvuQSDCMr1sX0/HNTXffv5SI7OQ4Vm1LTyz4JGHzfI0P6c4RIzXhXsi1ToGPTrJGDpT XDwlUTPinXYNpStxIMPA8olLfTxnjF+GQZ3tAcwDo4a61kiOQbomIIJn/scZAJFpHjnr TkgFUOsjkoVb3+JfhD4k52fnxkLkMDKOvPNBpLi+LWQKO6u0fo9wtZ8BCAecdoa/2pCr rWK5a3RfHjcPPfqcDw7zUUmzvs27HKBVoNXAn0U4o7DUpidBGE77SBfD2qR39B9H8UqI +fcQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:from:subject:message-id:references :mime-version:in-reply-to:date:reply-to:dkim-signature; bh=hGyIy/c9x7gid81AWLPwZ0ljJlv+xEg20udWYHo0hbA=; b=SrIP6FMqIV/SvxFFXNUqbn8NP+y8hxNJkj1oaXef5bvAFkMtK1o7L4j1AtWXFx8gRr OMJD2rV9MKfzLk2EWmyGsVUPAZW2MrS3ICqOlNQDcBKv45A98oUY5c2T2aALC5yd1Ay+ nPYWqzracWVVFBWZzir44ArIHXdIMiNrNBIL0xS/HI6zC8NELEAR4Nm3uhNyDeNH6u7f 6+A5iMdCE6eED8cqvoIyLcvPDC6sNIO/8fnrPwtyHnys7TKS5ViY1U+hzLtQLeL2kbRs yWqdOlP1wY7Uwlxz60FWSFIGKaygCPCT2aNTtDSbz7KMOR+Qk4Kp/gCMhrHf4akOLPFB fyDw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20210112 header.b=QqNc2gbe; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id hv9-20020a17090760c900b007919388d2d1si18060796ejc.970.2022.11.02.16.26.49; Wed, 02 Nov 2022 16:27:12 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20210112 header.b=QqNc2gbe; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229992AbiKBXZy (ORCPT <rfc822;yves.mi.zy@gmail.com> + 99 others); Wed, 2 Nov 2022 19:25:54 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33916 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229770AbiKBXYn (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Wed, 2 Nov 2022 19:24:43 -0400 Received: from mail-pl1-x649.google.com (mail-pl1-x649.google.com [IPv6:2607:f8b0:4864:20::649]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9F17E11A0A for <linux-kernel@vger.kernel.org>; Wed, 2 Nov 2022 16:20:34 -0700 (PDT) Received: by mail-pl1-x649.google.com with SMTP id q10-20020a170902f34a00b00186c5448b01so240702ple.4 for <linux-kernel@vger.kernel.org>; Wed, 02 Nov 2022 16:20:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:reply-to:from:to:cc:subject:date:message-id:reply-to; bh=hGyIy/c9x7gid81AWLPwZ0ljJlv+xEg20udWYHo0hbA=; b=QqNc2gbel1jInPNqFUgamY9IlriDQkvmhLgs/+APWLQZ26DgF8ww5iLxK5OkUpbYy3 HBY25YJUg8bQglW+8pK0XdmoWu5kp0V1X30MP4hvDgUq/lXrr/IjN5eYBpw2Cpmbjanr TqynEnzd9faNEz99LeseMmE1IXDN+BI4xwg7V9Sy2Mz5r4CleSP/B+U7AVSrFavYJ1Fj pLJTmPuFLhW67P7oNt/wLRQeJzFX3YhDs7A5R02nLIxeyDaFpeIL7acpvdeYKRt0I/5K OaH2yRJI3oUK/4Z+nQ16STa2tY+sAGIiXMgaJn/xhTNa56oRdIbBfXfqmK+HDZY/Eb8W RwKw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:reply-to:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=hGyIy/c9x7gid81AWLPwZ0ljJlv+xEg20udWYHo0hbA=; b=GC06KhITSHfB/u/cC+P5ICr9jPc/EOO8Q5WaD5+8ar1TXdqLzMiFCflJDMLvrRPmXv mTMAJpKT9soWYSyVjiIx5G1DN4LJI3EYjlRWO/DsHFlhvMx09RdIOCne5I4JGiubHF6u 8QCzF2YXxXcNC5RFQWfCr7Z8aJnWgEfEreA3Riua0bBcvB3Gg8msDO4om2EppGxKXdz/ IdyMtvOkHbGhLeABqDSfP5qmxMGBe8YHYKkdvRxnt3jkFVYsdU0rSgfmeQZWf4nsFsu0 2gbGjTPEWv4/Qokz18lDrC/FeYm522UcUG8IVi97cuou0dI8tF+e/KU1oDLLWFeGYuhw tLng== X-Gm-Message-State: ACrzQf3Dy/3swRBGwnfS9LbSQW2g8dvkgPfzb4Mx0aC2cN1MFKH9cJIe haQCdaqOnZk2I69DGvu4KW+03duCXXE= X-Received: from zagreus.c.googlers.com ([fda3:e722:ac3:cc00:7f:e700:c0a8:5c37]) (user=seanjc job=sendgmr) by 2002:a17:902:9308:b0:182:b2ba:755 with SMTP id bc8-20020a170902930800b00182b2ba0755mr27515720plb.107.1667431219005; Wed, 02 Nov 2022 16:20:19 -0700 (PDT) Reply-To: Sean Christopherson <seanjc@google.com> Date: Wed, 2 Nov 2022 23:19:05 +0000 In-Reply-To: <20221102231911.3107438-1-seanjc@google.com> Mime-Version: 1.0 References: <20221102231911.3107438-1-seanjc@google.com> X-Mailer: git-send-email 2.38.1.431.g37b22c650d-goog Message-ID: <20221102231911.3107438-39-seanjc@google.com> Subject: [PATCH 38/44] KVM: Disable CPU hotplug during hardware enabling From: Sean Christopherson <seanjc@google.com> To: Paolo Bonzini <pbonzini@redhat.com>, Marc Zyngier <maz@kernel.org>, Huacai Chen <chenhuacai@kernel.org>, Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>, Anup Patel <anup@brainfault.org>, Paul Walmsley <paul.walmsley@sifive.com>, Palmer Dabbelt <palmer@dabbelt.com>, Albert Ou <aou@eecs.berkeley.edu>, Christian Borntraeger <borntraeger@linux.ibm.com>, Janosch Frank <frankja@linux.ibm.com>, Claudio Imbrenda <imbrenda@linux.ibm.com>, Matthew Rosato <mjrosato@linux.ibm.com>, Eric Farman <farman@linux.ibm.com>, Sean Christopherson <seanjc@google.com>, Vitaly Kuznetsov <vkuznets@redhat.com> Cc: James Morse <james.morse@arm.com>, Alexandru Elisei <alexandru.elisei@arm.com>, Suzuki K Poulose <suzuki.poulose@arm.com>, Oliver Upton <oliver.upton@linux.dev>, Atish Patra <atishp@atishpatra.org>, David Hildenbrand <david@redhat.com>, kvm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, kvmarm@lists.cs.columbia.edu, linux-mips@vger.kernel.org, linuxppc-dev@lists.ozlabs.org, kvm-riscv@lists.infradead.org, linux-riscv@lists.infradead.org, linux-s390@vger.kernel.org, linux-kernel@vger.kernel.org, Isaku Yamahata <isaku.yamahata@intel.com>, Fabiano Rosas <farosas@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Chao Gao <chao.gao@intel.com>, Thomas Gleixner <tglx@linutronix.de>, Yuan Yao <yuan.yao@intel.com> Content-Type: text/plain; charset="UTF-8" X-Spam-Status: No, score=-9.6 required=5.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_NONE, SPF_HELO_NONE,SPF_PASS,USER_IN_DEF_DKIM_WL autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1748428791385425054?= X-GMAIL-MSGID: =?utf-8?q?1748428791385425054?= |
Series |
KVM: Rework kvm_init() and hardware enabling
|
|
Commit Message
Sean Christopherson
Nov. 2, 2022, 11:19 p.m. UTC
From: Chao Gao <chao.gao@intel.com> Disable CPU hotplug during hardware_enable_all() to prevent the corner case where if the following sequence occurs: 1. A hotplugged CPU marks itself online in cpu_online_mask 2. The hotplugged CPU enables interrupt before invoking KVM's ONLINE callback 3 hardware_enable_all() is invoked on another CPU right the hotplugged CPU will be included in on_each_cpu() and thus get sent through hardware_enable_nolock() before kvm_online_cpu() is called. start_secondary { ... set_cpu_online(smp_processor_id(), true); <- 1 ... local_irq_enable(); <- 2 ... cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); <- 3 } KVM currently fudges around this race by keeping track of which CPUs have done hardware enabling (see commit 1b6c016818a5 "KVM: Keep track of which cpus have virtualization enabled"), but that's an inefficient, convoluted, and hacky solution. Signed-off-by: Chao Gao <chao.gao@intel.com> [sean: split to separate patch, write changelog] Signed-off-by: Sean Christopherson <seanjc@google.com> --- arch/x86/kvm/x86.c | 8 +++++++- virt/kvm/kvm_main.c | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-)
Comments
On Wed, 2022-11-02 at 23:19 +0000, Sean Christopherson wrote: > From: Chao Gao <chao.gao@intel.com> > > Disable CPU hotplug during hardware_enable_all() to prevent the corner > case where if the following sequence occurs: > > 1. A hotplugged CPU marks itself online in cpu_online_mask > 2. The hotplugged CPU enables interrupt before invoking KVM's ONLINE > callback > 3 hardware_enable_all() is invoked on another CPU right > > the hotplugged CPU will be included in on_each_cpu() and thus get sent > through hardware_enable_nolock() before kvm_online_cpu() is called. > > start_secondary { ... > set_cpu_online(smp_processor_id(), true); <- 1 > ... > local_irq_enable(); <- 2 > ... > cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); <- 3 > } > > KVM currently fudges around this race by keeping track of which CPUs have > done hardware enabling (see commit 1b6c016818a5 "KVM: Keep track of which > cpus have virtualization enabled"), but that's an inefficient, convoluted, > and hacky solution. > > Signed-off-by: Chao Gao <chao.gao@intel.com> > [sean: split to separate patch, write changelog] > Signed-off-by: Sean Christopherson <seanjc@google.com> > --- > arch/x86/kvm/x86.c | 8 +++++++- > virt/kvm/kvm_main.c | 10 ++++++++++ > 2 files changed, 17 insertions(+), 1 deletion(-) > > diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c > index a7b1d916ecb2..a15e54ba0471 100644 > --- a/arch/x86/kvm/x86.c > +++ b/arch/x86/kvm/x86.c > @@ -9283,7 +9283,13 @@ static int kvm_x86_check_processor_compatibility(struct kvm_x86_init_ops *ops) > int cpu = smp_processor_id(); > struct cpuinfo_x86 *c = &cpu_data(cpu); > > - WARN_ON(!irqs_disabled()); > + /* > + * Compatibility checks are done when loading KVM and when enabling > + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > + * compatible, i.e. KVM should never perform a compatibility check on > + * an offline CPU. > + */ > + WARN_ON(!irqs_disabled() && cpu_active(cpu)); Comment doesn't match with the code? "KVM should never perform a compatibility check on on offline CPU" should be something like: WARN_ON(!cpu_online(cpu)); So, should the comment be something like below? "KVM compatibility check happens before CPU is marked as active". > > if (__cr4_reserved_bits(cpu_has, c) != > __cr4_reserved_bits(cpu_has, &boot_cpu_data)) > diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c > index fd9e39c85549..4e765ef9f4bd 100644 > --- a/virt/kvm/kvm_main.c > +++ b/virt/kvm/kvm_main.c > @@ -5088,6 +5088,15 @@ static int hardware_enable_all(void) > { > int r = 0; > > + /* > + * When onlining a CPU, cpu_online_mask is set before kvm_online_cpu() > + * is called, and so on_each_cpu() between them includes the CPU that > + * is being onlined. As a result, hardware_enable_nolock() may get > + * invoked before kvm_online_cpu(). > + * > + * Disable CPU hotplug to prevent scenarios where KVM sees > + */ The above sentence is broken. I think below comment Quoted from Isaku's series should be OK? /* * During onlining a CPU, cpu_online_mask is set before kvm_online_cpu() * is called. on_each_cpu() between them includes the CPU. As a result, * hardware_enable_nolock() may get invoked before kvm_online_cpu(). * This would enable hardware virtualization on that cpu without * compatibility checks, which can potentially crash system or break * running VMs. * * Disable CPU hotplug to prevent this case from happening. */ > + cpus_read_lock(); > raw_spin_lock(&kvm_count_lock); > > kvm_usage_count++; > @@ -5102,6 +5111,7 @@ static int hardware_enable_all(void) > } > > raw_spin_unlock(&kvm_count_lock); > + cpus_read_unlock(); > > return r; > }
On Wed, 2022-11-02 at 23:19 +0000, Sean Christopherson wrote: > From: Chao Gao <chao.gao@intel.com> > > Disable CPU hotplug during hardware_enable_all() to prevent the corner > case where if the following sequence occurs: > > 1. A hotplugged CPU marks itself online in cpu_online_mask > 2. The hotplugged CPU enables interrupt before invoking KVM's ONLINE > callback > 3 hardware_enable_all() is invoked on another CPU right > > the hotplugged CPU will be included in on_each_cpu() and thus get sent > through hardware_enable_nolock() before kvm_online_cpu() is called. > > start_secondary { ... > set_cpu_online(smp_processor_id(), true); <- 1 > ... > local_irq_enable(); <- 2 > ... > cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); <- 3 > } > > KVM currently fudges around this race by keeping track of which CPUs have > done hardware enabling (see commit 1b6c016818a5 "KVM: Keep track of which > cpus have virtualization enabled"), but that's an inefficient, convoluted, > and hacky solution. > > Signed-off-by: Chao Gao <chao.gao@intel.com> > [sean: split to separate patch, write changelog] > Signed-off-by: Sean Christopherson <seanjc@google.com> > --- > arch/x86/kvm/x86.c | 8 +++++++- > virt/kvm/kvm_main.c | 10 ++++++++++ > 2 files changed, 17 insertions(+), 1 deletion(-) > > diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c > index a7b1d916ecb2..a15e54ba0471 100644 > --- a/arch/x86/kvm/x86.c > +++ b/arch/x86/kvm/x86.c > @@ -9283,7 +9283,13 @@ static int kvm_x86_check_processor_compatibility(struct kvm_x86_init_ops *ops) > int cpu = smp_processor_id(); > struct cpuinfo_x86 *c = &cpu_data(cpu); > > - WARN_ON(!irqs_disabled()); > + /* > + * Compatibility checks are done when loading KVM and when enabling > + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > + * compatible, i.e. KVM should never perform a compatibility check on > + * an offline CPU. > + */ > + WARN_ON(!irqs_disabled() && cpu_active(cpu)); > Also, the logic of: !irqs_disabled() && cpu_active(cpu) is quite weird. The original "WARN(!irqs_disabled())" is reasonable because in STARTING section the IRQ is indeed disabled. But this doesn't make sense anymore after we move to ONLINE section, in which IRQ has already been enabled (see start_secondary()). IIUC the WARN_ON() doesn't get exploded is purely because there's an additional cpu_active(cpu) check. So, a more reasonable check should be something like: WARN_ON(irqs_disabled() || cpu_active(cpu) || !cpu_online(cpu)); Or we can simply do: WARN_ON(!cpu_online(cpu) || cpu_active(cpu)); (because I don't know whether it's possible IRQ can somehow get disabled in ONLINE section). Btw above is purely based on code analysis, but I haven't done any test.
On Thu, 2022-11-10 at 01:33 +0000, Huang, Kai wrote: > > @@ -9283,7 +9283,13 @@ static int > > kvm_x86_check_processor_compatibility(struct kvm_x86_init_ops *ops) > > int cpu = smp_processor_id(); > > struct cpuinfo_x86 *c = &cpu_data(cpu); > > > > - WARN_ON(!irqs_disabled()); > > + /* > > + * Compatibility checks are done when loading KVM and when enabling > > + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > > + * compatible, i.e. KVM should never perform a compatibility check > > on > > + * an offline CPU. > > + */ > > + WARN_ON(!irqs_disabled() && cpu_active(cpu)); > > > > Also, the logic of: > > !irqs_disabled() && cpu_active(cpu) > > is quite weird. > > The original "WARN(!irqs_disabled())" is reasonable because in STARTING > section > the IRQ is indeed disabled. > > But this doesn't make sense anymore after we move to ONLINE section, in which > IRQ has already been enabled (see start_secondary()). IIUC the WARN_ON() > doesn't get exploded is purely because there's an additional cpu_active(cpu) > check. > > So, a more reasonable check should be something like: > > WARN_ON(irqs_disabled() || cpu_active(cpu) || !cpu_online(cpu)); > > Or we can simply do: > > WARN_ON(!cpu_online(cpu) || cpu_active(cpu)); > > (because I don't know whether it's possible IRQ can somehow get disabled in > ONLINE section). > > Btw above is purely based on code analysis, but I haven't done any test. Hmm.. I wasn't thinking thoroughly. I forgot CPU compatibility check also happens on all online cpus when loading KVM. For this case, IRQ is disabled and cpu_active() is true. For the hotplug case, IRQ is enabled but cpu_active() is false. So WARN_ON(!irqs_disabled() && cpu_active(cpu)) looks reasonable. Sorry for the noise. Just needed some time to connect the comment with the code.
On Thu, 2022-11-10 at 01:08 +0000, Huang, Kai wrote: > > - WARN_ON(!irqs_disabled()); > > + /* > > + * Compatibility checks are done when loading KVM and when enabling > > + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > > + * compatible, i.e. KVM should never perform a compatibility check > > on > > + * an offline CPU. > > + */ > > + WARN_ON(!irqs_disabled() && cpu_active(cpu)); > > Comment doesn't match with the code? > > "KVM should never perform a compatibility check on on offline CPU" should be > something like: > > WARN_ON(!cpu_online(cpu)); > > So, should the comment be something like below? > > "KVM compatibility check happens before CPU is marked as active". Also ignore this one as I only thought about hotplug case.
On Thu, Nov 10, 2022, Huang, Kai wrote: > On Thu, 2022-11-10 at 01:33 +0000, Huang, Kai wrote: > > > @@ -9283,7 +9283,13 @@ static int > > > kvm_x86_check_processor_compatibility(struct kvm_x86_init_ops *ops) > > > int cpu = smp_processor_id(); > > > struct cpuinfo_x86 *c = &cpu_data(cpu); > > > > > > - WARN_ON(!irqs_disabled()); > > > + /* > > > + * Compatibility checks are done when loading KVM and when enabling > > > + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > > > + * compatible, i.e. KVM should never perform a compatibility check > > > on > > > + * an offline CPU. > > > + */ > > > + WARN_ON(!irqs_disabled() && cpu_active(cpu)); > > > > > > > Also, the logic of: > > > > !irqs_disabled() && cpu_active(cpu) > > > > is quite weird. > > > > The original "WARN(!irqs_disabled())" is reasonable because in STARTING > > section > > the IRQ is indeed disabled. > > > > But this doesn't make sense anymore after we move to ONLINE section, in which > > IRQ has already been enabled (see start_secondary()). IIUC the WARN_ON() > > doesn't get exploded is purely because there's an additional cpu_active(cpu) > > check. > > > > So, a more reasonable check should be something like: > > > > WARN_ON(irqs_disabled() || cpu_active(cpu) || !cpu_online(cpu)); > > > > Or we can simply do: > > > > WARN_ON(!cpu_online(cpu) || cpu_active(cpu)); > > > > (because I don't know whether it's possible IRQ can somehow get disabled in > > ONLINE section). > > > > Btw above is purely based on code analysis, but I haven't done any test. > > Hmm.. I wasn't thinking thoroughly. I forgot CPU compatibility check also > happens on all online cpus when loading KVM. For this case, IRQ is disabled and > cpu_active() is true. For the hotplug case, IRQ is enabled but cpu_active() is > false. > > So WARN_ON(!irqs_disabled() && cpu_active(cpu)) looks reasonable. Sorry for the > noise. Just needed some time to connect the comment with the code. No worries, more than once while working through this code, I've considered setting up one of those evidence boards from the movies with string and push pins to help connect all the dots.
On Thu, Nov 10, 2022, Huang, Kai wrote: > On Thu, 2022-11-10 at 01:33 +0000, Huang, Kai wrote: > > > @@ -9283,7 +9283,13 @@ static int > > > kvm_x86_check_processor_compatibility(struct kvm_x86_init_ops *ops) > > > int cpu = smp_processor_id(); > > > struct cpuinfo_x86 *c = &cpu_data(cpu); > > > > > > - WARN_ON(!irqs_disabled()); > > > + /* > > > + * Compatibility checks are done when loading KVM and when enabling > > > + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > > > + * compatible, i.e. KVM should never perform a compatibility check > > > on > > > + * an offline CPU. > > > + */ > > > + WARN_ON(!irqs_disabled() && cpu_active(cpu)); > > > > > > > Also, the logic of: > > > > !irqs_disabled() && cpu_active(cpu) > > > > is quite weird. > > > > The original "WARN(!irqs_disabled())" is reasonable because in STARTING > > section > > the IRQ is indeed disabled. > > > > But this doesn't make sense anymore after we move to ONLINE section, in which > > IRQ has already been enabled (see start_secondary()). IIUC the WARN_ON() > > doesn't get exploded is purely because there's an additional cpu_active(cpu) > > check. > > > > So, a more reasonable check should be something like: > > > > WARN_ON(irqs_disabled() || cpu_active(cpu) || !cpu_online(cpu)); > > > > Or we can simply do: > > > > WARN_ON(!cpu_online(cpu) || cpu_active(cpu)); > > > > (because I don't know whether it's possible IRQ can somehow get disabled in > > ONLINE section). > > > > Btw above is purely based on code analysis, but I haven't done any test. > > Hmm.. I wasn't thinking thoroughly. I forgot CPU compatibility check also > happens on all online cpus when loading KVM. For this case, IRQ is disabled and > cpu_active() is true. For the hotplug case, IRQ is enabled but cpu_active() is > false. Actually, you're right (and wrong). You're right in that the WARN is flawed. And the reason for that is because you're wrong about the hotplug case. In this version of things, the compatibility checks are routed through hardware enabling, i.e. this flow is used only when loading KVM. This helper should only be called via SMP function call, which means that IRQs should always be disabled.
On Tue, Nov 15, 2022, Sean Christopherson wrote: > On Thu, Nov 10, 2022, Huang, Kai wrote: > > On Thu, 2022-11-10 at 01:33 +0000, Huang, Kai wrote: > > > > @@ -9283,7 +9283,13 @@ static int > > > > kvm_x86_check_processor_compatibility(struct kvm_x86_init_ops *ops) > > > > int cpu = smp_processor_id(); > > > > struct cpuinfo_x86 *c = &cpu_data(cpu); > > > > > > > > - WARN_ON(!irqs_disabled()); > > > > + /* > > > > + * Compatibility checks are done when loading KVM and when enabling > > > > + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > > > > + * compatible, i.e. KVM should never perform a compatibility check > > > > on > > > > + * an offline CPU. > > > > + */ > > > > + WARN_ON(!irqs_disabled() && cpu_active(cpu)); > > > > > > > > > > Also, the logic of: > > > > > > !irqs_disabled() && cpu_active(cpu) > > > > > > is quite weird. > > > > > > The original "WARN(!irqs_disabled())" is reasonable because in STARTING > > > section > > > the IRQ is indeed disabled. > > > > > > But this doesn't make sense anymore after we move to ONLINE section, in which > > > IRQ has already been enabled (see start_secondary()). IIUC the WARN_ON() > > > doesn't get exploded is purely because there's an additional cpu_active(cpu) > > > check. > > > > > > So, a more reasonable check should be something like: > > > > > > WARN_ON(irqs_disabled() || cpu_active(cpu) || !cpu_online(cpu)); > > > > > > Or we can simply do: > > > > > > WARN_ON(!cpu_online(cpu) || cpu_active(cpu)); > > > > > > (because I don't know whether it's possible IRQ can somehow get disabled in > > > ONLINE section). > > > > > > Btw above is purely based on code analysis, but I haven't done any test. > > > > Hmm.. I wasn't thinking thoroughly. I forgot CPU compatibility check also > > happens on all online cpus when loading KVM. For this case, IRQ is disabled and > > cpu_active() is true. For the hotplug case, IRQ is enabled but cpu_active() is > > false. > > Actually, you're right (and wrong). You're right in that the WARN is flawed. And > the reason for that is because you're wrong about the hotplug case. In this version > of things, the compatibility checks are routed through hardware enabling, i.e. this > flow is used only when loading KVM. This helper should only be called via SMP function > call, which means that IRQs should always be disabled. Grr, but not routing through this helper is flawed in that KVM doesn't do the CR4 checks in the hardware enabling case. Don't think that changes the WARN, but other patches in this series need tweaks.
On Tue, 2022-11-15 at 20:16 +0000, Sean Christopherson wrote: > On Thu, Nov 10, 2022, Huang, Kai wrote: > > On Thu, 2022-11-10 at 01:33 +0000, Huang, Kai wrote: > > > > @@ -9283,7 +9283,13 @@ static int > > > > kvm_x86_check_processor_compatibility(struct kvm_x86_init_ops *ops) > > > > int cpu = smp_processor_id(); > > > > struct cpuinfo_x86 *c = &cpu_data(cpu); > > > > > > > > - WARN_ON(!irqs_disabled()); > > > > + /* > > > > + * Compatibility checks are done when loading KVM and when enabling > > > > + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > > > > + * compatible, i.e. KVM should never perform a compatibility check > > > > on > > > > + * an offline CPU. > > > > + */ > > > > + WARN_ON(!irqs_disabled() && cpu_active(cpu)); > > > > > > > > > > Also, the logic of: > > > > > > !irqs_disabled() && cpu_active(cpu) > > > > > > is quite weird. > > > > > > The original "WARN(!irqs_disabled())" is reasonable because in STARTING > > > section > > > the IRQ is indeed disabled. > > > > > > But this doesn't make sense anymore after we move to ONLINE section, in which > > > IRQ has already been enabled (see start_secondary()). IIUC the WARN_ON() > > > doesn't get exploded is purely because there's an additional cpu_active(cpu) > > > check. > > > > > > So, a more reasonable check should be something like: > > > > > > WARN_ON(irqs_disabled() || cpu_active(cpu) || !cpu_online(cpu)); > > > > > > Or we can simply do: > > > > > > WARN_ON(!cpu_online(cpu) || cpu_active(cpu)); > > > > > > (because I don't know whether it's possible IRQ can somehow get disabled in > > > ONLINE section). > > > > > > Btw above is purely based on code analysis, but I haven't done any test. > > > > Hmm.. I wasn't thinking thoroughly. I forgot CPU compatibility check also > > happens on all online cpus when loading KVM. For this case, IRQ is disabled and > > cpu_active() is true. For the hotplug case, IRQ is enabled but cpu_active() is > > false. > > Actually, you're right (and wrong). You're right in that the WARN is flawed. And > the reason for that is because you're wrong about the hotplug case. In this version > of things, the compatibility checks are routed through hardware enabling, i.e. this > flow is used only when loading KVM. This helper should only be called via SMP function > call, which means that IRQs should always be disabled. Did you mean below code change in later patch "[PATCH 39/44] KVM: Drop kvm_count_lock and instead protect kvm_usage_count with kvm_lock"? /* * Abort the CPU online process if hardware virtualization cannot * be enabled. Otherwise running VMs would encounter unrecoverable @@ -5039,13 +5039,16 @@ static int kvm_online_cpu(unsigned int cpu) if (kvm_usage_count) { WARN_ON_ONCE(atomic_read(&hardware_enable_failed)); + local_irq_save(flags); hardware_enable_nolock(NULL); + local_irq_restore(flags); +
On Wed, Nov 16, 2022, Huang, Kai wrote: > On Tue, 2022-11-15 at 20:16 +0000, Sean Christopherson wrote: > > On Thu, Nov 10, 2022, Huang, Kai wrote: > > > On Thu, 2022-11-10 at 01:33 +0000, Huang, Kai wrote: > > > Hmm.. I wasn't thinking thoroughly. I forgot CPU compatibility check also > > > happens on all online cpus when loading KVM. For this case, IRQ is disabled and > > > cpu_active() is true. For the hotplug case, IRQ is enabled but cpu_active() is > > > false. > > > > Actually, you're right (and wrong). You're right in that the WARN is flawed. And > > the reason for that is because you're wrong about the hotplug case. In this version > > of things, the compatibility checks are routed through hardware enabling, i.e. this > > flow is used only when loading KVM. This helper should only be called via SMP function > > call, which means that IRQs should always be disabled. > > Did you mean below code change in later patch "[PATCH 39/44] KVM: Drop > kvm_count_lock and instead protect kvm_usage_count with kvm_lock"? > > /* > * Abort the CPU online process if hardware virtualization cannot > * be enabled. Otherwise running VMs would encounter unrecoverable > @@ -5039,13 +5039,16 @@ static int kvm_online_cpu(unsigned int cpu) > if (kvm_usage_count) { > WARN_ON_ONCE(atomic_read(&hardware_enable_failed)); > > + local_irq_save(flags); > hardware_enable_nolock(NULL); > + local_irq_restore(flags); Sort of. What I was saying is that in this v1, the compatibility checks that are done during harware enabling are initiated from vendor code, i.e. VMX and SVM call {svm,vmx}_check_processor_compat() directly. As a result, the compat checks that are handled in common code: if (__cr4_reserved_bits(cpu_has, c) != __cr4_reserved_bits(cpu_has, &boot_cpu_data)) return -EIO; are skipped. And if that's fixed, then the above hardware_enable_nolock() call will bounce through kvm_x86_check_processor_compatibility() with IRQs enabled once the KVM hotplug hook is moved to the ONLINE section. As above, the simple "fix" would be to disable IRQs, but that's not actually necessary. The only requirement is that preemption is disabled so that the checks are done on the current CPU. The "IRQs disabled" check was a deliberately agressive WARN that was added to guard against doing compatibility checks from the "wrong" location. E.g. this is what I ended up with for a changelog to drop the irqs_disabled() check and for the end code (though it's not tested yet...) Drop kvm_x86_check_processor_compatibility()'s WARN that IRQs are disabled, as the ONLINE section runs with IRQs disabled. The WARN wasn't intended to be a requirement, e.g. disabling preemption is sufficient, the IRQ thing was purely an aggressive sanity check since the helper was only ever invoked via SMP function call. static int kvm_x86_check_processor_compatibility(void) { int cpu = smp_processor_id(); struct cpuinfo_x86 *c = &cpu_data(cpu); /* * Compatibility checks are done when loading KVM and when enabling * hardware, e.g. during CPU hotplug, to ensure all online CPUs are * compatible, i.e. KVM should never perform a compatibility check on * an offline CPU. */ WARN_ON(!cpu_online(cpu)); if (__cr4_reserved_bits(cpu_has, c) != __cr4_reserved_bits(cpu_has, &boot_cpu_data)) return -EIO; return static_call(kvm_x86_check_processor_compatibility)(); } int kvm_arch_hardware_enable(void) { struct kvm *kvm; struct kvm_vcpu *vcpu; unsigned long i; int ret; u64 local_tsc; u64 max_tsc = 0; bool stable, backwards_tsc = false; kvm_user_return_msr_cpu_online(); ret = kvm_x86_check_processor_compatibility(); if (ret) return ret; ret = static_call(kvm_x86_hardware_enable)(); if (ret != 0) return ret; .... }
On Wed, 2022-11-16 at 17:11 +0000, Sean Christopherson wrote: > On Wed, Nov 16, 2022, Huang, Kai wrote: > > On Tue, 2022-11-15 at 20:16 +0000, Sean Christopherson wrote: > > > On Thu, Nov 10, 2022, Huang, Kai wrote: > > > > On Thu, 2022-11-10 at 01:33 +0000, Huang, Kai wrote: > > > > Hmm.. I wasn't thinking thoroughly. I forgot CPU compatibility check also > > > > happens on all online cpus when loading KVM. For this case, IRQ is disabled and > > > > cpu_active() is true. For the hotplug case, IRQ is enabled but cpu_active() is > > > > false. > > > > > > Actually, you're right (and wrong). You're right in that the WARN is flawed. And > > > the reason for that is because you're wrong about the hotplug case. In this version > > > of things, the compatibility checks are routed through hardware enabling, i.e. this > > > flow is used only when loading KVM. This helper should only be called via SMP function > > > call, which means that IRQs should always be disabled. > > > > Did you mean below code change in later patch "[PATCH 39/44] KVM: Drop > > kvm_count_lock and instead protect kvm_usage_count with kvm_lock"? > > > > /* > > * Abort the CPU online process if hardware virtualization cannot > > * be enabled. Otherwise running VMs would encounter unrecoverable > > @@ -5039,13 +5039,16 @@ static int kvm_online_cpu(unsigned int cpu) > > if (kvm_usage_count) { > > WARN_ON_ONCE(atomic_read(&hardware_enable_failed)); > > > > + local_irq_save(flags); > > hardware_enable_nolock(NULL); > > + local_irq_restore(flags); > > Sort of. What I was saying is that in this v1, the compatibility checks that are > done during harware enabling are initiated from vendor code, i.e. VMX and SVM call > {svm,vmx}_check_processor_compat() directly. As a result, the compat checks that > are handled in common code: > > if (__cr4_reserved_bits(cpu_has, c) != > __cr4_reserved_bits(cpu_has, &boot_cpu_data)) > return -EIO; > > are skipped. And if that's fixed, then the above hardware_enable_nolock() call > will bounce through kvm_x86_check_processor_compatibility() with IRQs enabled > once the KVM hotplug hook is moved to the ONLINE section. Oh I see. So you still want the kvm_x86_ops->check_processor_compatibility(), in order to avoid duplicating the above code in SVM and VMX. > > As above, the simple "fix" would be to disable IRQs, but that's not actually > necessary. The only requirement is that preemption is disabled so that the checks > are done on the current CPU. > Probably even preemption is allowed, as long as the compatibility check is not scheduled to another cpu. > The "IRQs disabled" check was a deliberately > agressive WARN that was added to guard against doing compatibility checks from > the "wrong" location. > > E.g. this is what I ended up with for a changelog to drop the irqs_disabled() > check and for the end code (though it's not tested yet...) > > Drop kvm_x86_check_processor_compatibility()'s WARN that IRQs are > disabled, as the ONLINE section runs with IRQs disabled. The WARN wasn't ^ enabled. > intended to be a requirement, e.g. disabling preemption is sufficient, > the IRQ thing was purely an aggressive sanity check since the helper was > only ever invoked via SMP function call. > > > static int kvm_x86_check_processor_compatibility(void) > { > int cpu = smp_processor_id(); > struct cpuinfo_x86 *c = &cpu_data(cpu); > > /* > * Compatibility checks are done when loading KVM and when enabling > * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > * compatible, i.e. KVM should never perform a compatibility check on > * an offline CPU. > */ > WARN_ON(!cpu_online(cpu)); Looks good to me. Perhaps this also can be removed, though. And IMHO the removing of WARN_ON(!irq_disabled()) should be folded to the patch "[PATCH 37/44] KVM: Rename and move CPUHP_AP_KVM_STARTING to ONLINE section". Because moving from STARTING section to ONLINE section changes the IRQ status when the compatibility check is called. > > if (__cr4_reserved_bits(cpu_has, c) != > __cr4_reserved_bits(cpu_has, &boot_cpu_data)) > return -EIO; > > return static_call(kvm_x86_check_processor_compatibility)(); > } > > > int kvm_arch_hardware_enable(void) > { > struct kvm *kvm; > struct kvm_vcpu *vcpu; > unsigned long i; > int ret; > u64 local_tsc; > u64 max_tsc = 0; > bool stable, backwards_tsc = false; > > kvm_user_return_msr_cpu_online(); > > ret = kvm_x86_check_processor_compatibility(); > if (ret) > return ret; > > ret = static_call(kvm_x86_hardware_enable)(); > if (ret != 0) > return ret; > > > .... > }
On Thu, Nov 17, 2022, Huang, Kai wrote: > On Wed, 2022-11-16 at 17:11 +0000, Sean Christopherson wrote: > > static int kvm_x86_check_processor_compatibility(void) > > { > > int cpu = smp_processor_id(); > > struct cpuinfo_x86 *c = &cpu_data(cpu); > > > > /* > > * Compatibility checks are done when loading KVM and when enabling > > * hardware, e.g. during CPU hotplug, to ensure all online CPUs are > > * compatible, i.e. KVM should never perform a compatibility check on > > * an offline CPU. > > */ > > WARN_ON(!cpu_online(cpu)); > > Looks good to me. Perhaps this also can be removed, though. Hmm, it's a bit superfluous, but I think it could fire if KVM messed up CPU hotplug again, e.g. if the for_each_online_cpu() => IPI raced with CPU unplug. > And IMHO the removing of WARN_ON(!irq_disabled()) should be folded to the patch > "[PATCH 37/44] KVM: Rename and move CPUHP_AP_KVM_STARTING to ONLINE section". > Because moving from STARTING section to ONLINE section changes the IRQ status > when the compatibility check is called. Yep, that's what I have coded up, just smushed it all together here.
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index a7b1d916ecb2..a15e54ba0471 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -9283,7 +9283,13 @@ static int kvm_x86_check_processor_compatibility(struct kvm_x86_init_ops *ops) int cpu = smp_processor_id(); struct cpuinfo_x86 *c = &cpu_data(cpu); - WARN_ON(!irqs_disabled()); + /* + * Compatibility checks are done when loading KVM and when enabling + * hardware, e.g. during CPU hotplug, to ensure all online CPUs are + * compatible, i.e. KVM should never perform a compatibility check on + * an offline CPU. + */ + WARN_ON(!irqs_disabled() && cpu_active(cpu)); if (__cr4_reserved_bits(cpu_has, c) != __cr4_reserved_bits(cpu_has, &boot_cpu_data)) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index fd9e39c85549..4e765ef9f4bd 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -5088,6 +5088,15 @@ static int hardware_enable_all(void) { int r = 0; + /* + * When onlining a CPU, cpu_online_mask is set before kvm_online_cpu() + * is called, and so on_each_cpu() between them includes the CPU that + * is being onlined. As a result, hardware_enable_nolock() may get + * invoked before kvm_online_cpu(). + * + * Disable CPU hotplug to prevent scenarios where KVM sees + */ + cpus_read_lock(); raw_spin_lock(&kvm_count_lock); kvm_usage_count++; @@ -5102,6 +5111,7 @@ static int hardware_enable_all(void) } raw_spin_unlock(&kvm_count_lock); + cpus_read_unlock(); return r; }