From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.92.199 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427104; b=EOx2JNxO0aBfl2R1+w6BwbrQOTAZ+Y4Bx7k1kaZuMsn4RDQoT68LOvgeKXSQ0tPIq+4/ eCx6NDZK/nAnYBczy8HFBwasjOAh9Qc75npB/kUAFhh7RTfUf4olDlNunTOHs1w5TFdPw aDocCwo7WbS/RcmMjPtZD2T+Jm0nveLNcbDYfzzqVdgwciL7zatwSWfd0g4AAMbrdD30I rUQsySy0we/3iruzCZf7VOcEa/cTtFsaMENCAWQPYu5PhphK8Wi/ar7udZ+PbtgElTkfE lBuZldP9a8m1LY/KJ80B50waDS44LW+7WARyXfSrlPXE3lkvSETuCG02j8RNnamUsKg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427104; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=g6FawsJvxh2CZfs6XHDXBqTm2/HFt6BbeFCm+R7pPk4=; b=2+oMdAhjD2Qfga2fk6hfCtxp5LDZhmuVz0Qae9c9TX2+0Zi/3XrDOmhXQFkm3YRcSl3/ HBBtc4q3njFL3vEoKxaAaW7mDcZ+ch/KAKKOBx8M5tjNVgazkjAoO676IllHyncrO11mb EMhQjRH4FxCw9COYjGMSdyQGtAMSOkVjO0UGOhXqF6fQFj+65HnBevecy3p/PfTV3Gt6r mEXVmnIFOFigVyXK6NWnr9WdSBGkfxMIOpHNkxz7lH1l4RED9ON+OlJ8OzY81zs11CB0O t4gNpjqPFLWMLp62PbsDIM7iHByn2GgIhYTvYxKLR2oAkovroeJYb6cObtEoF3GPjVg== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b="RZqYeH/l"; arc=none smtp.remote-ip=90.155.92.199 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 74AA4606A325; Mon, 19 Oct 2026 16:24:57 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 74AA4606A325 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=desiato.srs.infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b=RZqYeH/l DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=g6FawsJvxh2CZfs6XHDXBqTm2/HFt6BbeFCm+R7pPk4=; b=RZqYeH/lqy4ybTTHcrfti2FGVM 7KXxMCOtEFk29Heoi0F1F9rG9rGds1rFHPuSw3w3BmWOrnDfJTzxY3fhvT45nWtjG7PWnrMuu01VT AlzOMqmVZSjuvVqGyI3SXVUbLdP0dYjMkB3yJVC4g+MLH9V/FrWV24Y23tXnTNqJrBs7zIZZM8kgq DXe9fA8zcZPsDy2OFJIYhNkStCzIfmqZ/Md7fvTrnPUbFLMxUIi/7syQAsXqupgQEhjc40hyyGZJ3 1bSC2ZqOCkC5eUij5PbnZldlo/DWJo5S5rtzS94Pe1AbO1gGRKPuMWD9F3YiZvVpyy7rqw0ssBD0a nfWbx3uQ==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by desiato.infradead.org with esmtpsa (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAK-00000003qsl-1HKC; Mon, 19 Oct 2026 16:24:48 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009gX-0aRY; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 01/12] KVM: x86/xen: Don't block memslot updates while halted in SCHEDOP_poll Date: Mon, 19 Oct 2026 17:24:32 +0100 Message-ID: <20261019162445.37219-2-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by desiato.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse kvm_xen_schedop_poll() halts the vCPU from within vcpu_run's exit handling, without dropping the vCPU's kvm->srcu read section as vcpu_block() is careful to do around every other halt. The poll wakes only on delivery of a polled event channel, its poll timer, a signal, or KVM_REQ_UNBLOCK =E2=80=94 and a memslot update raises none of those. Wit= h a zero timeout and a port which nobody is going to signal, the vCPU sleeps at the guest's pleasure, holding the read section the whole time. A memslot update then hangs, unkillably, in kvm_swap_active_memslots()'s synchronize_srcu() waiting for that reader to quiesce. Any guest with SCHEDOP_poll available can thus park its VMM's memslot ioctls more or less indefinitely. Drop the srcu read section across the halt. A hypercall is only ever processed from vcpu_run's exit handling, so the vCPU's own read lock accounting is always the live section here, and nothing between the unlock and the relock touches memslot-derived state: the pending-event check which precedes it takes its own read section, and the timer cleanup which follows does not need one. Found by Sashiko review while testing the series which lets Xen attribute ioctls wait out memslot updates =E2=80=94 those waits joined the same queue, with rather more visibility. Fixes: 1a65105a5aba ("KVM: x86/xen: handle PV spinlocks slowpath") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: David Woodhouse --- arch/x86/kvm/xen.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index 6aa00f746cba..2a0477ad0c91 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -1594,7 +1594,15 @@ static bool kvm_xen_schedop_poll(struct kvm_vcpu *vc= pu, bool is_64bit, mod_timer(&vcpu->arch.xen.poll_timer, jiffies + nsecs_to_jiffies(sched_poll.timeout)); =20 + /* + * Drop the vCPU's kvm->srcu read section across the halt, as + * vcpu_block() does: the halt is unbounded (with a zero + * timeout, entirely at the guest's pleasure), and a memslot + * update cannot complete until this read section quiesces. + */ + kvm_vcpu_srcu_read_unlock(vcpu); kvm_vcpu_halt(vcpu); + kvm_vcpu_srcu_read_lock(vcpu); =20 if (sched_poll.timeout) timer_delete(&vcpu->arch.xen.poll_timer); --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.50.34 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427097; b=pfbcJXE5F6p7YfOpv6jvXfac9WqBuUYUuY9CigJgIVAcuNaJnDS52NvMy1H0pqDl6IK2 P2kyLOFjhZXyLADEVGrMX8D+SF8Cxw5y9HVOxyZxJaxSBrs9ho/DGlZEaOthzCGRFcmE6 5srySuAaz9FUOTiqYdRgWzS1pq4wZo9FYcDYbmTlAhGUzL/9Fq0T1sF1BwX+8fc9xrtO6 cFpyMX44fQPTo9+tAfe28toYshFAdjqEmcOc1YtEpLTH0RSs9YbxzH5kPy3GeOI9xEDGu PnoVm7zjV+vjLz0WbsBgpSk2htzI1b4h2nXv6fTwH/1hjrht6KK441Mbhef8yZNy9uw== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427097; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=/VD9EG2KiE5OItNAh1xRhZZwJ3NvkM7B3uEI0rjcQOU=; b=juRX4iqGIG0v4vAvEFZiQu6mopm/wD07xfz2g9l6JZWsehlNlg6BpfTCi3NJaj9tP511 XVwD4WruwVmenrxsjjrbqZ/igf+0PYYZBIAcLjaPT/mSqPacwkIqiYmQx5DV6sMM9AMj6 x0b8q4hcTUIyXdc35+QS48dbVux1hS3cpWbpJ0sLj5kX1/WPL7n1XPbQDXsBkFFDeSLwX YsA1fUf4EEB+Vshu9oqXwIKIoL4G4FB+XDPcTPQg7lR6mRPTOSDaqkJWsy7fOUUZUcZN5 lglj2XCw5+nTlwv1xA4lTuU408kDr6CR/dkqTqfiPLIvrRECKyF8Lo9FXuVKQo2RCMQ== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=infradead.org; spf=none smtp.mailfrom=casper.srs.infradead.org; dkim=pass header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=casper.20170209 header.b=WVOW95rb; arc=none smtp.remote-ip=90.155.50.34 Received: from casper.infradead.org (casper.infradead.org [90.155.50.34]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9ED24606A31F; Mon, 19 Oct 2026 16:24:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 9ED24606A31F Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=casper.srs.infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=casper.20170209 header.b=WVOW95rb DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=/VD9EG2KiE5OItNAh1xRhZZwJ3NvkM7B3uEI0rjcQOU=; b=WVOW95rbqjl78HyTifdRtCP1aH NG8MvbDO9XfMXxeGYNRyZSvZUr69XApWv07FW4z+5paWkIqQcu3kY0RwWCYQj3Jojivt5W9Be9p48 Am15D75sC7sdDq1lHo9jFAqN2WLOghZpYzJpsMh/id1rv1Ia2VMGkzJW4CthQBSNsRjio3r92w38/ uU8+DDp2RRgWBbs3No2MhhFQF8ytYpkaDXuO0zL+5SFcxGAW6SDXe4DhcwHB5vfTkjjM7SED3N7gE OypVyFaZL2gAUg+pDIcD5+mk11+h2POhOe015qsDJiHq08HbM4mA1xiUzjLQHUE6VpkSuhYKPyz4b VHS+mhZg==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by casper.infradead.org with esmtpsa (Exim 4.99.1 #2 (Red Hat Linux)) id 1xIqAH-00000009VGL-3PBP; Mon, 19 Oct 2026 16:24:45 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009ga-0tUO; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 02/12] KVM: pfncache: Return -EAGAIN for a lookup which hits an invalid memslot Date: Mon, 19 Oct 2026 17:24:33 +0100 Message-ID: <20261019162445.37219-3-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse A memslot being deleted or moved is present in the active array but flagged KVM_MEMSLOT_INVALID =E2=80=94 a transient state which exists only within a single KVM_SET_USER_MEMORY_REGION call and is announced by a further generation bump when it resolves. gfn_to_hva_memslot() collapses that case and a genuinely unbacked gfn into one error, and the pfncache refresh reported both as -EFAULT. Distinguish them: return -EAGAIN when the slot exists but is invalid, mirroring what RET_PF_RETRY does for ordinary guest faults on an invalid slot. Existing users don't *need* to change; most will simply resolve again at the next refresh. But the distinct error code *allows* a caller to retry. Signed-off-by: David Woodhouse Assisted-by: LLM --- virt/kvm/pfncache.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/virt/kvm/pfncache.c b/virt/kvm/pfncache.c index 3659686b97c2..17aaebd76db8 100644 --- a/virt/kvm/pfncache.c +++ b/virt/kvm/pfncache.c @@ -304,7 +304,22 @@ static int __kvm_gpc_refresh(struct gfn_to_pfn_cache *= gpc, gpa_t gpa, unsigned l gpc->uhva =3D gfn_to_hva_memslot(gpc->memslot, gfn); =20 if (kvm_is_error_hva(gpc->uhva)) { - ret =3D -EFAULT; + /* + * A slot being deleted or moved is present + * but flagged invalid; the lookup failure is + * transient, resolved when the update + * completes (or reverts) and announced by a + * new memslot generation. Distinguish that + * from a genuinely unbacked gfn so that the + * caller may retry rather than report an + * error, as RET_PF_RETRY does for ordinary + * guest faults. + */ + if (gpc->memslot && + (gpc->memslot->flags & KVM_MEMSLOT_INVALID)) + ret =3D -EAGAIN; + else + ret =3D -EFAULT; goto out; } =20 --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.92.199 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427099; b=pm0Q/8Culv11c1xNCBGhU8uiSLRxLoKB74T6Ml6E9yD/auar94V5cerz8DH1ssjdpJsH PKueOil32waK/O8BKMNZ2MaNNrqMZRAJK7th2c7YQu3ntI6f1gG9iIjek1ESc7RDzwhzD 82ioaJRJgLZmsBcWAUwLGbDAMw8i42oz6kxcS+6Z495Nff3EdjgDW0qY0xEQt3wzwO4iC 9DZFIAA2X6MZFxftNreDJJvpwsfrUsSAiJg5uIUdnZF4naBsVx3ZMYjfvwSGb6fSCfszX wDFtW/OAiYRW2hRasM6Eoo3IDqPjJPHs8fooJRrtLI97n46QfuuYrMqT3P8Y6/CM2yw== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427099; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=HBGKRs32jhmzs4ZETDbtWHztbcbFrOPxCWLtzOPlv5w=; b=M89PzbD4H/E1wqk9DfVq+iLZH9CEvkSHpIpRrur1ly8M8d2XaBtMBUHGYPgW1Jm84Azu fygG3rJ0mg5ehcEARlM+C5+vqh90esc/qkGf03L0cJ0bg6tNGP5aSzIJc5G6tDYbUjewn 8a2hqqxeCpyE/dbfzR9BXS03hbDJc+/LchewuKky9isQNB7ky9pS65skcVMHErpC+35ft 6JxjpeRrO7SuQeqVLoqYEaQAsflgCSY+CNv6T6FeNzs4r+kEhjzdLdQ2Zg7T21/bhDE2f MCCqnIJG0GLUQBtFKmHTM6StPHfNrywWzakoqdDqhxrvZcYKNLOYHgrQBHFoUqjsFfg== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b=rLQbblym; arc=none smtp.remote-ip=90.155.92.199 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 76902606A328; Mon, 19 Oct 2026 16:24:57 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 76902606A328 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=desiato.srs.infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b=rLQbblym DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=HBGKRs32jhmzs4ZETDbtWHztbcbFrOPxCWLtzOPlv5w=; b=rLQbblymzvI6kXRTP7niDgbmhI KGPX1ZlBwKyh3mPoP4CFZmiiAzovE0s55pFfojbEQRKmOhBRkMCk3cOO50+8R7R1RR22foAgD71Go KSRnMXdRVvA6mR1xEjlU3xFSPHnlN3n05UDrLfuwRMPEAd+LfaHOTGDq0+ILRhmXGx42RXkZahfx5 W7Y/Y8nVBsTx//AWlhIvbKVPU/9LtK8hS5Jlr+/scN+gasKxjNAIk83GdfOc/At9Mj1k8gfM/N7/x DuQPz38DIFyr9fIR/Km38SP8RYffbYsZ4Rt3A4YTRevk+LO5UqfOArl8/5Hyw9DuPOGeH/aLGwAug zzqSOdqQ==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by desiato.infradead.org with esmtpsa (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAK-00000003qsm-1IaQ; Mon, 19 Oct 2026 16:24:48 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009gd-1DNT; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 03/12] KVM: pfncache: Add a 'wait' argument to the refresh and activate functions Date: Mon, 19 Oct 2026 17:24:34 +0100 Message-ID: <20261019162445.37219-4-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by desiato.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse A caller which does not hold kvm->srcu may now pass wait=3Dtrue to kvm_gpc_refresh() or kvm_gpc_activate() to wait out a concurrent memslot update instead of receiving -EAGAIN, looping with cond_resched() until the lookup resolves or a fatal signal is pending. (An HVA-based cache never consults the memslots, so kvm_gpc_activate_hva() keeps its signature.) The wait is a deliberate poll: a single memslot update bounds it, and a cond_resched() loop is far simpler than wiring up a wake from the update side. If a real workload ever cares, an rcuwait signalled on memslot-update completion =E2=80=94 mirroring mn_memslots_update_rcuwait for the inverse direction =E2=80=94 would be the follow-up. The refresh now takes its own kvm->srcu read lock for each attempt, so callers no longer need to hold it. Holding it with wait=3Dtrue would deadlock =E2=80=94 the wait would block the memslot update from completing = =E2=80=94 as would taking any lock held across the wait within a kvm->srcu read section. The wait therefore functions as a synchronize_srcu() to lockdep, via srcu_lock_sync(), making both cycles reportable. Signed-off-by: David Woodhouse Assisted-by: LLM --- arch/x86/kvm/msrs.c | 2 +- arch/x86/kvm/x86.c | 2 +- arch/x86/kvm/xen.c | 24 ++++---- include/linux/kvm_host.h | 24 ++++++-- virt/kvm/pfncache.c | 119 +++++++++++++++++++++++++++++---------- 5 files changed, 121 insertions(+), 50 deletions(-) diff --git a/arch/x86/kvm/msrs.c b/arch/x86/kvm/msrs.c index dd3bb04878ca..4c0fbba6e7ae 100644 --- a/arch/x86/kvm/msrs.c +++ b/arch/x86/kvm/msrs.c @@ -1280,7 +1280,7 @@ static void kvm_write_system_time(struct kvm_vcpu *vc= pu, gpa_t system_time, /* we verify if the enable bit is set... */ if (system_time & 1) kvm_gpc_activate(&vcpu->arch.pv_time, system_time & ~1ULL, - sizeof(struct pvclock_vcpu_time_info)); + sizeof(struct pvclock_vcpu_time_info), false); else kvm_gpc_deactivate(&vcpu->arch.pv_time); =20 diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 79468ddfe473..eadcaeee651a 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -1727,7 +1727,7 @@ static void kvm_setup_guest_pvclock(struct pvclock_vc= pu_time_info *ref_hv_clock, while (!kvm_gpc_check(gpc, offset + sizeof(*guest_hv_clock))) { read_unlock_irqrestore(&gpc->lock, flags); =20 - if (kvm_gpc_refresh(gpc, offset + sizeof(*guest_hv_clock))) + if (kvm_gpc_refresh(gpc, offset + sizeof(*guest_hv_clock), false)) return; =20 read_lock_irqsave(&gpc->lock, flags); diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index 2a0477ad0c91..ac27b4795d69 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -50,7 +50,7 @@ static int kvm_xen_shared_info_init(struct kvm *kvm) while (!kvm_gpc_check(gpc, PAGE_SIZE)) { read_unlock_irq(&gpc->lock); =20 - ret =3D kvm_gpc_refresh(gpc, PAGE_SIZE); + ret =3D kvm_gpc_refresh(gpc, PAGE_SIZE, false); if (ret) goto out; =20 @@ -161,7 +161,7 @@ static int xen_get_guest_pvclock(struct kvm_vcpu *vcpu, while (!kvm_gpc_check(gpc, offset + sizeof(*hv_clock))) { read_unlock_irqrestore(&gpc->lock, flags); =20 - r =3D kvm_gpc_refresh(gpc, offset + sizeof(*hv_clock)); + r =3D kvm_gpc_refresh(gpc, offset + sizeof(*hv_clock), false); if (r) return r; =20 @@ -435,7 +435,7 @@ static void kvm_xen_update_runstate_guest(struct kvm_vc= pu *v, bool atomic) if (atomic) return; =20 - if (kvm_gpc_refresh(gpc1, user_len1)) + if (kvm_gpc_refresh(gpc1, user_len1, false)) return; =20 read_lock_irqsave(&gpc1->lock, flags); @@ -488,7 +488,7 @@ static void kvm_xen_update_runstate_guest(struct kvm_vc= pu *v, bool atomic) * 64-bit mode, the second GPC won't have been set up. */ if (kvm_gpc_activate(gpc2, gpc1->gpa + user_len1, - user_len2)) + user_len2, false)) return; =20 /* @@ -669,7 +669,7 @@ void kvm_xen_inject_pending_events(struct kvm_vcpu *v) while (!kvm_gpc_check(gpc, sizeof(struct vcpu_info))) { read_unlock_irqrestore(&gpc->lock, flags); =20 - if (kvm_gpc_refresh(gpc, sizeof(struct vcpu_info))) + if (kvm_gpc_refresh(gpc, sizeof(struct vcpu_info), false)) return; =20 read_lock_irqsave(&gpc->lock, flags); @@ -757,7 +757,7 @@ int __kvm_xen_has_interrupt(struct kvm_vcpu *v) if (in_atomic() || !task_is_running(current)) return 1; =20 - if (kvm_gpc_refresh(gpc, sizeof(struct vcpu_info))) { + if (kvm_gpc_refresh(gpc, sizeof(struct vcpu_info), false)) { /* * If this failed, userspace has screwed up the * vcpu_info mapping. No interrupts for you. @@ -813,7 +813,7 @@ int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xe= n_hvm_attr *data) r =3D 0; } else { r =3D kvm_gpc_activate(&kvm->arch.xen.shinfo_cache, - gfn_to_gpa(gfn), PAGE_SIZE); + gfn_to_gpa(gfn), PAGE_SIZE, false); } } else { void __user * hva =3D u64_to_user_ptr(data->u.shared_info.hva); @@ -960,7 +960,7 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, struct= kvm_xen_vcpu_attr *data) break; =20 r =3D kvm_gpc_activate(&vcpu->arch.xen.vcpu_info_cache, - data->u.gpa, sizeof(struct vcpu_info)); + data->u.gpa, sizeof(struct vcpu_info), false); } else { if (data->u.hva =3D=3D 0) { kvm_gpc_deactivate(&vcpu->arch.xen.vcpu_info_cache); @@ -990,7 +990,7 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, struct= kvm_xen_vcpu_attr *data) =20 r =3D kvm_gpc_activate(&vcpu->arch.xen.vcpu_time_info_cache, data->u.gpa, - sizeof(struct pvclock_vcpu_time_info)); + sizeof(struct pvclock_vcpu_time_info), false); if (!r) kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu); break; @@ -1023,7 +1023,7 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, stru= ct kvm_xen_vcpu_attr *data) /* How much fits in the (first) page? */ sz1 =3D PAGE_SIZE - (data->u.gpa & ~PAGE_MASK); r =3D kvm_gpc_activate(&vcpu->arch.xen.runstate_cache, - data->u.gpa, sz1); + data->u.gpa, sz1, false); if (r) goto deactivate_out; =20 @@ -1034,7 +1034,7 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, stru= ct kvm_xen_vcpu_attr *data) sz2 =3D sz - sz1; BUG_ON((data->u.gpa + sz1) & ~PAGE_MASK); r =3D kvm_gpc_activate(&vcpu->arch.xen.runstate2_cache, - data->u.gpa + sz1, sz2); + data->u.gpa + sz1, sz2, false); if (r) goto deactivate_out; } @@ -2019,7 +2019,7 @@ static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *= xe, struct kvm *kvm) break; =20 idx =3D srcu_read_lock(&kvm->srcu); - rc =3D kvm_gpc_refresh(gpc, PAGE_SIZE); + rc =3D kvm_gpc_refresh(gpc, PAGE_SIZE, false); srcu_read_unlock(&kvm->srcu, idx); } while(!rc); =20 diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 3dd04605f2e5..94989a705ee6 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1474,16 +1474,24 @@ void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, str= uct kvm *kvm); * @gpc: struct gfn_to_pfn_cache object. * @gpa: guest physical address to map. * @len: sanity check; the range being access must fit a single page. + * @wait: wait out a concurrent memslot update instead of returning + * -EAGAIN. May not be used while this task holds any kvm->srcu + * read section, and no lock held across the wait may ever be + * taken within one: the update's synchronize_srcu() would + * deadlock against the wait. Lockdep checks both. * * @return: 0 for success. * -EINVAL for a mapping which would cross a page boundary. * -EFAULT for an untranslatable guest physical address. + * -EAGAIN if the memslot is being updated (only with + * wait =3D=3D false, or on a fatal signal while waiting). * * This primes a gfn_to_pfn_cache and links it into the @gpc->kvm's list f= or * invalidations to be processed. Callers are required to use kvm_gpc_che= ck() * to ensure that the cache is valid before accessing the target page. */ -int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned lon= g len); +int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned lon= g len, + bool wait); =20 /** * kvm_gpc_activate_hva - prepare a cached kernel mapping and HPA for a gi= ven HVA. @@ -1496,10 +1504,13 @@ int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, = gpa_t gpa, unsigned long len) * -EINVAL for a mapping which would cross a page boundary. * -EFAULT for an untranslatable guest physical address. * - * The semantics of this function are the same as those of kvm_gpc_activat= e(). It - * merely bypasses a layer of address translation. + * The semantics of this function are the same as those of kvm_gpc_activat= e(), + * except that an HVA-based cache never consults the memslots and so can + * neither return -EAGAIN nor wait: it merely bypasses a layer of address + * translation. */ -int kvm_gpc_activate_hva(struct gfn_to_pfn_cache *gpc, unsigned long hva, = unsigned long len); +int kvm_gpc_activate_hva(struct gfn_to_pfn_cache *gpc, unsigned long hva, + unsigned long len); =20 /** * kvm_gpc_check - check validity of a gfn_to_pfn_cache. @@ -1524,10 +1535,13 @@ bool kvm_gpc_check(struct gfn_to_pfn_cache *gpc, un= signed long len); * * @gpc: struct gfn_to_pfn_cache object. * @len: sanity check; the range being access must fit a single page. + * @wait: as kvm_gpc_activate(). * * @return: 0 for success. * -EINVAL for a mapping which would cross a page boundary. * -EFAULT for an untranslatable guest physical address. + * -EAGAIN if the memslot is being updated (only with + * wait =3D=3D false, or on a fatal signal while waiting). * * This will attempt to refresh a gfn_to_pfn_cache. Note that a successful * return from this function does not mean the page can be immediately @@ -1535,7 +1549,7 @@ bool kvm_gpc_check(struct gfn_to_pfn_cache *gpc, unsi= gned long len); * still lock and check the cache status, as this function does not return * with the lock still held to permit access. */ -int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsigned long len); +int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsigned long len, bool = wait); =20 /** * kvm_gpc_deactivate - deactivate and unlink a gfn_to_pfn_cache. diff --git a/virt/kvm/pfncache.c b/virt/kvm/pfncache.c index 17aaebd76db8..0e700c337f5a 100644 --- a/virt/kvm/pfncache.c +++ b/virt/kvm/pfncache.c @@ -378,23 +378,63 @@ static int __kvm_gpc_refresh(struct gfn_to_pfn_cache = *gpc, gpa_t gpa, unsigned l return ret; } =20 -int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsigned long len) +/* + * One refresh attempt, under the caller's refresh_lock. Takes a kvm->srcu + * read section for the attempt, as the memslot lookups consult the active + * memslots. + */ +static int gpc_refresh_locked(struct gfn_to_pfn_cache *gpc, gpa_t gpa, + unsigned long uhva) { - unsigned long uhva; + int idx, ret; =20 - guard(mutex)(&gpc->refresh_lock); + idx =3D srcu_read_lock(&gpc->kvm->srcu); + ret =3D __kvm_gpc_refresh(gpc, gpa, uhva); + srcu_read_unlock(&gpc->kvm->srcu, idx); =20 - if (!kvm_gpc_is_valid_len(gpc->gpa, gpc->uhva, len)) - return -EINVAL; + return ret; +} + +int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsigned long len, bool = wait) +{ + unsigned long uhva; + int ret; =20 /* - * If the GPA is valid then ignore the HVA, as a cache can be GPA-based - * or HVA-based, not both. For GPA-based caches, the HVA will be - * recomputed during refresh if necessary. + * The wait completes only after a memslot update's + * synchronize_srcu(&kvm->srcu), so it functions as one for lockdep: + * taking any lock held across it within a kvm->srcu read section + * is reported as the deadlock it would be. */ - uhva =3D kvm_is_error_gpa(gpc->gpa) ? gpc->uhva : KVM_HVA_ERR_BAD; + if (wait) + srcu_lock_sync(&gpc->kvm->srcu.dep_map); + + for (;;) { + scoped_guard(mutex, &gpc->refresh_lock) { + if (!kvm_gpc_is_valid_len(gpc->gpa, gpc->uhva, len)) + return -EINVAL; + + /* + * If the GPA is valid then ignore the HVA, as a cache + * can be GPA-based or HVA-based, not both. For + * GPA-based caches, the HVA will be recomputed during + * refresh if necessary. + */ + uhva =3D kvm_is_error_gpa(gpc->gpa) ? gpc->uhva : KVM_HVA_ERR_BAD; + + ret =3D gpc_refresh_locked(gpc, gpc->gpa, uhva); + } + + if (ret !=3D -EAGAIN || !wait || fatal_signal_pending(current)) + return ret; =20 - return __kvm_gpc_refresh(gpc, gpc->gpa, uhva); + /* + * Wait with refresh_lock dropped: a kvm->srcu reader blocked + * on it must be able to proceed and quiesce, or the memslot + * update being waited for could never complete. + */ + cond_resched(); + } } =20 void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm) @@ -410,36 +450,51 @@ void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struc= t kvm *kvm) } =20 static int __kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, uns= igned long uhva, - unsigned long len) + unsigned long len, bool wait) { struct kvm *kvm =3D gpc->kvm; =20 if (!kvm_gpc_is_valid_len(gpa, uhva, len)) return -EINVAL; =20 - guard(mutex)(&gpc->refresh_lock); + /* As in kvm_gpc_refresh(): the wait is a synchronize_srcu() to lockdep. = */ + if (wait) + srcu_lock_sync(&kvm->srcu.dep_map); =20 - if (!gpc->active) { - if (KVM_BUG_ON(gpc->valid, kvm)) - return -EIO; + for (;;) { + int ret; =20 - spin_lock(&kvm->gpc_lock); - list_add(&gpc->list, &kvm->gpc_list); - spin_unlock(&kvm->gpc_lock); + scoped_guard(mutex, &gpc->refresh_lock) { + if (!gpc->active) { + if (KVM_BUG_ON(gpc->valid, kvm)) + return -EIO; =20 - /* - * Activate the cache after adding it to the list, a concurrent - * refresh must not establish a mapping until the cache is - * reachable by mmu_notifier events. - */ - write_lock_irq(&gpc->lock); - gpc->active =3D true; - write_unlock_irq(&gpc->lock); + spin_lock(&kvm->gpc_lock); + list_add(&gpc->list, &kvm->gpc_list); + spin_unlock(&kvm->gpc_lock); + + /* + * Activate the cache after adding it to the + * list, a concurrent refresh must not + * establish a mapping until the cache is + * reachable by mmu_notifier events. + */ + write_lock_irq(&gpc->lock); + gpc->active =3D true; + write_unlock_irq(&gpc->lock); + } + ret =3D gpc_refresh_locked(gpc, gpa, uhva); + } + + if (ret !=3D -EAGAIN || !wait || fatal_signal_pending(current)) + return ret; + + cond_resched(); } - return __kvm_gpc_refresh(gpc, gpa, uhva); } =20 -int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned lon= g len) +int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned lon= g len, + bool wait) { /* * Explicitly disallow INVALID_GPA so that the magic value can be used @@ -448,15 +503,17 @@ int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gp= a_t gpa, unsigned long len) if (WARN_ON_ONCE(kvm_is_error_gpa(gpa))) return -EINVAL; =20 - return __kvm_gpc_activate(gpc, gpa, KVM_HVA_ERR_BAD, len); + return __kvm_gpc_activate(gpc, gpa, KVM_HVA_ERR_BAD, len, wait); } =20 -int kvm_gpc_activate_hva(struct gfn_to_pfn_cache *gpc, unsigned long uhva,= unsigned long len) +int kvm_gpc_activate_hva(struct gfn_to_pfn_cache *gpc, unsigned long uhva, + unsigned long len) { if (!access_ok((void __user *)uhva, len)) return -EINVAL; =20 - return __kvm_gpc_activate(gpc, INVALID_GPA, uhva, len); + /* An HVA-based cache never consults the memslots, so never waits. */ + return __kvm_gpc_activate(gpc, INVALID_GPA, uhva, len, false); } =20 void kvm_gpc_deactivate(struct gfn_to_pfn_cache *gpc) --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.50.34 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427102; b=mrxpi52EIV8Oki/UMUoqBJ71hWACwLMS7+xy4WS6ttIkQSGoTg3QD4kbyfoAim5x4RVF 20Suyx/t+AxxylUlfKNn4fMOo8ZcxVl8P5+bsc3FmHZLs5Lh7qjNnmNDM5NV9ASSRfFF2 +RgOXUKXsfH1G9mbbVMWVhVE+YHRIYck+ynl1b2q9Gvsm1jesrKPnTvFIHG16bd9ghpyO OHqJlx2sqOqCeZQ7nZiRt/ftGwLathXEftGpiZ2YG3DTAsyZWLYc6rTpt4PzlBaEcpD98 hzoQZoc2TiobxiPfgTO5NaH80qNjzm43AXGCi+t32OAuI7QWJo4uTX5bvykn72OMIVg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427102; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=zjqCyCGBCCDyRk9Gb16bTRiDfPQSCM5vy/BOvodCgA8=; b=g07G2lJq77DdGwdPpJT5iMTPSwqbtNXHRa0KFsNvGrDT5s4UiXmmTGZ3656kJT1cmqAV rHzyMJ3bapV1IdmXJqoaz2AqjTy9z/oVyUHX5COsuLzZQelbhIEvM77+0rwA5HYA2gv3D VhTup4rGDyTPROr85FZQ/1l2XS2zas0WqrFddi2cMPzXQXSdzmZJa89EWb/ESVAa9qnCN iJ+S97DrPPxd/JnksR5AsoOA+LLemlQcNZrl36S5Fo18wbym6Ei0LQYWwPO0glsF/SIjf 8V237pE9M338GXuHuODIZ73BoRcp3TKvBXEDkXjX5gnWAsgNuGggyGQ1u5KIFE2pZEw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail header.from=infradead.org; spf=none smtp.mailfrom=casper.srs.infradead.org; arc=none smtp.remote-ip=90.155.50.34 Received: from casper.infradead.org (casper.infradead.org [90.155.50.34]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9EBB1606A319; Mon, 19 Oct 2026 16:24:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 9EBB1606A319 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=casper.srs.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=zjqCyCGBCCDyRk9Gb16bTRiDfPQSCM5vy/BOvodCgA8=; b=bgiYRe53i1XdaYRJpGMT/1rLt4 K7o5Hp/pIU5bwxi/JqXQp0cTo27PXz891gjFPh8cNv5L+idxUF/Ux9Ke8BsQtCp/R3iN+lxvx1Zef UxiOjoRZwRAmKceSJ1v6e8s7BGJlRT3ifF4PQLoePpJBPy1XkVZwtoSrMd8UYY1yyJOdTfwIHGq+i xO/OqwvZeveuYDVSYHHKeoW3IJ8Cif9wKB1j2u4wmwS87fWd9ahVVNmzNiPFu0HVTLC2aAeAyqFb5 r4Jhtth7ucP9CI+HQ9H8B7xH+EKLtBjUHcLyoT9vzYdQr+pjWlewJM7kIQSBCP9kt/jHpNnsGPzc2 iU0LQuPA==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by casper.infradead.org with esmtpsa (Exim 4.99.1 #2 (Red Hat Linux)) id 1xIqAH-00000009VGJ-3MbY; Mon, 19 Oct 2026 16:24:45 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009gg-1MLb; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 04/12] KVM: x86/xen: Wait out memslot updates in the Xen attribute ioctls Date: Mon, 19 Oct 2026 17:24:35 +0100 Message-ID: <20261019162445.37219-5-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse Activating a shared_info, vcpu_info, pvclock or runstate cache can fail if it races a memslot update: previously with -EFAULT, now with -EAGAIN. These ioctls may sleep, so pass wait and let the activation ride the update out; userspace never sees the transient state. No waiting happens while xen_lock is held. A vCPU writing the hypercall page MSR blocks on xen_lock from within its kvm->srcu read section, and the memslot update cannot complete until that section quiesces, so waiting while holding the lock would deadlock all three. kvm_xen_shared_info_init() is called with the lock held =E2=80=94 keeping t= he wallclock publish single-writer =E2=80=94 and drops it internally across any refresh; the publish location is chosen by re-reading long_mode under the re-taken lock. The activation calls wait with the lock dropped at the call site. Callers get no state continuity across those waits; concurrent SET_ATTR calls racing through the window are userspace's own race, and the last one wins. Document the rule at the lock's definition. The hypercall page MSR write cannot wait at all =E2=80=94 it would be waiti= ng out its own srcu read section =E2=80=94 so its shared_info re-init passes wait=3Dfalse and ignores -EAGAIN, skipping the wallclock update and leaving the new mode's wallclock stale =E2=80=94 or, on a first bitness change, never written. Nothing retries that, but the guest latches its bitness via the hypercall page early in boot, when nothing should be changing memslots anyway. A real failure (-EFAULT) still fails the WRMSR as it always did. kvm_xen_shared_info_init() gains the wait flag for its refresh. It and xen_get_guest_pvclock() keep a kvm->srcu read section around each kvm_gpc_check() and the access through the mapping =E2=80=94 the check consults the memslots =E2=80=94 but drop it across the refresh. The kvm->srcu read sections around the activation calls go away; they existed to satisfy the old refresh contract, and holding one across a waiting activation would deadlock against the memslot update. The runstate updates in kvm_xen_vcpu_set_attr() still read guest state through the caches, so they keep narrow read sections of their own. Signed-off-by: David Woodhouse Assisted-by: LLM --- arch/x86/include/asm/kvm_host.h | 6 ++ arch/x86/kvm/xen.c | 129 +++++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 35 deletions(-) diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_hos= t.h index 3af7395c2430..ed633a39a97a 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -1108,6 +1108,12 @@ struct kvm_hv { #ifdef CONFIG_KVM_XEN /* Xen emulation context */ struct kvm_xen { + /* + * Taken from within kvm->srcu read sections (e.g. by a vCPU + * writing the hypercall page MSR), so never held while waiting + * out a memslot update: the update waits for those read + * sections to quiesce. + */ struct mutex xen_lock; u32 xen_version; bool long_mode; diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index ac27b4795d69..8c49ec8aa4d9 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -36,7 +36,7 @@ static bool kvm_xen_hcall_evtchn_send(struct kvm_vcpu *vc= pu, u64 param, u64 *r); =20 DEFINE_STATIC_KEY_DEFERRED_FALSE(kvm_xen_enabled, HZ); =20 -static int kvm_xen_shared_info_init(struct kvm *kvm) +static int kvm_xen_shared_info_init(struct kvm *kvm, bool wait) { struct gfn_to_pfn_cache *gpc =3D &kvm->arch.xen.shinfo_cache; struct pvclock_wall_clock *wc; @@ -44,17 +44,35 @@ static int kvm_xen_shared_info_init(struct kvm *kvm) u32 wc_version; u64 wall_nsec; int ret =3D 0; - int idx =3D srcu_read_lock(&kvm->srcu); + int idx; =20 - read_lock_irq(&gpc->lock); - while (!kvm_gpc_check(gpc, PAGE_SIZE)) { + /* We only really use xen_lock here to serialize wallclock updates. */ + lockdep_assert_held(&kvm->arch.xen.xen_lock); + + /* + * kvm_gpc_check() consults the memslots, so take kvm->srcu + * around each check and the write through the mapping =E2=80=94 but not + * across the refresh. + */ + + for (;;) { + idx =3D srcu_read_lock(&kvm->srcu); + read_lock_irq(&gpc->lock); + if (kvm_gpc_check(gpc, PAGE_SIZE)) + break; read_unlock_irq(&gpc->lock); + srcu_read_unlock(&kvm->srcu, idx); =20 - ret =3D kvm_gpc_refresh(gpc, PAGE_SIZE, false); + /* + * Drop xen_lock across the refresh, which may wait out a + * memslot update that cannot complete while a vCPU is + * blocked on xen_lock within its kvm->srcu read section. + */ + mutex_unlock(&kvm->arch.xen.xen_lock); + ret =3D kvm_gpc_refresh(gpc, PAGE_SIZE, wait); + mutex_lock(&kvm->arch.xen.xen_lock); if (ret) goto out; - - read_lock_irq(&gpc->lock); } =20 /* @@ -98,9 +116,9 @@ static int kvm_xen_shared_info_init(struct kvm *kvm) =20 wc->version =3D wc_version + 1; read_unlock_irq(&gpc->lock); + srcu_read_unlock(&kvm->srcu, idx); =20 out: - srcu_read_unlock(&kvm->srcu, idx); return ret; } =20 @@ -155,21 +173,29 @@ static int xen_get_guest_pvclock(struct kvm_vcpu *vcp= u, unsigned int offset) { unsigned long flags; - int r; + int idx, r; =20 - read_lock_irqsave(&gpc->lock, flags); - while (!kvm_gpc_check(gpc, offset + sizeof(*hv_clock))) { + /* + * As in kvm_xen_shared_info_init(): kvm_gpc_check() consults the + * memslots, so take kvm->srcu around each check and the copy, but + * not across the refresh. + */ + for (;;) { + idx =3D srcu_read_lock(&vcpu->kvm->srcu); + read_lock_irqsave(&gpc->lock, flags); + if (kvm_gpc_check(gpc, offset + sizeof(*hv_clock))) + break; read_unlock_irqrestore(&gpc->lock, flags); + srcu_read_unlock(&vcpu->kvm->srcu, idx); =20 r =3D kvm_gpc_refresh(gpc, offset + sizeof(*hv_clock), false); if (r) return r; - - read_lock_irqsave(&gpc->lock, flags); } =20 memcpy(hv_clock, gpc->khva + offset, sizeof(*hv_clock)); read_unlock_irqrestore(&gpc->lock, flags); + srcu_read_unlock(&vcpu->kvm->srcu, idx); =20 /* * Sanity check TSC shift+multiplier to verify the guest's view of time @@ -776,7 +802,6 @@ int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xe= n_hvm_attr *data) { int r =3D -ENOENT; =20 - switch (data->type) { case KVM_XEN_ATTR_TYPE_LONG_MODE: if (!IS_ENABLED(CONFIG_64BIT) && data->u.long_mode) { @@ -792,19 +817,23 @@ int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_= xen_hvm_attr *data) * to make the call anyway. */ r =3D kvm->arch.xen.shinfo_cache.active ? - kvm_xen_shared_info_init(kvm) : 0; + kvm_xen_shared_info_init(kvm, true) : 0; mutex_unlock(&kvm->arch.xen.xen_lock); } break; =20 case KVM_XEN_ATTR_TYPE_SHARED_INFO: case KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA: { - int idx; - + /* + * Deactivation stays under xen_lock, but an activation may + * wait out a memslot update and so must not hold it: a vCPU + * may block on xen_lock from within the kvm->srcu read + * section which that update must wait out. Concurrent + * SET_ATTR calls racing in the window are userspace's own + * race; the cache layer serializes them, last one wins. + */ mutex_lock(&kvm->arch.xen.xen_lock); =20 - idx =3D srcu_read_lock(&kvm->srcu); - if (data->type =3D=3D KVM_XEN_ATTR_TYPE_SHARED_INFO) { gfn_t gfn =3D data->u.shared_info.gfn; =20 @@ -812,8 +841,10 @@ int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_x= en_hvm_attr *data) kvm_gpc_deactivate(&kvm->arch.xen.shinfo_cache); r =3D 0; } else { + mutex_unlock(&kvm->arch.xen.xen_lock); r =3D kvm_gpc_activate(&kvm->arch.xen.shinfo_cache, - gfn_to_gpa(gfn), PAGE_SIZE, false); + gfn_to_gpa(gfn), PAGE_SIZE, true); + mutex_lock(&kvm->arch.xen.xen_lock); } } else { void __user * hva =3D u64_to_user_ptr(data->u.shared_info.hva); @@ -829,10 +860,8 @@ int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_x= en_hvm_attr *data) } } =20 - srcu_read_unlock(&kvm->srcu, idx); - if (!r && kvm->arch.xen.shinfo_cache.active) - r =3D kvm_xen_shared_info_init(kvm); + r =3D kvm_xen_shared_info_init(kvm, true); =20 mutex_unlock(&kvm->arch.xen.xen_lock); break; @@ -937,7 +966,6 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, struct= kvm_xen_vcpu_attr *data) int idx, r =3D -ENOENT; =20 mutex_lock(&vcpu->kvm->arch.xen.xen_lock); - idx =3D srcu_read_lock(&vcpu->kvm->srcu); =20 switch (data->type) { case KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO: @@ -959,8 +987,16 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, struc= t kvm_xen_vcpu_attr *data) if (!IS_ALIGNED(data->u.gpa, sizeof(u32))) break; =20 + /* + * As with the shared_info attributes: a waiting + * activation must not hold xen_lock, on which a + * vCPU may block from within the kvm->srcu read + * section the memslot update has to wait out. + */ + mutex_unlock(&vcpu->kvm->arch.xen.xen_lock); r =3D kvm_gpc_activate(&vcpu->arch.xen.vcpu_info_cache, - data->u.gpa, sizeof(struct vcpu_info), false); + data->u.gpa, sizeof(struct vcpu_info), true); + mutex_lock(&vcpu->kvm->arch.xen.xen_lock); } else { if (data->u.hva =3D=3D 0) { kvm_gpc_deactivate(&vcpu->arch.xen.vcpu_info_cache); @@ -988,9 +1024,11 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, stru= ct kvm_xen_vcpu_attr *data) break; } =20 + mutex_unlock(&vcpu->kvm->arch.xen.xen_lock); r =3D kvm_gpc_activate(&vcpu->arch.xen.vcpu_time_info_cache, data->u.gpa, - sizeof(struct pvclock_vcpu_time_info), false); + sizeof(struct pvclock_vcpu_time_info), true); + mutex_lock(&vcpu->kvm->arch.xen.xen_lock); if (!r) kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu); break; @@ -1022,10 +1060,14 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, st= ruct kvm_xen_vcpu_attr *data) =20 /* How much fits in the (first) page? */ sz1 =3D PAGE_SIZE - (data->u.gpa & ~PAGE_MASK); + + mutex_unlock(&vcpu->kvm->arch.xen.xen_lock); r =3D kvm_gpc_activate(&vcpu->arch.xen.runstate_cache, - data->u.gpa, sz1, false); - if (r) + data->u.gpa, sz1, true); + if (r) { + mutex_lock(&vcpu->kvm->arch.xen.xen_lock); goto deactivate_out; + } =20 /* Either map the second page, or deactivate the second GPC */ if (sz1 >=3D sz) { @@ -1034,12 +1076,17 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, st= ruct kvm_xen_vcpu_attr *data) sz2 =3D sz - sz1; BUG_ON((data->u.gpa + sz1) & ~PAGE_MASK); r =3D kvm_gpc_activate(&vcpu->arch.xen.runstate2_cache, - data->u.gpa + sz1, sz2, false); - if (r) + data->u.gpa + sz1, sz2, true); + if (r) { + mutex_lock(&vcpu->kvm->arch.xen.xen_lock); goto deactivate_out; + } } + mutex_lock(&vcpu->kvm->arch.xen.xen_lock); =20 + idx =3D srcu_read_lock(&vcpu->kvm->srcu); kvm_xen_update_runstate_guest(vcpu, false); + srcu_read_unlock(&vcpu->kvm->srcu, idx); break; } case KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_CURRENT: @@ -1052,7 +1099,9 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, stru= ct kvm_xen_vcpu_attr *data) break; } =20 + idx =3D srcu_read_lock(&vcpu->kvm->srcu); kvm_xen_update_runstate(vcpu, data->u.runstate.state); + srcu_read_unlock(&vcpu->kvm->srcu, idx); r =3D 0; break; =20 @@ -1132,10 +1181,12 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, st= ruct kvm_xen_vcpu_attr *data) vcpu->arch.xen.runstate_times[RUNSTATE_offline] +=3D data->u.runstate.time_offline; =20 + idx =3D srcu_read_lock(&vcpu->kvm->srcu); if (data->u.runstate.state <=3D RUNSTATE_offline) kvm_xen_update_runstate(vcpu, data->u.runstate.state); else if (vcpu->arch.xen.runstate_cache.active) kvm_xen_update_runstate_guest(vcpu, false); + srcu_read_unlock(&vcpu->kvm->srcu, idx); r =3D 0; break; =20 @@ -1181,7 +1232,6 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, stru= ct kvm_xen_vcpu_attr *data) break; } =20 - srcu_read_unlock(&vcpu->kvm->srcu, idx); mutex_unlock(&vcpu->kvm->arch.xen.xen_lock); return r; } @@ -1326,11 +1376,20 @@ int kvm_xen_write_hypercall_page(struct kvm_vcpu *v= cpu, u64 data) =20 /* * Re-initialize shared_info to put the wallclock in the - * correct place. + * correct place. This cannot wait out a memslot update + * from within the vCPU's kvm->srcu read section, so if + * one races (-EAGAIN), the wallclock update is simply + * skipped =E2=80=94 leaving the new mode's wallclock stale, or + * never yet written. The guest latches its bitness here + * early in boot, when nothing should be changing memslots + * anyway. A real failure still fails the WRMSR as before. */ - if (kvm->arch.xen.shinfo_cache.active && - kvm_xen_shared_info_init(kvm)) - r =3D 1; + if (kvm->arch.xen.shinfo_cache.active) { + int ret =3D kvm_xen_shared_info_init(kvm, false); + + if (ret && ret !=3D -EAGAIN) + r =3D 1; + } } mutex_unlock(&kvm->arch.xen.xen_lock); =20 --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.92.199 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427100; b=mNkq3VwW730pJBjXK7Qfia5NK1YjhKM1G0rfDNOcmoI7RxYl8GsgVXJ19A6f029EuVCD BuyFjgvkq1Pj8DgkhzRxSgbIbNid2gch0mjVHWgWfVIahz45dPvsl8tKAzW2wkiucoxNP v6m9LE34qm1mUHxd24uScUBTDw4/waJYuYsEl2TlAWlbsVol86KtET8fsed6fwv4Gf5Tg +fBnq9lSEF7EWsMGQNhnQmC/Z9HVDC8jilo3uwpyQOqqZ6yfAyKX6HW+zxbkwNF+gdEDI yVdEjT/bIgwjPOQwdLRaNPY0wSdyQqenwjzT7ADi9TamtmJF27LaSEuyMRPItwYo6/A== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427100; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=6MKBy2LzwKc5tYKfWDsUXaCqB+jnQ5/Uc3KirDlScOU=; b=DSW0Q1aFJE7r9w+WJmsZtyXS16eYv2ohN1+KfNA6+Ww/m4XiWfY4q0UB6+404OwbtK/f PcwORGsaGlF8gT0yAXoCDz1y81uyW/TZpAxelQgGjEhbLbJiocFrytkiKPqclw1Vnn7wV xe3+hB5/ocqoxzX3i9LirinnnzR3j1VCOeX6k6a8HRHG/Rqb58B4CogVyVNyqioB5vazH gdlkooKb/kBGW4yYlgKMBpQ88kiASmWwpYD7IpNXVOoS/RqPyZp9RLDmYAHDxsMTE8M5r d2dmNvaXE8Zjhdg9Bg+yd2CC8IsvcFbDwyHtnxOR5kHKWcQNdW9Bfzhv083YgswyqFA== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b=AoTupm1w; arc=none smtp.remote-ip=90.155.92.199 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 748F0606A320; Mon, 19 Oct 2026 16:24:57 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 748F0606A320 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=desiato.srs.infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b=AoTupm1w DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=6MKBy2LzwKc5tYKfWDsUXaCqB+jnQ5/Uc3KirDlScOU=; b=AoTupm1wxEFh+2JaCBgpbgU0ic TbRK8WtR/+MVOxAXWCBs6eAdFLyGjEZMLk68spb4zp/ATSfNr2UAfLg172l1k2zNIiCMNph3n93xt sj+rq4ZcGI5xncBejrD+OyK6FthZS3l6Nn54vphoI1SSY4PEiruR5q8+zfMTiPOD7BdW6nMvtY/ZN fYPlGyDYaDsKpiWkZoXumqPcaQDw/ZcK2LecvUwyg8JTnAk+CqD3updqkGuzlesSQWzHqIHuRD/kx zqOYd45MoM9L1sa2BgRRLdUg/++9zS+MYTHUkEvmBeArUpPNdPByNAuEzfUCvxf2XU08p5FIFcuN4 aL+rKMLg==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by desiato.infradead.org with esmtpsa (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAK-00000003qsj-1Gta; Mon, 19 Oct 2026 16:24:48 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009gj-1aDd; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 05/12] KVM: x86/xen: Don't lose the timer event when delivery fails transiently Date: Mon, 19 Oct 2026 17:24:36 +0100 Message-ID: <20261019162445.37219-6-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by desiato.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse kvm_xen_inject_timer_irqs() can drop events if kvm_xen_set_evtchn() fails transiently, as it can when a memslot is in the process of being updated. Defer the delivery in that case by leaving ->arch.xen.timer_pending set, so that it gets retried. Raise KVM_REQ_UNBLOCK for the retry, as the timer callback does when it defers: without it, nothing aborts the guest entry, and a vCPU which takes no further exits would hold the owed event indefinitely. The request survives to kvm_vcpu_exit_request(), bouncing the vCPU back through vcpu_run()'s injection pass until the memslot update completes. If the shared_info cache has been deactivated (-EINVAL), clear timer_pending =E2=80=94 a halted vCPU would otherwise spin forever, as kvm_cpu_has_pending_timer() aborts every block while nothing can deliver the event =E2=80=94 but keep timer_expires, so the GET_ATTR/SET_ATTR migration flow still carries the event and re-fires it on re-arm. If delivery fails with -EFAULT, the shared_info gfn is genuinely unbacked and can never succeed: drop the event and complete the timer rather than spin on it. True Xen does not have this problem: the shared_info page is owned by Xen and always exists, even when not mapped into the guest. The equivalent for a KVM/Xen VMM is KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA, which has no memslot dependency and is the recommended mode; this fixes the legacy GFN mode, which xen_shinfo_test exercises. Don't re-arm the hrtimer in the KVM_XEN_VCPU_GET_ATTR(TIMER) flush while a delivery is still pending; a re-fired callback racing the retry would deliver twice. Re-arm through kvm_xen_start_timer(), which recomputes the expiry from timer_expires: the hrtimer's own expiry field is stale when the timer last fired through the direct callback path, which doesn't program the hrtimer. A timer still armed across the flush is always restored; one which already fired is not re-fired if its port is out of range, where the immediate -EINVAL would destroy the owed timer_expires state just reported to userspace. The drop is vanishingly rare upstream, but hangs roughly half of all xen_shinfo_test runs once a subsequent atomic SRCU conversion of the pfncache widens the window; hence fixing it first. Fixes: 536395260582 ("KVM: x86/xen: handle PV timers oneshot mode") Assisted-by: LLM Signed-off-by: David Woodhouse --- arch/x86/kvm/xen.c | 108 ++++++++++++++---- .../selftests/kvm/x86/xen_shinfo_test.c | 11 +- 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index 8c49ec8aa4d9..8a9d6963fc05 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -36,6 +36,19 @@ static bool kvm_xen_hcall_evtchn_send(struct kvm_vcpu *v= cpu, u64 param, u64 *r); =20 DEFINE_STATIC_KEY_DEFERRED_FALSE(kvm_xen_enabled, HZ); =20 +static inline int max_evtchn_port(bool has_64bit_shinfo) +{ + if (has_64bit_shinfo) + return EVTCHN_2L_NR_CHANNELS; + else + return COMPAT_EVTCHN_2L_NR_CHANNELS; +} + +static inline int kvm_max_evtchn_port(struct kvm *kvm) +{ + return max_evtchn_port(kvm_xen_has_64bit_shinfo(kvm)); +} + static int kvm_xen_shared_info_init(struct kvm *kvm, bool wait) { struct gfn_to_pfn_cache *gpc =3D &kvm->arch.xen.shinfo_cache; @@ -126,14 +139,47 @@ void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu) { if (atomic_read(&vcpu->arch.xen.timer_pending) > 0) { struct kvm_xen_evtchn e; + int rc; =20 e.vcpu_id =3D vcpu->vcpu_id; e.vcpu_idx =3D vcpu->vcpu_idx; e.port =3D vcpu->arch.xen.timer_virq; e.priority =3D KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEVEL; =20 - kvm_xen_set_evtchn(&e, vcpu->kvm); + rc =3D kvm_xen_set_evtchn(&e, vcpu->kvm); + + /* + * Leave timer_pending set if the memslot is being updated + * (-EAGAIN), and raise KVM_REQ_UNBLOCK =E2=80=94 as the timer + * callback does when it defers =E2=80=94 so that the entry is + * aborted and this retried from vcpu_run(), rather than + * the guest running with the owed event undelivered until + * some unrelated VM exit. + */ + if (rc =3D=3D -EAGAIN) { + kvm_make_request(KVM_REQ_UNBLOCK, vcpu); + return; + } + + /* + * If the event cannot currently be delivered (-EINVAL: + * deactivated shared_info, or an out-of-range port), clear + * timer_pending =E2=80=94 a halted vCPU would otherwise spin on + * kvm_cpu_has_pending_timer() =E2=80=94 but keep timer_expires, + * which still reports the event as owed through + * KVM_XEN_VCPU_GET_ATTR; re-arming the timer with an expiry + * in the past fires it again. + */ + if (rc =3D=3D -EINVAL) { + atomic_set(&vcpu->arch.xen.timer_pending, 0); + return; + } =20 + /* + * Delivered - or failed in a way which can never succeed + * (-EFAULT: a genuinely unbacked shared_info gfn). Either + * way the event is complete: drop it rather than spin. + */ vcpu->arch.xen.timer_expires =3D 0; atomic_set(&vcpu->arch.xen.timer_pending, 0); } @@ -1315,17 +1361,21 @@ int kvm_xen_vcpu_get_attr(struct kvm_vcpu *vcpu, st= ruct kvm_xen_vcpu_attr *data) r =3D 0; break; =20 - case KVM_XEN_VCPU_ATTR_TYPE_TIMER: + case KVM_XEN_VCPU_ATTR_TYPE_TIMER: { + bool timer_armed =3D false; + /* - * Ensure a consistent snapshot of state is captured, with a - * timer either being pending, or the event channel delivered - * to the corresponding bit in the shared_info. Not still - * lurking in the timer_pending flag for deferred delivery. - * Purely as an optimisation, if the timer_expires field is - * zero, that means the timer isn't active (or even in the - * timer_pending flag) and there is no need to cancel it. + * Ensure a consistent snapshot of state is captured: a timer + * which already fired has either been delivered to the + * corresponding bit in the shared_info, or is reported as + * still owed (expires_ns !=3D 0, if the shared_info page was + * unavailable). Purely as an optimisation, if the + * timer_expires field is zero, that means the timer isn't + * active (or even in the timer_pending flag) and there is + * no need to cancel it. */ if (vcpu->arch.xen.timer_expires) { + timer_armed =3D hrtimer_active(&vcpu->arch.xen.timer); hrtimer_cancel(&vcpu->arch.xen.timer); kvm_xen_inject_timer_irqs(vcpu); } @@ -1341,13 +1391,34 @@ int kvm_xen_vcpu_get_attr(struct kvm_vcpu *vcpu, st= ruct kvm_xen_vcpu_attr *data) * That's fine, as the guest won't even have had a chance * to run and handle the interrupt. Asserting an already * pending event channel is idempotent. + * + * Don't re-arm if the flush above failed transiently and + * left the timer pending: delivery is owned by the retry + * on the guest entry path, and an immediate hrtimer fire + * would race it =E2=80=94 the callback's timer_pending suppression + * check is not serialized against the entry path, so a + * late callback could re-assert a port the entry path had + * already delivered and the guest already consumed. + * + * An expiry already in the past fires immediately: the + * event is still owed. + * + * A timer still armed (not yet fired) across the flush is + * always restored, whatever its port. One which already + * fired is not re-fired if its port is out of range, where + * the immediate -EINVAL from the delivery path would destroy + * the timer_expires state just reported to userspace. */ - if (vcpu->arch.xen.timer_expires) - hrtimer_start_expires(&vcpu->arch.xen.timer, - HRTIMER_MODE_ABS_HARD); + if (vcpu->arch.xen.timer_expires && + !atomic_read(&vcpu->arch.xen.timer_pending) && + (timer_armed || + vcpu->arch.xen.timer_virq < kvm_max_evtchn_port(vcpu->kvm))) + kvm_xen_start_timer(vcpu, vcpu->arch.xen.timer_expires, + false); =20 r =3D 0; break; + } =20 case KVM_XEN_VCPU_ATTR_TYPE_UPCALL_VECTOR: data->u.vector =3D vcpu->arch.xen.upcall_vector; @@ -1520,19 +1591,6 @@ static int kvm_xen_hypercall_complete_userspace(stru= ct kvm_vcpu *vcpu) return kvm_xen_hypercall_set_result(vcpu, run->xen.u.hcall.result); } =20 -static inline int max_evtchn_port(bool has_64bit_shinfo) -{ - if (has_64bit_shinfo) - return EVTCHN_2L_NR_CHANNELS; - else - return COMPAT_EVTCHN_2L_NR_CHANNELS; -} - -static inline int kvm_max_evtchn_port(struct kvm *kvm) -{ - return max_evtchn_port(kvm_xen_has_64bit_shinfo(kvm)); -} - static bool wait_pending_event(struct kvm_vcpu *vcpu, bool has_64bit_shinf= o, int nr_ports, evtchn_port_t *ports) { diff --git a/tools/testing/selftests/kvm/x86/xen_shinfo_test.c b/tools/test= ing/selftests/kvm/x86/xen_shinfo_test.c index aaa414c677ad..3b11d0ee253f 100644 --- a/tools/testing/selftests/kvm/x86/xen_shinfo_test.c +++ b/tools/testing/selftests/kvm/x86/xen_shinfo_test.c @@ -356,11 +356,12 @@ static void guest_code(void) wait_for_timer: /* * Poll for a timer wake event while the worker thread is mucking with - * the shared info. KVM XEN drops timer IRQs if the shared info is - * invalid when the timer expires. Arbitrarily poll 100 times before - * giving up and asking the VMM to re-arm the timer. 100 polls should - * consume enough time to beat on KVM without taking too long if the - * timer IRQ is dropped due to an invalid event channel. + * the shared info. KVM is now supposed to retry a timer IRQ whose + * delivery hit an invalid shared info rather than drop it, but older + * kernels drop it on the floor. Arbitrarily poll 100 times before + * giving up and asking the VMM to re-arm the timer, so the test still + * completes (and documents the VMM-side recovery protocol) on + * unfixed kernels. */ for (i =3D 0; i < 100 && !guest_saw_irq; i++) __xen_hypercall(__HYPERVISOR_sched_op, SCHEDOP_poll, &p); --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.92.199 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427104; b=a+xkQLrxxJjFuucse6qDuXhPSERPZyx1ePVKbEil3KbUKQS1u0Higli60qjDtGJKNjNB mvfMjrxPQNgIw/Cfn1CaBH+bLmiQ3FGF6OYeSWFzMqnAV2kilPeCMexDLwPpnkOPg9m/y XzNsYoH6ZPeccwMS4fS85JJ1D710v9B2gTiypQFaCnv0jgC0OGqHX6Drweh+fs9UUGGng 6X6cMUwMbo72+Yb6fjKszY4M3AIerCtUyomA8jl2Zbu+7das9AVZtAH13A9qpOl6Xjubm UaZnJxZYJ5SCSk1ifsPqy+J2Ab/S9RiTNrT5wF/0kTFjcOMn/udj0m1R1aqa9JQaraA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427104; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=ECUTBB7ArwVe9UCJJJhtwHakpZEGbMTx+Y/yUx8wMLY=; b=NJ7B3MocvZOP9V4qFm48QbQrjNfvCWupzsmBDraZXlRJMwskeZXWhR9UvVtD65KSeXRM ksG6qUrru1yDREjteABhrsCMsG5JSehrL7NLcoOcnNN7wyUc7Gb8J/p+xky4pJHNSY+Az iWV31rO7+0fGLyPgEFy9SlflohrrxJqpDk3H1bJxWZn6RMUD6RZtJv5KL6wklpat20hLn KfLg+x7lhXq3FKv15ma6aMcRBIIk2Rx6tAPEEgDk0ep0Cxd+cwE6xgXVB/lEnP0TlimkI 9+dhvnnGZEjoVNaZlI58eTlasY3Qbsdj1HKPJdQl96Xz92HcVmGB4IHbEjD2sbUcstQ== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; arc=none smtp.remote-ip=90.155.92.199 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 749AC606A321; Mon, 19 Oct 2026 16:24:57 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 749AC606A321 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=desiato.srs.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=ECUTBB7ArwVe9UCJJJhtwHakpZEGbMTx+Y/yUx8wMLY=; b=aHj4bAdBHU/jUy4fQjdT2/wBF1 ub+LRiqOus9RhoFT4yCFkMK1tvrqi+i6t29aP6H8Xz9d/1QwV4+0OEZ82A7CNHq9Py2h4s0G1bonE kqaLP4upAhhkfou6dzDrd6qdqHxYlESYrsDAHewagrqRS/cjMXfCoPub+yNS69BIk264YieD7q3N3 8dq3Cqmg8W//z9nVkLmBv9CIJPHdSsaiXcLeOjRjEN/QV1UHV6XT3hM94FHtOf+vhAgRrUqPoQvAL trYW93EO9Ij76MdSDVhin9fxslgrMB33waTaTpnCauegQL0MiokvNQTpZdiL0UrCMuVQM9MIZpSKK eHebJQ8Q==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by desiato.infradead.org with esmtpsa (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAK-00000003qsk-1IVO; Mon, 19 Oct 2026 16:24:48 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009gm-1tUQ; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 06/12] KVM: x86/xen: Retry event channel delivery which races a memslot update Date: Mon, 19 Oct 2026 17:24:37 +0100 Message-ID: <20261019162445.37219-7-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by desiato.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse Event channel delivery now returns -EAGAIN when it races a memslot update. It always failed; now the failure is distinguishable, and instead of dropping the event each caller reacts according to its context: - kvm_xen_hvm_evtchn_send(): the KVM_XEN_HVM_EVTCHN_SEND ioctl. May sleep: passes wait =3D=3D true, and kvm_gpc_refresh() rides the update out. - evtchn_set_fn(): irqfd routing, from the irqfd workqueue after the atomic fast path failed. May also wait. - kvm_xen_hcall_evtchn_send(): the guest's own EVTCHNOP_send, in vcpu_run's exit handling under the vCPU's kvm->srcu read section =E2=80=94 the very reader the memslot update is waiting out, so waiting there deadlocks, and any guest can trigger it. Instead, leave the hypercall uncompleted, with RIP not advanced, and the guest re-executes it; each attempt cycles the srcu read section on the way back in, letting the update complete. This is how Xen itself restarts preempted hypercalls, and the pending delivery is pure guest state, so there is nothing extra to migrate. - kvm_xen_inject_timer_irqs(): the timer retry added by the previous commit =E2=80=94 timer_pending *is* its pending state, so it keeps that and retries on the next entry pass. The entry pass holds the vCPU's kvm->srcu, so it may not wait. The KVM_XEN_VCPU_GET_ATTR timer flush doesn't need to wait: an undelivered event is reported as still owed in the snapshot. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260920215341.5ECE91F000FF@smtp.kernel= .org Signed-off-by: David Woodhouse Assisted-by: LLM --- arch/x86/kvm/xen.c | 58 +++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index 8a9d6963fc05..c0b9ac674255 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -30,9 +30,10 @@ #include "cpuid.h" #include "trace.h" =20 -static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *xe, struct kvm *kvm); +static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *xe, struct kvm *kvm, + bool wait); static int kvm_xen_setattr_evtchn(struct kvm *kvm, struct kvm_xen_hvm_attr= *data); -static bool kvm_xen_hcall_evtchn_send(struct kvm_vcpu *vcpu, u64 param, u6= 4 *r); +static int kvm_xen_hcall_evtchn_send(struct kvm_vcpu *vcpu, u64 param, u64= *r); =20 DEFINE_STATIC_KEY_DEFERRED_FALSE(kvm_xen_enabled, HZ); =20 @@ -146,7 +147,7 @@ void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu) e.port =3D vcpu->arch.xen.timer_virq; e.priority =3D KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEVEL; =20 - rc =3D kvm_xen_set_evtchn(&e, vcpu->kvm); + rc =3D kvm_xen_set_evtchn(&e, vcpu->kvm, false); =20 /* * Leave timer_pending set if the memslot is being updated @@ -1900,8 +1901,21 @@ int kvm_xen_hypercall(struct kvm_vcpu *vcpu) } break; case __HYPERVISOR_event_channel_op: - if (params[0] =3D=3D EVTCHNOP_send) - handled =3D kvm_xen_hcall_evtchn_send(vcpu, params[1], &r); + if (params[0] =3D=3D EVTCHNOP_send) { + int ret =3D kvm_xen_hcall_evtchn_send(vcpu, params[1], &r); + + /* + * Delivery raced a memslot update; waiting for it + * here would deadlock under vcpu_run's kvm->srcu + * read section. Leave the hypercall uncompleted =E2=80=94 + * RIP not advanced =E2=80=94 so the guest re-executes it. + * Each attempt cycles the srcu read section on the + * way back in, letting the update complete. + */ + if (ret =3D=3D -EAGAIN) + return 1; + handled =3D !ret; + } break; case __HYPERVISOR_sched_op: handled =3D kvm_xen_hcall_sched_op(vcpu, is_64bit, params[0], @@ -2092,7 +2106,8 @@ int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe= , struct kvm *kvm) return rc; } =20 -static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *xe, struct kvm *kvm) +static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *xe, struct kvm *kvm, + bool wait) { bool mm_borrowed =3D false; int rc; @@ -2129,16 +2144,13 @@ static int kvm_xen_set_evtchn(struct kvm_xen_evtchn= *xe, struct kvm *kvm) */ do { struct gfn_to_pfn_cache *gpc =3D &kvm->arch.xen.shinfo_cache; - int idx; =20 rc =3D kvm_xen_set_evtchn_fast(xe, kvm); if (rc !=3D -EWOULDBLOCK) break; =20 - idx =3D srcu_read_lock(&kvm->srcu); - rc =3D kvm_gpc_refresh(gpc, PAGE_SIZE, false); - srcu_read_unlock(&kvm->srcu, idx); - } while(!rc); + rc =3D kvm_gpc_refresh(gpc, PAGE_SIZE, wait); + } while (!rc); =20 if (mm_borrowed) kthread_unuse_mm(kvm->mm); @@ -2153,7 +2165,7 @@ static int evtchn_set_fn(struct kvm_kernel_irq_routin= g_entry *e, struct kvm *kvm if (!level) return -EINVAL; =20 - return kvm_xen_set_evtchn(&e->xen_evtchn, kvm); + return kvm_xen_set_evtchn(&e->xen_evtchn, kvm, true); } =20 /* @@ -2227,7 +2239,7 @@ int kvm_xen_hvm_evtchn_send(struct kvm *kvm, struct k= vm_irq_routing_xen_evtchn * e.vcpu_idx =3D -1; e.priority =3D uxe->priority; =20 - ret =3D kvm_xen_set_evtchn(&e, kvm); + ret =3D kvm_xen_set_evtchn(&e, kvm, true); =20 /* * None of that 'return 1 if it actually got delivered' nonsense. @@ -2456,7 +2468,11 @@ static int kvm_xen_setattr_evtchn(struct kvm *kvm, s= truct kvm_xen_hvm_attr *data return kvm_xen_eventfd_assign(kvm, data); } =20 -static bool kvm_xen_hcall_evtchn_send(struct kvm_vcpu *vcpu, u64 param, u6= 4 *r) +/* + * Returns 0 with *r set if handled, -EAGAIN if the hypercall should be + * re-executed by the guest, or -ENOENT to hand it to userspace. + */ +static int kvm_xen_hcall_evtchn_send(struct kvm_vcpu *vcpu, u64 param, u64= *r) { struct evtchnfd *evtchnfd; struct evtchn_send send; @@ -2466,7 +2482,7 @@ static bool kvm_xen_hcall_evtchn_send(struct kvm_vcpu= *vcpu, u64 param, u64 *r) BUILD_BUG_ON(sizeof(send) !=3D 4); if (kvm_read_guest_virt(vcpu, param, &send, sizeof(send), &e)) { *r =3D -EFAULT; - return true; + return 0; } =20 /* @@ -2475,18 +2491,22 @@ static bool kvm_xen_hcall_evtchn_send(struct kvm_vc= pu *vcpu, u64 param, u64 *r) */ evtchnfd =3D xa_load(&vcpu->kvm->arch.xen.evtchn_ports, send.port); if (!evtchnfd) - return false; + return -ENOENT; =20 if (evtchnfd->deliver.port.port) { - int ret =3D kvm_xen_set_evtchn(&evtchnfd->deliver.port, vcpu->kvm); + int ret =3D kvm_xen_set_evtchn(&evtchnfd->deliver.port, + vcpu->kvm, false); + + if (ret =3D=3D -EAGAIN) + return ret; if (ret < 0 && ret !=3D -ENOTCONN) - return false; + return -ENOENT; } else { eventfd_signal(evtchnfd->deliver.eventfd.ctx); } =20 *r =3D 0; - return true; + return 0; } =20 void kvm_xen_init_vcpu(struct kvm_vcpu *vcpu) --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.50.34 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427097; b=BrePvOKycDIitvcu21SXRmmCAb1uTmXhVW4H1q0HxxmzfBXHEsJaulVxxer3DZ6RhYpN 5owiDJgIWQLXmsdUcHfrvuPE+EmLnE7TVYzyViyEaJ5MsMk2rZKmOnoSrhFtMd2fY+U09 AD3o4oSm5X+Uv1hfyT1fgO8HObNlTBL9oA/Sea/kC4cizO+GUFut8ahpMeh37goOcFi57 TJFR3EhZunjIh9n3dy7ibXa6g+E3bz31DNsuh6qsux1JrfU85O8TJE0O1SyM8UcB/TjLv 6wfT9X8LkTiBPKqYwJwRabTdIV3UbzlY/YGmTW+A00B8394jrjxGrGYW+t2qDXTR/0w== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427097; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=MMsmnyojUwvfLS70C6005NDLUir89RrnKsT7qQtTj8E=; b=IUvdwMHWlQR0p7V1teE+gnHfMDcS4uu+Peh4Zz4kdoICn/eMAVY6kjYyRU0ytA9dmCiO T2uceJEIDB645PFuPjMtKxHnyGG43d8caZ1pC1aBgfitmbG2lImdK8jnyCvg5wEQGBQc8 U0mwpa+jGdqNA8Z8bsiKNoJCN0yYd55T/uOGx47iJayiVfnOP9VYI4po0iy/fmO6AVQ38 yQNyZjnT4AMtJWB52/bepfdw1oSC08n1pBTwS7HnHDlOO6e/83I/adNs9qjWRcgwcL3E9 DQEHPIo6QCmmCVCK5VIN59LtBkWQpjvnotaor4G42sxn3yotB4OyFv/7bZZ/3SUqeEg== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=infradead.org; spf=none smtp.mailfrom=casper.srs.infradead.org; dkim=pass header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=casper.20170209 header.b=g3kMOlJ3; arc=none smtp.remote-ip=90.155.50.34 Received: from casper.infradead.org (casper.infradead.org [90.155.50.34]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9EC87606A31A; Mon, 19 Oct 2026 16:24:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 9EC87606A31A Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=casper.srs.infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=casper.20170209 header.b=g3kMOlJ3 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=MMsmnyojUwvfLS70C6005NDLUir89RrnKsT7qQtTj8E=; b=g3kMOlJ3I7LgQvgmxUDIiGw35t gusY9NogLvucUbvU6/ySeirM7N0IESQfjpx1pkGlCfex2+g9KmG83QCxgzsX7DIDiw9oakArJYKMf e+9Y4/eP042f7hA1Y4e+OQs+T/HzeiLkXcl/+gbDGm1UEEjDSQ0w9IxxYbiq1l5jiDXQViN9Mw4Qa ZvZta9wV73EPE66aERnsvnmLxCrkKUm8+QJeAvWKgI7zvNrzKg6DeUJsUINDG6cyahrkzGc50YYQ9 Ov72UJbnobKJ4sDDsJgOttvxeFKqukI+KT1dIN+gwcXEUzN+BOTpBd4rrN6/lBabvadWBVPM8yuHh ZhPxf+9Q==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by casper.infradead.org with esmtpsa (Exim 4.99.1 #2 (Red Hat Linux)) id 1xIqAH-00000009VGK-3P8J; Mon, 19 Oct 2026 16:24:45 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009gp-2Cpj; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 07/12] KVM: x86/xen: Extract delivery of event to vCPU into a separate helper Date: Mon, 19 Oct 2026 17:24:38 +0100 Message-ID: <20261019162445.37219-8-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.infradead.org. See http://www.infradead.org/rpr.html From: Sean Christopherson Move the marking of an event as pending in the target vCPU's vcpu_info into a separate helper, __kvm_xen_set_evtchn_fast(), invoked after the shared_info processing is complete and its RCU read-side critical section has been exited. This makes both halves easier to read. No functional change intended. Signed-off-by: Sean Christopherson [dwmw2: rebased onto the -EAGAIN series. Sean's series first converted the GPC locks to read_trylock(), which this series does not carry, so the helper takes the plain read lock and the original's trylock failure path does not apply. The caller's kvm->srcu section extends across the helper call, since kvm_gpc_check() on the vcpu_info cache consults the memslot generation and the irqfd path enters holding only irq_srcu; in Sean's series that was covered by a guard(srcu) spanning the whole function. Note the "no functional change" is from the original; here the lock scopes do change: kvm_xen_check_poller() and the upcall vector injection now run outside the GPC read locks (and with interrupts restored), deliberately, so that the vCPU kick and the APIC map walk are not nested inside them.] Signed-off-by: David Woodhouse Assisted-by: LLM --- arch/x86/kvm/xen.c | 158 ++++++++++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 61 deletions(-) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index c0b9ac674255..6a1162caee9b 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -1970,6 +1970,89 @@ static void kvm_xen_check_poller(struct kvm_vcpu *vc= pu, int port) } } =20 +static void __kvm_xen_set_evtchn_fast(struct kvm_vcpu *vcpu, int port_word= _bit, + bool has_64bit_shinfo) +{ + struct gfn_to_pfn_cache *gpc =3D &vcpu->arch.xen.vcpu_info_cache; + unsigned long vi_pending_sel_ofs; + bool inject_upcall =3D false; + bool kick_vcpu =3D false; + unsigned long flags; + bool old; + + vi_pending_sel_ofs =3D has_64bit_shinfo ? + offsetof(struct vcpu_info, evtchn_pending_sel) : + offsetof(struct compat_vcpu_info, evtchn_pending_sel); + + /* Now switch to the vCPU's vcpu_info to set the index and pending_sel */ + read_lock_irqsave(&gpc->lock, flags); + if (!kvm_gpc_check(gpc, sizeof(struct vcpu_info))) { + /* + * Could not access the vcpu_info. Set the bit in-kernel and + * prod the vCPU to deliver it for itself. Note that an MSI is + * *not* an acceptable substitute here even if the vCPU has an + * upcall vector: the pending bit only exists in + * evtchn_pending_sel so far, and the vCPU has to be forced + * out of the guest so that kvm_xen_inject_pending_events() + * can refresh the cache and propagate it to the guest. + */ + if (!test_and_set_bit(port_word_bit, &vcpu->arch.xen.evtchn_pending_sel)) + kick_vcpu =3D true; + goto out_unlock; + } + inject_upcall =3D true; + + /* + * Explicitly use a 32-bit btsl instead of test_and_set_bit(), which + * would use btsq on x86-64. The vcpu_info is guest-controlled and only + * required to be 32-bit aligned, so a 64-bit access could generate a + * split-lock #AC. + * + * Note, this does not apply to the test_and_set_bit() on pending_bits + * in the caller: that is in the per-VM shared_info, which is page + * aligned, so the access is guaranteed to be 64-bit aligned. + */ + old =3D GEN_BINARY_RMWcc(LOCK_PREFIX "btsl", + *(u32 *)(gpc->khva + vi_pending_sel_ofs), + c, "Ir", port_word_bit); + if (!old) { + struct vcpu_info *vi =3D gpc->khva; + + /* No need for compat handling */ + BUILD_BUG_ON(offsetof(struct vcpu_info, evtchn_upcall_pending) !=3D + offsetof(struct compat_vcpu_info, evtchn_upcall_pending)); + + WRITE_ONCE(vi->evtchn_upcall_pending, 1); + kick_vcpu =3D true; + } + +out_unlock: + read_unlock_irqrestore(&gpc->lock, flags); + + /* + * Deliver the upcall or kick the vCPU only after dropping the GPC + * read lock. Both paths end up in kvm_vcpu_kick(), and the MSI + * delivery also walks the APIC map and takes APIC locks; none of + * that wants to be nested inside the GPC read lock, which a + * concurrent invalidation may be waiting to take for write. + */ + if (!kick_vcpu) + return; + + /* + * For the per-vCPU lapic vector, deliver it as MSI =E2=80=94 but only if= the + * vcpu_info was actually updated above. If it wasn't, the vCPU must + * be kicked instead (see above). + */ + if (inject_upcall && vcpu->arch.xen.upcall_vector) { + kvm_xen_inject_vcpu_vector(vcpu); + return; + } + + kvm_make_request(KVM_REQ_UNBLOCK, vcpu); + kvm_vcpu_kick(vcpu); +} + /* * The return value from this function is propagated to kvm_set_irq() API, * so it returns: @@ -1984,11 +2067,10 @@ int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *= xe, struct kvm *kvm) { struct gfn_to_pfn_cache *gpc =3D &kvm->arch.xen.shinfo_cache; bool has_64bit_shinfo =3D kvm_xen_has_64bit_shinfo(kvm); - unsigned long *pending_bits, *mask_bits, vi_pending_sel_ofs; + unsigned long *pending_bits, *mask_bits; struct kvm_vcpu *vcpu; unsigned long flags; int port_word_bit; - bool kick_vcpu =3D false; int vcpu_idx, idx, rc; =20 vcpu_idx =3D READ_ONCE(xe->vcpu_idx); @@ -2010,23 +2092,19 @@ int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *= xe, struct kvm *kvm) =20 read_lock_irqsave(&gpc->lock, flags); if (!kvm_gpc_check(gpc, PAGE_SIZE)) - goto out_rcu; + goto out_unlock; =20 if (has_64bit_shinfo) { struct shared_info *shinfo =3D gpc->khva; pending_bits =3D (unsigned long *)&shinfo->evtchn_pending; mask_bits =3D (unsigned long *)&shinfo->evtchn_mask; port_word_bit =3D xe->port / 64; - - vi_pending_sel_ofs =3D offsetof(struct vcpu_info, evtchn_pending_sel); } else { struct compat_shared_info *shinfo =3D gpc->khva; pending_bits =3D (unsigned long *)&shinfo->evtchn_pending; mask_bits =3D (unsigned long *)&shinfo->evtchn_mask; port_word_bit =3D xe->port / 32; =20 - vi_pending_sel_ofs =3D offsetof(struct compat_vcpu_info, evtchn_pending_= sel); - /* test_and_set_bit() needs 64-bit alignment, but that's OK */ BUILD_BUG_ON(offsetof(struct compat_shared_info, evtchn_pending) & 7); } @@ -2042,66 +2120,24 @@ int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *= xe, struct kvm *kvm) rc =3D 0; /* It was already raised */ } else if (test_bit(xe->port, mask_bits)) { rc =3D -ENOTCONN; /* Masked */ - kvm_xen_check_poller(vcpu, xe->port); } else { - bool old; - rc =3D 1; /* Delivered to the bitmap in shared_info. */ - /* Now switch to the vCPU's vcpu_info to set the index and pending_sel */ - read_unlock_irqrestore(&gpc->lock, flags); - gpc =3D &vcpu->arch.xen.vcpu_info_cache; - - read_lock_irqsave(&gpc->lock, flags); - if (!kvm_gpc_check(gpc, sizeof(struct vcpu_info))) { - /* - * Could not access the vcpu_info. Set the bit in-kernel - * and prod the vCPU to deliver it for itself. - */ - if (!test_and_set_bit(port_word_bit, &vcpu->arch.xen.evtchn_pending_sel= )) - kick_vcpu =3D true; - goto out_rcu; - } - - /* - * Explicitly use a 32-bit btsl instead of test_and_set_bit(), - * which would use btsq on x86-64. The vcpu_info is guest- - * controlled and only required to be 32-bit aligned, so a - * 64-bit access could generate a split-lock #AC. - * - * Note, this does not apply to the test_and_set_bit() on - * pending_bits above: that is in the per-VM shared_info, which - * is page aligned, so the access is guaranteed to be 64-bit - * aligned. - */ - old =3D GEN_BINARY_RMWcc(LOCK_PREFIX "btsl", - *(u32 *)(gpc->khva + vi_pending_sel_ofs), - c, "Ir", port_word_bit); - if (!old) { - struct vcpu_info *vi =3D gpc->khva; - - /* No need for compat handling */ - BUILD_BUG_ON(offsetof(struct vcpu_info, evtchn_upcall_pending) !=3D - offsetof(struct compat_vcpu_info, evtchn_upcall_pending)); - - WRITE_ONCE(vi->evtchn_upcall_pending, 1); - kick_vcpu =3D true; - } - - /* For the per-vCPU lapic vector, deliver it as MSI. */ - if (kick_vcpu && vcpu->arch.xen.upcall_vector) { - kvm_xen_inject_vcpu_vector(vcpu); - kick_vcpu =3D false; - } } =20 - out_rcu: + out_unlock: read_unlock_irqrestore(&gpc->lock, flags); - srcu_read_unlock(&kvm->srcu, idx); =20 - if (kick_vcpu) { - kvm_make_request(KVM_REQ_UNBLOCK, vcpu); - kvm_vcpu_kick(vcpu); - } + /* + * Both of these can kick the vCPU, so keep them outside the GPC + * read lock; an invalidation may be waiting to take it for write. + */ + if (rc =3D=3D -ENOTCONN) + kvm_xen_check_poller(vcpu, xe->port); + else if (rc =3D=3D 1) + __kvm_xen_set_evtchn_fast(vcpu, port_word_bit, + has_64bit_shinfo); + + srcu_read_unlock(&kvm->srcu, idx); =20 return rc; } --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.92.199 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427100; b=DEoGectesNo97vs4g80zg9G8CTl3leQO/HM1dQjlmcZRt2AOmshOKwM9TslGBsKrx7w2 vpiQvqABUoc5JoSF97Bwz7UHHY6TbiHIeMItyd7B5yZsd2xDZbwDMMTX3kWRI+Y/DXPeH H+j82mXQlhoLAB8tYC7nDeM9V1wSpFp1BYjyLB2y86Fjfb/o6CfY0t3yLptVZkFYCL9mr JGkbvmwmyFeRuaNrD/DFIb4xJe7tykEn479c4vxg3My3lYfQ10701JXoClYiR0rElOGlF ljQHXCHiKDzaFqqiTuNwKJo+IGFnumXvLZuiuZrBiYRyu0L+c5nJ8Dty5ovTg+emLhQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427100; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=sxmo9YnZnRqj76uOw3248z+dlkVfVDS2Xn4tGIzz6Pk=; b=l6ceCldY8qsnRVDeVLZsq9k9782WALlVwEytFZDW9DfHWN82cE/dq8ajQYOD3zIZvC0m Tt+msH2ZDPjrnTdgrTamcyFFojkbkOU/RoRzDzgIL1kg9ZGtPT6+ZmejAiyJnnOAxMbqC hXnUmH2h8GeKmKLvQWVdefEVJ2G/qrvyeAuOXuRUCEedubMBM4cF0n+XjUXR9XPPUK4vW 0ZRSjv+g/TRUtEA3ZIE4gE0DI+Q4kRAdT03pcyVeDGyy6e0HMGqi2f0cripw+If/8b1zx AbSvdYKgpHQMCrPEZd8961fQGyNoZ1vIriOegfpxVrf4IPpTFdOG1UXDEvcyOACayfw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b=J9O7A103; arc=none smtp.remote-ip=90.155.92.199 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 74A8B606A322; Mon, 19 Oct 2026 16:24:57 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 74A8B606A322 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=desiato.srs.infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=desiato.20200630 header.b=J9O7A103 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding: MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc:To:From: Reply-To:Content-Type:Content-ID:Content-Description; bh=sxmo9YnZnRqj76uOw3248z+dlkVfVDS2Xn4tGIzz6Pk=; b=J9O7A1030rwCLfMGhbZvOdUW1G pIv+VdrlLMVZmCNlnH/xKAdzYSH75+784CmQS1CFRl8+5E7yEk9TGf0mORLNkzzsNq6HQhOlr+sTa reObJ0BFFmKSmZ02CLxPdnkLEen7vVDsFqL+rHER0YK+/5GXvgPscMR8FdhkmqvV2hoHQzCrZcbr6 E7H3PyebzE04VhwQ2qBtfLFGWhkh7dlFnSaK0omy3gE0cqK0e8JUOt64VaJkwAFRllNTUChBWpWia 2glRipQddRosbYcpap8vVq4DoUOxBWopPhSlf8F+/5vK/tsgdYAJRnG/XFtMJUCYdZMXUp3sTFtlk gaflDsVA==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by desiato.infradead.org with esmtpsa (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAK-00000003qsn-1Hdh; Mon, 19 Oct 2026 16:24:48 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009gs-2WHw; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 08/12] KVM: x86/xen: Explicitly tag "shared info" page as never being dirty tracked Date: Mon, 19 Oct 2026 17:24:39 +0100 Message-ID: <20261019162445.37219-9-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by desiato.infradead.org. See http://www.infradead.org/rpr.html Content-Type: text/plain; charset="utf-8" From: Sean Christopherson Explicitly mark the Xen shared info page as never being dirty tracked so that higher-level gpc APIs can be added to automatically take care of things like dirty tracking, without reintroducing the bug fixed by commit 55749769fe60 ("KVM: x86: Fix wall clock writes in Xen shared_info not to mark page dirty"). And because the code _looks_ buggy. No functional change intended. Signed-off-by: Sean Christopherson Signed-off-by: David Woodhouse Assisted-by: LLM --- arch/x86/kvm/xen.c | 2 +- include/linux/kvm_host.h | 15 +++++++++++---- include/linux/kvm_types.h | 1 + virt/kvm/pfncache.c | 4 +++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index 6a1162caee9b..0ba684bf01d3 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -2577,7 +2577,7 @@ void kvm_xen_init_vm(struct kvm *kvm) { mutex_init(&kvm->arch.xen.xen_lock); xa_init(&kvm->arch.xen.evtchn_ports); - kvm_gpc_init(&kvm->arch.xen.shinfo_cache, kvm); + __kvm_gpc_init(&kvm->arch.xen.shinfo_cache, kvm, true); } =20 void kvm_xen_destroy_vm(struct kvm *kvm) diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 94989a705ee6..ba23f4e0a4c0 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1456,16 +1456,23 @@ int kvm_vcpu_write_guest(struct kvm_vcpu *vcpu, gpa= _t gpa, const void *data, unsigned long len); =20 /** - * kvm_gpc_init - initialize gfn_to_pfn_cache. + * __kvm_gpc_init - initialize gfn_to_pfn_cache. * * @gpc: struct gfn_to_pfn_cache object. * @kvm: pointer to kvm instance. + * @never_dirty: the cache never needs to mark its page dirty in its mem= slot. * * This sets up a gfn_to_pfn_cache by initializing locks and assigning the * immutable attributes. Note, the cache must be zero-allocated (or zeroe= d by - * the caller before init). + * the caller before init). Most callers want the kvm_gpc_init() wrapper. */ -void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm); +void __kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm, + bool never_dirty); + +static inline void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *= kvm) +{ + __kvm_gpc_init(gpc, kvm, false); +} =20 /** * kvm_gpc_activate - prepare a cached kernel mapping and HPA for a given = guest @@ -1990,7 +1997,7 @@ static inline void kvm_gpc_mark_dirty_in_slot(struct = gfn_to_pfn_cache *gpc) { lockdep_assert_held(&gpc->lock); =20 - if (!gpc->memslot) + if (!gpc->memslot || gpc->never_dirty) return; =20 mark_page_dirty_in_slot(gpc->kvm, gpc->memslot, gpa_to_gfn(gpc->gpa)); diff --git a/include/linux/kvm_types.h b/include/linux/kvm_types.h index a568d8e6f4e8..e850adc3f47e 100644 --- a/include/linux/kvm_types.h +++ b/include/linux/kvm_types.h @@ -94,6 +94,7 @@ struct gfn_to_pfn_cache { kvm_pfn_t pfn; bool active; bool valid; + bool never_dirty; }; =20 #ifdef KVM_ARCH_NR_OBJS_PER_MEMORY_CACHE diff --git a/virt/kvm/pfncache.c b/virt/kvm/pfncache.c index 0e700c337f5a..8498abb746ad 100644 --- a/virt/kvm/pfncache.c +++ b/virt/kvm/pfncache.c @@ -437,7 +437,8 @@ int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsig= ned long len, bool wait) } } =20 -void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm) +void __kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm, + bool never_dirty) { rwlock_init(&gpc->lock); mutex_init(&gpc->refresh_lock); @@ -447,6 +448,7 @@ void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct = kvm *kvm) gpc->gpa =3D INVALID_GPA; gpc->uhva =3D KVM_HVA_ERR_BAD; gpc->active =3D gpc->valid =3D false; + gpc->never_dirty =3D never_dirty; } =20 static int __kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, uns= igned long uhva, --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.50.34 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427097; b=x3uyj/CvCtpiiHUKiIs4f7k1+q03oVLhax7i6XcPIR1Q0QBGOLSx7h6cu7+Q8XeFbFad jYAxvksZcmLjKr4BIJVke6oqNmaOYNsBt0Pr5EevfBSM9v3qTLQM9YC+x4oDjXbKvcQ3R obbj7zHBwGXJ30ntxPO+zJwMRMO322Ksc4yXHFGlNF46syr/8RUYDgTO9wC9hiHYTHlPB 6Ft88bmfcLc94zqZRJLN48DjZJFKBkdxv4E6UbGbr7pQszMr4EsjIs13TdkznwayrZzvC JV/E/glTewmx+XfgoLF8dmFrLOJ+5qMPdcpCB99bEoZ7lbG+ZpigF7lSCXxIXceC6VA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427097; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=lsIBRDyGsUxhmxU8cMvjJEMNTpeEAej86qkXKTSOoyY=; b=5Hk1DwEC8pOoQzA0EABGRsROsn+xYwZfQHKKE/USeiWQtbCpqUk41eiN1J/z3Gwsyy3U zjLqCsWkfkqf9F8/lWfkncs2qHlRBFaplzOSNBzZPCrJzlsYPpwXjnkw+U6ds89fIO3U9 EHlUgc6Y0B9Ly8ZBpEBFBbEfUXx78S6vL+Sjmq6f1hJ1XyNg0nJEoODgZE9rgAYMhFABd 7r90UbQHH+yZPTyo03kWPcqLEfYtF+jZMr8gvSsKeBU/UqQQT7iK5ud6rdGQy1jSoy6ot W2o5IZx3vG2ocfhyaBgO88bvWMNKbl1B9CFCU4LplrHn0UD/sS3oTWL3jtmokn9XDMw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=infradead.org; spf=none smtp.mailfrom=casper.srs.infradead.org; dkim=pass header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=casper.20170209 header.b=ii4IKZk2; arc=none smtp.remote-ip=90.155.50.34 Received: from casper.infradead.org (casper.infradead.org [90.155.50.34]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9ECFB606A31E; Mon, 19 Oct 2026 16:24:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 9ECFB606A31E Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=casper.srs.infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=infradead.org header.i=@infradead.org header.a=rsa-sha256 header.s=casper.20170209 header.b=ii4IKZk2 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc:To:From: Reply-To:Content-Type:Content-ID:Content-Description; bh=lsIBRDyGsUxhmxU8cMvjJEMNTpeEAej86qkXKTSOoyY=; b=ii4IKZk26l8tad8TNRc9zAqP4y ShMFpHuRFZR/5GCDoovI/VVnW97+8dGXQay1WQeaCeMXkkI23BospBqZ9Ls4oc/qmBU1gXe7EzUxD Wn5fO2B1G7Hn4msJxyMc9zfhNAtdKQuCOAtN7rJ0pWtzOqTKnVsQXKBLzUpvgWRRR27DL4QukT3ao ne8+WzIVFmBYfNcG3byumJzXIPh+HH0yqP4r9kYAAK7sFDeYiKBEOUGRa+1/Za50kbT07UGX/ZY79 q54MsbHR6kFRSjlaAizOa+t6VgrKAISL3ke7HU6TyAU7sWT2dsxcASFQYjximoGeL0LX5TBp9CEE7 r4v44+1Q==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by casper.infradead.org with esmtpsa (Exim 4.99.1 #2 (Red Hat Linux)) id 1xIqAH-00000009VGP-3riH; Mon, 19 Oct 2026 16:24:45 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009h3-2pta; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 09/12] KVM: x86/xen: Don't dirty track "vCPU info" page Date: Mon, 19 Oct 2026 17:24:40 +0100 Message-ID: <20261019162445.37219-10-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.infradead.org. See http://www.infradead.org/rpr.html Content-Type: text/plain; charset="utf-8" From: Sean Christopherson Give the Xen per-vCPU info page the same treatment as the per-VM shared info page, and never mark it dirty, as KVM clearly relies on userspace to assume the page is always dirty. While the page is marked dirty on writes via kvm_xen_inject_pending_events(), it's not marked dirty when written by __kvm_xen_set_evtchn_fast(). Furthermore, as was the case with the shared info page, writes in the event channel fastpath may be done without an active vCPU, e.g. when called via timer callback or irqfd injection. I.e. attempting to fix the fastpath would run afoul of the same issue that was fixed by commit 55749769fe60 ("KVM: x86: Fix wall clock writes in Xen shared_info not to mark page dirty"). This also covers the pvclock writes kvm_guest_time_update() makes through the same cache; userspace assuming the page is always dirty covers those equally. Signed-off-by: Sean Christopherson [dwmw2: also update the KVM_GET_DIRTY_LOG note in api.rst, which named only the shared_info page] Signed-off-by: David Woodhouse Assisted-by: LLM --- Documentation/virt/kvm/api.rst | 5 +++-- arch/x86/kvm/xen.c | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index e0430cc750c9..b24aac070b11 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -385,8 +385,9 @@ The bits in the dirty bitmap are cleared before the ioc= tl returns, unless KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2 is enabled. For more information, see the description of the capability. =20 -Note that the Xen shared_info page, if configured, shall always be assumed -to be dirty. KVM will not explicitly mark it such. +Note that the Xen shared_info page and vcpu_info area, if configured, +shall always be assumed to be dirty. KVM will not explicitly mark them +such. =20 =20 4.10 KVM_RUN diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index 0ba684bf01d3..94eb55a6d702 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -778,7 +778,6 @@ void kvm_xen_inject_pending_events(struct kvm_vcpu *v) WRITE_ONCE(vi->evtchn_upcall_pending, 1); } =20 - kvm_gpc_mark_dirty_in_slot(gpc); read_unlock_irqrestore(&gpc->lock, flags); =20 /* For the per-vCPU lapic vector, deliver it as MSI. */ @@ -2556,7 +2555,7 @@ void kvm_xen_init_vcpu(struct kvm_vcpu *vcpu) =20 kvm_gpc_init(&vcpu->arch.xen.runstate_cache, vcpu->kvm); kvm_gpc_init(&vcpu->arch.xen.runstate2_cache, vcpu->kvm); - kvm_gpc_init(&vcpu->arch.xen.vcpu_info_cache, vcpu->kvm); + __kvm_gpc_init(&vcpu->arch.xen.vcpu_info_cache, vcpu->kvm, true); kvm_gpc_init(&vcpu->arch.xen.vcpu_time_info_cache, vcpu->kvm); } =20 --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.92.199 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427099; b=qTxDF4fKSPiulai9ZhzS4JYA6WRBEnplOdm8VFBXz8yNnkjtBxDVlHI6GnmQeMFLa3Xn ZNiNFIysuhSshYEeq0njN3DLdRGn9n8DRLICZeuJUIVK057LEj3M3AX3LK09XvO88ggIW 8rQpjTKeihj4O54Gbmkgk2Xg+NHFwl8TS/IO4YYlV6pjl2dOR69cqwq7MzZcSJ8aC/1BS WUyuxE0lgG8FdsLItYsRAc/xJlrMwAAbCxTih56rOhVb0Z+BB8/ReBO0DSm17lw6dlgo/ luFHyM3P/fj6MmITkpfaoywt1Y4Dj71EOnt1bQ85eGC/TktGKxEka1toZwOHsHdmUYg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427099; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=cFcjSX1F1kgoXLz+gVxRBvZE16O8bRWdEkwbe+t5w/8=; b=nUO/Ymndx6UZdqXIcaRjk7Cg/I7gbgyFfkjmzmA4CadO6t2ARiEbuJupS0OJs+7l36jj tm3WGMYcaAA4KyN1e/XI4q4tDlz37R/wEIYUP8fzK8ljGUG3cAy5rwkdZrDF/xLfYbSkD EitqUK4Q8CbfjrqhCnigi+HrGTqAO1q2EkJEQEyx5Fp5BAIR7J9OD4o9O3Cr1t0lTKNut sSeUf42ad7L/2sXR8gQC08X4Vo2ZtwtgPe3rKF8FDJlsqDdw4S3EtehXSVxCRLF0rCtb/ RvvccEFraUpSUwBICutjwoSc95/ZVBUYHnXFty2N19MD3J2Y8GEcEhlD+0Hx1xyJidg== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; arc=none smtp.remote-ip=90.155.92.199 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 74AC0606A326; Mon, 19 Oct 2026 16:24:57 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 74AC0606A326 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=desiato.srs.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=cFcjSX1F1kgoXLz+gVxRBvZE16O8bRWdEkwbe+t5w/8=; b=XLuE1yYy9yjBFmLdj+GA1C+fFF xsIvO/uhY3KXnrgvo0PXA8TnG25Z2/7afQnDTBFByZr8dh8tVDAz0sN4mIr0YJh6LG3g61Egi6kEN NI93085TwcojVANhO5TBpZC+GSPwyNBuB10RvDmK3ZSh4rGOG3DIucBfoq0KKzJcM34yn0Px/UiPX LC4B/ifxOmV9HHn0B8zSTnfAaiaxy6tA0+mWPB3MSNsdT8p2WLvXsQuTLVu3zcW/gwqhD12mQBD79 1xJMpH7LO3uCfFumkXXvIUb/wkm7R9dGEGSO36e2PT0ORygQ6wCPmxVnMjMb5LOs8ZxNwjX41jQx+ LwshNjvQ==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by desiato.infradead.org with esmtpsa (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAK-00000003qso-1HsD; Mon, 19 Oct 2026 16:24:48 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009h7-3EHy; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 10/12] KVM: x86: Request the guest TLB flush from record_steal_time() Date: Mon, 19 Oct 2026 17:24:41 +0100 Message-ID: <20261019162445.37219-11-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by desiato.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse record_steal_time() performs a TLB flush on the guest's behalf when the guest sets KVM_VCPU_FLUSH_TLB. With shadow paging, kvm_vcpu_flush_tlb_guest() ends up in mmu_sync_children(), which can reschedule via cond_resched_rwlock_write(). That is why the flush currently sits between a user_access_end() and a fresh user_access_begin(): it cannot be done while the guest page is mapped for access. Raise KVM_REQ_TLB_FLUSH_GUEST instead of flushing inline, and move the processing of KVM_REQ_STEAL_UPDATE ahead of where the TLB flush requests are serviced in vcpu_enter_guest() so that the flush still happens before the vCPU enters the guest. There is no requirement for it to complete any earlier than that. kvm_make_request() on the vCPU which is running is nothing more than a barrier and a set_bit(), so unlike the flush itself it can be done from any context =E2=80=94 which lets the surrounding access be simplified later. Note that the xchg() must stay where it is: it atomically consumes any KVM_VCPU_FLUSH_TLB which the guest has set, as required by commit b043138246a4 ("x86/KVM: Make sure KVM_VCPU_FLUSH_TLB flag is not missed"). Reported-by: Sashiko AI review Closes: https://lore.kernel.org/all/20260530061932.7849D1F00893@smtp.kernel= .org Suggested-by: Sean Christopherson Signed-off-by: David Woodhouse Assisted-by: LLM --- arch/x86/kvm/x86.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index eadcaeee651a..20119d07b477 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -2106,8 +2106,22 @@ static void record_steal_time(struct kvm_vcpu *vcpu) =20 trace_kvm_pv_tlb_flush(vcpu->vcpu_id, st_preempted & KVM_VCPU_FLUSH_TLB); + /* + * Request the flush instead of performing it inline. With + * shadow paging kvm_vcpu_flush_tlb_guest() can reschedule (via + * mmu_sync_children()), which is why the user access above has + * to be closed before it. Requesting the flush is just a bit + * set on the running vCPU, and the request is serviced before + * the vCPU enters the guest; nothing requires the flush to + * have completed any earlier than that. + * + * Note the xchg above must remain as-is: it atomically + * consumes any KVM_VCPU_FLUSH_TLB the guest set, as required + * by commit b043138246a4 ("x86/KVM: Make sure + * KVM_VCPU_FLUSH_TLB flag is not missed"). + */ if (st_preempted & KVM_VCPU_FLUSH_TLB) - kvm_vcpu_flush_tlb_guest(vcpu); + kvm_make_request(KVM_REQ_TLB_FLUSH_GUEST, vcpu); =20 if (!user_access_begin(st, sizeof(*st))) goto dirty; @@ -8089,6 +8103,14 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) if (unlikely(r)) goto out; } + /* + * Process the steal time update before the TLB flush requests + * are serviced below; record_steal_time() requests + * KVM_REQ_TLB_FLUSH_GUEST on the guest's behalf rather than + * performing the flush itself. + */ + if (kvm_check_request(KVM_REQ_STEAL_UPDATE, vcpu)) + record_steal_time(vcpu); if (kvm_check_request(KVM_REQ_MMU_SYNC, vcpu)) kvm_mmu_sync_roots(vcpu); if (kvm_check_request(KVM_REQ_LOAD_MMU_PGD, vcpu)) @@ -8138,8 +8160,6 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) r =3D 1; goto out; } - if (kvm_check_request(KVM_REQ_STEAL_UPDATE, vcpu)) - record_steal_time(vcpu); if (kvm_check_request(KVM_REQ_PMU, vcpu)) kvm_pmu_handle_event(vcpu); if (kvm_check_request(KVM_REQ_PMI, vcpu)) --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.50.34 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427097; b=TnTJkLbSaKxsHXvgPJvUieVFt4IO1WPuxStKzAKKUhtddWBwiATLi7XKPXhlD3AwBrs+ 0a6BQvgF8T/y3AKDuUniTQzyWXIfDL30xPsNFms9jZ39cFjNwZmvrIFh5QwVwT+MEQ4Xl cGw7TPB8g37b5a+jzDDLOLOO0WWEll1YVntgZ//dCspwIe26adUpeE/He+7vb1g1AwPkP /y5Xsb9eejOlRWQF55Dh53Yw68Ygv1bchDcxKs52dZM9ajHtDPJxLdQMTirtgmyxIxwll 2s9brdxyu6ztwv+pMJvu1Jq+3KVziqkFVkaxd5ng7u5gN/EhUyQGc5FnCGsai9mYMBg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427097; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=+ROGGbrwRxlzxKuroErkPGxq0J1T+FijXvzUEmP253o=; b=wkYa9BqAVW1kokkmj0BgYuCDEo7C+6beFQV0AYiXzKGLiRlRjTfIBxeaVVcuXeGdf0p9 e8JotSHJG5TEJUU35DiMorpQYt8dzzttsfbjZc80Dn1pkkiXiQfobm1+/E451eHkom7pM Zi+7/h9J36GWUacs3fF1SyBdW87uq2KKYzLHu561Rq9KcQCuycRg5ezt+I0nqb8ZqvchW iA60R+tRUotj0Fm1GoQkLKT0ccIINDbUo03EzeVfl/vw7sYXzg/rqp+0VNefEb8J15Nxc hJUYBd+3s+sW5I5uhm/F17J9JNlFio9FGRRk8mDVtbXo/Ba77qzO+zv4Uuyik2/5JPA== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail header.from=infradead.org; spf=none smtp.mailfrom=casper.srs.infradead.org; arc=none smtp.remote-ip=90.155.50.34 Received: from casper.infradead.org (casper.infradead.org [90.155.50.34]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9ECE1606A31D; Mon, 19 Oct 2026 16:24:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 9ECE1606A31D Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=casper.srs.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=+ROGGbrwRxlzxKuroErkPGxq0J1T+FijXvzUEmP253o=; b=vtJd7WPmuk/DgBvf71j20tgiVQ wyGJPG23Q06ZKuDUGsr8JJ6FV4/UjQskCsxvAUW78H7H5skcQ4E6vTcl4Ml1pI9MidltDsohgoWSZ Ee/gf0e2UeNM8+h1YyMf+B0wsnzqkwnd/+lwleWqAkQfqdzUtuBh8qsx70tyY7rclf+cGYkEIpE6F jR+pNPVzfJsSxUuZIbNBlutWl8UU6sGKOBS0DyzvQWYKPrly14bn/7HzHdNTIDSS3F5SuZj5juuJK WVCrfctGbLV5ZMSNBu4JWpQG0Xfs250Z0lC27Cl6CkmqPiZdFZekCFwf5gXnA4nFxWwFOqQ+SQIbD CpU74KJg==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by casper.infradead.org with esmtpsa (Exim 4.99.1 #2 (Red Hat Linux)) id 1xIqAH-00000009VGQ-48rv; Mon, 19 Oct 2026 16:24:46 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009hB-3Mgk; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 11/12] KVM: selftests: Add Xen evtchn vs memslot-update race test Date: Mon, 19 Oct 2026 17:24:42 +0100 Message-ID: <20261019162445.37219-12-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse Race a guest EVTCHNOP_send loop against a host thread which deletes and recreates the shared_info memslot. Delivery which hits the update window now returns -EAGAIN and the hypercall is re-executed; if KVM instead waited in the vCPU's exit-handling context =E2=80=94 under vcpu_run= 's kvm->srcu read section =E2=80=94 it would deadlock against kvm_swap_active_memslots() waiting out that reader, and this test livelocks within seconds. Signed-off-by: David Woodhouse Assisted-by: LLM --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../kvm/x86/xen_evtchn_memslot_race_test.c | 208 ++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/xen_evtchn_memslot_race= _test.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selft= ests/kvm/Makefile.kvm index 96bab7002d39..e7e17d19d6cb 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -144,6 +144,7 @@ TEST_GEN_PROGS_x86 +=3D x86/xss_msr_test TEST_GEN_PROGS_x86 +=3D x86/debug_regs TEST_GEN_PROGS_x86 +=3D x86/tsc_msrs_test TEST_GEN_PROGS_x86 +=3D x86/vmx_pmu_caps_test +TEST_GEN_PROGS_x86 +=3D x86/xen_evtchn_memslot_race_test TEST_GEN_PROGS_x86 +=3D x86/xen_shinfo_test TEST_GEN_PROGS_x86 +=3D x86/xen_vmcall_test TEST_GEN_PROGS_x86 +=3D x86/sev_dbg_test diff --git a/tools/testing/selftests/kvm/x86/xen_evtchn_memslot_race_test.c= b/tools/testing/selftests/kvm/x86/xen_evtchn_memslot_race_test.c new file mode 100644 index 000000000000..08dd1aac884f --- /dev/null +++ b/tools/testing/selftests/kvm/x86/xen_evtchn_memslot_race_test.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * xen_evtchn_memslot_race_test + * + * A guest EVTCHNOP_send whose delivery races a DELETE/re-CREATE of the + * memslot holding the shared_info page. The delivery path runs in the + * vCPU's exit-handling context, under vcpu_run's kvm->srcu read + * section; if it waits there for the memslot update to complete, it + * deadlocks against kvm_swap_active_memslots()'s + * synchronize_srcu_expedited() waiting out that very read section. + * Regression test for that deadlock: KVM must leave the hypercall + * uncompleted so the guest re-executes it, rather than wait in exit + * context. + * + * The guest sends events in a tight loop; a host thread deletes and + * recreates the shared_info slot in a tight loop. On a broken host + * this livelocks within a second or two (the test then dies by + * timeout); on a fixed host the guest completes all N + * iterations (sends which hit the no-slot gap between DELETE and + * re-CREATE legitimately fail; most deliver). + */ +#include +#include + +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" + +#include + +#define SHINFO_REGION_GPA 0xc0000000ULL +#define SHINFO_REGION_SLOT 10 + +#define EVTCHN_PORT 15 +#define TEST_ITERATIONS 4000 + +#define EVTCHNOP_send 4 +#define __HYPERVISOR_event_channel_op 32 + +#define XEN_HYPERCALL_MSR 0x40000000 + +#define EVTCHNSTAT_ipi 5 + +struct evtchn_send { + uint32_t port; +}; + +static void guest_code(void) +{ + struct evtchn_send s =3D { .port =3D EVTCHN_PORT }; + int delivered =3D 0; + int i; + + for (i =3D 0; i < TEST_ITERATIONS; i++) { + /* + * The delivery may legitimately fail while the shared_info + * slot is mid-churn (between DELETE and re-CREATE there is + * no slot at all, which is -EFAULT, not a transient). The + * failure mode under test is the vCPU never coming back at + * all (deadlock -> timeout), not the hypercall rc. + */ + if (!__xen_hypercall(__HYPERVISOR_event_channel_op, + EVTCHNOP_send, &s)) + delivered++; + GUEST_SYNC(i); + } + + /* The churn must not have starved delivery entirely. */ + GUEST_ASSERT(delivered); + + /* Report the delivered count (always > TEST_ITERATIONS - sync i). */ + GUEST_SYNC(TEST_ITERATIONS + delivered); + + GUEST_DONE(); +} + +static atomic_bool churn_stop; + +static void *memslot_churn(void *arg) +{ + struct kvm_vm *vm =3D arg; + + while (!atomic_load(&churn_stop)) { + vm_mem_region_delete(vm, SHINFO_REGION_SLOT); + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, + SHINFO_REGION_GPA, + SHINFO_REGION_SLOT, 2, 0); + } + return NULL; +} + +int main(int argc, char *argv[]) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + pthread_t thread; + struct ucall uc; + int done =3D 0; + int r; + + int xen_caps =3D kvm_check_cap(KVM_CAP_XEN_HVM); + + TEST_REQUIRE(xen_caps & KVM_XEN_HVM_CONFIG_SHARED_INFO); + TEST_REQUIRE(xen_caps & KVM_XEN_HVM_CONFIG_EVTCHN_SEND); + + vm =3D vm_create_with_one_vcpu(&vcpu, guest_code); + + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, + SHINFO_REGION_GPA, SHINFO_REGION_SLOT, 2, 0); + + struct kvm_xen_hvm_config hvmc =3D { + .flags =3D KVM_XEN_HVM_CONFIG_INTERCEPT_HCALL | + KVM_XEN_HVM_CONFIG_EVTCHN_SEND, + .msr =3D XEN_HYPERCALL_MSR, + }; + vm_ioctl(vm, KVM_XEN_HVM_CONFIG, &hvmc); + + struct kvm_xen_hvm_attr lm =3D { + .type =3D KVM_XEN_ATTR_TYPE_LONG_MODE, + .u.long_mode =3D 1, + }; + vm_ioctl(vm, KVM_XEN_HVM_SET_ATTR, &lm); + + struct kvm_xen_hvm_attr ha =3D { + .type =3D KVM_XEN_ATTR_TYPE_SHARED_INFO, + .u.shared_info.gfn =3D SHINFO_REGION_GPA / PAGE_SIZE, + }; + vm_ioctl(vm, KVM_XEN_HVM_SET_ATTR, &ha); + + struct kvm_xen_vcpu_attr vi =3D { + .type =3D KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO, + .u.gpa =3D SHINFO_REGION_GPA + 0x40, + }; + vcpu_ioctl(vcpu, KVM_XEN_VCPU_SET_ATTR, &vi); + + /* Loop the port back to this vCPU as an IPI. */ + struct kvm_xen_hvm_attr evt =3D { + .type =3D KVM_XEN_ATTR_TYPE_EVTCHN, + .u.evtchn.send_port =3D EVTCHN_PORT, + .u.evtchn.type =3D EVTCHNSTAT_ipi, + .u.evtchn.flags =3D 0, + .u.evtchn.deliver.port.port =3D EVTCHN_PORT, + .u.evtchn.deliver.port.vcpu =3D vcpu->id, + .u.evtchn.deliver.port.priority =3D KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEV= EL, + }; + vm_ioctl(vm, KVM_XEN_HVM_SET_ATTR, &evt); + + r =3D pthread_create(&thread, NULL, memslot_churn, vm); + TEST_ASSERT(!r, "pthread_create() failed: %d", r); + + /* + * Self-guard: on a kernel with the deadlock under test, the vCPU + * never returns from KVM_RUN. Die with SIGALRM rather than hang + * when run standalone. One budget for the whole run =E2=80=94 a healthy + * kernel finishes in seconds, and a per-iteration bound would + * impose a KVM_RUN latency claim this test doesn't make. + */ + alarm(90); + + while (!done) { + vcpu_run(vcpu); + + /* + * In the window where the shared_info slot is fully deleted + * (between the churn thread's DELETE and re-CREATE), the + * delivery fails with -EFAULT and the hypercall bounces to + * userspace as KVM_EXIT_XEN. Complete it with an error + * result and keep going; the failure mode under test is + * the vCPU never coming back (deadlock -> timeout), not + * the hypercall rc. + */ + if (vcpu->run->exit_reason =3D=3D KVM_EXIT_XEN && + vcpu->run->xen.type =3D=3D KVM_EXIT_XEN_HCALL) { + vcpu->run->xen.u.hcall.result =3D -EFAULT; + continue; + } + + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_SYNC: + /* + * The final sync reports how many of the sends were + * delivered: purely a diagnostic for how much of the + * churn the guest actually raced with. + */ + if (uc.args[1] >=3D TEST_ITERATIONS) + pr_info("%lu of %u sends delivered\n", + uc.args[1] - TEST_ITERATIONS, + TEST_ITERATIONS); + break; + case UCALL_DONE: + done =3D 1; + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + default: + TEST_FAIL("Unexpected ucall %lu", uc.cmd); + } + } + + atomic_store(&churn_stop, true); + pthread_join(thread, NULL); + alarm(0); + + kvm_vm_free(vm); + return 0; +} --=20 2.43.0 From nobody Sat Oct 24 14:06:45 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=90.155.50.34 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792427102; b=SVUuoo/Iyw4+gtoKKLk8BuK2C7sXT4AahdHW0MjNsFoyax8VWoGxC9enXIf0SXcROX8k 44B84730O7GBiRqBlmUj+C5+RE1yFvAOXSpKA8CFes9KHE7Hv9fPLjkm4rRZnhTmxJlhO JpBBSYkDEeJUJDC2eoJUpIU+2SpqIomGC6wjkKPTrMrC3YCUMQFywp7n5n+Lod6W6iyMq 6eWwqvqWVG51VrJnjaesxCfPnKa8xLLseQ3f+9FL98uOn1Qme6ZgdL95WGlcJAlHJcuQd 3OLDdq3S6FBrZdomBlN3YY6q11i/z/vYT261+B/TztJpKCBnVl+fhRIcqc+h5Kd5mvg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792427102; h=DMARC-Filter:DKIM-Signature:Received:Received:From:To:Cc:Subject: Date:Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Type:Content-Transfer-Encoding:Sender:X-SRS-Rewrite; bh=ClGhwyKqY7iwWEkCZXSiNmWOQpf0ARU4x1WgJZbziLM=; b=hRr1McrAnNDJs4wPPFZWEtvX92BTS//Qv4nEHqn9SbaDs1t2GoPjzFcJKZYo16FseHdt C1yGQiyNfAvzMgaFrQEGOZb44nhXB8pM3zdTasHjTa7/OukuB+aD6UKxiF9cYBkZi+xLn f4MDEwTvKP50cSiFpag6jxNTUj/h/nW3dgCvJbRjVBoLGKrYdSmf26xXLYDsFXM81ltDL 05jvC5H/giZhnuHQVz5SoyJDJVhUWpwKk7933j9eufb4h15eGf8Rfzb88AVjtvRN5uiBa 99dPFsYyi2r7kCu0Db3bhN2G6uGrLaxcqL0uJRQan7YMol/+bp/gaK8WLvUjZoZ8MyA== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail header.from=infradead.org; spf=none smtp.mailfrom=casper.srs.infradead.org; arc=none smtp.remote-ip=90.155.50.34 Received: from casper.infradead.org (casper.infradead.org [90.155.50.34]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9ECB5606A31C; Mon, 19 Oct 2026 16:24:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 9ECB5606A31C Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=casper.srs.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=ClGhwyKqY7iwWEkCZXSiNmWOQpf0ARU4x1WgJZbziLM=; b=rqPGk1ZyqMHVRJ2mLi9534HZ6q qoAbnLcPsFHTJ0iqU0QvZKsfebtfUpqERF4auwpDdB2tHMBm/y0tFSLPdb3RphFnFVbt++kl/oDxM NFRnaKHDJCG7THzwzKzuXWyJ+t7Hs7+OHpp5aDIrUSXUZs0gPjHXhl9J+L+gqUkWXUAdY3VfVpvXe lC34BJFGmF01zniQ3Qhs/8o9U+Os+/H3Gg0OIkhlUtjmGeGKBv7rKJ8AyIMeIv7uCydHmsKoZ3Acx 9Z0Zck40pS+nkBp1SMA/fjCcLJc/3K7XLAYquGqGji7gjiTBra4OQQqtKRCtDNfzMETObl3WDsT/+ nFaq1sQg==; Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by casper.infradead.org with esmtpsa (Exim 4.99.1 #2 (Red Hat Linux)) id 1xIqAI-00000009VGR-04z3; Mon, 19 Oct 2026 16:24:46 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xIqAH-000000009hF-3Uzn; Mon, 19 Oct 2026 17:24:45 +0100 From: David Woodhouse To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Paolo Bonzini , Sean Christopherson , Paul Durrant , Vitaly Kuznetsov , Fred Griffoul , David Woodhouse Subject: [PATCH v4 12/12] KVM: selftests: Fix unreachable timer recovery in xen_shinfo_test Date: Mon, 19 Oct 2026 17:24:43 +0100 Message-ID: <20261019162445.37219-13-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019162445.37219-1-dwmw2@infradead.org> References: <20261019162445.37219-1-dwmw2@infradead.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse The final phase of xen_shinfo_test waits for a timer event while a worker thread repeatedly deactivates and reactivates the shared_info cache. The guest polls up to 100 times before asking the VMM to re-arm the timer, documenting the recovery protocol for kernels which drop a timer event that cannot be delivered. But the poll has no timeout. If the timer fires while the guest is already blocked in SCHEDOP_poll and the event is dropped =E2=80=94 which is precisely the case the recovery exists for =E2=80=94 nothing ever wakes the vCPU, the 100-poll loop never advances, and the test hangs with the SIGALRM watchdog disabled for this phase. The recovery protocol is only reachable when the drop happens to occur while the guest is awake. Give the poll a 10ms timeout, as the TEST_POLL_TIMEOUT phase already does, so a dropped event leads to the documented re-arm protocol instead of sleeping forever. The window for the drop is vanishingly small upstream, but widens dramatically once cache deactivation incurs a grace-period wait, hanging roughly a third of all runs. Fixes: a51abbbf2531 ("KVM: selftests: Add tests in xen_shinfo_test to detec= t lock races") Assisted-by: LLM Signed-off-by: David Woodhouse --- tools/testing/selftests/kvm/x86/xen_shinfo_test.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/kvm/x86/xen_shinfo_test.c b/tools/test= ing/selftests/kvm/x86/xen_shinfo_test.c index 3b11d0ee253f..729c0ba9ab4e 100644 --- a/tools/testing/selftests/kvm/x86/xen_shinfo_test.c +++ b/tools/testing/selftests/kvm/x86/xen_shinfo_test.c @@ -350,7 +350,14 @@ static void guest_code(void) p =3D (struct sched_poll) { .ports =3D ports, .nr_ports =3D 1, - .timeout =3D 0 + /* + * The poll must have a timeout: if the timer event is dropped + * while we are already blocked in the poll, nothing else will + * ever wake us, and the recovery protocol below could never + * run. (As with TEST_POLL_TIMEOUT above, KVM treats the + * timeout as relative.) + */ + .timeout =3D 10000000, }; =20 wait_for_timer: --=20 2.43.0