From fa8833c085b6ce066f424fd46871c7862c015ef9 Mon Sep 17 00:00:00 2001 From: Peter Hilber Date: Fri, 5 Jun 2026 16:29:21 +0200 Subject: [PATCH 01/54] virtio-mmio: add support for transport version 3 Virtio MMIO transport version 3 allows device reset to complete asynchronously. Unlike version 2, where writing zero to Status must complete the reset before the write returns, version 3 requires the driver to poll Status until it reads back zero before considering reset complete. Update virtio-mmio accordingly: accept transport version 3 and, during reset, wait for Status to become zero. Keep the polling loop unbounded, consistent with virtio-pci, since the reset callback does not return an error code. Signed-off-by: Peter Hilber Link: https://github.com/oasis-tcs/virtio-spec/commit/bb1dd2e1fe89b862f38f15873d835a698b196f89 Message-ID: <20260605142921.2824-1-peter.hilber@oss.qualcomm.com> Signed-off-by: Michael S. Tsirkin --- drivers/virtio/virtio_mmio.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index 510b7c4efdff..316f03b97356 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -55,6 +55,7 @@ #define pr_fmt(fmt) "virtio-mmio: " fmt #include +#include #include #include #include @@ -114,9 +115,9 @@ static int vm_finalize_features(struct virtio_device *vdev) vring_transport_features(vdev); /* Make sure there are no mixed devices */ - if (vm_dev->version == 2 && + if (vm_dev->version >= 2 && !__virtio_test_bit(vdev, VIRTIO_F_VERSION_1)) { - dev_err(&vdev->dev, "New virtio-mmio devices (version 2) must provide VIRTIO_F_VERSION_1 feature!\n"); + dev_err(&vdev->dev, "New virtio-mmio devices (version >= 2) must provide VIRTIO_F_VERSION_1 feature!\n"); return -EINVAL; } @@ -254,6 +255,12 @@ static void vm_reset(struct virtio_device *vdev) /* 0 status means a reset. */ writel(0, vm_dev->base + VIRTIO_MMIO_STATUS); + + if (vm_dev->version >= 3) { + /* Wait for reset to complete. */ + while (vm_get_status(vdev)) + fsleep(1000); + } } @@ -600,7 +607,7 @@ static int virtio_mmio_probe(struct platform_device *pdev) /* Check device version */ vm_dev->version = readl(vm_dev->base + VIRTIO_MMIO_VERSION); - if (vm_dev->version < 1 || vm_dev->version > 2) { + if (vm_dev->version < 1 || vm_dev->version > 3) { dev_err(&pdev->dev, "Version %ld not supported!\n", vm_dev->version); rc = -ENXIO; From 281eb4732aae5473141b84e106fe906c69b2ff3d Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 5 Jul 2026 02:24:18 -0400 Subject: [PATCH 02/54] virtio_balloon: disable indirect descriptors The page reporting callback submits an sg list to the reporting virtqueue. With VIRTIO_RING_F_INDIRECT_DESC negotiated and total_sg > 1 (which it typically is), virtqueue_add reports it to the host by allocating an indirect descriptor via kmalloc(GFP_KERNEL). This is not pretty: the reporting worker isolates potentially hundreds of MB of free pages from the buddy allocator (reported pages are at least pageblock_order, and the sg can contain up to PAGE_REPORTING_CAPACITY entries of varying orders). As the result, very theoretically, the kmalloc might trigger OOM when we have in fact a ton of free memory. Clear VIRTIO_RING_F_INDIRECT_DESC, to avoid using indirect descriptors. Fixes: b0c504f15471 ("virtio-balloon: add support for providing free page reports to host") Assisted-by: Claude:claude-opus-4-6 Acked-by: David Hildenbrand (Arm) Signed-off-by: Michael S. Tsirkin Message-ID: <73fac8a629fd9aca7bb3265ac243a769c28af25d.1783232420.git.mst@redhat.com> --- drivers/virtio/virtio_balloon.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 581ac799d974..69160da9cd5d 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -1165,6 +1166,11 @@ static int virtballoon_validate(struct virtio_device *vdev) else if (!virtio_has_feature(vdev, VIRTIO_BALLOON_F_PAGE_POISON)) __virtio_clear_bit(vdev, VIRTIO_BALLOON_F_REPORTING); + /* + * Disable indirect descriptors to avoid memory allocation in + * virtqueue_add during page reporting. + */ + __virtio_clear_bit(vdev, VIRTIO_RING_F_INDIRECT_DESC); __virtio_clear_bit(vdev, VIRTIO_F_ACCESS_PLATFORM); return 0; } From bd670e5dfd2b01fd9692f61fa1456434c54026a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linfeng=20Sun=C2=A0?= Date: Sat, 20 Jun 2026 18:09:59 +0800 Subject: [PATCH 03/54] vdpa_sim: fix cleanup after worker creation failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vdpasim_create() leaves vdpasim->worker as an ERR_PTR when kthread_run_worker() fails. The error path then drops the device reference, which releases the partially initialized simulator. vdpasim_free() unconditionally passes the worker pointer to kthread_destroy_worker(), so the ERR_PTR is dereferenced and can trigger a general protection fault. Store the worker error, clear the pointer, and only clean up the worker when it was successfully initialized. Also make the release path tolerate partially initialized objects by guarding virtqueue and IOTLB cleanup, since the same release path can be reached from other initialization failures. I found this bug myself, though the patch was written with AI assistance. Fixes: 76acfa7bc54f ("vdpa_sim: use kthread worker") Assisted-by: OpenAI-Codex:GPT-5 Reviewed-by: Eugenio Pérez Signed-off-by: Linfeng Sun  Message-ID: <20260620100959.2070316-1-slf@hdu.edu.cn> Signed-off-by: Michael S. Tsirkin --- drivers/vdpa/vdpa_sim/vdpa_sim.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim.c b/drivers/vdpa/vdpa_sim/vdpa_sim.c index 4d116644851d..c748fe451163 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim.c @@ -233,8 +233,11 @@ struct vdpasim *vdpasim_create(struct vdpasim_dev_attr *dev_attr, kthread_init_work(&vdpasim->work, vdpasim_work_fn); vdpasim->worker = kthread_run_worker(0, "vDPA sim worker: %s", dev_attr->name); - if (IS_ERR(vdpasim->worker)) + if (IS_ERR(vdpasim->worker)) { + ret = PTR_ERR(vdpasim->worker); + vdpasim->worker = NULL; goto err_iommu; + } mutex_init(&vdpasim->mutex); spin_lock_init(&vdpasim->iommu_lock); @@ -746,18 +749,24 @@ static void vdpasim_free(struct vdpa_device *vdpa) struct vdpasim *vdpasim = vdpa_to_sim(vdpa); int i; - kthread_cancel_work_sync(&vdpasim->work); - kthread_destroy_worker(vdpasim->worker); + if (vdpasim->worker) { + kthread_cancel_work_sync(&vdpasim->work); + kthread_destroy_worker(vdpasim->worker); + } - for (i = 0; i < vdpasim->dev_attr.nvqs; i++) { - vringh_kiov_cleanup(&vdpasim->vqs[i].out_iov); - vringh_kiov_cleanup(&vdpasim->vqs[i].in_iov); + if (vdpasim->vqs) { + for (i = 0; i < vdpasim->dev_attr.nvqs; i++) { + vringh_kiov_cleanup(&vdpasim->vqs[i].out_iov); + vringh_kiov_cleanup(&vdpasim->vqs[i].in_iov); + } } vdpasim->dev_attr.free(vdpasim); - for (i = 0; i < vdpasim->dev_attr.nas; i++) - vhost_iotlb_reset(&vdpasim->iommu[i]); + if (vdpasim->iommu) { + for (i = 0; i < vdpasim->dev_attr.nas; i++) + vhost_iotlb_reset(&vdpasim->iommu[i]); + } kfree(vdpasim->iommu); kfree(vdpasim->iommu_pt); kfree(vdpasim->vqs); From bb652c245b5b7081bb612a90f60cd59da373d049 Mon Sep 17 00:00:00 2001 From: Octavian Purdila Date: Mon, 22 Jun 2026 22:27:56 +0000 Subject: [PATCH 04/54] iov_iter: export iov_iter_restore Export iov_iter_restore so that it can be used by modules. This is needed by the virtio vsock transport (which can be built as a module) to restore the msg_iter state when transmission fails. Acked-by: Stefano Garzarella Signed-off-by: Octavian Purdila Message-ID: <20260622222757.2130402-2-tavip@google.com> Signed-off-by: Michael S. Tsirkin --- lib/iov_iter.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/iov_iter.c b/lib/iov_iter.c index c2484551a4e8..227c6ee69da8 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -1491,6 +1491,7 @@ void iov_iter_restore(struct iov_iter *i, struct iov_iter_state *state) i->__iov -= state->nr_segs - i->nr_segs; i->nr_segs = state->nr_segs; } +EXPORT_SYMBOL_FOR_MODULES(iov_iter_restore, "vmw_vsock_virtio_transport_common"); /* * Extract a list of contiguous pages from an ITER_FOLIOQ iterator. This does From 5ab3b28c5b3b49b4b9ef580df08c1e160dff51f0 Mon Sep 17 00:00:00 2001 From: Octavian Purdila Date: Mon, 22 Jun 2026 22:27:57 +0000 Subject: [PATCH 05/54] vsock/virtio: restore msg_iter on transmission failure When transmission fails in virtio_transport_send_pkt_info, the msg_iter might have been partially advanced. If we don't restore it, the next attempt to send data will use an incorrect iterator state, leading to desync and warnings like "send_pkt() returns 0, but X expected". Specifically, this can happen in the following scenario, triggered by the syzkaller repro: 1. A write-only VMA (PROT_WRITE only) is partially populated by a prior TUN write that failed with -EIO but still faulted in some pages). 2. A vsock sendmmsg call with MSG_ZEROCOPY requests transmission of a buffer from this VMA. 3. The first packet (64KB) is sent successfully because the pages are populated. 4. The second packet allocation fails because GUP fast pins the first page but GUP slow fails on the next unpopulated page due to PROT_WRITE-only permissions. 5. The iterator is advanced by the partially successful GUP (68KB total advanced: 64KB from first packet + 4KB from second), but the send loop breaks and only reports 64KB sent. This creates a 4KB desync. 6. The next retry starts with a non-zero iov_offset, disabling zerocopy and falling back to copy mode. 7. In copy mode, the transmission succeeds for the next packets but exhausts the iterator early because of the desync. 8. The final retry sees an empty iterator but zerocopy is re-enabled (offset resets). It attempts to send the remaining bytes with zerocopy but pins 0 pages, creating an empty packet. 9. The transport sends the empty packet, triggering the warning because the returned bytes (header only) do not match the expected payload size. 10. The loop continues to spin, allocating ubuf_info each time, eventually exhausting sysctl_optmem_max and returning -ENOMEM to userspace. Restore msg_iter to its original state before the packet allocation and transmission attempt if they fail. Fixes: e0718bd82e27 ("vsock: enable setting SO_ZEROCOPY") Reported-by: syzbot+28e5f3d207b14bae122a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=28e5f3d207b14bae122a Assisted-by: gemini:gemini-3.1-pro Reviewed-by: Stefano Garzarella Signed-off-by: Octavian Purdila Message-ID: <20260622222757.2130402-3-tavip@google.com> Signed-off-by: Michael S. Tsirkin --- net/vmw_vsock/virtio_transport_common.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 8becad81279c..20984f48284c 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -302,6 +302,7 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk, u32 max_skb_len = VIRTIO_VSOCK_MAX_PKT_BUF_SIZE; u32 src_cid, src_port, dst_cid, dst_port; const struct virtio_transport *t_ops; + struct iov_iter_state msg_iter_state; struct virtio_vsock_sock *vvs; struct ubuf_info *uarg = NULL; u32 pkt_len = info->pkt_len; @@ -375,8 +376,17 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk, struct sk_buff *skb; size_t skb_len; + /* Save iterator state in case allocation or transmission fails + * so we can restore it and retry. + */ + if (info->msg) + iov_iter_save_state(&info->msg->msg_iter, &msg_iter_state); + skb_len = min(max_skb_len, rest_len); + /* Note: virtio_transport_alloc_skb() can advance info->msg->msg_iter + * even if it fails (e.g. partial GUP success). + */ skb = virtio_transport_alloc_skb(info, skb_len, can_zcopy, uarg, src_cid, src_port, @@ -406,6 +416,9 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk, break; } while (rest_len); + if (info->msg && ret < 0) + iov_iter_restore(&info->msg->msg_iter, &msg_iter_state); + virtio_transport_put_credit(vvs, rest_len); /* msg_zerocopy_realloc() initializes the ubuf_info refcnt to 1. From f77a956f6a19f9463ef1527c9d0cda50dded6b92 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 22 Jun 2026 01:52:15 -0500 Subject: [PATCH 06/54] crypto: virtio - bound the akcipher result length virtio_crypto_dataq_akcipher_callback() sets the result length from the device-reported response length without bounding it to the destination buffer, which was allocated for the original request length. sg_copy_from_buffer() then reads that many bytes from the destination buffer; a backend reporting a larger length over-reads adjacent kernel heap into the caller's scatterlist (an out-of-bounds read). Clamp the reported length to the originally requested destination length. A conforming device reports no more than that, so valid results are unaffected. Fixes: a36bd0ad9fbf ("virtio-crypto: adjust dst_len at ops callback") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Message-ID: <20260622-b4-disp-3a2c09a8-v2-1-d1a809281db4@proton.me> Signed-off-by: Michael S. Tsirkin --- drivers/crypto/virtio/virtio_crypto_akcipher_algs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/crypto/virtio/virtio_crypto_akcipher_algs.c b/drivers/crypto/virtio/virtio_crypto_akcipher_algs.c index d8d452cac391..64ea141f018c 100644 --- a/drivers/crypto/virtio/virtio_crypto_akcipher_algs.c +++ b/drivers/crypto/virtio/virtio_crypto_akcipher_algs.c @@ -88,7 +88,8 @@ static void virtio_crypto_dataq_akcipher_callback(struct virtio_crypto_request * } /* actual length may be less than dst buffer */ - akcipher_req->dst_len = len - sizeof(vc_req->status); + akcipher_req->dst_len = min_t(unsigned int, len - sizeof(vc_req->status), + akcipher_req->dst_len); sg_copy_from_buffer(akcipher_req->dst, sg_nents(akcipher_req->dst), vc_akcipher_req->dst_buf, akcipher_req->dst_len); virtio_crypto_akcipher_finalize_req(vc_akcipher_req, akcipher_req, error); From 8634a92ee595d16f12e08ebd24d637b99f78b6fd Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Mon, 22 Jun 2026 16:03:22 +0100 Subject: [PATCH 07/54] crypto: virtio - fix missing le64_to_cpu() conversions There are two cases of sending a __le64 type to a print function so fix this by adding le64_to_cpu() which fixes the following (prototype) sparse warnings: drivers/crypto/virtio/virtio_crypto_skcipher_algs.c:234:17: warning: incorrect type in argument 3 (different base types) drivers/crypto/virtio/virtio_crypto_skcipher_algs.c:234:17: expected unsigned long long drivers/crypto/virtio/virtio_crypto_skcipher_algs.c:234:17: got restricted __le64 [usertype] session_id drivers/crypto/virtio/virtio_crypto_akcipher_algs.c:196:17: warning: incorrect type in argument 3 (different base types) drivers/crypto/virtio/virtio_crypto_akcipher_algs.c:196:17: expected unsigned long long drivers/crypto/virtio/virtio_crypto_akcipher_algs.c:196:17: got restricted __le64 [usertype] session_id Signed-off-by: Ben Dooks Message-ID: <20260622150322.526375-1-ben.dooks@codethink.co.uk> Signed-off-by: Michael S. Tsirkin --- drivers/crypto/virtio/virtio_crypto_akcipher_algs.c | 3 ++- drivers/crypto/virtio/virtio_crypto_skcipher_algs.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/crypto/virtio/virtio_crypto_akcipher_algs.c b/drivers/crypto/virtio/virtio_crypto_akcipher_algs.c index 64ea141f018c..9078f22978b7 100644 --- a/drivers/crypto/virtio/virtio_crypto_akcipher_algs.c +++ b/drivers/crypto/virtio/virtio_crypto_akcipher_algs.c @@ -195,7 +195,8 @@ static int virtio_crypto_alg_akcipher_close_session(struct virtio_crypto_akciphe if (ctrl_status->status != VIRTIO_CRYPTO_OK) { pr_err("virtio_crypto: Close session failed status: %u, session_id: 0x%llx\n", - ctrl_status->status, destroy_session->session_id); + ctrl_status->status, + le64_to_cpu(destroy_session->session_id)); err = -EINVAL; goto out; } diff --git a/drivers/crypto/virtio/virtio_crypto_skcipher_algs.c b/drivers/crypto/virtio/virtio_crypto_skcipher_algs.c index e82fc16cab25..3ca441ae2759 100644 --- a/drivers/crypto/virtio/virtio_crypto_skcipher_algs.c +++ b/drivers/crypto/virtio/virtio_crypto_skcipher_algs.c @@ -232,7 +232,8 @@ static int virtio_crypto_alg_skcipher_close_session( if (ctrl_status->status != VIRTIO_CRYPTO_OK) { pr_err("virtio_crypto: Close session failed status: %u, session_id: 0x%llx\n", - ctrl_status->status, destroy_session->session_id); + ctrl_status->status, + le64_to_cpu(destroy_session->session_id)); err = -EINVAL; goto out; From 2187be212179243f33da1271248571aca3e99a3f Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Tue, 10 Mar 2026 09:40:46 +0100 Subject: [PATCH 08/54] virtio: Add ID for virtio media Add VIRTIO_ID_MEDIA definition for virtio-media. Signed-off-by: Albert Esteve Message-ID: <20260310-virtio-media-id-v1-1-be211bcf682b@redhat.com> Signed-off-by: Michael S. Tsirkin --- include/uapi/linux/virtio_ids.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index 6c12db16faa3..f9056af0c622 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -69,6 +69,7 @@ #define VIRTIO_ID_BT 40 /* virtio bluetooth */ #define VIRTIO_ID_GPIO 41 /* virtio gpio */ #define VIRTIO_ID_SPI 45 /* virtio spi */ +#define VIRTIO_ID_MEDIA 48 /* virtio media */ /* * Virtio Transitional IDs From 0d8aebe089b4ba887e792884cf041ce9f1040ff4 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 16:08:43 +0200 Subject: [PATCH 09/54] virtio: add virtio_device_shutdown() helper The generic virtio bus .shutdown handler, virtio_dev_shutdown(), breaks and resets a device once it has established that the driver has no .shutdown of its own. A driver that does implement .shutdown, to quiesce its own activity first, still needs the same break and reset afterwards and would otherwise have to open code it. Factor the break + synchronize_cbs + reset sequence out of virtio_dev_shutdown() into an exported virtio_device_shutdown() helper so such drivers can reuse it instead of duplicating the core logic. No functional change. Signed-off-by: Denis V. Lunev Reviewed-by: David Hildenbrand (Arm) Signed-off-by: Michael S. Tsirkin Message-ID: <20260624140846.2616797-2-den@openvz.org> --- drivers/virtio/virtio.c | 41 +++++++++++++++++++++++++++-------------- include/linux/virtio.h | 1 + 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/drivers/virtio/virtio.c b/drivers/virtio/virtio.c index 299fa83be1d5..75bb4ffe3b87 100644 --- a/drivers/virtio/virtio.c +++ b/drivers/virtio/virtio.c @@ -401,6 +401,32 @@ static const struct cpumask *virtio_irq_get_affinity(struct device *_d, return dev->config->get_vq_affinity(dev, irq_vec); } +/** + * virtio_device_shutdown - break and reset a device on shutdown + * @dev: the device + * + * Drivers with their own .shutdown method should quiesce their activity and + * then call this to stop the device the way the generic shutdown path does. + */ +void virtio_device_shutdown(struct virtio_device *dev) +{ + /* + * Some devices get wedged if you kick them after they are + * reset. Mark all vqs as broken to make sure we don't. + */ + virtio_break_device(dev); + /* + * Guarantee that any callback will see vq->broken as true. + */ + virtio_synchronize_cbs(dev); + /* + * As IOMMUs are reset on shutdown, this will block device access to memory. + * Some devices get wedged if this happens, so reset to make sure it does not. + */ + dev->config->reset(dev); +} +EXPORT_SYMBOL_GPL(virtio_device_shutdown); + static void virtio_dev_shutdown(struct device *_d) { struct virtio_device *dev = dev_to_virtio(_d); @@ -419,20 +445,7 @@ static void virtio_dev_shutdown(struct device *_d) return; } - /* - * Some devices get wedged if you kick them after they are - * reset. Mark all vqs as broken to make sure we don't. - */ - virtio_break_device(dev); - /* - * Guarantee that any callback will see vq->broken as true. - */ - virtio_synchronize_cbs(dev); - /* - * As IOMMUs are reset on shutdown, this will block device access to memory. - * Some devices get wedged if this happens, so reset to make sure it does not. - */ - dev->config->reset(dev); + virtio_device_shutdown(dev); } static int virtio_dev_num_vf(struct device *dev) diff --git a/include/linux/virtio.h b/include/linux/virtio.h index 93e573c56563..f923e42cfd01 100644 --- a/include/linux/virtio.h +++ b/include/linux/virtio.h @@ -213,6 +213,7 @@ int virtio_device_freeze(struct virtio_device *dev); int virtio_device_restore(struct virtio_device *dev); #endif void virtio_reset_device(struct virtio_device *dev); +void virtio_device_shutdown(struct virtio_device *dev); int virtio_device_reset_prepare(struct virtio_device *dev); int virtio_device_reset_done(struct virtio_device *dev); From 29536a923a9412812eb3e378258019b54538a220 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 16:08:44 +0200 Subject: [PATCH 10/54] virtio_balloon: factor out virtballoon_quiesce() virtballoon_remove() stops all of the balloon's asynchronous work (the free page reporting worker, the inflate/deflate and stats workers, the OOM notifier and the free page shrinker) before tearing the device down. A following change needs the same teardown from a .shutdown handler, so move it into a virtballoon_quiesce() helper. No functional change. Signed-off-by: Denis V. Lunev Reviewed-by: David Hildenbrand (Arm) Signed-off-by: Michael S. Tsirkin Message-ID: <20260624140846.2616797-3-den@openvz.org> --- drivers/virtio/virtio_balloon.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 69160da9cd5d..6daba1257d39 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -1096,26 +1096,39 @@ static void remove_common(struct virtio_balloon *vb) vb->vdev->config->del_vqs(vb->vdev); } -static void virtballoon_remove(struct virtio_device *vdev) +/* + * Stop all asynchronous balloon work. The device must still be alive so that + * in-flight requests can drain via the host before it is reset or freed. + */ +static void virtballoon_quiesce(struct virtio_balloon *vb) { - struct virtio_balloon *vb = vdev->priv; + struct virtio_device *vdev = vb->vdev; - if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_REPORTING)) + if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_REPORTING)) page_reporting_unregister(&vb->pr_dev_info); - if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_DEFLATE_ON_OOM)) + if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_DEFLATE_ON_OOM)) unregister_oom_notifier(&vb->oom_nb); - if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) + if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) virtio_balloon_unregister_shrinker(vb); + spin_lock_irq(&vb->stop_update_lock); vb->stop_update = true; spin_unlock_irq(&vb->stop_update_lock); cancel_work_sync(&vb->update_balloon_size_work); cancel_work_sync(&vb->update_balloon_stats_work); - if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) { + if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) cancel_work_sync(&vb->report_free_page_work); +} + +static void virtballoon_remove(struct virtio_device *vdev) +{ + struct virtio_balloon *vb = vdev->priv; + + virtballoon_quiesce(vb); + + if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) destroy_workqueue(vb->balloon_wq); - } remove_common(vb); mutex_destroy(&vb->balloon_lock); From 7e17eef04600c399c7e0f5ce765da5cf9d40d8e1 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 16:08:45 +0200 Subject: [PATCH 11/54] virtio_balloon: quiesce balloon work before device shutdown Commit 8bd2fa086a04 ("virtio: break and reset virtio devices on device_shutdown()") added a generic virtio bus .shutdown handler that breaks and resets every virtio device during device_shutdown(), i.e. on reboot and kexec. virtio_balloon provides no .shutdown of its own, so that generic path runs while the balloon's asynchronous work is still armed. Once the device has been broken, virtqueue_add_inbuf() in virtballoon_free_page_report() returns -EIO and trips its WARN_ON_ONCE(). On a kernel booted with panic_on_warn that turns an ordinary reboot, for example a kexec based upgrade, into a fatal panic in the middle of device_shutdown(), so the machine never reaches the new kernel. Relaxing that single WARN_ON_ONCE() would only hide the symptom: the inflate/deflate and OOM paths do not warn, they call wait_event(vb->acked, ...) and would instead block forever on a broken queue that can no longer complete. The device has to be quiesced, not just kept quiet. Add a .shutdown handler that quiesces the balloon via the shared virtballoon_quiesce() helper while the device is still alive, and only then breaks and resets it via virtio_device_shutdown(). Unlike virtballoon_remove() the balloon workqueue is not destroyed, as shutdown does not free the device and cancel_work_sync() together with stop_update already prevent any further work from being queued. Fixes: 8bd2fa086a04 ("virtio: break and reset virtio devices on device_shutdown()") Signed-off-by: Denis V. Lunev Reviewed-by: David Hildenbrand (Arm) Signed-off-by: Michael S. Tsirkin Message-ID: <20260624140846.2616797-4-den@openvz.org> --- drivers/virtio/virtio_balloon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 6daba1257d39..70450e922d8b 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -1135,6 +1135,12 @@ static void virtballoon_remove(struct virtio_device *vdev) kfree(vb); } +static void virtballoon_shutdown(struct virtio_device *vdev) +{ + virtballoon_quiesce(vdev->priv); + virtio_device_shutdown(vdev); +} + #ifdef CONFIG_PM_SLEEP static int virtballoon_freeze(struct virtio_device *vdev) { @@ -1205,6 +1211,7 @@ static struct virtio_driver virtio_balloon_driver = { .validate = virtballoon_validate, .probe = virtballoon_probe, .remove = virtballoon_remove, + .shutdown = virtballoon_shutdown, .config_changed = virtballoon_changed, #ifdef CONFIG_PM_SLEEP .freeze = virtballoon_freeze, From 198eda395067b56ee605de076f0519620f4e5d90 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 16:08:46 +0200 Subject: [PATCH 12/54] virtio_balloon: warn on failed buffer add in tell_host() tell_host() ignores the return value of virtqueue_add_outbuf() and goes on to kick the queue and wait_event() for the host's ack. The comment claims "We should always be able to add one buffer to an empty queue", but that does not hold once the virtqueue has been broken (e.g. on device shutdown): the add then fails with -EIO and the following wait_event() would block forever on a buffer the host can never return. Warn and bail out on failure, mirroring virtballoon_free_page_report(). Suggested-by: David Hildenbrand Signed-off-by: Denis V. Lunev Signed-off-by: Michael S. Tsirkin Message-ID: <20260624140846.2616797-5-den@openvz.org> --- drivers/virtio/virtio_balloon.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 70450e922d8b..0d3531005a17 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -185,16 +185,18 @@ static void tell_host(struct virtio_balloon *vb, struct virtqueue *vq) { struct scatterlist sg; unsigned int len; + int err; sg_init_one(&sg, vb->pfns, sizeof(vb->pfns[0]) * vb->num_pfns); /* We should always be able to add one buffer to an empty queue. */ - virtqueue_add_outbuf(vq, &sg, 1, vb, GFP_KERNEL); + err = virtqueue_add_outbuf(vq, &sg, 1, vb, GFP_KERNEL); + if (WARN_ON_ONCE(err)) + return; virtqueue_kick(vq); /* When host has read buffer, this completes via balloon_ack */ wait_event(vb->acked, virtqueue_get_buf(vq, &len)); - } static int virtballoon_free_page_report(struct page_reporting_dev_info *pr_dev_info, From d62fb5cc8a432b0b89de8940d2121cc93ffd2f8d Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 17:40:01 +0200 Subject: [PATCH 13/54] virtio_balloon: warn on failed buffer add in stats_handle_request() Like tell_host(), stats_handle_request() ignores the return value of virtqueue_add_outbuf() and kicks the queue regardless. The same "we should always be able to add one buffer to an empty queue" assumption does not hold once the virtqueue has been broken (e.g. on device shutdown), where the add fails with -EIO. Unlike tell_host() it does not wait_event() afterwards so it cannot hang, but it still kicks a queue with nothing queued. Warn and bail out on failure, mirroring tell_host() and virtballoon_free_page_report(). Suggested-by: David Hildenbrand Signed-off-by: Denis V. Lunev Reviewed-by: David Hildenbrand (Arm) Signed-off-by: Michael S. Tsirkin Message-ID: <20260624154001.2733242-1-den@openvz.org> --- drivers/virtio/virtio_balloon.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 0d3531005a17..ab3e3d887e00 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -446,6 +446,7 @@ static void stats_handle_request(struct virtio_balloon *vb) struct virtqueue *vq; struct scatterlist sg; unsigned int len, num_stats; + int err; num_stats = update_balloon_stats(vb); @@ -453,7 +454,9 @@ static void stats_handle_request(struct virtio_balloon *vb) if (!virtqueue_get_buf(vq, &len)) return; sg_init_one(&sg, vb->stats, sizeof(vb->stats[0]) * num_stats); - virtqueue_add_outbuf(vq, &sg, 1, vb, GFP_KERNEL); + err = virtqueue_add_outbuf(vq, &sg, 1, vb, GFP_KERNEL); + if (WARN_ON_ONCE(err)) + return; virtqueue_kick(vq); } From 92a7b138f2453bf067628de0c5ec563cc8ad16d5 Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Fri, 26 Jun 2026 15:04:38 +0800 Subject: [PATCH 14/54] vhost/net: fix clear_user start address in VHOST_GET_FEATURES_ARRAY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The clear_user() call in VHOST_GET_FEATURES_ARRAY incorrectly starts at argp, which is the beginning of the features array, overwriting the data just written by copy_to_user(). It should start after the copied elements at argp + copied * sizeof(u64) to only zero the trailing unused space. Use size_mul() for both the offset and length calculations so the arithmetic stays consistent with the surrounding code and remains overflow-safe. Fixes: 333c515d1896 ("vhost-net: allow configuring extended features") Signed-off-by: Yufeng Wang Acked-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260626070438.59149-1-r4o5m6e8o@163.com> --- drivers/vhost/net.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 6949b704166d..38d9c184082d 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1777,7 +1777,8 @@ static long vhost_net_ioctl(struct file *f, unsigned int ioctl, return -EFAULT; /* Zero the trailing space provided by user-space, if any */ - if (clear_user(argp, size_mul(count - copied, sizeof(u64)))) + if (clear_user(argp + size_mul(copied, sizeof(u64)), + size_mul(count - copied, sizeof(u64)))) return -EFAULT; return 0; case VHOST_SET_FEATURES_ARRAY: From b07513e7475fb33d6dee95569baca60379a1b79a Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Thu, 18 Jun 2026 18:02:54 +0800 Subject: [PATCH 15/54] tools/virtio: Remove unsupported --batch option from vhost_net_test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vhost_net_test has --batch in longopts, but not in help. The parser never handles 'b', so --batch hits assert(0). Remove the unsupported option. Signed-off-by: Yichong Chen Acked-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: --- tools/virtio/vhost_net_test.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/virtio/vhost_net_test.c b/tools/virtio/vhost_net_test.c index 389d99a6d7c7..566e15420bb6 100644 --- a/tools/virtio/vhost_net_test.c +++ b/tools/virtio/vhost_net_test.c @@ -450,11 +450,6 @@ static const struct option longopts[] = { .val = 'n', .has_arg = required_argument, }, - { - .name = "batch", - .val = 'b', - .has_arg = required_argument, - }, { } }; From 135f0abe31397694c06f5e28bf93d5e123756abc Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Fri, 26 Jun 2026 10:05:44 +0800 Subject: [PATCH 16/54] vdpa_sim: clear pending_kick on device reset vdpasim_kick_vq() sets pending_kick when a virtqueue is kicked while the device is suspended (!running but DRIVER_OK). vdpasim_resume() later replays kicks for all virtqueues when pending_kick is set. vdpasim_do_reset() clears running and status but leaves pending_kick unchanged. If a kick is deferred during suspend and the device is reset before resume, a later resume can spuriously kick every virtqueue even though no new work was queued after reset. Clear pending_kick in vdpasim_do_reset() together with the other device state that must not survive a reset. Tested-on: openEuler VM (6.16.8, /usr/src/linux-6.16.8) Tested-by: Xiong Weimin Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260626020545.607600-2-15927021679@163.com> --- drivers/vdpa/vdpa_sim/vdpa_sim.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim.c b/drivers/vdpa/vdpa_sim/vdpa_sim.c index c748fe451163..aa3741b8778d 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim.c @@ -161,6 +161,7 @@ static void vdpasim_do_reset(struct vdpasim *vdpasim, u32 flags) } vdpasim->running = false; + vdpasim->pending_kick = false; spin_unlock(&vdpasim->iommu_lock); vdpasim->features = 0; From 7b411448c668bfab0d0e572cee5a592cea396ce7 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Fri, 26 Jun 2026 10:05:45 +0800 Subject: [PATCH 17/54] vdpa_sim: hold iommu_lock across dma_unmap passthrough transition vdpasim_dma_map() updates the IOTLB and the passthrough (iommu_pt) state under iommu_lock. vdpasim_dma_unmap() clears iommu_pt and resets the IOTLB before taking iommu_lock, then deletes the mapping while holding the lock. A concurrent dma_map(), dma_unmap(), or reset path that also touches the same address space can therefore observe or modify the IOTLB and iommu_pt state without consistent locking. Perform the passthrough transition and range deletion under the same iommu_lock scope, matching dma_map(). Tested-on: openEuler VM (6.16.8, /usr/src/linux-6.16.8) Tested-by: Xiong Weimin Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260626020545.607600-3-15927021679@163.com> --- drivers/vdpa/vdpa_sim/vdpa_sim.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim.c b/drivers/vdpa/vdpa_sim/vdpa_sim.c index aa3741b8778d..cfa88a60a38f 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim.c @@ -733,12 +733,11 @@ static int vdpasim_dma_unmap(struct vdpa_device *vdpa, unsigned int asid, if (asid >= vdpasim->dev_attr.nas) return -EINVAL; + spin_lock(&vdpasim->iommu_lock); if (vdpasim->iommu_pt[asid]) { vhost_iotlb_reset(&vdpasim->iommu[asid]); vdpasim->iommu_pt[asid] = false; } - - spin_lock(&vdpasim->iommu_lock); vhost_iotlb_del_range(&vdpasim->iommu[asid], iova, iova + size - 1); spin_unlock(&vdpasim->iommu_lock); From 656662dc53e6431fbfabb5f39103cb940838bf82 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Mon, 29 Jun 2026 11:31:46 +0800 Subject: [PATCH 18/54] virtio_dma_buf: fix typo in kdoc comment: get_uid -> get_uuid The @get_uid tag in the virtio_dma_buf_ops kdoc comment is a typo; the actual field name is get_uuid. Fixes: a0308938ec81 ("virtio: add dma-buf support for exported objects") Signed-off-by: Li RongQing Signed-off-by: Michael S. Tsirkin Message-ID: <20260629033146.2209-1-lirongqing@baidu.com> --- include/linux/virtio_dma_buf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/virtio_dma_buf.h b/include/linux/virtio_dma_buf.h index a2fdf217ac62..545ac5f17a54 100644 --- a/include/linux/virtio_dma_buf.h +++ b/include/linux/virtio_dma_buf.h @@ -17,7 +17,7 @@ * @ops: the base dma_buf_ops. ops.attach MUST be virtio_dma_buf_attach. * @device_attach: [optional] callback invoked by virtio_dma_buf_attach during * all attach operations. - * @get_uid: [required] callback to get the uuid of the exported object. + * @get_uuid: [required] callback to get the uuid of the exported object. */ struct virtio_dma_buf_ops { struct dma_buf_ops ops; From ddf8b4388af7afc3fbdc2002c9c109e88fa27ddf Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Tue, 30 Jun 2026 12:59:52 +0800 Subject: [PATCH 19/54] virtio_mem: fix hardcoded 'vm' variable in bbm iteration macros virtio_mem_bbm_for_each_bb() and virtio_mem_bbm_for_each_bb_rev() accept a '_vm' parameter to allow callers to pass any variable name referring to the virtio_mem instance. However, the 'for' loop initializer and part of the loop condition use the bare name 'vm' instead of the macro parameter '_vm'. Fix by replacing all bare 'vm->' references inside the macros with the '_vm' parameter, and wrap in parentheses following kernel macro conventions. Signed-off-by: Li RongQing Acked-by: David Hildenbrand (Arm) Signed-off-by: Michael S. Tsirkin Message-ID: <20260630045952.2188-1-lirongqing@baidu.com> --- drivers/virtio/virtio_mem.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/virtio/virtio_mem.c b/drivers/virtio/virtio_mem.c index 11c441501582..82a285c0926d 100644 --- a/drivers/virtio/virtio_mem.c +++ b/drivers/virtio/virtio_mem.c @@ -423,14 +423,14 @@ static int virtio_mem_bbm_bb_states_prepare_next_bb(struct virtio_mem *vm) } #define virtio_mem_bbm_for_each_bb(_vm, _bb_id, _state) \ - for (_bb_id = vm->bbm.first_bb_id; \ - _bb_id < vm->bbm.next_bb_id && _vm->bbm.bb_count[_state]; \ + for (_bb_id = (_vm)->bbm.first_bb_id; \ + _bb_id < (_vm)->bbm.next_bb_id && (_vm)->bbm.bb_count[_state]; \ _bb_id++) \ if (virtio_mem_bbm_get_bb_state(_vm, _bb_id) == _state) #define virtio_mem_bbm_for_each_bb_rev(_vm, _bb_id, _state) \ - for (_bb_id = vm->bbm.next_bb_id - 1; \ - _bb_id >= vm->bbm.first_bb_id && _vm->bbm.bb_count[_state]; \ + for (_bb_id = (_vm)->bbm.next_bb_id - 1; \ + _bb_id >= (_vm)->bbm.first_bb_id && (_vm)->bbm.bb_count[_state]; \ _bb_id--) \ if (virtio_mem_bbm_get_bb_state(_vm, _bb_id) == _state) From dc3f1eef9ab678c396baf5df12aba61db061aa8c Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Mon, 29 Jun 2026 11:35:38 +0800 Subject: [PATCH 20/54] virtio_pci: fix wrong queue index for admin vq in intx path In vp_find_vqs_intx(), the admin vq was set up using the local queue_idx counter instead of avq->vq_index (the actual queue index obtained from the device). This differs from vp_find_vqs_msix() which correctly uses avq->vq_index. Using the wrong index causes the admin virtqueue to be mapped to an incorrect hardware queue. Fix it by using avq->vq_index consistent with the msix path. Fixes: af22bbe1f4a5 ("virtio: create admin queues alongside other virtqueues") Signed-off-by: Li RongQing Message-ID: <20260629033538.2476-1-lirongqing@baidu.com> Signed-off-by: Michael S. Tsirkin --- drivers/virtio/virtio_pci_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c index 164f480b18a6..10371ecbc054 100644 --- a/drivers/virtio/virtio_pci_common.c +++ b/drivers/virtio/virtio_pci_common.c @@ -499,7 +499,7 @@ static int vp_find_vqs_intx(struct virtio_device *vdev, unsigned int nvqs, if (!avq_num) return 0; sprintf(avq->name, "avq.%u", avq->vq_index); - vq = vp_setup_vq(vdev, queue_idx++, vp_modern_avq_done, avq->name, + vq = vp_setup_vq(vdev, avq->vq_index, vp_modern_avq_done, avq->name, false, VIRTIO_MSI_NO_VECTOR, &vp_dev->admin_vq.info); if (IS_ERR(vq)) { From 6b7108712a4b1c37cac69815aede1dde202b3187 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:26 +0800 Subject: [PATCH 21/54] nvdimm: preserve flush callback -ENOMEM nvdimm_flush() maps provider flush failures to -EIO. Keep that default because provider callbacks can report host-side or backend failures that should remain generic I/O errors to the guest. Guest-side allocation failures should not be reported as I/O errors. In the virtio-pmem path, the flush request allocation can fail with -ENOMEM before any request is submitted to the host. Mapping that to -EIO makes resource pressure look like media failure. Preserve -ENOMEM from provider callbacks and continue to map other non-zero provider failures to -EIO. The generic flush path still returns 0, and pmem_submit_bio() already converts errno values to block status for bio completion. Suggested-by: Pankaj Gupta Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-2-me@linux.beauty> --- drivers/nvdimm/region_devs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvdimm/region_devs.c b/drivers/nvdimm/region_devs.c index 5e079d61cbaa..39669eb4ce34 100644 --- a/drivers/nvdimm/region_devs.c +++ b/drivers/nvdimm/region_devs.c @@ -1093,7 +1093,8 @@ int nvdimm_flush(struct nd_region *nd_region, struct bio *bio) if (!nd_region->flush) rc = generic_nvdimm_flush(nd_region); else { - if (nd_region->flush(nd_region, bio)) + rc = nd_region->flush(nd_region, bio); + if (rc && rc != -ENOMEM) rc = -EIO; } From c644a2f8fef5618fcf453c591177700fd07dd024 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:27 +0800 Subject: [PATCH 22/54] nvdimm: pmem: keep PREFLUSH before data writes pmem_submit_bio() records a REQ_PREFLUSH error, but continues to copy the bio data and can later overwrite the error with a successful REQ_FUA flush. That lets data writes run after a failed preflush and can complete the bio successfully despite the failed ordering barrier. Run the REQ_PREFLUSH flush synchronously before touching the bio data and complete the bio with the flush error if it fails. Keep asynchronous flush chaining for REQ_FUA. At that point, data copy has completed and the parent bio can wait for the chained flush bio. Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-3-me@linux.beauty> --- drivers/nvdimm/pmem.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c index 92c67fbbc1c8..05d3de33e270 100644 --- a/drivers/nvdimm/pmem.c +++ b/drivers/nvdimm/pmem.c @@ -208,8 +208,14 @@ static void pmem_submit_bio(struct bio *bio) struct pmem_device *pmem = bio->bi_bdev->bd_disk->private_data; struct nd_region *nd_region = to_region(pmem); - if (bio->bi_opf & REQ_PREFLUSH) - ret = nvdimm_flush(nd_region, bio); + if (bio->bi_opf & REQ_PREFLUSH) { + ret = nvdimm_flush(nd_region, NULL); + if (ret) { + bio->bi_status = errno_to_blk_status(ret); + bio_endio(bio); + return; + } + } do_acct = blk_queue_io_stat(bio->bi_bdev->bd_disk->queue); if (do_acct) @@ -229,7 +235,7 @@ static void pmem_submit_bio(struct bio *bio) if (do_acct) bio_end_io_acct(bio, start); - if (bio->bi_opf & REQ_FUA) + if ((bio->bi_opf & REQ_FUA) && !bio->bi_status) ret = nvdimm_flush(nd_region, bio); if (ret) From 009e18ca5e35069127825d04fc6e5603d771f6c3 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:28 +0800 Subject: [PATCH 23/54] nvdimm: pmem: guard data loop for dataless bios pmem_submit_bio() handles flush-only bios before and after the data loop. Keep dataless bios out of bio_for_each_segment() so the data path only walks bios that actually carry bvec data. Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-4-me@linux.beauty> --- drivers/nvdimm/pmem.c | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c index 05d3de33e270..82ee1ddb3a44 100644 --- a/drivers/nvdimm/pmem.c +++ b/drivers/nvdimm/pmem.c @@ -217,23 +217,29 @@ static void pmem_submit_bio(struct bio *bio) } } - do_acct = blk_queue_io_stat(bio->bi_bdev->bd_disk->queue); - if (do_acct) - start = bio_start_io_acct(bio); - bio_for_each_segment(bvec, bio, iter) { - if (op_is_write(bio_op(bio))) - rc = pmem_do_write(pmem, bvec.bv_page, bvec.bv_offset, - iter.bi_sector, bvec.bv_len); - else - rc = pmem_do_read(pmem, bvec.bv_page, bvec.bv_offset, - iter.bi_sector, bvec.bv_len); - if (rc) { - bio->bi_status = rc; - break; + if (bio_has_data(bio)) { + do_acct = blk_queue_io_stat(bio->bi_bdev->bd_disk->queue); + if (do_acct) + start = bio_start_io_acct(bio); + bio_for_each_segment(bvec, bio, iter) { + if (op_is_write(bio_op(bio))) + rc = pmem_do_write(pmem, bvec.bv_page, + bvec.bv_offset, + iter.bi_sector, + bvec.bv_len); + else + rc = pmem_do_read(pmem, bvec.bv_page, + bvec.bv_offset, + iter.bi_sector, + bvec.bv_len); + if (rc) { + bio->bi_status = rc; + break; + } } + if (do_acct) + bio_end_io_acct(bio, start); } - if (do_acct) - bio_end_io_acct(bio, start); if ((bio->bi_opf & REQ_FUA) && !bio->bi_status) ret = nvdimm_flush(nd_region, bio); From 40f356e610df95728074b1fc2e2ccb54ca1b5659 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:29 +0800 Subject: [PATCH 24/54] nvdimm: virtio_pmem: stop allocating child flush bio pmem_submit_bio() passes the parent bio to nvdimm_flush() for REQ_FUA. For virtio-pmem this makes async_pmem_flush() allocate and submit a child PREFLUSH bio chained to the parent. That child allocation is in the block submit path. Making it blocking with GFP_NOIO can consume the same global bio mempool that submit_bio() uses, while making it GFP_ATOMIC can fail under pressure. A forced failure of the child allocation produced: virtio_pmem: forcing child bio allocation failure for test Buffer I/O error on dev pmem0, logical block 0, lost sync page write EXT4-fs (pmem0): I/O error while writing superblock EXT4-fs (pmem0): mount failed Avoid the child bio without turning REQ_FUA into a synchronous submit-path wait. Let provider flush callbacks return NVDIMM_FLUSH_ASYNC after taking ownership of parent bio completion. pmem_submit_bio() returns in that case, and virtio-pmem queues an ordered WQ_MEM_RECLAIM work item that runs the existing host flush path and completes the parent bio. This keeps the asynchronous completion model of the child-bio path while removing the child bio allocation from the submit path. Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-5-me@linux.beauty> --- drivers/nvdimm/nd_virtio.c | 54 +++++++++++++++++++++++++----------- drivers/nvdimm/pmem.c | 5 +++- drivers/nvdimm/region_devs.c | 2 ++ drivers/nvdimm/virtio_pmem.c | 17 +++++++++++- drivers/nvdimm/virtio_pmem.h | 4 +++ include/linux/libnvdimm.h | 9 ++++++ 6 files changed, 73 insertions(+), 18 deletions(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index 4176046627be..8e16b7780be1 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -9,6 +9,12 @@ #include "virtio_pmem.h" #include "nd.h" +struct virtio_pmem_flush_work { + struct work_struct work; + struct nd_region *nd_region; + struct bio *bio; +}; + /* The interrupt handler */ void virtio_pmem_host_ack(struct virtqueue *vq) { @@ -107,30 +113,46 @@ static int virtio_pmem_flush(struct nd_region *nd_region) return err; }; +static void virtio_pmem_flush_work(struct work_struct *work) +{ + struct virtio_pmem_flush_work *flush; + int err; + + flush = container_of(work, struct virtio_pmem_flush_work, work); + err = virtio_pmem_flush(flush->nd_region); + if (err > 0) + err = -EIO; + if (err) + flush->bio->bi_status = errno_to_blk_status(err); + bio_endio(flush->bio); + kfree(flush); +} + /* The asynchronous flush callback function */ int async_pmem_flush(struct nd_region *nd_region, struct bio *bio) { - /* - * Create child bio for asynchronous flush and chain with - * parent bio. Otherwise directly call nd_region flush. - */ - if (bio && bio->bi_iter.bi_sector != -1) { - struct bio *child = bio_alloc(bio->bi_bdev, 0, - REQ_OP_WRITE | REQ_PREFLUSH, - GFP_ATOMIC); + struct virtio_device *vdev = nd_region->provider_data; + struct virtio_pmem *vpmem = vdev->priv; + struct virtio_pmem_flush_work *flush; + int err; - if (!child) + if (bio && bio->bi_iter.bi_sector != -1) { + flush = kmalloc_obj(*flush, GFP_NOIO); + if (!flush) return -ENOMEM; - bio_clone_blkg_association(child, bio); - child->bi_iter.bi_sector = -1; - bio_chain(child, bio); - submit_bio(child); - return 0; + + INIT_WORK(&flush->work, virtio_pmem_flush_work); + flush->nd_region = nd_region; + flush->bio = bio; + queue_work(vpmem->flush_wq, &flush->work); + return NVDIMM_FLUSH_ASYNC; } - if (virtio_pmem_flush(nd_region)) + + err = virtio_pmem_flush(nd_region); + if (err > 0) return -EIO; - return 0; + return err; }; EXPORT_SYMBOL_GPL(async_pmem_flush); MODULE_DESCRIPTION("Virtio Persistent Memory Driver"); diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c index 82ee1ddb3a44..30a51c365ce8 100644 --- a/drivers/nvdimm/pmem.c +++ b/drivers/nvdimm/pmem.c @@ -241,8 +241,11 @@ static void pmem_submit_bio(struct bio *bio) bio_end_io_acct(bio, start); } - if ((bio->bi_opf & REQ_FUA) && !bio->bi_status) + if ((bio->bi_opf & REQ_FUA) && !bio->bi_status) { ret = nvdimm_flush(nd_region, bio); + if (ret == NVDIMM_FLUSH_ASYNC) + return; + } if (ret) bio->bi_status = errno_to_blk_status(ret); diff --git a/drivers/nvdimm/region_devs.c b/drivers/nvdimm/region_devs.c index 39669eb4ce34..24f42b4650ba 100644 --- a/drivers/nvdimm/region_devs.c +++ b/drivers/nvdimm/region_devs.c @@ -1094,6 +1094,8 @@ int nvdimm_flush(struct nd_region *nd_region, struct bio *bio) rc = generic_nvdimm_flush(nd_region); else { rc = nd_region->flush(nd_region, bio); + if (rc > 0) + return rc; if (rc && rc != -ENOMEM) rc = -EIO; } diff --git a/drivers/nvdimm/virtio_pmem.c b/drivers/nvdimm/virtio_pmem.c index 77b196661905..9cf822a6c0c3 100644 --- a/drivers/nvdimm/virtio_pmem.c +++ b/drivers/nvdimm/virtio_pmem.c @@ -67,10 +67,17 @@ static int virtio_pmem_probe(struct virtio_device *vdev) mutex_init(&vpmem->flush_lock); vpmem->vdev = vdev; vdev->priv = vpmem; + vpmem->flush_wq = alloc_ordered_workqueue("virtio-pmem-flush", + WQ_MEM_RECLAIM); + if (!vpmem->flush_wq) { + err = -ENOMEM; + goto out_err; + } + err = init_vq(vpmem); if (err) { dev_err(&vdev->dev, "failed to initialize virtio pmem vq's\n"); - goto out_err; + goto out_wq; } if (virtio_has_feature(vdev, VIRTIO_PMEM_F_SHMEM_REGION)) { @@ -131,6 +138,8 @@ static int virtio_pmem_probe(struct virtio_device *vdev) nvdimm_bus_unregister(vpmem->nvdimm_bus); out_vq: vdev->config->del_vqs(vdev); +out_wq: + destroy_workqueue(vpmem->flush_wq); out_err: return err; } @@ -138,14 +147,20 @@ static int virtio_pmem_probe(struct virtio_device *vdev) static void virtio_pmem_remove(struct virtio_device *vdev) { struct nvdimm_bus *nvdimm_bus = dev_get_drvdata(&vdev->dev); + struct virtio_pmem *vpmem = vdev->priv; nvdimm_bus_unregister(nvdimm_bus); + drain_workqueue(vpmem->flush_wq); vdev->config->del_vqs(vdev); virtio_reset_device(vdev); + destroy_workqueue(vpmem->flush_wq); } static int virtio_pmem_freeze(struct virtio_device *vdev) { + struct virtio_pmem *vpmem = vdev->priv; + + drain_workqueue(vpmem->flush_wq); vdev->config->del_vqs(vdev); virtio_reset_device(vdev); diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h index f72cf17f9518..e6dfc10ce076 100644 --- a/drivers/nvdimm/virtio_pmem.h +++ b/drivers/nvdimm/virtio_pmem.h @@ -15,6 +15,7 @@ #include #include #include +#include struct virtio_pmem_request { struct virtio_pmem_req req; @@ -39,6 +40,9 @@ struct virtio_pmem { /* Serialize flush requests to the device. */ struct mutex flush_lock; + /* Complete asynchronous FUA flushes outside the submit path. */ + struct workqueue_struct *flush_wq; + /* nvdimm bus registers virtio pmem device */ struct nvdimm_bus *nvdimm_bus; struct nvdimm_bus_descriptor nd_desc; diff --git a/include/linux/libnvdimm.h b/include/linux/libnvdimm.h index 28f086c4a187..d929d83abf3b 100644 --- a/include/linux/libnvdimm.h +++ b/include/linux/libnvdimm.h @@ -126,6 +126,15 @@ struct nd_mapping_desc { struct bio; struct resource; struct nd_region; + +/* + * Provider flush callback return values: + * 0: flush completed synchronously + * <0: flush failed + * >0: flush completion was queued and @bio will be completed later + */ +#define NVDIMM_FLUSH_ASYNC 1 + struct nd_region_desc { struct resource *res; struct nd_mapping_desc *mapping; From decd93e2246fcef1bc5b9266e10d9e8169b7575a Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:30 +0800 Subject: [PATCH 25/54] nvdimm: virtio_pmem: use GFP_NOIO for flush requests virtio_pmem_flush() can run from pmem_submit_bio() while filesystem IO is waiting on the flush completion. The request object allocation can sleep, but it should not enter filesystem or block IO reclaim from this flush path. Use GFP_NOIO for the request allocation. The virtqueue descriptor allocation still uses GFP_ATOMIC because it runs under pmem_lock. Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-6-me@linux.beauty> --- drivers/nvdimm/nd_virtio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index 8e16b7780be1..a35044afddf3 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -61,7 +61,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region) return -EIO; } - req_data = kmalloc_obj(*req_data); + req_data = kmalloc_obj(*req_data, GFP_NOIO); if (!req_data) return -ENOMEM; From 811808761e19fdea1c25b7c76734b8945f758f27 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:31 +0800 Subject: [PATCH 26/54] nvdimm: virtio_pmem: always wake -ENOSPC waiters virtio_pmem_host_ack() reclaims virtqueue descriptors with virtqueue_get_buf(). The -ENOSPC waiter wakeup is tied to completing the returned token. If token completion is skipped for any reason, reclaimed descriptors may not wake a waiter and the submitter may sleep forever waiting for a free slot. Always wake one -ENOSPC waiter for each virtqueue completion before touching the returned token. Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-7-me@linux.beauty> --- drivers/nvdimm/nd_virtio.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index a35044afddf3..fcb26a595d7c 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -15,26 +15,33 @@ struct virtio_pmem_flush_work { struct bio *bio; }; +static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem) +{ + struct virtio_pmem_request *req_buf; + + if (list_empty(&vpmem->req_list)) + return; + + req_buf = list_first_entry(&vpmem->req_list, + struct virtio_pmem_request, list); + req_buf->wq_buf_avail = true; + wake_up(&req_buf->wq_buf); + list_del(&req_buf->list); +} + /* The interrupt handler */ void virtio_pmem_host_ack(struct virtqueue *vq) { struct virtio_pmem *vpmem = vq->vdev->priv; - struct virtio_pmem_request *req_data, *req_buf; + struct virtio_pmem_request *req_data; unsigned long flags; unsigned int len; spin_lock_irqsave(&vpmem->pmem_lock, flags); while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) { + virtio_pmem_wake_one_waiter(vpmem); req_data->done = true; wake_up(&req_data->host_acked); - - if (!list_empty(&vpmem->req_list)) { - req_buf = list_first_entry(&vpmem->req_list, - struct virtio_pmem_request, list); - req_buf->wq_buf_avail = true; - wake_up(&req_buf->wq_buf); - list_del(&req_buf->list); - } } spin_unlock_irqrestore(&vpmem->pmem_lock, flags); } From 08e72a5ba1ab9dc0adf993ff0f4d606a1e3445a8 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:32 +0800 Subject: [PATCH 27/54] nvdimm: virtio_pmem: use READ_ONCE()/WRITE_ONCE() for wait flags Use READ_ONCE()/WRITE_ONCE() for the wait_event() flags (done and wq_buf_avail). They are observed by waiters without pmem_lock, so make the accesses explicit single loads/stores and avoid compiler reordering/caching across the wait/wake paths. Acked-by: Pankaj Gupta Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-8-me@linux.beauty> --- drivers/nvdimm/nd_virtio.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index fcb26a595d7c..8c0d4347938a 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -24,9 +24,9 @@ static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem) req_buf = list_first_entry(&vpmem->req_list, struct virtio_pmem_request, list); - req_buf->wq_buf_avail = true; + list_del_init(&req_buf->list); + WRITE_ONCE(req_buf->wq_buf_avail, true); wake_up(&req_buf->wq_buf); - list_del(&req_buf->list); } /* The interrupt handler */ @@ -40,7 +40,7 @@ void virtio_pmem_host_ack(struct virtqueue *vq) spin_lock_irqsave(&vpmem->pmem_lock, flags); while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) { virtio_pmem_wake_one_waiter(vpmem); - req_data->done = true; + WRITE_ONCE(req_data->done, true); wake_up(&req_data->host_acked); } spin_unlock_irqrestore(&vpmem->pmem_lock, flags); @@ -72,7 +72,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region) if (!req_data) return -ENOMEM; - req_data->done = false; + WRITE_ONCE(req_data->done, false); init_waitqueue_head(&req_data->host_acked); init_waitqueue_head(&req_data->wq_buf); INIT_LIST_HEAD(&req_data->list); @@ -93,12 +93,12 @@ static int virtio_pmem_flush(struct nd_region *nd_region) GFP_ATOMIC)) == -ENOSPC) { dev_info(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n"); - req_data->wq_buf_avail = false; + WRITE_ONCE(req_data->wq_buf_avail, false); list_add_tail(&req_data->list, &vpmem->req_list); spin_unlock_irqrestore(&vpmem->pmem_lock, flags); /* A host response results in "host_ack" getting called */ - wait_event(req_data->wq_buf, req_data->wq_buf_avail); + wait_event(req_data->wq_buf, READ_ONCE(req_data->wq_buf_avail)); spin_lock_irqsave(&vpmem->pmem_lock, flags); } err1 = virtqueue_kick(vpmem->req_vq); @@ -112,7 +112,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region) err = -EIO; } else { /* A host response results in "host_ack" getting called */ - wait_event(req_data->host_acked, req_data->done); + wait_event(req_data->host_acked, READ_ONCE(req_data->done)); err = le32_to_cpu(req_data->resp.ret); } From e57140944b5a47a7fd5a142faab29a02af040bc8 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:33 +0800 Subject: [PATCH 28/54] nvdimm: virtio_pmem: refcount requests for token lifetime KASAN reports slab-use-after-free in __wake_up_common(): BUG: KASAN: slab-use-after-free in __wake_up_common+0x114/0x160 Read of size 8 at addr ffff88810fdcb710 by task swapper/0/0 CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.19.0-next-20260220-00006-g1eae5f204ec3 #4 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux 1.17.0-2-2 04/01/2014 Call Trace: dump_stack_lvl+0x6d/0xb0 print_report+0x170/0x4e2 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 ? __virt_addr_valid+0x1dc/0x380 kasan_report+0xbc/0xf0 ? __wake_up_common+0x114/0x160 ? __wake_up_common+0x114/0x160 __wake_up_common+0x114/0x160 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 __wake_up+0x36/0x60 virtio_pmem_host_ack+0x11d/0x3b0 ? sched_balance_domains+0x29f/0xb00 ? __pfx_virtio_pmem_host_ack+0x10/0x10 ? _raw_spin_lock_irqsave+0x98/0x100 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 vring_interrupt+0x1c9/0x5e0 ? __pfx_vp_interrupt+0x10/0x10 vp_vring_interrupt+0x87/0x100 ? __pfx_vp_interrupt+0x10/0x10 __handle_irq_event_percpu+0x17f/0x550 ? __pfx__raw_spin_lock+0x10/0x10 handle_irq_event+0xab/0x1c0 handle_fasteoi_irq+0x276/0xae0 __common_interrupt+0x65/0x130 common_interrupt+0x78/0xa0 virtio_pmem_host_ack() wakes a request that has already been freed by the submitter. This happens when the request token is still reachable via the virtqueue, but virtio_pmem_flush() returns and frees it. Fix the token lifetime by refcounting struct virtio_pmem_request. virtio_pmem_flush() holds a submitter reference, and the virtqueue holds an extra reference once the request is queued. The completion path drops the virtqueue reference, and the submitter drops its reference before returning. Fixes: 6e84200c0a29 ("virtio-pmem: Add virtio pmem driver") Cc: stable@vger.kernel.org Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-9-me@linux.beauty> --- drivers/nvdimm/nd_virtio.c | 32 ++++++++++++++++++++++++++++---- drivers/nvdimm/virtio_pmem.h | 2 ++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index 8c0d4347938a..1cf53f75b128 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -15,6 +15,14 @@ struct virtio_pmem_flush_work { struct bio *bio; }; +static void virtio_pmem_req_release(struct kref *kref) +{ + struct virtio_pmem_request *req; + + req = container_of(kref, struct virtio_pmem_request, kref); + kfree(req); +} + static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem) { struct virtio_pmem_request *req_buf; @@ -42,6 +50,7 @@ void virtio_pmem_host_ack(struct virtqueue *vq) virtio_pmem_wake_one_waiter(vpmem); WRITE_ONCE(req_data->done, true); wake_up(&req_data->host_acked); + kref_put(&req_data->kref, virtio_pmem_req_release); } spin_unlock_irqrestore(&vpmem->pmem_lock, flags); } @@ -72,6 +81,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region) if (!req_data) return -ENOMEM; + kref_init(&req_data->kref); WRITE_ONCE(req_data->done, false); init_waitqueue_head(&req_data->host_acked); init_waitqueue_head(&req_data->wq_buf); @@ -89,10 +99,23 @@ static int virtio_pmem_flush(struct nd_region *nd_region) * to req_list and wait for host_ack to wake us up when free * slots are available. */ - while ((err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data, - GFP_ATOMIC)) == -ENOSPC) { + for (;;) { + err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data, + GFP_ATOMIC); + if (!err) { + /* + * Take the virtqueue reference while @pmem_lock is + * held so completion cannot run concurrently. + */ + kref_get(&req_data->kref); + break; + } - dev_info(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n"); + if (err != -ENOSPC) + break; + + dev_info_ratelimited(&vdev->dev, + "failed to send command to virtio pmem device, no free slots in the virtqueue\n"); WRITE_ONCE(req_data->wq_buf_avail, false); list_add_tail(&req_data->list, &vpmem->req_list); spin_unlock_irqrestore(&vpmem->pmem_lock, flags); @@ -101,6 +124,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region) wait_event(req_data->wq_buf, READ_ONCE(req_data->wq_buf_avail)); spin_lock_irqsave(&vpmem->pmem_lock, flags); } + err1 = virtqueue_kick(vpmem->req_vq); spin_unlock_irqrestore(&vpmem->pmem_lock, flags); /* @@ -116,7 +140,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region) err = le32_to_cpu(req_data->resp.ret); } - kfree(req_data); + kref_put(&req_data->kref, virtio_pmem_req_release); return err; }; diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h index e6dfc10ce076..3af92588bd9d 100644 --- a/drivers/nvdimm/virtio_pmem.h +++ b/drivers/nvdimm/virtio_pmem.h @@ -12,12 +12,14 @@ #include #include +#include #include #include #include #include struct virtio_pmem_request { + struct kref kref; struct virtio_pmem_req req; struct virtio_pmem_resp resp; From 3f14003ce7f03cd77cea54ff072d437016b9393e Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:34 +0800 Subject: [PATCH 29/54] nvdimm: virtio_pmem: publish done with release/acquire virtio_pmem_host_ack() publishes the device response by setting done and waking the submitter. The submitter reads resp.ret after wait_event() observes done. Use smp_store_release() on done and smp_load_acquire() in the wait condition so the response read is ordered after completion. Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-10-me@linux.beauty> --- drivers/nvdimm/nd_virtio.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index 1cf53f75b128..e4e4284ae19e 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -23,6 +23,19 @@ static void virtio_pmem_req_release(struct kref *kref) kfree(req); } +static void virtio_pmem_signal_done(struct virtio_pmem_request *req) +{ + /* Pairs with smp_load_acquire() in virtio_pmem_req_done(). */ + smp_store_release(&req->done, true); + wake_up(&req->host_acked); +} + +static bool virtio_pmem_req_done(struct virtio_pmem_request *req) +{ + /* Pairs with smp_store_release() in virtio_pmem_signal_done(). */ + return smp_load_acquire(&req->done); +} + static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem) { struct virtio_pmem_request *req_buf; @@ -48,8 +61,7 @@ void virtio_pmem_host_ack(struct virtqueue *vq) spin_lock_irqsave(&vpmem->pmem_lock, flags); while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) { virtio_pmem_wake_one_waiter(vpmem); - WRITE_ONCE(req_data->done, true); - wake_up(&req_data->host_acked); + virtio_pmem_signal_done(req_data); kref_put(&req_data->kref, virtio_pmem_req_release); } spin_unlock_irqrestore(&vpmem->pmem_lock, flags); @@ -136,7 +148,8 @@ static int virtio_pmem_flush(struct nd_region *nd_region) err = -EIO; } else { /* A host response results in "host_ack" getting called */ - wait_event(req_data->host_acked, READ_ONCE(req_data->done)); + wait_event(req_data->host_acked, + virtio_pmem_req_done(req_data)); err = le32_to_cpu(req_data->resp.ret); } From 8de2bc46dbeadbcb6c1e119d72ffa59a630195fb Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:35 +0800 Subject: [PATCH 30/54] nvdimm: virtio_pmem: isolate DMA request buffers The virtio-pmem request object stores wait queues, flags, and list pointers next to buffers mapped for virtqueue DMA. The response buffer is mapped DMA_FROM_DEVICE, so non-coherent DMA invalidation must not share a cache line with CPU-owned fields. Keep the request buffer outside the DMA-from-device group and wrap only the response buffer with __dma_from_device_group_begin/end. Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-11-me@linux.beauty> --- drivers/nvdimm/virtio_pmem.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h index 3af92588bd9d..8843a8b96587 100644 --- a/drivers/nvdimm/virtio_pmem.h +++ b/drivers/nvdimm/virtio_pmem.h @@ -10,6 +10,7 @@ #ifndef _LINUX_VIRTIO_PMEM_H #define _LINUX_VIRTIO_PMEM_H +#include #include #include #include @@ -20,8 +21,6 @@ struct virtio_pmem_request { struct kref kref; - struct virtio_pmem_req req; - struct virtio_pmem_resp resp; /* Wait queue to process deferred work after ack from host */ wait_queue_head_t host_acked; @@ -31,6 +30,11 @@ struct virtio_pmem_request { wait_queue_head_t wq_buf; bool wq_buf_avail; struct list_head list; + + struct virtio_pmem_req req; + __dma_from_device_group_begin(resp); + struct virtio_pmem_resp resp; + __dma_from_device_group_end(resp); }; struct virtio_pmem { From 36e33955aee0020f9fb9fa4d65f0b8a152c78752 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:36 +0800 Subject: [PATCH 31/54] nvdimm: virtio_pmem: converge broken virtqueue to -EIO dmesg reports virtqueue failure and device reset: virtio_pmem virtio2: failed to send command to virtio pmem device, no free slots in the virtqueue virtio_pmem virtio2: virtio pmem device needs a reset virtio_pmem_flush() can wait for a free virtqueue descriptor (-ENOSPC). It can also wait for host completion. If the request virtqueue breaks, those waiters may never make progress. One example is notify failure from virtqueue_kick(). Track a device-level broken state and converge the failure to -EIO. New requests fail fast, -ENOSPC waiters are unlinked and woken, and the currently submitted request is woken so its host_acked waiter can return without waiting forever for host completion. Completed requests are forced to report an error after the queue is marked broken. Also serialize async parent-bio flush work against the broken state with pmem_lock. That way remove and freeze either drain work queued before virtio_pmem_mark_broken(), or later callers see nvdimm_flush() complete the parent bio synchronously with -EIO instead of queuing work after the drain point. Do not detach unused buffers from an active virtqueue. Runtime broken-queue handling only stops new submissions and wakes local waiters. Removal resets the device first. It then drains request tokens. After that, the device no longer owns the buffers when the virtqueue reference is dropped. Closes: https://lore.kernel.org/r/202512250116.ewtzlD0g-lkp@intel.com/ Signed-off-by: Li Chen Link: https://lore.kernel.org/r/202512250116.ewtzlD0g-lkp@intel.com/ Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-12-me@linux.beauty> --- drivers/nvdimm/nd_virtio.c | 126 +++++++++++++++++++++++++++++++---- drivers/nvdimm/virtio_pmem.c | 16 ++++- drivers/nvdimm/virtio_pmem.h | 8 +++ 3 files changed, 136 insertions(+), 14 deletions(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index e4e4284ae19e..a6820300cbe8 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -36,6 +36,12 @@ static bool virtio_pmem_req_done(struct virtio_pmem_request *req) return smp_load_acquire(&req->done); } +static void virtio_pmem_complete_err(struct virtio_pmem_request *req) +{ + req->resp.ret = cpu_to_le32(1); + virtio_pmem_signal_done(req); +} + static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem) { struct virtio_pmem_request *req_buf; @@ -50,6 +56,63 @@ static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem) wake_up(&req_buf->wq_buf); } +static void virtio_pmem_wake_all_waiters(struct virtio_pmem *vpmem) +{ + struct virtio_pmem_request *req, *tmp; + + list_for_each_entry_safe(req, tmp, &vpmem->req_list, list) { + list_del_init(&req->list); + WRITE_ONCE(req->wq_buf_avail, true); + wake_up(&req->wq_buf); + } +} + +static void virtio_pmem_clear_inflight(struct virtio_pmem *vpmem, + struct virtio_pmem_request *req) +{ + if (vpmem->req_inflight == req) + vpmem->req_inflight = NULL; +} + +static void virtio_pmem_wake_inflight(struct virtio_pmem *vpmem) +{ + struct virtio_pmem_request *req = vpmem->req_inflight; + + if (req) + wake_up(&req->host_acked); +} + +void virtio_pmem_mark_broken(struct virtio_pmem *vpmem) +{ + if (!READ_ONCE(vpmem->broken)) { + WRITE_ONCE(vpmem->broken, true); + dev_err_once(&vpmem->vdev->dev, "virtqueue is broken\n"); + } + + virtio_pmem_wake_inflight(vpmem); + virtio_pmem_wake_all_waiters(vpmem); +} +EXPORT_SYMBOL_GPL(virtio_pmem_mark_broken); + +void virtio_pmem_drain(struct virtio_pmem *vpmem) +{ + struct virtio_pmem_request *req; + unsigned int len; + + while ((req = virtqueue_get_buf(vpmem->req_vq, &len)) != NULL) { + virtio_pmem_clear_inflight(vpmem, req); + virtio_pmem_complete_err(req); + kref_put(&req->kref, virtio_pmem_req_release); + } + + while ((req = virtqueue_detach_unused_buf(vpmem->req_vq)) != NULL) { + virtio_pmem_clear_inflight(vpmem, req); + virtio_pmem_complete_err(req); + kref_put(&req->kref, virtio_pmem_req_release); + } +} +EXPORT_SYMBOL_GPL(virtio_pmem_drain); + /* The interrupt handler */ void virtio_pmem_host_ack(struct virtqueue *vq) { @@ -60,8 +123,12 @@ void virtio_pmem_host_ack(struct virtqueue *vq) spin_lock_irqsave(&vpmem->pmem_lock, flags); while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) { + virtio_pmem_clear_inflight(vpmem, req_data); virtio_pmem_wake_one_waiter(vpmem); - virtio_pmem_signal_done(req_data); + if (READ_ONCE(vpmem->broken)) + virtio_pmem_complete_err(req_data); + else + virtio_pmem_signal_done(req_data); kref_put(&req_data->kref, virtio_pmem_req_release); } spin_unlock_irqrestore(&vpmem->pmem_lock, flags); @@ -89,6 +156,9 @@ static int virtio_pmem_flush(struct nd_region *nd_region) return -EIO; } + if (READ_ONCE(vpmem->broken)) + return -EIO; + req_data = kmalloc_obj(*req_data, GFP_NOIO); if (!req_data) return -ENOMEM; @@ -105,13 +175,18 @@ static int virtio_pmem_flush(struct nd_region *nd_region) sgs[1] = &ret; spin_lock_irqsave(&vpmem->pmem_lock, flags); - /* - * If virtqueue_add_sgs returns -ENOSPC then req_vq virtual - * queue does not have free descriptor. We add the request - * to req_list and wait for host_ack to wake us up when free - * slots are available. - */ + /* + * If virtqueue_add_sgs returns -ENOSPC then req_vq virtual + * queue does not have free descriptor. We add the request + * to req_list and wait for host_ack to wake us up when free + * slots are available. + */ for (;;) { + if (READ_ONCE(vpmem->broken)) { + err = -EIO; + break; + } + err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data, GFP_ATOMIC); if (!err) { @@ -120,6 +195,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region) * held so completion cannot run concurrently. */ kref_get(&req_data->kref); + vpmem->req_inflight = req_data; break; } @@ -133,24 +209,41 @@ static int virtio_pmem_flush(struct nd_region *nd_region) spin_unlock_irqrestore(&vpmem->pmem_lock, flags); /* A host response results in "host_ack" getting called */ - wait_event(req_data->wq_buf, READ_ONCE(req_data->wq_buf_avail)); + wait_event(req_data->wq_buf, + READ_ONCE(req_data->wq_buf_avail) || + READ_ONCE(vpmem->broken)); spin_lock_irqsave(&vpmem->pmem_lock, flags); + + if (READ_ONCE(vpmem->broken)) + break; } - err1 = virtqueue_kick(vpmem->req_vq); + if (err == -EIO || virtqueue_is_broken(vpmem->req_vq)) + virtio_pmem_mark_broken(vpmem); + + err1 = true; + if (!err && !READ_ONCE(vpmem->broken)) { + err1 = virtqueue_kick(vpmem->req_vq); + if (!err1) + virtio_pmem_mark_broken(vpmem); + } spin_unlock_irqrestore(&vpmem->pmem_lock, flags); /* * virtqueue_add_sgs failed with error different than -ENOSPC, we can't * do anything about that. */ - if (err || !err1) { + if (READ_ONCE(vpmem->broken) || err || !err1) { dev_info(&vdev->dev, "failed to send command to virtio pmem device\n"); err = -EIO; } else { /* A host response results in "host_ack" getting called */ wait_event(req_data->host_acked, - virtio_pmem_req_done(req_data)); - err = le32_to_cpu(req_data->resp.ret); + virtio_pmem_req_done(req_data) || + READ_ONCE(vpmem->broken)); + if (virtio_pmem_req_done(req_data)) + err = le32_to_cpu(req_data->resp.ret); + else + err = -EIO; } kref_put(&req_data->kref, virtio_pmem_req_release); @@ -178,6 +271,7 @@ int async_pmem_flush(struct nd_region *nd_region, struct bio *bio) struct virtio_device *vdev = nd_region->provider_data; struct virtio_pmem *vpmem = vdev->priv; struct virtio_pmem_flush_work *flush; + unsigned long flags; int err; if (bio && bio->bi_iter.bi_sector != -1) { @@ -188,7 +282,15 @@ int async_pmem_flush(struct nd_region *nd_region, struct bio *bio) INIT_WORK(&flush->work, virtio_pmem_flush_work); flush->nd_region = nd_region; flush->bio = bio; + + spin_lock_irqsave(&vpmem->pmem_lock, flags); + if (READ_ONCE(vpmem->broken)) { + spin_unlock_irqrestore(&vpmem->pmem_lock, flags); + kfree(flush); + return -EIO; + } queue_work(vpmem->flush_wq, &flush->work); + spin_unlock_irqrestore(&vpmem->pmem_lock, flags); return NVDIMM_FLUSH_ASYNC; } diff --git a/drivers/nvdimm/virtio_pmem.c b/drivers/nvdimm/virtio_pmem.c index 9cf822a6c0c3..36664a5ea25e 100644 --- a/drivers/nvdimm/virtio_pmem.c +++ b/drivers/nvdimm/virtio_pmem.c @@ -25,6 +25,8 @@ static int init_vq(struct virtio_pmem *vpmem) spin_lock_init(&vpmem->pmem_lock); INIT_LIST_HEAD(&vpmem->req_list); + vpmem->req_inflight = NULL; + WRITE_ONCE(vpmem->broken, false); return 0; }; @@ -148,11 +150,21 @@ static void virtio_pmem_remove(struct virtio_device *vdev) { struct nvdimm_bus *nvdimm_bus = dev_get_drvdata(&vdev->dev); struct virtio_pmem *vpmem = vdev->priv; + unsigned long flags; + + spin_lock_irqsave(&vpmem->pmem_lock, flags); + virtio_pmem_mark_broken(vpmem); + spin_unlock_irqrestore(&vpmem->pmem_lock, flags); + + drain_workqueue(vpmem->flush_wq); + virtio_reset_device(vdev); + + spin_lock_irqsave(&vpmem->pmem_lock, flags); + virtio_pmem_drain(vpmem); + spin_unlock_irqrestore(&vpmem->pmem_lock, flags); nvdimm_bus_unregister(nvdimm_bus); - drain_workqueue(vpmem->flush_wq); vdev->config->del_vqs(vdev); - virtio_reset_device(vdev); destroy_workqueue(vpmem->flush_wq); } diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h index 8843a8b96587..0b90777d7658 100644 --- a/drivers/nvdimm/virtio_pmem.h +++ b/drivers/nvdimm/virtio_pmem.h @@ -56,6 +56,12 @@ struct virtio_pmem { /* List to store deferred work if virtqueue is full */ struct list_head req_list; + /* Request currently owned by the virtqueue. */ + struct virtio_pmem_request *req_inflight; + + /* Fail fast and wake waiters if the request virtqueue is broken. */ + bool broken; + /* Synchronize virtqueue data */ spinlock_t pmem_lock; @@ -65,5 +71,7 @@ struct virtio_pmem { }; void virtio_pmem_host_ack(struct virtqueue *vq); +void virtio_pmem_mark_broken(struct virtio_pmem *vpmem); +void virtio_pmem_drain(struct virtio_pmem *vpmem); int async_pmem_flush(struct nd_region *nd_region, struct bio *bio); #endif From 74d7d137f4bb18cb4957d97667bdf988ee9ff918 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:37 +0800 Subject: [PATCH 32/54] nvdimm: virtio_pmem: drain requests in freeze virtio_pmem_freeze() currently deletes virtqueues and resets the device without waking threads waiting for a virtqueue descriptor or a host completion. Mark the request virtqueue broken before reset. This makes new submissions fail fast and lets -ENOSPC waiters leave the wait list. Reset the device before draining used and unused request tokens, then delete the virtqueues. This wakes waiters with -EIO. It also keeps the detach call on a quiesced device. Clear req_vq after del_vqs(). Make drain tolerate a NULL queue so remove after freeze does not dereference a stale virtqueue pointer. Also make virtio_pmem_flush() stop checking req_vq once the broken state is visible. A waiter woken by freeze/remove can resume after del_vqs() has cleared req_vq. Signed-off-by: Li Chen Signed-off-by: Michael S. Tsirkin Message-ID: <20260630092338.2094628-13-me@linux.beauty> --- drivers/nvdimm/nd_virtio.c | 5 +++++ drivers/nvdimm/virtio_pmem.c | 34 +++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index a6820300cbe8..3b8be79a20a0 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -99,6 +99,9 @@ void virtio_pmem_drain(struct virtio_pmem *vpmem) struct virtio_pmem_request *req; unsigned int len; + if (!vpmem->req_vq) + return; + while ((req = virtqueue_get_buf(vpmem->req_vq, &len)) != NULL) { virtio_pmem_clear_inflight(vpmem, req); virtio_pmem_complete_err(req); @@ -218,6 +221,8 @@ static int virtio_pmem_flush(struct nd_region *nd_region) break; } + if (READ_ONCE(vpmem->broken)) + err = -EIO; if (err == -EIO || virtqueue_is_broken(vpmem->req_vq)) virtio_pmem_mark_broken(vpmem); diff --git a/drivers/nvdimm/virtio_pmem.c b/drivers/nvdimm/virtio_pmem.c index 36664a5ea25e..7ee3fb1779f7 100644 --- a/drivers/nvdimm/virtio_pmem.c +++ b/drivers/nvdimm/virtio_pmem.c @@ -17,11 +17,16 @@ static struct virtio_device_id id_table[] = { /* Initialize virt queue */ static int init_vq(struct virtio_pmem *vpmem) { + int err; + /* single vq */ vpmem->req_vq = virtio_find_single_vq(vpmem->vdev, virtio_pmem_host_ack, "flush_queue"); - if (IS_ERR(vpmem->req_vq)) - return PTR_ERR(vpmem->req_vq); + if (IS_ERR(vpmem->req_vq)) { + err = PTR_ERR(vpmem->req_vq); + vpmem->req_vq = NULL; + return err; + } spin_lock_init(&vpmem->pmem_lock); INIT_LIST_HEAD(&vpmem->req_list); @@ -31,6 +36,15 @@ static int init_vq(struct virtio_pmem *vpmem) return 0; }; +static void virtio_pmem_del_vqs(struct virtio_pmem *vpmem) +{ + if (!vpmem->req_vq) + return; + + vpmem->vdev->config->del_vqs(vpmem->vdev); + vpmem->req_vq = NULL; +} + static int virtio_pmem_validate(struct virtio_device *vdev) { struct virtio_shm_region shm_reg; @@ -139,7 +153,7 @@ static int virtio_pmem_probe(struct virtio_device *vdev) virtio_reset_device(vdev); nvdimm_bus_unregister(vpmem->nvdimm_bus); out_vq: - vdev->config->del_vqs(vdev); + virtio_pmem_del_vqs(vpmem); out_wq: destroy_workqueue(vpmem->flush_wq); out_err: @@ -164,18 +178,28 @@ static void virtio_pmem_remove(struct virtio_device *vdev) spin_unlock_irqrestore(&vpmem->pmem_lock, flags); nvdimm_bus_unregister(nvdimm_bus); - vdev->config->del_vqs(vdev); + virtio_pmem_del_vqs(vpmem); destroy_workqueue(vpmem->flush_wq); } static int virtio_pmem_freeze(struct virtio_device *vdev) { struct virtio_pmem *vpmem = vdev->priv; + unsigned long flags; + + spin_lock_irqsave(&vpmem->pmem_lock, flags); + virtio_pmem_mark_broken(vpmem); + spin_unlock_irqrestore(&vpmem->pmem_lock, flags); drain_workqueue(vpmem->flush_wq); - vdev->config->del_vqs(vdev); virtio_reset_device(vdev); + spin_lock_irqsave(&vpmem->pmem_lock, flags); + virtio_pmem_drain(vpmem); + spin_unlock_irqrestore(&vpmem->pmem_lock, flags); + + virtio_pmem_del_vqs(vpmem); + return 0; } From 23ae56d9e74c122f95cae71ae3b9fc259fb88446 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Wed, 1 Jul 2026 19:36:08 +0800 Subject: [PATCH 33/54] vdpa/mlx5: fix wrong list iterated in add_direct_chain error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In add_direct_chain(), newly allocated direct MR entries are added to the local list 'tmp', which is spliced into mr->head only on success. On the error path, the cleanup loop was incorrectly iterating over mr->head instead of tmp. Fix by iterating over 'tmp' in the err_alloc cleanup path. Fixes: 94abbccdf291 ("vdpa/mlx5: Add shared memory registration code") Signed-off-by: Li RongQing Acked-by: Eugenio Pérez Reviewed-by: Dragos Tatulea Signed-off-by: Michael S. Tsirkin Message-ID: <20260701113608.1972-1-lirongqing@baidu.com> --- drivers/vdpa/mlx5/core/mr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/vdpa/mlx5/core/mr.c b/drivers/vdpa/mlx5/core/mr.c index 77a479aeaa85..b0c5ff23d022 100644 --- a/drivers/vdpa/mlx5/core/mr.c +++ b/drivers/vdpa/mlx5/core/mr.c @@ -481,7 +481,7 @@ static int add_direct_chain(struct mlx5_vdpa_dev *mvdev, return 0; err_alloc: - list_for_each_entry_safe(dmr, n, &mr->head, list) { + list_for_each_entry_safe(dmr, n, &tmp, list) { list_del_init(&dmr->list); unmap_direct_mr(mvdev, dmr); kfree(dmr); From 7eeda5e2487276ed87960655fdae1c97d9779a0e Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:27:32 +0800 Subject: [PATCH 34/54] vdpa: alibaba: add missing MODULE_DEVICE_TABLE() The driver has a match table for the pci bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Signed-off-by: Michael S. Tsirkin Message-ID: <20260704152732.55338-1-pengpeng@iscas.ac.cn> --- drivers/vdpa/alibaba/eni_vdpa.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/vdpa/alibaba/eni_vdpa.c b/drivers/vdpa/alibaba/eni_vdpa.c index e476504db0c8..fd6fdba46094 100644 --- a/drivers/vdpa/alibaba/eni_vdpa.c +++ b/drivers/vdpa/alibaba/eni_vdpa.c @@ -545,6 +545,7 @@ static struct pci_device_id eni_pci_ids[] = { VIRTIO_ID_NET) }, { 0 }, }; +MODULE_DEVICE_TABLE(pci, eni_pci_ids); static struct pci_driver eni_vdpa_driver = { .name = "alibaba-eni-vdpa", From 346da62d9f9ccca81cf279c361137d1fe2fe0c7f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 08:25:46 +0800 Subject: [PATCH 35/54] vdpa: octeon_ep: add missing MODULE_DEVICE_TABLE() The driver has a match table for the pci bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Signed-off-by: Michael S. Tsirkin Message-ID: <20260705002546.85004-1-pengpeng@iscas.ac.cn> --- drivers/vdpa/octeon_ep/octep_vdpa_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/vdpa/octeon_ep/octep_vdpa_main.c b/drivers/vdpa/octeon_ep/octep_vdpa_main.c index 5b35993750f5..6d8ccdc14fb6 100644 --- a/drivers/vdpa/octeon_ep/octep_vdpa_main.c +++ b/drivers/vdpa/octeon_ep/octep_vdpa_main.c @@ -979,6 +979,7 @@ static struct pci_device_id octep_pci_vdpa_map[] = { { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, OCTEP_VDPA_DEVID_CN103K_VF) }, { 0 }, }; +MODULE_DEVICE_TABLE(pci, octep_pci_vdpa_map); static struct pci_driver octep_pci_vdpa = { .name = OCTEP_VDPA_DRIVER_NAME, From 2de85565762ebd3c6a7fb397cafdfcabb8cd8caa Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Mon, 6 Jul 2026 14:09:02 +0800 Subject: [PATCH 36/54] vdpa/mlx5: fix wrong MLX5_ADDR_OF struct type in alloc_inout() In alloc_inout(), the qpc field offset was computed using MLX5_ADDR_OF(rst2init_qp_in, ...) in both the INIT2RTR_QP and RTR2RTS_QP cases. This is a copy-paste error: each case should use its own input structure type to get the correct qpc offset. Fix the INIT2RTR_QP case to use MLX5_ADDR_OF(init2rtr_qp_in, ...) and the RTR2RTS_QP case to use MLX5_ADDR_OF(rtr2rts_qp_in, ...). Signed-off-by: Li RongQing Reviewed-by: Dragos Tatulea Signed-off-by: Michael S. Tsirkin Message-ID: <20260706060902.2341-1-lirongqing@baidu.com> --- drivers/vdpa/mlx5/net/mlx5_vnet.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/vdpa/mlx5/net/mlx5_vnet.c b/drivers/vdpa/mlx5/net/mlx5_vnet.c index ad0d5fbbbca8..eb431a1471a0 100644 --- a/drivers/vdpa/mlx5/net/mlx5_vnet.c +++ b/drivers/vdpa/mlx5/net/mlx5_vnet.c @@ -1080,7 +1080,7 @@ static void alloc_inout(struct mlx5_vdpa_net *ndev, int cmd, void **in, int *inl MLX5_SET(init2rtr_qp_in, *in, opcode, cmd); MLX5_SET(init2rtr_qp_in, *in, uid, ndev->mvdev.res.uid); MLX5_SET(init2rtr_qp_in, *in, qpn, qpn); - qpc = MLX5_ADDR_OF(rst2init_qp_in, *in, qpc); + qpc = MLX5_ADDR_OF(init2rtr_qp_in, *in, qpc); MLX5_SET(qpc, qpc, mtu, MLX5_QPC_MTU_256_BYTES); MLX5_SET(qpc, qpc, log_msg_max, 30); MLX5_SET(qpc, qpc, remote_qpn, rqpn); @@ -1098,7 +1098,7 @@ static void alloc_inout(struct mlx5_vdpa_net *ndev, int cmd, void **in, int *inl MLX5_SET(rtr2rts_qp_in, *in, opcode, cmd); MLX5_SET(rtr2rts_qp_in, *in, uid, ndev->mvdev.res.uid); MLX5_SET(rtr2rts_qp_in, *in, qpn, qpn); - qpc = MLX5_ADDR_OF(rst2init_qp_in, *in, qpc); + qpc = MLX5_ADDR_OF(rtr2rts_qp_in, *in, qpc); pp = MLX5_ADDR_OF(qpc, qpc, primary_address_path); MLX5_SET(ads, pp, ack_timeout, 14); MLX5_SET(qpc, qpc, retry_count, 7); From 68e00d9212929805b40dcb9166755610f4f4acee Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Tue, 14 Jul 2026 10:43:52 +0800 Subject: [PATCH 37/54] virtio: rtc: time out alarm requests RTC class operations run with rtc_device.ops_lock held. The virtio RTC alarm requests currently wait without a timeout for the device to return their requestq buffers. On surprise removal, virtio-pci marks the virtqueues broken before unregistering the virtio device. If an alarm request is waiting when the device stops responding, viortc_remove() blocks in viortc_class_stop() while trying to acquire ops_lock. The request cannot complete and device removal hangs until the waiting task is signalled. Use the same 60-second timeout as clock read requests for alarm reads, alarm programming, and alarm interrupt enable requests. The existing message reference counting keeps a timed-out request alive until a late response or device teardown. Fixes: 9d4f22fd563e ("virtio_rtc: Add RTC class driver") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: GuoHan Zhao Reviewed-by: Peter Hilber Signed-off-by: Michael S. Tsirkin Message-ID: <20260714024352.71307-1-zhaoguohan@kylinos.cn> --- drivers/virtio/virtio_rtc_driver.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/virtio/virtio_rtc_driver.c b/drivers/virtio/virtio_rtc_driver.c index 4419735b0f0d..74616ba5be11 100644 --- a/drivers/virtio/virtio_rtc_driver.c +++ b/drivers/virtio/virtio_rtc_driver.c @@ -574,8 +574,8 @@ static int viortc_msg_xfer(struct viortc_vq *vq, struct viortc_msg *msg, * read requests */ -/** timeout for clock readings, where timeouts are considered non-fatal */ -#define VIORTC_MSG_READ_TIMEOUT secs_to_jiffies(60) +/** timeout for runtime requests, where timeouts are considered non-fatal */ +#define VIORTC_MSG_TIMEOUT secs_to_jiffies(60) /** * viortc_read() - VIRTIO_RTC_REQ_READ wrapper @@ -600,7 +600,7 @@ int viortc_read(struct viortc_dev *viortc, u16 vio_clk_id, u64 *reading) VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), - VIORTC_MSG_READ_TIMEOUT); + VIORTC_MSG_TIMEOUT); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); @@ -642,7 +642,7 @@ int viortc_read_cross(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter, VIORTC_MSG_WRITE(hdl, hw_counter, &hw_counter); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), - VIORTC_MSG_READ_TIMEOUT); + VIORTC_MSG_TIMEOUT); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); @@ -809,7 +809,7 @@ int viortc_read_alarm(struct viortc_dev *viortc, u16 vio_clk_id, VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), - 0); + VIORTC_MSG_TIMEOUT); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); @@ -858,7 +858,7 @@ int viortc_set_alarm(struct viortc_dev *viortc, u16 vio_clk_id, u64 alarm_time, VIORTC_MSG_WRITE(hdl, flags, &flags); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), - 0); + VIORTC_MSG_TIMEOUT); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); @@ -900,7 +900,7 @@ int viortc_set_alarm_enabled(struct viortc_dev *viortc, u16 vio_clk_id, VIORTC_MSG_WRITE(hdl, flags, &flags); ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), - 0); + VIORTC_MSG_TIMEOUT); if (ret) { dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, ret); From b40b933e8dff8f1af7b4dab53df8ba6880b3588d Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Tue, 14 Jul 2026 10:44:34 +0800 Subject: [PATCH 38/54] vhost: fix inaccurate kdoc in iotlb helpers Correct missing "if" in the add_range_ctx return description, and align vhost_iotlb_alloc documentation with its NULL return on allocation failure. Signed-off-by: xiongweimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260714024434.188302-1-15927021679@163.com> --- drivers/vhost/iotlb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/vhost/iotlb.c b/drivers/vhost/iotlb.c index a1d4376a5b87..3e5748e4d5fd 100644 --- a/drivers/vhost/iotlb.c +++ b/drivers/vhost/iotlb.c @@ -50,7 +50,7 @@ EXPORT_SYMBOL_GPL(vhost_iotlb_map_free); * @perm: access permission of this range * @opaque: the opaque pointer for the new mapping * - * Returns an error last is smaller than start or memory allocation + * Returns an error if last is smaller than start or memory allocation * fails */ int vhost_iotlb_add_range_ctx(struct vhost_iotlb *iotlb, @@ -162,11 +162,11 @@ void vhost_iotlb_init(struct vhost_iotlb *iotlb, unsigned int limit, EXPORT_SYMBOL_GPL(vhost_iotlb_init); /** - * vhost_iotlb_alloc - add a new vhost IOTLB + * vhost_iotlb_alloc - allocate a new vhost IOTLB * @limit: maximum number of IOTLB entries * @flags: VHOST_IOTLB_FLAG_XXX * - * Returns an error is memory allocation fails + * Returns NULL if memory allocation fails */ struct vhost_iotlb *vhost_iotlb_alloc(unsigned int limit, unsigned int flags) { From 3b0320a27554ec26b822ee0fe42cd4b34890525e Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Tue, 14 Jul 2026 10:45:13 +0800 Subject: [PATCH 39/54] virtio: fix article before virtio in dma-buf comment Use "a virtio" rather than "an virtio". Signed-off-by: xiongweimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260714024513.188571-1-15927021679@163.com> --- drivers/virtio/virtio_dma_buf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_dma_buf.c b/drivers/virtio/virtio_dma_buf.c index 95c10632f84a..901282d82f0f 100644 --- a/drivers/virtio/virtio_dma_buf.c +++ b/drivers/virtio/virtio_dma_buf.c @@ -14,7 +14,7 @@ * struct embedded in a virtio_dma_buf_ops. * * This wraps dma_buf_export() to allow virtio drivers to create a dma-buf - * for an virtio exported object that can be queried by other virtio drivers + * for a virtio exported object that can be queried by other virtio drivers * for the object's UUID. */ struct dma_buf *virtio_dma_buf_export From c9b38c0ff7b6d68b06c2b2363be5a8c374c713d7 Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Tue, 14 Jul 2026 10:45:27 +0800 Subject: [PATCH 40/54] vdpa/solidrun: fix typos in snet_ctrl comments Correct "readind" and "the an error" in the DPU control path comments. Signed-off-by: xiongweimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260714024527.188645-1-15927021679@163.com> --- drivers/vdpa/solidrun/snet_ctrl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/vdpa/solidrun/snet_ctrl.c b/drivers/vdpa/solidrun/snet_ctrl.c index 3cef2571d15d..e284c3a06717 100644 --- a/drivers/vdpa/solidrun/snet_ctrl.c +++ b/drivers/vdpa/solidrun/snet_ctrl.c @@ -124,10 +124,10 @@ static int snet_wait_for_dpu_completion(struct snet_ctrl_regs __iomem *ctrl_regs * reading the in_process and error bits in the control register. * (2) Write the request opcode and the VQ idx in the opcode register * and write the buffer size in the control register. - * (3) Start readind chunks of data, chunk_ready bit indicates that a + * (3) Start reading chunks of data, chunk_ready bit indicates that a * data chunk is available, we signal that we read the data by clearing the bit. * (4) Detect that the transfer is completed when the in_process bit - * in the control register is cleared or when the an error appears. + * in the control register is cleared or when an error appears. */ static int snet_ctrl_read_from_dpu(struct snet *snet, u16 opcode, u16 vq_idx, void *buffer, u32 buf_size) From 7eaf82117b9ee8c40b568cddbc50864d4e204eae Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Tue, 14 Jul 2026 11:24:17 +0800 Subject: [PATCH 41/54] virtio_mem: fix typo in comment Correct "actipn" to "action". Signed-off-by: xiongweimin Reviewed-by: Parav Pandit Acked-by: David Hildenbrand (Arm) Signed-off-by: Michael S. Tsirkin Message-ID: <20260714032417.201353-1-xiongwm2026@163.com> --- drivers/virtio/virtio_mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_mem.c b/drivers/virtio/virtio_mem.c index 82a285c0926d..e18dd736f2ec 100644 --- a/drivers/virtio/virtio_mem.c +++ b/drivers/virtio/virtio_mem.c @@ -1080,7 +1080,7 @@ static int virtio_mem_memory_notifier_cb(struct notifier_block *nb, atomic64_sub(size, &vm->offline_size); /* * Start adding more memory once we onlined half of our - * threshold. Don't trigger if it's possibly due to our actipn + * threshold. Don't trigger if it's possibly due to our action * (e.g., us adding memory which gets onlined immediately from * the core). */ From 315584667fd1d1dcf721fbe4e1aca6e2415b3f25 Mon Sep 17 00:00:00 2001 From: Gabriel Somlo Date: Wed, 15 Jul 2026 11:19:08 -0400 Subject: [PATCH 42/54] MAINTAINERS: remove Gabriel from LiteX and fw-cfg drivers I no longer have the bandwidth to look after these drivers, so I'm leaving them in the able hands of my co-maintainers. Signed-off-by: Gabriel Somlo Signed-off-by: Michael S. Tsirkin Message-ID: <20260715151908.1534002-1-gsomlo@gmail.com> --- MAINTAINERS | 2 -- 1 file changed, 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 8014b9f8253e..1003bc75184d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15005,7 +15005,6 @@ F: lib/tests/list-test.c LITEX PLATFORM M: Karol Gugala M: Mateusz Holenko -M: Gabriel Somlo M: Joel Stanley S: Maintained F: Documentation/devicetree/bindings/*/litex,*.yaml @@ -21916,7 +21915,6 @@ S: Maintained F: drivers/net/ipa/ QEMU MACHINE EMULATOR AND VIRTUALIZER SUPPORT -M: Gabriel Somlo M: "Michael S. Tsirkin" L: qemu-devel@nongnu.org S: Maintained From 11f79e2780043ad94424a8db02a7a9022ba93252 Mon Sep 17 00:00:00 2001 From: Weimin Xiong Date: Thu, 16 Jul 2026 13:43:53 +0800 Subject: [PATCH 43/54] vdpa/mlx5: roll back MR update after VQ setup failure mlx5_vdpa_change_map() must install the new MR before rebuilding or resuming virtqueues, because both paths read the MR keys from mvdev->mres.mr[]. If rebuilding the virtqueue resources fails, the new MR must not remain installed after its reference is released. Keep an extra reference to the old MR before replacing it. On setup failure, restore the old MR; the saved reference then becomes the map reference, while replacing the new MR drops its map reference. Make mlx5_vdpa_change_map() consume new_mr on all error paths so that set_map_data() does not release an MR already released during rollback. v2: - Keep the new MR installed while virtqueues are rebuilt. - Restore the old MR only after setup_vq_resources() fails. Signed-off-by: Weimin Xiong Signed-off-by: Michael S. Tsirkin Message-ID: <20260716054353.155805-1-xiongwm2026@163.com> --- drivers/vdpa/mlx5/net/mlx5_vnet.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/vdpa/mlx5/net/mlx5_vnet.c b/drivers/vdpa/mlx5/net/mlx5_vnet.c index eb431a1471a0..8563fec2855d 100644 --- a/drivers/vdpa/mlx5/net/mlx5_vnet.c +++ b/drivers/vdpa/mlx5/net/mlx5_vnet.c @@ -3055,18 +3055,24 @@ static int mlx5_vdpa_change_map(struct mlx5_vdpa_dev *mvdev, unsigned int asid) { struct mlx5_vdpa_net *ndev = to_mlx5_vdpa_ndev(mvdev); + struct mlx5_vdpa_mr *old_mr; bool teardown = !is_resumable(ndev); int err; suspend_vqs(ndev, 0, ndev->cur_num_vqs); if (teardown) { err = save_channels_info(ndev); - if (err) + if (err) { + mlx5_vdpa_put_mr(mvdev, new_mr); return err; + } teardown_vq_resources(ndev); } + /* Keep the old MR alive in case rebuilding the VQs fails. */ + old_mr = mvdev->mres.mr[asid]; + mlx5_vdpa_get_mr(mvdev, old_mr); mlx5_vdpa_update_mr(mvdev, new_mr, asid); for (int i = 0; i < mvdev->max_vqs; i++) @@ -3074,17 +3080,22 @@ static int mlx5_vdpa_change_map(struct mlx5_vdpa_dev *mvdev, MLX5_VIRTQ_MODIFY_MASK_DESC_GROUP_MKEY; if (!(mvdev->status & VIRTIO_CONFIG_S_DRIVER_OK) || mvdev->suspended) - return 0; + goto out; if (teardown) { restore_channels_info(ndev); err = setup_vq_resources(ndev, true); - if (err) + if (err) { + /* The saved reference becomes the restored map reference. */ + mlx5_vdpa_update_mr(mvdev, old_mr, asid); return err; + } } resume_vqs(ndev, 0, ndev->cur_num_vqs); +out: + mlx5_vdpa_put_mr(mvdev, old_mr); return 0; } @@ -3368,15 +3379,11 @@ static int set_map_data(struct mlx5_vdpa_dev *mvdev, struct vhost_iotlb *iotlb, err = mlx5_vdpa_change_map(mvdev, new_mr, asid); if (err) { mlx5_vdpa_err(mvdev, "change map failed(%d)\n", err); - goto out_err; + return err; } } return mlx5_vdpa_update_cvq_iotlb(mvdev, iotlb, asid); - -out_err: - mlx5_vdpa_put_mr(mvdev, new_mr); - return err; } static int mlx5_vdpa_set_map(struct vdpa_device *vdev, unsigned int asid, From 0d0eff39ceb3dcbf7847a6f4517086c207c60081 Mon Sep 17 00:00:00 2001 From: Jinqian Yang Date: Thu, 16 Jul 2026 19:59:40 +0800 Subject: [PATCH 44/54] virtio_ring: fix infinite loop in virtnet_poll_cleantx when device is broken virtnet_poll_cleantx() contains a do-while loop that cleans up transmitted TX buffers and calls virtqueue_enable_cb_delayed() to check whether more buffers need processing. When the virtio backend stops responding during guest reboot, used->idx is never updated, so virtqueue_enable_cb_delayed() always returns false and the loop never terminates. Then it will block reboot process, and the guest will hang. The problem occurs during guest reboot under network traffic: 1. kernel_restart() -> device_shutdown() traverses the device list 2. virtio_dev_shutdown() calls virtio_break_device() which sets vq->broken = true 3. virtio_dev_shutdown() then calls virtio_synchronize_cbs() to wait for in-flight callbacks to complete 4. A virtio interrupt fires, softirq is deferred to ksoftirqd which calls net_rx_action() -> virtnet_poll() -> virtnet_poll_cleantx() 5. virtnet_poll_cleantx() enters the do-while loop and never exits because the QEMU backend has stopped updating used->idx, despite vq->broken having been set to true in step 2. Since the loop runs inside ksoftirqd (a SCHED_OTHER kthread), it is visible to the scheduler and does not trigger a hard lockup. However, the kthread never leaves the loop, so RCU detects it as a CPU stall and reports it periodically. Meanwhile, the reboot process remains blocked in device_shutdown() because virtio_dev_shutdown() cannot complete its synchronization step, and the guest hangs permanently. This can be reproduced on a guest with a virtio-net device: run iperf3 traffic in the guest, then trigger reboot. The reboot occasionally hangs permanently with RCU stall on ksoftirqd. Observed on ARM64 KVM guest: CPU#1 RCU stall (ksoftirqd/1), repeated periodically: virtqueue_enable_cb_delayed_split <- virtnet_poll <- __napi_poll <- net_rx_action <- handle_softirqs <- run_ksoftirqd <- smpboot_thread_fn <- kthread Fix by adding a vq->broken check in virtqueue_enable_cb_delayed(), so that the loop exits immediately when the device is broken, allowing the device shutdown to proceed. Signed-off-by: Jinqian Yang Reviewed-by: Xuan Zhuo Signed-off-by: Michael S. Tsirkin Message-ID: <20260716115940.394832-1-yangjinqian1@huawei.com> --- drivers/virtio/virtio_ring.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index b438dc2ce1b8..5c169fbb418a 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -3233,6 +3233,14 @@ bool virtqueue_enable_cb_delayed(struct virtqueue *_vq) { struct vring_virtqueue *vq = to_vvq(_vq); + /* + * When the device is broken there is no point in polling used->idx, + * the backend will never update it. Return true to let callers + * exit their cleanup loops instead of spinning forever. + */ + if (unlikely(vq->broken)) + return true; + if (vq->event_triggered) data_race(vq->event_triggered = false); From 3e4cddec63db5dc5c199101df7ac0504975b1203 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 16 Jul 2026 22:13:43 +0800 Subject: [PATCH 45/54] vdpa: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Signed-off-by: Michael S. Tsirkin Message-ID: <20260716141349.158824-1-panchuang@vivo.com> --- drivers/vdpa/octeon_ep/octep_vdpa_main.c | 4 +--- drivers/vdpa/virtio_pci/vp_vdpa.c | 12 +++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/vdpa/octeon_ep/octep_vdpa_main.c b/drivers/vdpa/octeon_ep/octep_vdpa_main.c index 6d8ccdc14fb6..23e280a29209 100644 --- a/drivers/vdpa/octeon_ep/octep_vdpa_main.c +++ b/drivers/vdpa/octeon_ep/octep_vdpa_main.c @@ -170,10 +170,8 @@ static int octep_request_irqs(struct octep_hw *oct_hw, irqreturn_t (*irq_handler irq = pci_irq_vector(pdev, idx); ret = devm_request_irq(&pdev->dev, irq, irq_handler, 0, dev_name(&pdev->dev), oct_hw); - if (ret) { - dev_err(&pdev->dev, "Failed to register interrupt handler\n"); + if (ret) goto free_irqs; - } oct_hw->irqs[idx] = irq; } oct_hw->requested_irqs = nb_irqs; diff --git a/drivers/vdpa/virtio_pci/vp_vdpa.c b/drivers/vdpa/virtio_pci/vp_vdpa.c index 51ffc245a038..f2eb654b1665 100644 --- a/drivers/vdpa/virtio_pci/vp_vdpa.c +++ b/drivers/vdpa/virtio_pci/vp_vdpa.c @@ -189,11 +189,8 @@ static int vp_vdpa_request_irq(struct vp_vdpa *vp_vdpa) vp_vdpa_vq_handler, 0, vp_vdpa->vring[i].msix_name, &vp_vdpa->vring[i]); - if (ret) { - dev_err(&pdev->dev, - "vp_vdpa: fail to request irq for vq %d\n", i); + if (ret) goto err; - } vp_modern_queue_vector(mdev, i, msix_vec); vp_vdpa->vring[i].irq = irq; msix_vec++; @@ -204,11 +201,8 @@ static int vp_vdpa_request_irq(struct vp_vdpa *vp_vdpa) irq = pci_irq_vector(pdev, msix_vec); ret = devm_request_irq(&pdev->dev, irq, vp_vdpa_config_handler, 0, vp_vdpa->msix_name, vp_vdpa); - if (ret) { - dev_err(&pdev->dev, - "vp_vdpa: fail to request irq for config: %d\n", ret); - goto err; - } + if (ret) + goto err; vp_modern_config_vector(mdev, msix_vec); vp_vdpa->config_irq = irq; From 9059c62f11974d2c55b8acacd04a6d3777b4f9fe Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Thu, 16 Jul 2026 11:02:36 +0800 Subject: [PATCH 46/54] vhost: reject zero-size IOTLB INVALIDATE Reject VHOST_IOTLB_INVALIDATE messages with size == 0 to prevent iova + size - 1 from underflowing to U64_MAX, which would incorrectly delete the entire IOTLB. Signed-off-by: xiongweimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260716030236.124322-1-xiongwm2026@163.com> --- drivers/vhost/vhost.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 269efad90369..14637cff0bd4 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -1660,6 +1660,10 @@ static int vhost_process_iotlb_msg(struct vhost_dev *dev, u32 asid, ret = -EFAULT; break; } + if (!msg->size) { + ret = -EINVAL; + break; + } vhost_vq_meta_reset(dev); vhost_iotlb_del_range(dev->iotlb, msg->iova, msg->iova + msg->size - 1); From bce2a966f7ed8f6215d16c8e9783153e805c13ff Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Mon, 20 Jul 2026 09:44:21 +0800 Subject: [PATCH 47/54] tools/virtio: Fix userspace typo in vringh test comment Fix a misspelling of "userspace" in the vringh test description. Signed-off-by: GuoHan Zhao Signed-off-by: Michael S. Tsirkin Message-ID: <20260720014421.89345-1-zhaoguohan@kylinos.cn> --- tools/virtio/vringh_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/virtio/vringh_test.c b/tools/virtio/vringh_test.c index 5ea6d29bc992..84961b9ab5ff 100644 --- a/tools/virtio/vringh_test.c +++ b/tools/virtio/vringh_test.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -/* Simple test of virtio code, entirely in userpsace. */ +/* Simple test of virtio code, entirely in userspace. */ #define _GNU_SOURCE #include #include From 05d32474ab547fb097cfe3f3eb00a2aa7cdf4067 Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Mon, 20 Jul 2026 09:45:06 +0800 Subject: [PATCH 48/54] tools/virtio: Fix control typo in trace agent comment Fix a misspelling of "control" in the trace agent controller description. Signed-off-by: GuoHan Zhao Signed-off-by: Michael S. Tsirkin Message-ID: <20260720014506.90012-1-zhaoguohan@kylinos.cn> --- tools/virtio/virtio-trace/trace-agent-ctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/virtio/virtio-trace/trace-agent-ctl.c b/tools/virtio/virtio-trace/trace-agent-ctl.c index 39860be6e2d8..9577579e86da 100644 --- a/tools/virtio/virtio-trace/trace-agent-ctl.c +++ b/tools/virtio/virtio-trace/trace-agent-ctl.c @@ -84,7 +84,7 @@ static int wait_order(int ctl_fd) } /* - * contol read/write threads by handling global_run_operation + * control read/write threads by handling global_run_operation */ void *rw_ctl_loop(int ctl_fd) { From 0ff906166f00ac7b2ccc36c57be6ad4be0dd9e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Tue, 7 Jul 2026 14:24:59 +0200 Subject: [PATCH 49/54] vduse: store control device pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This helps log the errors in next patches. The alternative is to perform a linear search for it with class_find_device_by_devt(class, devt), as device_destroy do for cleaning. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707122502.239022-2-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 10dcf016bfb0..861a8093daa0 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -163,6 +163,7 @@ static DEFINE_IDR(vduse_idr); static dev_t vduse_major; static struct cdev vduse_ctrl_cdev; +static const struct device *vduse_ctrl_dev; static struct cdev vduse_cdev; static struct workqueue_struct *vduse_irq_wq; static struct workqueue_struct *vduse_irq_bound_wq; @@ -2531,7 +2532,6 @@ static void vduse_mgmtdev_exit(void) static int vduse_init(void) { int ret; - struct device *dev; ret = class_register(&vduse_class); if (ret) @@ -2548,9 +2548,10 @@ static int vduse_init(void) if (ret) goto err_ctrl_cdev; - dev = device_create(&vduse_class, NULL, vduse_major, NULL, "control"); - if (IS_ERR(dev)) { - ret = PTR_ERR(dev); + vduse_ctrl_dev = device_create(&vduse_class, NULL, vduse_major, NULL, "control"); + if (IS_ERR(vduse_ctrl_dev)) { + ret = PTR_ERR(vduse_ctrl_dev); + vduse_ctrl_dev = NULL; goto err_device; } From a596238c2ab6944861404dc597133ffd4ad062d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Tue, 7 Jul 2026 14:25:00 +0200 Subject: [PATCH 50/54] vduse: add VDUSE_GET_FEATURES ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an ioctl to allow VDUSE instances to query the available features supported by the kernel module. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707122502.239022-3-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 7 +++++++ include/uapi/linux/vduse.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 861a8093daa0..f3f24cc59eb0 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -51,6 +51,9 @@ #define IRQ_UNBOUND -1 +/* Supported VDUSE features */ +static const uint64_t vduse_features; + /* * VDUSE instance have not asked the vduse API version, so assume 0. * @@ -2342,6 +2345,10 @@ static long vduse_ioctl(struct file *file, unsigned int cmd, ret = vduse_destroy_dev(name); break; } + case VDUSE_GET_FEATURES: + ret = put_user(vduse_features, (u64 __user *)argp); + break; + default: ret = -EINVAL; break; diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index 361eea511c21..89aa3b448c0a 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -63,6 +63,9 @@ struct vduse_dev_config { */ #define VDUSE_DESTROY_DEV _IOW(VDUSE_BASE, 0x03, char[VDUSE_NAME_MAX]) +/* Get the VDUSE supported features */ +#define VDUSE_GET_FEATURES _IOR(VDUSE_BASE, 0x04, __u64) + /* The ioctls for VDUSE device (/dev/vduse/$NAME) */ /** From 3b441820cb61ac1137dd4b336adfb4892cda4233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Tue, 7 Jul 2026 14:25:01 +0200 Subject: [PATCH 51/54] vduse: add VDUSE_SET_FEATURES ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an ioctl to allow VDUSE instances to set the VDUSE features supported by the userland VDUSE instance. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707122502.239022-4-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 24 ++++++++++++++++++++++++ include/uapi/linux/vduse.h | 3 +++ 2 files changed, 27 insertions(+) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index f3f24cc59eb0..2292cabe4270 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -159,6 +159,7 @@ struct vduse_dev_msg { struct vduse_control { u64 api_version; + u64 vduse_features; }; static DEFINE_MUTEX(vduse_lock); @@ -2348,7 +2349,29 @@ static long vduse_ioctl(struct file *file, unsigned int cmd, case VDUSE_GET_FEATURES: ret = put_user(vduse_features, (u64 __user *)argp); break; + case VDUSE_SET_FEATURES: { + u64 features; + ret = -EFAULT; + if (get_user(features, (u64 __user *)argp)) { + dev_dbg(vduse_ctrl_dev, "Could not get vduse features"); + break; + } + + ret = -EINVAL; + if (features & ~vduse_features) { + dev_dbg(vduse_ctrl_dev, + "Invalid features in %llx, expected %llx", + features, vduse_features); + break; + } + + ret = 0; + control->vduse_features = features; + dev_dbg(vduse_ctrl_dev, "Set features %llx", features); + + break; + } default: ret = -EINVAL; break; @@ -2375,6 +2398,7 @@ static int vduse_open(struct inode *inode, struct file *file) return -ENOMEM; control->api_version = VDUSE_API_VERSION_NOT_ASKED; + control->vduse_features = 0; file->private_data = control; return 0; diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index 89aa3b448c0a..f14c965bb7f6 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -66,6 +66,9 @@ struct vduse_dev_config { /* Get the VDUSE supported features */ #define VDUSE_GET_FEATURES _IOR(VDUSE_BASE, 0x04, __u64) +/* Set the VDUSE features */ +#define VDUSE_SET_FEATURES _IOW(VDUSE_BASE, 0x05, __u64) + /* The ioctls for VDUSE device (/dev/vduse/$NAME) */ /** From 4c318d91cc60a0c2c94bcb6db2630baed43e9387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Tue, 7 Jul 2026 14:25:02 +0200 Subject: [PATCH 52/54] vduse: add F_QUEUE_READY feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the VDUSE_F_QUEUE_READY feature flag. This allows the kernel module to explicitly signal userspace when a specific virtqueue has been enabled. In scenarios like Live Migration of VirtIO net devices, the dataplane starts after the control virtqueue allowing QEMU to apply configuration in the destination device. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707122502.239022-5-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 73 +++++++++++++++++++++++------- include/uapi/linux/vduse.h | 18 ++++++++ 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 2292cabe4270..87d6748b50cc 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -9,6 +9,7 @@ */ #include "linux/virtio_net.h" +#include #include #include #include @@ -52,7 +53,7 @@ #define IRQ_UNBOUND -1 /* Supported VDUSE features */ -static const uint64_t vduse_features; +static const uint64_t vduse_features = BIT_U64(VDUSE_F_QUEUE_READY); /* * VDUSE instance have not asked the vduse API version, so assume 0. @@ -76,6 +77,7 @@ struct vduse_virtqueue { u32 group; spinlock_t kick_lock; spinlock_t irq_lock; + spinlock_t ready_lock; struct eventfd_ctx *kickfd; struct vdpa_callback cb; struct work_struct inject; @@ -119,6 +121,7 @@ struct vduse_dev { char *name; struct mutex lock; spinlock_t msg_lock; + u64 vduse_features; u64 msg_unique; u32 msg_timeout; wait_queue_head_t waitq; @@ -513,7 +516,9 @@ static void vduse_dev_reset(struct vduse_dev *dev) for (i = 0; i < dev->vq_num; i++) { struct vduse_virtqueue *vq = dev->vqs[i]; - vq->ready = false; + scoped_guard(spinlock_bh, &vq->ready_lock) { + vq->ready = false; + } vq->desc_addr = 0; vq->driver_addr = 0; vq->device_addr = 0; @@ -555,16 +560,15 @@ static int vduse_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 idx, static void vduse_vq_kick(struct vduse_virtqueue *vq) { - spin_lock(&vq->kick_lock); - if (!vq->ready) - goto unlock; + guard(spinlock)(&vq->kick_lock); + scoped_guard(spinlock_bh, &vq->ready_lock) + if (!vq->ready) + return; if (vq->kickfd) eventfd_signal(vq->kickfd); else vq->kicked = true; -unlock: - spin_unlock(&vq->kick_lock); } static void vduse_vq_kick_work(struct work_struct *work) @@ -624,7 +628,30 @@ static void vduse_vdpa_set_vq_ready(struct vdpa_device *vdpa, { struct vduse_dev *dev = vdpa_to_vduse(vdpa); struct vduse_virtqueue *vq = dev->vqs[idx]; + struct vduse_dev_msg msg = { 0 }; + int r; + if (dev->vduse_features & BIT_U64(VDUSE_F_QUEUE_READY)) { + msg.req.type = VDUSE_SET_VQ_READY; + msg.req.vq_ready.num = idx; + msg.req.vq_ready.ready = !!ready; + + r = vduse_dev_msg_sync(dev, &msg); + + if (r < 0) { + dev_dbg(&vdpa->dev, "device refuses to set vq %u ready %u", + idx, ready); + + /* We can't do better than break the device in this case */ + spin_lock(&dev->msg_lock); + vduse_dev_broken(dev); + spin_unlock(&dev->msg_lock); + + return; + } + } + + guard(spinlock_bh)(&vq->ready_lock); vq->ready = ready; } @@ -633,6 +660,7 @@ static bool vduse_vdpa_get_vq_ready(struct vdpa_device *vdpa, u16 idx) struct vduse_dev *dev = vdpa_to_vduse(vdpa); struct vduse_virtqueue *vq = dev->vqs[idx]; + guard(spinlock_bh)(&vq->ready_lock); return vq->ready; } @@ -1120,15 +1148,16 @@ static int vduse_kickfd_setup(struct vduse_dev *dev, } else if (eventfd->fd != VDUSE_EVENTFD_DEASSIGN) return 0; - spin_lock(&vq->kick_lock); + guard(spinlock)(&vq->kick_lock); if (vq->kickfd) eventfd_ctx_put(vq->kickfd); vq->kickfd = ctx; + + guard(spinlock_bh)(&vq->ready_lock); if (vq->ready && vq->kicked && vq->kickfd) { eventfd_signal(vq->kickfd); vq->kicked = false; } - spin_unlock(&vq->kick_lock); return 0; } @@ -1159,10 +1188,10 @@ static void vduse_vq_irq_inject(struct work_struct *work) struct vduse_virtqueue *vq = container_of(work, struct vduse_virtqueue, inject); - spin_lock_bh(&vq->irq_lock); + guard(spinlock_bh)(&vq->irq_lock); + guard(spinlock_bh)(&vq->ready_lock); if (vq->ready && vq->cb.callback) vq->cb.callback(vq->cb.private); - spin_unlock_bh(&vq->irq_lock); } static bool vduse_vq_signal_irqfd(struct vduse_virtqueue *vq) @@ -1172,12 +1201,12 @@ static bool vduse_vq_signal_irqfd(struct vduse_virtqueue *vq) if (!vq->cb.trigger) return false; - spin_lock_irq(&vq->irq_lock); + guard(spinlock_irq)(&vq->irq_lock); + guard(spinlock_irq)(&vq->ready_lock); if (vq->ready && vq->cb.trigger) { eventfd_signal(vq->cb.trigger); signal = true; } - spin_unlock_irq(&vq->irq_lock); return signal; } @@ -1515,7 +1544,9 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, vq_info.split.avail_index = vq->state.split.avail_index; - vq_info.ready = vq->ready; + scoped_guard(spinlock_bh, &vq->ready_lock) { + vq_info.ready = vq->ready; + } ret = -EFAULT; if (copy_to_user(argp, &vq_info, sizeof(vq_info))) @@ -1745,7 +1776,9 @@ static long vduse_dev_compat_ioctl(struct file *file, unsigned int cmd, vq_info.split.avail_index = vq->state.split.avail_index; - vq_info.ready = vq->ready; + scoped_guard(spinlock_bh, &vq->ready_lock) { + vq_info.ready = vq->ready; + } ret = -EFAULT; if (copy_to_user(argp, &vq_info, @@ -1958,6 +1991,7 @@ static int vduse_dev_init_vqs(struct vduse_dev *dev, u32 vq_align, u32 vq_num) INIT_WORK(&dev->vqs[i]->kick, vduse_vq_kick_work); spin_lock_init(&dev->vqs[i]->kick_lock); spin_lock_init(&dev->vqs[i]->irq_lock); + spin_lock_init(&dev->vqs[i]->ready_lock); cpumask_setall(&dev->vqs[i]->irq_affinity); kobject_init(&dev->vqs[i]->kobj, &vq_type); @@ -2193,7 +2227,8 @@ static struct attribute *vduse_dev_attrs[] = { ATTRIBUTE_GROUPS(vduse_dev); static int vduse_create_dev(struct vduse_dev_config *config, - void *config_buf, u64 api_version) + void *config_buf, u64 api_version, + uint64_t vduse_features) { int ret; struct vduse_dev *dev; @@ -2215,6 +2250,9 @@ static int vduse_create_dev(struct vduse_dev_config *config, dev->device_features = config->features; dev->device_id = config->device_id; dev->vendor_id = config->vendor_id; + dev->vduse_features = vduse_features; + dev_dbg(vduse_ctrl_dev, "Creating device %s with features 0x%llx", + config->name, vduse_features); dev->nas = (dev->api_version < VDUSE_API_VERSION_1) ? 1 : config->nas; dev->as = kzalloc_objs(dev->as[0], dev->nas); @@ -2330,7 +2368,8 @@ static long vduse_ioctl(struct file *file, unsigned int cmd, break; } config.name[VDUSE_NAME_MAX - 1] = '\0'; - ret = vduse_create_dev(&config, buf, control->api_version); + ret = vduse_create_dev(&config, buf, control->api_version, + control->vduse_features); if (ret) kvfree(buf); break; diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index f14c965bb7f6..7285f8570237 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -14,6 +14,9 @@ #define VDUSE_API_VERSION_1 1 +/* The VDUSE instance expects a request for vq ready */ +#define VDUSE_F_QUEUE_READY 0 + /* * Get the version of VDUSE API that kernel supported (VDUSE_API_VERSION). * This is used for future extension. @@ -331,6 +334,7 @@ enum vduse_req_type { VDUSE_SET_STATUS, VDUSE_UPDATE_IOTLB, VDUSE_SET_VQ_GROUP_ASID, + VDUSE_SET_VQ_READY, }; /** @@ -378,6 +382,15 @@ struct vduse_iova_range_v2 { __u32 padding; }; +/** + * struct vduse_vq_ready - Virtqueue ready request message + * @num: Virtqueue number + */ +struct vduse_vq_ready { + __u32 num; + __u32 ready; +}; + /** * struct vduse_dev_request - control request * @type: request type @@ -388,6 +401,7 @@ struct vduse_iova_range_v2 { * @iova: IOVA range for updating * @iova_v2: IOVA range for updating if API_VERSION >= 1 * @vq_group_asid: ASID of a virtqueue group + * @vq_ready: Virtqueue ready request * @padding: padding * * Structure used by read(2) on /dev/vduse/$NAME. @@ -405,6 +419,10 @@ struct vduse_dev_request { */ struct vduse_iova_range_v2 iova_v2; struct vduse_vq_group_asid vq_group_asid; + + /* Only if VDUSE_F_QUEUE_READY is negotiated */ + struct vduse_vq_ready vq_ready; + __u32 padding[32]; }; }; From 675087c762f95c498524164de7417f9f77d3ed15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Tue, 7 Jul 2026 14:33:43 +0200 Subject: [PATCH 53/54] vduse: do not take rwsem at reset work flush MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Next patches need to check suspend flag at this work item, and the rwlock is used to protect the suspend flag update. If the work takes the rwlock too it will produce a deadlock. Make flushing work do nothing when called by de-initializing everything: vq->ready, vq->kickfd, vq->cb.callback. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707123344.244575-2-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 67 ++++++++++++++++-------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 87d6748b50cc..9aff26fbb583 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -502,46 +502,49 @@ static void vduse_dev_reset(struct vduse_dev *dev) vduse_domain_reset_bounce_map(domain); } - down_write(&dev->rwsem); + scoped_guard(rwsem_write, &dev->rwsem) { + dev->status = 0; + dev->driver_features = 0; + dev->generation++; + spin_lock(&dev->irq_lock); + dev->config_cb.callback = NULL; + dev->config_cb.private = NULL; + spin_unlock(&dev->irq_lock); + + for (i = 0; i < dev->vq_num; i++) { + struct vduse_virtqueue *vq = dev->vqs[i]; + + scoped_guard(spinlock_bh, &vq->ready_lock) { + vq->ready = false; + } + vq->desc_addr = 0; + vq->driver_addr = 0; + vq->device_addr = 0; + vq->num = 0; + memset(&vq->state, 0, sizeof(vq->state)); + + spin_lock(&vq->kick_lock); + vq->kicked = false; + if (vq->kickfd) + eventfd_ctx_put(vq->kickfd); + vq->kickfd = NULL; + spin_unlock(&vq->kick_lock); + + spin_lock(&vq->irq_lock); + vq->cb.callback = NULL; + vq->cb.private = NULL; + vq->cb.trigger = NULL; + spin_unlock(&vq->irq_lock); + } + } - dev->status = 0; - dev->driver_features = 0; - dev->generation++; - spin_lock(&dev->irq_lock); - dev->config_cb.callback = NULL; - dev->config_cb.private = NULL; - spin_unlock(&dev->irq_lock); flush_work(&dev->inject); - for (i = 0; i < dev->vq_num; i++) { struct vduse_virtqueue *vq = dev->vqs[i]; - scoped_guard(spinlock_bh, &vq->ready_lock) { - vq->ready = false; - } - vq->desc_addr = 0; - vq->driver_addr = 0; - vq->device_addr = 0; - vq->num = 0; - memset(&vq->state, 0, sizeof(vq->state)); - - spin_lock(&vq->kick_lock); - vq->kicked = false; - if (vq->kickfd) - eventfd_ctx_put(vq->kickfd); - vq->kickfd = NULL; - spin_unlock(&vq->kick_lock); - - spin_lock(&vq->irq_lock); - vq->cb.callback = NULL; - vq->cb.private = NULL; - vq->cb.trigger = NULL; - spin_unlock(&vq->irq_lock); flush_work(&vq->inject); flush_work(&vq->kick); } - - up_write(&dev->rwsem); } static int vduse_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 idx, From b282418bc366194677eafd1dad180d92254586ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Tue, 7 Jul 2026 14:33:44 +0200 Subject: [PATCH 54/54] vduse: Add suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement suspend operation for vduse devices, so vhost-vdpa will offer that backend feature and userspace can effectively suspend the device. This is a must before get virtqueue indexes (base) for live migration, since the device could modify them after userland gets them. This patch does not implement resume, so VMM resets the whole device to recover from a live migration failure. Resume optimization can be implemented on top of these patches, as other vDPA devices have done in the past. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707123344.244575-3-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 95 +++++++++++++++++++++++++++--- include/uapi/linux/vduse.h | 4 ++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 9aff26fbb583..9891cd2cf712 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -53,7 +53,8 @@ #define IRQ_UNBOUND -1 /* Supported VDUSE features */ -static const uint64_t vduse_features = BIT_U64(VDUSE_F_QUEUE_READY); +static const uint64_t vduse_features = BIT_U64(VDUSE_F_QUEUE_READY) | + BIT_U64(VDUSE_F_SUSPEND); /* * VDUSE instance have not asked the vduse API version, so assume 0. @@ -85,6 +86,7 @@ struct vduse_virtqueue { int irq_effective_cpu; struct cpumask irq_affinity; struct kobject kobj; + struct vduse_dev *dev; }; struct vduse_dev; @@ -134,6 +136,7 @@ struct vduse_dev { int minor; bool broken; bool connected; + bool suspended; u64 api_version; u64 device_features; u64 driver_features; @@ -503,6 +506,7 @@ static void vduse_dev_reset(struct vduse_dev *dev) } scoped_guard(rwsem_write, &dev->rwsem) { + dev->suspended = false; dev->status = 0; dev->driver_features = 0; dev->generation++; @@ -563,6 +567,10 @@ static int vduse_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 idx, static void vduse_vq_kick(struct vduse_virtqueue *vq) { + guard(rwsem_read)(&vq->dev->rwsem); + if (vq->dev->suspended) + return; + guard(spinlock)(&vq->kick_lock); scoped_guard(spinlock_bh, &vq->ready_lock) if (!vq->ready) @@ -927,6 +935,27 @@ static int vduse_vdpa_set_map(struct vdpa_device *vdpa, return 0; } +static int vduse_vdpa_suspend(struct vdpa_device *vdpa) +{ + struct vduse_dev *dev = vdpa_to_vduse(vdpa); + struct vduse_dev_msg msg = { 0 }; + int ret; + + msg.req.type = VDUSE_SUSPEND; + + ret = vduse_dev_msg_sync(dev, &msg); + if (ret == 0) { + scoped_guard(rwsem_write, &dev->rwsem) + dev->suspended = true; + + cancel_work_sync(&dev->inject); + for (u32 i = 0; i < dev->vq_num; i++) + cancel_work_sync(&dev->vqs[i]->inject); + } + + return ret; +} + static void vduse_vdpa_free(struct vdpa_device *vdpa) { struct vduse_dev *dev = vdpa_to_vduse(vdpa); @@ -968,6 +997,41 @@ static const struct vdpa_config_ops vduse_vdpa_config_ops = { .free = vduse_vdpa_free, }; +static const struct vdpa_config_ops vduse_vdpa_config_ops_with_suspend = { + .set_vq_address = vduse_vdpa_set_vq_address, + .kick_vq = vduse_vdpa_kick_vq, + .set_vq_cb = vduse_vdpa_set_vq_cb, + .set_vq_num = vduse_vdpa_set_vq_num, + .get_vq_size = vduse_vdpa_get_vq_size, + .get_vq_group = vduse_get_vq_group, + .set_vq_ready = vduse_vdpa_set_vq_ready, + .get_vq_ready = vduse_vdpa_get_vq_ready, + .set_vq_state = vduse_vdpa_set_vq_state, + .get_vq_state = vduse_vdpa_get_vq_state, + .get_vq_align = vduse_vdpa_get_vq_align, + .get_device_features = vduse_vdpa_get_device_features, + .set_driver_features = vduse_vdpa_set_driver_features, + .get_driver_features = vduse_vdpa_get_driver_features, + .set_config_cb = vduse_vdpa_set_config_cb, + .get_vq_num_max = vduse_vdpa_get_vq_num_max, + .get_device_id = vduse_vdpa_get_device_id, + .get_vendor_id = vduse_vdpa_get_vendor_id, + .get_status = vduse_vdpa_get_status, + .set_status = vduse_vdpa_set_status, + .get_config_size = vduse_vdpa_get_config_size, + .get_config = vduse_vdpa_get_config, + .set_config = vduse_vdpa_set_config, + .get_generation = vduse_vdpa_get_generation, + .set_vq_affinity = vduse_vdpa_set_vq_affinity, + .get_vq_affinity = vduse_vdpa_get_vq_affinity, + .reset = vduse_vdpa_reset, + .set_map = vduse_vdpa_set_map, + .set_group_asid = vduse_set_group_asid, + .get_vq_map = vduse_get_vq_map, + .suspend = vduse_vdpa_suspend, + .free = vduse_vdpa_free, +}; + static void vduse_dev_sync_single_for_device(union virtio_map token, dma_addr_t dma_addr, size_t size, enum dma_data_direction dir) @@ -1180,6 +1244,10 @@ static void vduse_dev_irq_inject(struct work_struct *work) { struct vduse_dev *dev = container_of(work, struct vduse_dev, inject); + guard(rwsem_read)(&dev->rwsem); + if (dev->suspended) + return; + spin_lock_bh(&dev->irq_lock); if (dev->config_cb.callback) dev->config_cb.callback(dev->config_cb.private); @@ -1191,6 +1259,10 @@ static void vduse_vq_irq_inject(struct work_struct *work) struct vduse_virtqueue *vq = container_of(work, struct vduse_virtqueue, inject); + guard(rwsem_read)(&vq->dev->rwsem); + if (vq->dev->suspended) + return; + guard(spinlock_bh)(&vq->irq_lock); guard(spinlock_bh)(&vq->ready_lock); if (vq->ready && vq->cb.callback) @@ -1201,6 +1273,10 @@ static bool vduse_vq_signal_irqfd(struct vduse_virtqueue *vq) { bool signal = false; + guard(rwsem_read)(&vq->dev->rwsem); + if (vq->dev->suspended) + return false; + if (!vq->cb.trigger) return false; @@ -1220,9 +1296,9 @@ static int vduse_dev_queue_irq_work(struct vduse_dev *dev, { int ret = -EINVAL; - down_read(&dev->rwsem); - if (!(dev->status & VIRTIO_CONFIG_S_DRIVER_OK)) - goto unlock; + guard(rwsem_read)(&dev->rwsem); + if (dev->suspended || !(dev->status & VIRTIO_CONFIG_S_DRIVER_OK)) + return ret; ret = 0; if (irq_effective_cpu == IRQ_UNBOUND) @@ -1230,8 +1306,6 @@ static int vduse_dev_queue_irq_work(struct vduse_dev *dev, else queue_work_on(irq_effective_cpu, vduse_irq_bound_wq, irq_work); -unlock: - up_read(&dev->rwsem); return ret; } @@ -1989,6 +2063,7 @@ static int vduse_dev_init_vqs(struct vduse_dev *dev, u32 vq_align, u32 vq_num) } dev->vqs[i]->index = i; + dev->vqs[i]->dev = dev; dev->vqs[i]->irq_effective_cpu = IRQ_UNBOUND; INIT_WORK(&dev->vqs[i]->inject, vduse_vq_irq_inject); INIT_WORK(&dev->vqs[i]->kick, vduse_vq_kick_work); @@ -2465,12 +2540,18 @@ static struct vduse_mgmt_dev *vduse_mgmt; static int vduse_dev_init_vdpa(struct vduse_dev *dev, const char *name) { struct vduse_vdpa *vdev; + const struct vdpa_config_ops *ops; if (dev->vdev) return -EEXIST; + if (dev->vduse_features & BIT_U64(VDUSE_F_SUSPEND)) + ops = &vduse_vdpa_config_ops_with_suspend; + else + ops = &vduse_vdpa_config_ops; + vdev = vdpa_alloc_device(struct vduse_vdpa, vdpa, dev->dev, - &vduse_vdpa_config_ops, &vduse_map_ops, + ops, &vduse_map_ops, dev->ngroups, dev->nas, name, true); if (IS_ERR(vdev)) return PTR_ERR(vdev); diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index 7285f8570237..b7f8c04a0a44 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -17,6 +17,9 @@ /* The VDUSE instance expects a request for vq ready */ #define VDUSE_F_QUEUE_READY 0 +/* The VDUSE instance expects a request for suspend */ +#define VDUSE_F_SUSPEND 1 + /* * Get the version of VDUSE API that kernel supported (VDUSE_API_VERSION). * This is used for future extension. @@ -335,6 +338,7 @@ enum vduse_req_type { VDUSE_UPDATE_IOTLB, VDUSE_SET_VQ_GROUP_ASID, VDUSE_SET_VQ_READY, + VDUSE_SUSPEND, }; /**