From nobody Sat Oct 24 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DF02D550DBA; Tue, 13 Oct 2026 20:48:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924525; cv=none; b=ZObdvBw0pmwI2wOi+YShBjudUhToj7RKCpew+nD6GPpBnHISmvtOM9iSSYkEjh9YEjXpdfeWSpxERqaGFlQM/dIqooqtVhKUSXdMM0Pzp0cLVn+UnkBz82iQZdS9LK0MHS1JdxykBae2DGeo0CgHVjLkLuTYpVFV+QrhVXJdddQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924525; c=relaxed/simple; bh=jdGNxbHHhF/6fdAGZaaomlxeX+vyF6KNZj7stzlAGj4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=We139XV0VEaVCmA8VVwwwmuj55L4HH0h9eZUfyXPfmiskz9INnzASyYG4851BuvPNL8AO3ma5tlzOccHL58JcQHQDtSM8hXGXVl7JYxPulUqzRuPNxhswZrHAg7RNWOQnvsxh3XsPOwee63JOdA9RomqdRHfVKa0RsBReRsKkYs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=g5+lmaK6; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="g5+lmaK6" 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=/VD9EG2KiE5OItNAh1xRhZZwJ3NvkM7B3uEI0rjcQOU=; b=g5+lmaK6Usufy0wiVGr8b97jFJ 9VFfEM95KJZoeWAL2+x31LOGVBJIidcVnFAZeKHm0HEc7d7yxv8l4l2b1PwPd7q9kR17o2xk8OWSd YHX7CbqApq2MS0OdFj6g2Oz+M5HT3C3XiCJCKmSJ6GkRsoaGEGO8OZzql+zHBmQjDxdPGfHuNBUMs dkMBB5ilPHkQ8siCykuANkJycK1sfk+FWKZY2LGtj5fcogHtyRgvxVYyGbMj4tPCvu5FlEVFmnRmJ PiwyANWnHT8Ry1Ldyvcpm62wzQRu5HdMAsKic9ug/5k99wYVhrA694crBuGUQjI4M2NwHpNEzbCK7 1jjtHhvg==; 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 1xGjQB-0000000G4C3-160J; Tue, 13 Oct 2026 20:48:38 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uA9-2OoQ; Tue, 13 Oct 2026 21:48:24 +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 01/10] KVM: pfncache: Return -EAGAIN for a lookup which hits an invalid memslot Date: Tue, 13 Oct 2026 21:48:14 +0100 Message-ID: <20261013204824.454208-2-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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 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 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 534F354EECB; Tue, 13 Oct 2026 20:48:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924523; cv=none; b=M0XndgAkjrlpcDR6uwXD7VGlfL2DLmqPYlGtZq+F84ID1RolH8mVVWvTRtaftbE4eW52rT7OWyKpW/sxGzdYDPNOte3Zn2RMOH1OXT1wg/j0XPnrH2ASOXgX0jGcB3gJ3pCGzV/oEY3iDoYV1nr9hBbZMNaEqqLiKr3iAj6lBJ4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924523; c=relaxed/simple; bh=LmmW12cqdAsFXwl+3hY4llUMSfiCZ0n3lbyL8fSPMDA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=YQWniu5HxCeHh7vXRYhXlfjDYfWDDGGK3+zOP2yWoSjSbjFJhu2Qyh36rGpANOO5TxnUFp8KwaSGl3ChDXVdfK6WlV//0sOWJg60JiIdnChL9rMZGqHr4kS77EzNlghST3OJxxQX147CYe+CVfY/2RiC/xveS/pM+uf1Pz3I854= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=XBHhd+6Z; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="XBHhd+6Z" 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=Or9qWonUOiBVNTXio0dO7dNYdRAsovFXzCL0x3qrXfE=; b=XBHhd+6ZOKPHy5F4AXXc1XNbyP OGHPDZqYen9o5uQXwJbm+7v7gkL1OaNbqTAzV251cHCRo6S2HaYrw3FG1PNvo8R4Apz991whH1OeI MNUqFIzZac+5httimflDFSLycqMEEPbgLEbNqd//PxRjvbyBgF8xSg4z6aXngpoP8OUJkw5Rx2Wgn WfKdKRuOSssXFFOtAHskMCFjSjW5lbt1v3RAQ17/qprfdEJjq8fj17MaMzQxB+dWczRxcXJc6jlsZ CgZDTsojaue8EvrET5FnDWRWVjUIhJd4Rcm3ViIeSY7LPF5uE6cNFWCUdQULYPa7pzFzO48jsNe25 alYFDOBw==; 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 1xGjQB-0000000G4C1-15yn; Tue, 13 Oct 2026 20:48:37 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uAC-2YFh; Tue, 13 Oct 2026 21:48:24 +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 02/10] KVM: pfncache: Add a 'wait' argument to the refresh and activate functions Date: Tue, 13 Oct 2026 21:48:15 +0100 Message-ID: <20261013204824.454208-3-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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 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 and is rejected with a lockdep assertion. 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 | 28 +++++----- include/linux/kvm_host.h | 17 ++++-- virt/kvm/pfncache.c | 114 ++++++++++++++++++++++++++++----------- 5 files changed, 112 insertions(+), 51 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 6aa00f746cba..fae893cd5762 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); @@ -825,7 +825,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_hva(&kvm->arch.xen.shinfo_cache, - (unsigned long)hva, PAGE_SIZE); + (unsigned long)hva, PAGE_SIZE, false); } } =20 @@ -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); @@ -973,7 +973,7 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, struct= kvm_xen_vcpu_attr *data) break; =20 r =3D kvm_gpc_activate_hva(&vcpu->arch.xen.vcpu_info_cache, - data->u.hva, sizeof(struct vcpu_info)); + data->u.hva, sizeof(struct vcpu_info), false); } =20 if (!r) @@ -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; } @@ -2011,7 +2011,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..88c1ee37550c 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1474,16 +1474,23 @@ 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 (lockdep-asserted): the update's + * synchronize_srcu() would deadlock against the wait. * * @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. @@ -1499,7 +1506,8 @@ int kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gp= a_t gpa, unsigned long len) * The semantics of this function are the same as those of kvm_gpc_activat= e(). 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 +1532,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 +1546,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..8504e26fa111 100644 --- a/virt/kvm/pfncache.c +++ b/virt/kvm/pfncache.c @@ -378,23 +378,57 @@ 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) +{ + int idx, ret; + + idx =3D srcu_read_lock(&gpc->kvm->srcu); + ret =3D __kvm_gpc_refresh(gpc, gpa, uhva); + srcu_read_unlock(&gpc->kvm->srcu, idx); + + return ret; +} + +int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsigned long len, bool = wait) { unsigned long uhva; + int ret; =20 - guard(mutex)(&gpc->refresh_lock); + if (wait) + lockdep_assert(!srcu_read_lock_held(&gpc->kvm->srcu)); =20 - if (!kvm_gpc_is_valid_len(gpc->gpa, gpc->uhva, len)) - return -EINVAL; + for (;;) { + scoped_guard(mutex, &gpc->refresh_lock) { + if (!kvm_gpc_is_valid_len(gpc->gpa, gpc->uhva, len)) + return -EINVAL; =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. - */ - uhva =3D kvm_is_error_gpa(gpc->gpa) ? gpc->uhva : KVM_HVA_ERR_BAD; + /* + * 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 +444,50 @@ 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); + if (wait) + lockdep_assert(!srcu_read_lock_held(&kvm->srcu)); =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 +496,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 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5C50A556B96; Tue, 13 Oct 2026 20:48:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924521; cv=none; b=LqqL5/GYTg/pAEsiTUBkDv+7ZGm7oRDt+wFYubgdngDAs2IGtmZgEcWPaeIK+hbwImGKuBPK3GlxT3QMzfN+avAOmFiSELSBnAh7EaqxtVa+rpEePk1uKxQvzRPojqZ+dlaPlSlYgR7pQvueVj2czDovVlyg9yMmdaHneWlA5Vg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924521; c=relaxed/simple; bh=Tv2kGfqjqBZqM57c4IkOohVNXpbSagmRJYCc7oE5Loo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=CdTzsl/PskkDtyzJuMlU3CJsIU1EpIyL/FXngIh+RecLqImkiUa2tiBpecv7dmX/vx2HP4tqSrNAUPdIMflfL2TmGKdO5T89cF5lx9WLsYZ0gWa6PTyfNWLnKn5RwbzGIgZU1t8MSALLxX5yTuO1n5hhSK7VbcITDKP0cuRsm1E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=pLLhiEA8; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="pLLhiEA8" 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=PvqYcbuaavEwOqc4z/KeIP6uAeJxRczsrGjkTaDLvHs=; b=pLLhiEA8IKC8+fnSrRshcPE8Y5 KQJgf5R+YIseEKjbsemMa0c1rRnaHFh9t/YquR1tX6+UJBAEhOQpS0eycZ8ZRpiX6KxboQYtpITPw XYtiUOyRZjRrMAoXFQ5wV4Z0M1ERVFTaVrVtsJTOaX+/vWGwYdFLg/jsXsSYEBSlE+dblAwqHdEGf G0kExwj+z6NuImR4khFRNhtw4ljFnI030cuokAc8/LNtGS2ilsRtJuYL/VSEALvatxaegaL1Zwlxg NHQKkvEW/ubLepjApG0T76Hv8jTBI9dMmA7T7oQtCej37EFaYCq+GzfVE4C6DZEGyQ5PyHRzE73s6 +Dn7y8Dg==; 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 1xGjQB-0000000G4C8-16aR; Tue, 13 Oct 2026 20:48:37 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uAF-2fqN; Tue, 13 Oct 2026 21:48:24 +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 03/10] KVM: x86/xen: Wait out memslot updates in the Xen attribute ioctls Date: Tue, 13 Oct 2026 21:48:16 +0100 Message-ID: <20261013204824.454208-4-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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: 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. kvm_xen_shared_info_init() gains the same flag for its refresh: true from the attribute ioctls, false from the hypercall page MSR write, which runs in vCPU context under kvm->srcu and keeps its existing failure behaviour. That one could retry by restarting the WRMSR, but that is painful to plumb for an error which only matters to legacy userspace (the non-_HVA shared_info). 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/kvm/xen.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index fae893cd5762..76708b896026 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,13 +44,12 @@ 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); =20 read_lock_irq(&gpc->lock); while (!kvm_gpc_check(gpc, PAGE_SIZE)) { read_unlock_irq(&gpc->lock); =20 - ret =3D kvm_gpc_refresh(gpc, PAGE_SIZE, false); + ret =3D kvm_gpc_refresh(gpc, PAGE_SIZE, wait); if (ret) goto out; =20 @@ -100,7 +99,6 @@ static int kvm_xen_shared_info_init(struct kvm *kvm) read_unlock_irq(&gpc->lock); =20 out: - srcu_read_unlock(&kvm->srcu, idx); return ret; } =20 @@ -792,19 +790,15 @@ 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; - 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 @@ -813,7 +807,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, false); + gfn_to_gpa(gfn), PAGE_SIZE, true); } } else { void __user * hva =3D u64_to_user_ptr(data->u.shared_info.hva); @@ -825,14 +819,12 @@ int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_= xen_hvm_attr *data) r =3D 0; } else { r =3D kvm_gpc_activate_hva(&kvm->arch.xen.shinfo_cache, - (unsigned long)hva, PAGE_SIZE, false); + (unsigned long)hva, PAGE_SIZE); } } =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 +929,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: @@ -960,7 +951,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), false); + data->u.gpa, sizeof(struct vcpu_info), true); } else { if (data->u.hva =3D=3D 0) { kvm_gpc_deactivate(&vcpu->arch.xen.vcpu_info_cache); @@ -973,7 +964,7 @@ int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, struct= kvm_xen_vcpu_attr *data) break; =20 r =3D kvm_gpc_activate_hva(&vcpu->arch.xen.vcpu_info_cache, - data->u.hva, sizeof(struct vcpu_info), false); + data->u.hva, sizeof(struct vcpu_info)); } =20 if (!r) @@ -990,7 +981,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), false); + sizeof(struct pvclock_vcpu_time_info), true); if (!r) kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu); break; @@ -1023,7 +1014,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, false); + data->u.gpa, sz1, true); if (r) goto deactivate_out; =20 @@ -1034,12 +1025,14 @@ 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); + data->u.gpa + sz1, sz2, true); if (r) goto deactivate_out; } =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 +1045,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 +1127,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 +1178,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; } @@ -1329,7 +1325,7 @@ int kvm_xen_write_hypercall_page(struct kvm_vcpu *vcp= u, u64 data) * correct place. */ if (kvm->arch.xen.shinfo_cache.active && - kvm_xen_shared_info_init(kvm)) + kvm_xen_shared_info_init(kvm, false)) r =3D 1; } mutex_unlock(&kvm->arch.xen.xen_lock); --=20 2.43.0 From nobody Sat Oct 24 13:21:28 2026 Received: from casper.infradead.org (casper.infradead.org [90.155.50.34]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9887655199D; Tue, 13 Oct 2026 20:48:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.50.34 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924520; cv=none; b=KWWzm0coUw0nANNs0VbcY+Gv9osDKtrMJp5UsY9RQogXw9rftnkC2yjbU2rhSP3pXj6P18Bvx+AUjxw1RZsjBGHDZ5Ziosw6YXBC/0IBgLMt4TzWf+/c42BkExEbWI2qsPkvGqDYYvZVMZFwelgnqEM1wd7eQhZC6OfS6L9wxd8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924520; c=relaxed/simple; bh=zlPuA7YFnFriiiIyBGmvTv1Y6Or/TwAkfSBHzruNWi4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=YiLxYmv52jvjNs9f6Y5urc+Zio9AIqQufp83edFa98JzTvA8NWy4wmbvESVUahDIC28KHgXn4IlwFFl1ow1obKF0CYZGx9si5xXC5LLkzMulhgDa9EZoF+V4hB2WYhH2n8VD8W72JMChkgIucgrCY4wpJsZrHCof1FIesiNBLHY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=casper.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=KCiKUn6U; arc=none smtp.client-ip=90.155.50.34 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) header.d=infradead.org header.i=@infradead.org header.b="KCiKUn6U" 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=3Pt73DAjy+LiqLIPNn8m05dWHfc70bDX9fJL2l508LQ=; b=KCiKUn6U8KKSSDFfCCeC8JpWZc XJdGN9DjHsuqfG4RehE9hoFyecWSYJ5R/aeXxgYjsfhm/SUcn1s+JndK9zwio4Fkg1nipi/txCpfX IGSL1DkPZAF04GYz44wTceMZfRftccP+RF7fXo4YW5fdBuaRKxwn5sOYFkEnZoff34efJf8s+bolE d5eDN8TaAs/J5K/PylnhjzuQls5EI7nDCBpaKHzJt1LqlSv2zx1CCMyhG2uTKTxMwKpOhTzp2LA4O adHJVG8xBpTDu3XZ8wl8aNs87T0EgnxWjQdKvLurJCR2WRlUIuE75YcaxtlfyN6KPv7KayKMKQyaG omsDLHiQ==; 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 1xGjQ9-0000000FmWe-0jTG; Tue, 13 Oct 2026 20:48:27 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uAI-2syM; Tue, 13 Oct 2026 21:48:24 +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 04/10] KVM: x86/xen: Don't lose the timer event when delivery fails transiently Date: Tue, 13 Oct 2026 21:48:17 +0100 Message-ID: <20261013204824.454208-5-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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 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. The same applies when the shared_info cache has been deactivated (-EINVAL). 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. The drop is vanishingly rare upstream, but hangs roughly half of all xen_shinfo_test runs once the following atomic SRCU conversion 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 | 36 ++++++++++++++----- .../selftests/kvm/x86/xen_shinfo_test.c | 11 +++--- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index 76708b896026..aea6572163d0 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -106,13 +106,23 @@ 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); + + /* + * Deliberately leave timer_pending set if the memslot is + * being updated (-EAGAIN) or the shared_info cache has + * been deactivated (-EINVAL); delivery is retried on the + * next entry pass. + */ + if (rc =3D=3D -EAGAIN || rc =3D=3D -EINVAL) + return; =20 vcpu->arch.xen.timer_expires =3D 0; atomic_set(&vcpu->arch.xen.timer_pending, 0); @@ -1263,12 +1273,13 @@ int kvm_xen_vcpu_get_attr(struct kvm_vcpu *vcpu, st= ruct kvm_xen_vcpu_attr *data) =20 case KVM_XEN_VCPU_ATTR_TYPE_TIMER: /* - * 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 + * 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 with the pending flag set, if + * the shared_info page was transiently 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) { @@ -1287,8 +1298,17 @@ int kvm_xen_vcpu_get_attr(struct kvm_vcpu *vcpu, str= uct 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. */ - if (vcpu->arch.xen.timer_expires) + if (vcpu->arch.xen.timer_expires && + !atomic_read(&vcpu->arch.xen.timer_pending)) hrtimer_start_expires(&vcpu->arch.xen.timer, HRTIMER_MODE_ABS_HARD); =20 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 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5306E5505F8; Tue, 13 Oct 2026 20:48:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924523; cv=none; b=X6jXBTKYIaCyOh8AiAc5MkPvi5uMXo/bjVedzM+rpSRlPYB7lTm4RqrbFq7kcmnI8oGBx42j4GZvvvjKj28EwOT8XL6+wA/uGTNAplvoI05S0w/T7sa7wwT2/YHFKfg7sVP/8lAb7IHwzLX850kyspnjvKTHpdV9HPWC4snYvcU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924523; c=relaxed/simple; bh=uogmRdzxzmZ64l0HWk4ttWwMhBd/nKESqloQFHSXb8c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=fBkn+4BdH0c5h/IGfJIu/qX9QCOEnhFmqsrmb2wHsdOO6Dhz99zx8Uh9X/t3jBfRxXFxElnyDn4hNnjZ3GBUFvH0hSKf1XlCOhFdb8Exj/pc650ETM3JG6AGuQIR2Ct7a2U8+1/KedOX8jhXdnIrGamG9uCn32zHz+CtGc+wlww= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=M6fPKxf1; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="M6fPKxf1" 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=8vnFWRVZ0PaBjCtFDFpdh5SJtBWKVRiJVfaR1ujX0LU=; b=M6fPKxf1pIB9tI1//Zp4VAM0mX V3m2GMICs5gwJJnG+ZCA1XN/G7vu7jHd57ZR4Efy1vvX86ct5d9vlE7LZKVt6Gd6tBqRnyf2Y7gt8 2NqQZ3EiIwlqrLqwQSKvmC1RZiUCdgT9p5jIcXKMcOpU+Ilnd/VDeBSlcfB9G+Cd8FmlxtE7JcFrb PEMLX3h1A4X7ljtz07kOt4BUJgdl+nEz7juaTbgegrC+7HrnS/SzKVEUFMne1N3l+O9HjR4ILsnIO 7bnulkSfolC3uJJDkJK6gBdV12wArKg7BPif0zAC5hGZsYavVQImL9Be8n7u9ILHTi10Nn1/32nzd 99tGc2uQ==; 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 1xGjQB-0000000G4C2-0wla; Tue, 13 Oct 2026 20:48:36 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uAL-3C1u; Tue, 13 Oct 2026 21:48:24 +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 05/10] KVM: x86/xen: Retry event channel delivery which races a memslot update Date: Tue, 13 Oct 2026 21:48:18 +0100 Message-ID: <20261013204824.454208-6-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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. (Its other caller, the KVM_XEN_VCPU_GET_ATTR timer flush, must not wait under xen_lock =E2=80=94 a vCPU writing the hypercall page MSR blocks on that lock inside its kvm->srcu section =E2=80=94 and doesn't need to: 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/irq.c | 2 +- arch/x86/kvm/xen.c | 65 ++++++++++++++++++++++++++++++---------------- arch/x86/kvm/xen.h | 4 +-- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/arch/x86/kvm/irq.c b/arch/x86/kvm/irq.c index 871977288272..4e986eb4c3c3 100644 --- a/arch/x86/kvm/irq.c +++ b/arch/x86/kvm/irq.c @@ -171,7 +171,7 @@ void kvm_inject_pending_timer_irqs(struct kvm_vcpu *vcp= u) if (lapic_in_kernel(vcpu)) kvm_inject_apic_timer_irqs(vcpu); if (kvm_xen_timer_enabled(vcpu)) - kvm_xen_inject_timer_irqs(vcpu); + kvm_xen_inject_timer_irqs(vcpu, false); } =20 void __kvm_migrate_timers(struct kvm_vcpu *vcpu) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index aea6572163d0..ad7708be6c04 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 @@ -102,7 +103,7 @@ static int kvm_xen_shared_info_init(struct kvm *kvm, bo= ol wait) return ret; } =20 -void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu) +void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu, bool wait) { if (atomic_read(&vcpu->arch.xen.timer_pending) > 0) { struct kvm_xen_evtchn e; @@ -113,7 +114,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, wait); =20 /* * Deliberately leave timer_pending set if the memslot is @@ -661,10 +662,11 @@ void kvm_xen_inject_vcpu_vector(struct kvm_vcpu *v) */ void kvm_xen_inject_pending_events(struct kvm_vcpu *v) { - unsigned long evtchn_pending_sel =3D READ_ONCE(v->arch.xen.evtchn_pending= _sel); + unsigned long evtchn_pending_sel; struct gfn_to_pfn_cache *gpc =3D &v->arch.xen.vcpu_info_cache; unsigned long flags; =20 + evtchn_pending_sel =3D READ_ONCE(v->arch.xen.evtchn_pending_sel); if (!evtchn_pending_sel) return; =20 @@ -1284,7 +1286,7 @@ int kvm_xen_vcpu_get_attr(struct kvm_vcpu *vcpu, stru= ct kvm_xen_vcpu_attr *data) */ if (vcpu->arch.xen.timer_expires) { hrtimer_cancel(&vcpu->arch.xen.timer); - kvm_xen_inject_timer_irqs(vcpu); + kvm_xen_inject_timer_irqs(vcpu, false); } =20 data->u.timer.port =3D vcpu->arch.xen.timer_virq; @@ -1791,8 +1793,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], @@ -1983,7 +1998,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; @@ -2020,16 +2036,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); @@ -2044,7 +2057,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 /* @@ -2118,7 +2131,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. @@ -2347,7 +2360,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; @@ -2357,7 +2374,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 /* @@ -2366,18 +2383,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) diff --git a/arch/x86/kvm/xen.h b/arch/x86/kvm/xen.h index 9d04e350bdb1..f6202510bbd6 100644 --- a/arch/x86/kvm/xen.h +++ b/arch/x86/kvm/xen.h @@ -100,7 +100,7 @@ static inline int kvm_xen_has_pending_timer(struct kvm_= vcpu *vcpu) return 0; } =20 -void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu); +void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu, bool wait); #else static inline int kvm_xen_write_hypercall_page(struct kvm_vcpu *vcpu, u64 = data) { @@ -161,7 +161,7 @@ static inline int kvm_xen_has_pending_timer(struct kvm_= vcpu *vcpu) return 0; } =20 -static inline void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu) +static inline void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu, bool w= ait) { } =20 --=20 2.43.0 From nobody Sat Oct 24 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 687DB54EED8; Tue, 13 Oct 2026 20:48:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924515; cv=none; b=bmH2s3GLD9FwYfly9p848ONp2VDRbeMrsLY7Pj9lkKnWeisPgmsBzutUE9cp+ecwhv/MJCjLtlo5RvKP6CzLynMR420HI5BabDJfpzMPAObfUVGuxq6K7ErCXtwTvee/aIkDUXHEHJyq1Yffw70LoyxbnvVqQsdxBN69YgHIxjg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924515; c=relaxed/simple; bh=q3gnfMz4oCyt3KOHWq1xpxpOISGxCzuU7MSF/AVG3bs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=jg6QuVf3LAGgbeC7kGM/gmEPGMvvkZYwLty73xE3EWYMetWw34KAlEcqVKFKa4mhQNlNodZLo4W1h6tArteSl2eIPoODxFG6qj/ovX2NbFcHctaH1hoKtUZinHvumAMm84A8xEBx+E/IG4IC/zgbqtkKj+n/+Q54Cc88fsho1kE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=pzFdOlKd; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="pzFdOlKd" 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=TM9+Mq7Q2/Yjru1WOFAqdi1F1rmRmNguvnwSkO9NSIA=; b=pzFdOlKdWObmZaRdF24v+a6pGK OKydwfxmX+zzOqLsAy9N8L+AiCsi/cEx8/iE/cydcdzSowifBxFITuuecXmC/iAMB05FXcrGkXIVo Ww1gQLch86MofQE0Jev9whIYinDp56MKiAL9UWau6f/Y1ajghTjOnCloVQ0vCc0K2mxsl9qsobdFb nC478eFhWmGWvtWpI7fXb1Os1dLmt9NvCdOQAKXqPrxofVtvb5ie0oCMGkg7TDI7GYbwTYOdqYj6p M7j+m0wolbmncXsGyvJDmJcalbYFvcKvsYh4J0Mo21m7oSYoqUerunNSqJbY8ajuEMT/ydB/iTg1G MhkcqIWQ==; 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 1xGjQB-0000000G4C6-0wyc; Tue, 13 Oct 2026 20:48:28 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uAO-3Q61; Tue, 13 Oct 2026 21:48:24 +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 06/10] KVM: x86/xen: Extract delivery of event to vCPU into a separate helper Date: Tue, 13 Oct 2026 21:48:19 +0100 Message-ID: <20261013204824.454208-7-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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: 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.] 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 ad7708be6c04..a931458f0c71 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -1862,6 +1862,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: @@ -1876,11 +1959,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); @@ -1902,23 +1984,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); } @@ -1934,66 +2012,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 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 686AE54EECD; Tue, 13 Oct 2026 20:48:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924515; cv=none; b=epCqqYoBe7DzokVnfMfISpU736pAGsCjX/9UZbYO/0TZlAjnPij+oDyZaYzkHQ+mZhOIBOKJvmxEtBGYx/yQVEcZG7JPGXcd/wugMV2PN3UFm00Muhg5FcvREb0VwfAjgsnhq5pzKrm8D7VUYMzx6UynKeLw5Fr9MehAB/7iTCw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924515; c=relaxed/simple; bh=8om9V/DyETgvuFphhbJVnh9cLn0luWevSfUI5IQwszk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ZjLDNXqsUx3wWOCFtedMGg8z9QCaB6C6jk86XkHkMTmkAywEn4Mlccl3y9y7diUgjY8Q2a825EspEZLW5vhAJeuU8wOpGEvbkT7X+m7/DquKwD2hVv0FPHFjxsFuoljmJoYPUjRWGPBA4Qwm26Kwizaui0Bi6Du4mBcvxEIqLS0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=pagtNIBK; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="pagtNIBK" 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=Lnoyd/b7vQkSLPiZQCMZRxDzuqDlmVvMn6HckRCcbl8=; b=pagtNIBKr7qvXJuSJDqDZXUgVI jcgefa3gV0KW8QCwQrrRy5uJGnOicq7w0GOteXfgu6nLI3WFuA5Viz5eNn8lgiCfpSgD8S9GKX2ck vQVEa0JgGllna3xWmmLmUyIOgVZdmTJWtB1zeY8fLnHdT8k6G/bW0CkJmL4lLGHuP6fx2j+UqrSa6 1q0rr5kzJiaQo1hBxq56V3Pv0GPGbnTSZh5OprbetMv/siWkWX8pqGxPzOvGmOJeakj+Lu2wn7Djx XvK0rRCMIXR0gr8WUsvzq4o+RbbItPhs8acrJxp2KMZvdQy+1jcMfNLWBw4YXXda61lgZVTy5R2oG Y86WOsrg==; 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 1xGjQB-0000000G4C5-0wlh; Tue, 13 Oct 2026 20:48:27 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uAR-3jXg; Tue, 13 Oct 2026 21:48:24 +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 07/10] KVM: x86/xen: Explicitly tag "shared info" page as never being dirty tracked Date: Tue, 13 Oct 2026 21:48:20 +0100 Message-ID: <20261013204824.454208-8-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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 | 10 ++++++++-- include/linux/kvm_types.h | 1 + virt/kvm/pfncache.c | 4 +++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index a931458f0c71..f8162914d6c6 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -2469,7 +2469,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 88c1ee37550c..1abd3811b953 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1465,7 +1465,13 @@ int kvm_vcpu_write_guest(struct kvm_vcpu *vcpu, gpa_= t gpa, const void *data, * immutable attributes. Note, the cache must be zero-allocated (or zeroe= d by * the caller before init). */ -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 @@ -1987,7 +1993,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 8504e26fa111..0e2c9cfa8056 100644 --- a/virt/kvm/pfncache.c +++ b/virt/kvm/pfncache.c @@ -431,7 +431,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); @@ -441,6 +442,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 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6853F54EECB; Tue, 13 Oct 2026 20:48:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924514; cv=none; b=Pvu6rgS4797H1bJzaDUcbbruWEBiUvZ+LhInFqmFgrpKaQK28uYZeeoAq1/imKkyiOlfKc0/roWEuoeq9DTfo+7fB4HKfrPqzGpZU+Y/EdcDSF8k/LG2L5i1jr+ihnUyRU3G1kQiMya7bxyOLwpvK24DzfXwAl9UXKmtiw1U7h8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924514; c=relaxed/simple; bh=EnhxD+LWNnhWyE9YN2CMbuwe5f1Jhsf7zQTTGO2Dedw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kd7W1GBp1B9nukfRWKJpDBFDUpo75wrQaiKA7Lfladfybv/nioHKYf6/giLNVkLB5MLEgcTIoC2RflAdsh6ONJw8AJnJnvKTay1EU4o/ZI4y9xYfaYvhjbns0vfqNIBMPuDYxm5+oaerwD0kCjsVTUBkFjkBwh+Rwv/e1u2azyc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=ejH7M4Zs; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="ejH7M4Zs" 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=2v5mBlVK392iL/Y43x3cPeO25GmK/+93NMJMoT0UAh0=; b=ejH7M4ZsbUYhoYFjsPnn1k6Jmk I6Bbaoq6l34h1+VP9LtzNd2cpxeqv6AlW08WnzDOKvaluDAwBHrLgD/KQZW8w3JF6dFZZUQCwsn0t CFS9VKQyeNpIFmdWtpzWnmEdJ/R+LvqHu9vtysn3qeIEpFmMur/+Kry6Cf9AE+yCpdETJHcwa46eZ xUzZ7PMYaHij4XaazSMpgfQji7RZNB6sRFlwtOtgcAoVpswoLz3P23uPXzyJKo5U0LL1jRQpIyd+s WCHxwmCfnjHr96y3UQEn7wJDiqMZz1TotA9OlaTi5jGNcH/kpNz+E+idQ52DHv+TKvY8Bobk3a/mc tcUj7ugw==; 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 1xGjQB-0000000G4C7-0wr6; Tue, 13 Oct 2026 20:48:28 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uAU-3rs3; Tue, 13 Oct 2026 21:48:24 +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 08/10] KVM: x86/xen: Don't dirty track "vCPU info" page Date: Tue, 13 Oct 2026 21:48:21 +0100 Message-ID: <20261013204824.454208-9-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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 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 Signed-off-by: David Woodhouse Assisted-by: LLM --- arch/x86/kvm/xen.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index f8162914d6c6..ef0d59ebc6a7 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -715,7 +715,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. */ @@ -2448,7 +2447,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 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 095EE552922; Tue, 13 Oct 2026 20:48:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924517; cv=none; b=ImdmQQ88wRzNa4MmIGaJ7yxIl+qbIlu9e7/lz32IkefD8NCdRDxtkjOpSMRR020NaA6GssdgfXN7yuUQdeZE/Fr7Q2L6zpWIB+HC0LqV5TMUZJBcJw8VyfXzg8Thd54K1KDKQCLoScrNcX/3xwLqh1ZIgmlbVB/yaWL0nOs8JkU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924517; c=relaxed/simple; bh=jWEUlnvBG6BR3Fn5XvPTfHgt/B+EEBNZ7YcT5wUtMx4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=XAK/upeZfPxCKUakxiD2DhAY6zpwXs2RVVLjzrrvLQcYRRvYfxtzLif5eeA+DvC+kZGCb8tz8n9oPKMYQvGlnV+z6u/cIzE9LpQBd7zV+diATjgwTfHa8X1Rz31u0jrcBBbE+pwbNBuOkh+TQK+J4YrGwgfrRWMOI7yhmLhqaGU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=RKLYw8mA; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="RKLYw8mA" 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=RKLYw8mAGmOedSlmYKB6UkTYtA wjcmmt/Wihy61S22S8GQgy3qkH5pDf+y2B6TTK/kixPFrs48k+zSSrumPKK3SrMYBxhZH4yStkri3 QrvkR7pSuBqNO00M5RiHEXrtQwVB7QsoFmWszRJBwgE31g1kHARRo5u3HC9dM0X2Zv1zn2IHwnKiP LI07l2o9Y99wWaV7F0ffm63OTNE6tW2OeSKIvC0uVud+kRcje2yXFvs3PnprVfM6Ml/99R6Qo6Py/ GqMmRU6PO5+UNKkWlgJjanbc8lTF/Tp+qq1DlUs+zUFfzGmSE5jamEGcZLIXoURHJSuYm9CzS67Zw ibLrIbHQ==; 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 1xGjQB-0000000G4C9-15tE; Tue, 13 Oct 2026 20:48:34 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ8-00000001uAX-3zyj; Tue, 13 Oct 2026 21:48:24 +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 09/10] KVM: x86: Request the guest TLB flush from record_steal_time() Date: Tue, 13 Oct 2026 21:48:22 +0100 Message-ID: <20261013204824.454208-10-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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 13:21:28 2026 Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 63D52556B81; Tue, 13 Oct 2026 20:48:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.92.199 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924521; cv=none; b=Uc3qEd1BSvfUDVHxuteeS/UQJpEJ6e2Js1+5F0zTX4kNeXIm/+SvTpBg943+wl3x100Xzn8yQpx7N7myihe9Wv2Atv0tvEVDO6vyBwt/IRw5djsQFwxHdVM8VndGkzUVu6XAXDPSR2HJhjUdS6Urlner1Rgzp+CxMv9KexU5xG0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791924521; c=relaxed/simple; bh=JIQYfSl4FHO7RBWGs+X9PTpxL/QlAOxSwIT/tiEcNng=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=KRfdQHxRFMVRnRvBgxIk1qFOr3i9qdJSv/isbtCI4b6tzR/dnt9L0KShJfnQbbXVu+R4F0EOjDptKazyZN/X0mFsuhbq8GXOVCzZGzIpCZbu/5heQgQjy1MmIHGSO3UYr/KOpYbK+WhL2uVHsNtzvGtEJUd73NoeUOa+zggjuSU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=desiato.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=lmNgv1sE; arc=none smtp.client-ip=90.155.92.199 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) header.d=infradead.org header.i=@infradead.org header.b="lmNgv1sE" 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=Fqo6lpO9UQiTdCHFymA7Pg1AUf+HpjLRc+2WggDizO8=; b=lmNgv1sE4NK7JJ4bQu5Sxhaz3B ktwqhHDLcIXM5ctlSCmzowgRuHGQ5/fdGm6lDDlXsqgJTCAP3M/yDO44W4KvntiyVlIvPy55Hrrtx BzF9Zb+2nqwU0eUXdVNjnWJBD+yHm1mSN2oyrBrdkuzDNYZRRwNVlgZ2r1TJIxUbdsSV4EYpPCoP3 Bwt3m7I5lt3Uo3yMxYkr37uEjgmuZHauv+3+IOzjiQjf6v69B9C/sFp1QniWLG/qYK0dnTHgI29M/ zg230crwh0FXK7nUNLKAV+uskIMbzCiWvePwWYVfSLOeP+23+D9klBBzJCVjO0PF9YKsP1NCFdvfI q/nkgz8w==; 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 1xGjQB-0000000G4CA-0wr2; Tue, 13 Oct 2026 20:48:35 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.5 #2 (Red Hat Linux)) id 1xGjQ9-00000001uAa-06z6; Tue, 13 Oct 2026 21:48:25 +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 10/10] KVM: selftests: Add Xen evtchn vs memslot-update race test Date: Tue, 13 Oct 2026 21:48:23 +0100 Message-ID: <20261013204824.454208-11-dwmw2@infradead.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261013204824.454208-1-dwmw2@infradead.org> References: <20261013204824.454208-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: 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 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 | 178 ++++++++++++++++++ 2 files changed, 179 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..2b6974d3d464 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/xen_evtchn_memslot_race_test.c @@ -0,0 +1,178 @@ +// 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 defer the delivery to + * the next guest entry rather than spin 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 every send eventually delivers and the + * test completes N iterations. + */ +#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 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. + */ + __xen_hypercall(__HYPERVISOR_event_channel_op, EVTCHNOP_send, &s); + GUEST_SYNC(i); + } + + 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); + + 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: + 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); + + kvm_vm_free(vm); + return 0; +} --=20 2.43.0