From 60039faf8135ff05714a2014e54597ebfe07340f Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 5 Jul 2026 05:38:57 -0400 Subject: virtio_balloon: prime stats vq after virtio_device_ready() The virtio spec requires the driver not to kick the device before DRIVER_OK is set. init_vqs() primes the stats virtqueue with a buffer and kicks the device before virtio_device_ready() is called in virtballoon_probe(), violating this requirement. Further, if the device responds to the early kick by processing the buffer before DRIVER_OK, stats_request() fires and queues update_balloon_stats_work. Should probe then fail and free vb, the work runs against freed memory. To fix, move buffer setup to after DRIVER_OK. Be careful to disable update_balloon_stats_work while this is going on, to make sure it does not race with the setup. setup_vqs() warns but does not fail probe or restore if virtqueue_add_outbuf() fails; the call never actually fails in these contexts since the queue is freshly initialized and empty. Testing: tested that stats still work after the change. Fixes: 9564e138b1f6 ("virtio: Add memory statistics reporting to the balloon driver (V4)") Reported-by: Sashiko:gemini-3.1-pro-preview Cc: David Hildenbrand Assisted-by: Claude:claude-sonnet-4-6 Message-ID: Signed-off-by: Michael S. Tsirkin --- drivers/virtio/virtio_balloon.c | 51 ++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 581ac799d974..7c5ef4e5c879 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -611,25 +611,9 @@ static int init_vqs(struct virtio_balloon *vb) vb->inflate_vq = vqs[VIRTIO_BALLOON_VQ_INFLATE]; vb->deflate_vq = vqs[VIRTIO_BALLOON_VQ_DEFLATE]; if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_STATS_VQ)) { - struct scatterlist sg; - unsigned int num_stats; vb->stats_vq = vqs[VIRTIO_BALLOON_VQ_STATS]; - - /* - * Prime this virtqueue with one buffer so the hypervisor can - * use it to signal us later (it can't be broken yet!). - */ - num_stats = update_balloon_stats(vb); - - sg_init_one(&sg, vb->stats, sizeof(vb->stats[0]) * num_stats); - err = virtqueue_add_outbuf(vb->stats_vq, &sg, 1, vb, - GFP_KERNEL); - if (err) { - dev_warn(&vb->vdev->dev, "%s: add stat_vq failed\n", - __func__); - return err; - } - virtqueue_kick(vb->stats_vq); + /* Prevent update_balloon_stats_work from accessing the stats vq. */ + disable_work(&vb->update_balloon_stats_work); } if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) @@ -916,6 +900,33 @@ static int virtio_balloon_register_shrinker(struct virtio_balloon *vb) return 0; } +static void setup_vqs(struct virtio_balloon *vb) +{ + struct scatterlist sg; + unsigned int num_stats; + bool ret; + + if (!virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_STATS_VQ)) + return; + + /* + * Prime this virtqueue with one buffer so the hypervisor can + * use it to signal us later (it can't be broken yet!). + */ + num_stats = update_balloon_stats(vb); + sg_init_one(&sg, vb->stats, sizeof(vb->stats[0]) * num_stats); + if (virtqueue_add_outbuf(vb->stats_vq, &sg, 1, vb, GFP_KERNEL)) { + dev_warn(&vb->vdev->dev, "%s: add stat_vq failed\n", __func__); + return; + } + virtqueue_kick(vb->stats_vq); + + ret = enable_and_queue_work(system_freezable_wq, + &vb->update_balloon_stats_work); + /* Make sure we balanced enable/disable, or we won't report stats. */ + WARN_ON_ONCE(!ret); +} + static int virtballoon_probe(struct virtio_device *vdev) { struct virtio_balloon *vb; @@ -1056,6 +1067,8 @@ static int virtballoon_probe(struct virtio_device *vdev) virtio_device_ready(vdev); + setup_vqs(vb); + if (towards_target(vb)) virtballoon_changed(vdev); return 0; @@ -1145,6 +1158,8 @@ static int virtballoon_restore(struct virtio_device *vdev) virtio_device_ready(vdev); + setup_vqs(vb); + if (towards_target(vb)) virtballoon_changed(vdev); update_balloon_size(vb); -- cgit v1.2.3 From 6236e765f16d2648adbaa49bfc539df28cc255ac Mon Sep 17 00:00:00 2001 From: Pavel Tikhomirov Date: Mon, 20 Jul 2026 13:22:37 +0300 Subject: vhost/vsock: split out vhost_vsock_drop_backends helper Split the actual backend dropping part from vhost_vsock_stop. We're going to need it for the VHOST_RESET_OWNER implementation in the following patch, when vsock->dev.mutex is already taken and owner is checked. Signed-off-by: Pavel Tikhomirov Signed-off-by: Andrey Drobyshev Reviewed-by: Pavel Tikhomirov Reviewed-by: Stefano Garzarella Message-ID: <20260720102241.371610-2-andrey.drobyshev@virtuozzo.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vsock.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 9aaab6bb8061..b12221ce6faf 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -664,9 +664,24 @@ err: return ret; } -static int vhost_vsock_stop(struct vhost_vsock *vsock, bool check_owner) +static void vhost_vsock_drop_backends(struct vhost_vsock *vsock) { + struct vhost_virtqueue *vq; size_t i; + + lockdep_assert_held(&vsock->dev.mutex); + + for (i = 0; i < ARRAY_SIZE(vsock->vqs); i++) { + vq = &vsock->vqs[i]; + + mutex_lock(&vq->mutex); + vhost_vq_set_backend(vq, NULL); + mutex_unlock(&vq->mutex); + } +} + +static int vhost_vsock_stop(struct vhost_vsock *vsock, bool check_owner) +{ int ret = 0; mutex_lock(&vsock->dev.mutex); @@ -677,14 +692,7 @@ static int vhost_vsock_stop(struct vhost_vsock *vsock, bool check_owner) goto err; } - for (i = 0; i < ARRAY_SIZE(vsock->vqs); i++) { - struct vhost_virtqueue *vq = &vsock->vqs[i]; - - mutex_lock(&vq->mutex); - vhost_vq_set_backend(vq, NULL); - mutex_unlock(&vq->mutex); - } - + vhost_vsock_drop_backends(vsock); err: mutex_unlock(&vsock->dev.mutex); return ret; -- cgit v1.2.3 From 7c4fedf10a831b471dd94ab7bed499c305db27c9 Mon Sep 17 00:00:00 2001 From: Andrey Drobyshev Date: Mon, 20 Jul 2026 13:22:38 +0300 Subject: vhost/vsock: suppress EHOSTUNREACH fast-fail during CPR pause Earlier commit bb26ed5f3a8b ("vhost/vsock: Refuse the connection immediately when guest isn't ready") added a fast-fail in vhost_transport_send_pkt(). It rejects every host send with -EHOSTUNREACH until the destination calls SET_RUNNING(1). The fast-fail condition checks whether device's backends are dropped, and if they're, the guest is considered to be not ready. However, there might be other reasons for backends to be nulled. In particular, when QEMU is performing CPR (checkpoint-restore) migration, device ownership is being RESET and SET again, which leads to backends drop and reattach. If we end up connecting during this window, an AF_VSOCK client gets -EHOSTUNREACH, which is wrong. Add an 'ever_started' flag which is set once in vhost_vsock_start() and is never cleared. The behaviour changes to: * When device was never started -> flag is unset -> no listener can exist yet -> fast-fail; * Once the device starts -> flag is set -> we don't fast-fail -> we queue and preserve during any later stop / CPR pause. The VHOST_RESET_OWNER ioctl is implemented in a following patch, and without RESET_OWNER the problem we fix here isn't manifesting - thus this patch is a preparation to support RESET_OWNER. Important caveat: after the first start, a connect during any stopped window is queued instead of fast-failed. That was the behaviour before the patch bb26ed5f3a8b, and we're restoring it now. However we still keep the behaviour originally intended by that commit (i.e. fast-fail if there's no real listener yet) while fixing the CPR path. Suggested-by: Stefano Garzarella Signed-off-by: Denis V. Lunev Signed-off-by: Andrey Drobyshev Reviewed-by: Pavel Tikhomirov Reviewed-by: Stefano Garzarella Message-ID: <20260720102241.371610-3-andrey.drobyshev@virtuozzo.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vsock.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index b12221ce6faf..27169a09e87e 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -61,6 +61,7 @@ struct vhost_vsock { u32 guest_cid; bool seqpacket_allow; + bool ever_started; /* set on first SET_RUNNING(1); never cleared */ }; static u32 vhost_transport_get_local_cid(void) @@ -302,17 +303,12 @@ vhost_transport_send_pkt(struct sk_buff *skb, struct net *net) return -ENODEV; } - /* Fast-fail if the guest hasn't enabled the RX vq yet. Queuing the packet - * and making the caller wait is pointless: even if the guest manages to init - * within the timeout, it'll immediately reply with RST, because there's no - * listener on the port yet. - * - * vhost_vq_get_backend() without vq->mutex is acceptable here: locking - * the mutex would be too expensive in this hot path, and we already have - * all the outcomes covered: if the backend becomes NULL right after the check, - * vhost_transport_do_send_pkt() will check it under the mutex anyway. + /* Fast-fail until the guest first enables the device (SET_RUNNING(1)). + * Before that there is no listener, so queuing is pointless. + * 'ever_started' is never cleared, so once we're up we keep queuing + * across later stop / CPR-pause windows. */ - if (unlikely(!data_race(vhost_vq_get_backend(&vsock->vqs[VSOCK_VQ_RX])))) { + if (unlikely(!READ_ONCE(vsock->ever_started))) { rcu_read_unlock(); kfree_skb(skb); return -EHOSTUNREACH; @@ -640,6 +636,11 @@ static int vhost_vsock_start(struct vhost_vsock *vsock) mutex_unlock(&vq->mutex); } + /* Set 'ever_started' flag on the first start; never cleared, so send_pkt + * keeps queuing (instead of fast-failing) on later stop / CPR pauses. + */ + WRITE_ONCE(vsock->ever_started, true); + /* Some packets may have been queued before the device was started, * let's kick the send worker to send them. */ @@ -728,6 +729,7 @@ static int vhost_vsock_dev_open(struct inode *inode, struct file *file) vsock->guest_cid = 0; /* no CID assigned yet */ vsock->seqpacket_allow = false; + vsock->ever_started = false; atomic_set(&vsock->queued_replies, 0); -- cgit v1.2.3 From 863c6d539ac64ec3fb1e08a4e2fe7f5aa7d1a781 Mon Sep 17 00:00:00 2001 From: Andrey Drobyshev Date: Mon, 20 Jul 2026 13:22:39 +0300 Subject: vhost/vsock: re-scan TX virtqueue on device start During QEMU CPR live-update (and VHOST_RESET_OWNER in general) the guest keeps running while the host drops and later re-attaches vhost backends. If the guest adds a buffer to the TX virtqueue (guest->host) and kicks while the backend is temporarily NULL (between vhost_vsock_drop_backends() and the next vhost_vsock_start()), then the kick is delivered to the vhost worker, handle_tx_kick() sees a NULL backend and returns, and the kick signal is consumed. The buffer is then left in the ring. Then upon device start vhost_vsock_start() only re-kicks the RX send worker, never the TX VQ, so the buffer is processed only if the guest happens to kick again. But if the guest itself is now waiting for data from the host, it will never kick TX VQ again, and we end up in a deadlock. The issue itself is pre-existing, but it only manifests during a device pause caused by VHOST_RESET_OWNER. Namely, the deadlock is reproduced during active host->guest socat data transfer under multiple consecutive CPR live-update's. To fix this, in vhost_vsock_start(), after kicking the RX send worker, also queue the TX vq poll so any buffers the guest enqueued while we were paused get scanned. The VHOST_RESET_OWNER ioctl itself is implemented in the following patch, thus this patch is a preparation to support VHOST_RESET_OWNER. Signed-off-by: Andrey Drobyshev Reviewed-by: Pavel Tikhomirov Reviewed-by: Stefano Garzarella Message-ID: <20260720102241.371610-4-andrey.drobyshev@virtuozzo.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vsock.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 27169a09e87e..d5022d21120b 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -646,6 +646,13 @@ static int vhost_vsock_start(struct vhost_vsock *vsock) */ vhost_vq_work_queue(&vsock->vqs[VSOCK_VQ_RX], &vsock->send_pkt_work); + /* The guest may have added TX buffers while the device was stopped + * (e.g. across VHOST_RESET_OWNER) and their kicks got consumed by + * the NULL-backend window. Re-scan the TX VQ, mirroring the RX + * send-worker kick above. + */ + vhost_poll_queue(&vsock->vqs[VSOCK_VQ_TX].poll); + mutex_unlock(&vsock->dev.mutex); return 0; -- cgit v1.2.3 From 0088c10f7cb7adc585c1d3f47f4dc46b3b14ba02 Mon Sep 17 00:00:00 2001 From: Andrey Drobyshev Date: Mon, 20 Jul 2026 13:22:40 +0300 Subject: vhost: synchronize with RCU readers when freeing workers vhost_vq_work_queue() only holds the RCU read lock while it dereferences vq->worker and queues work on it. vhost_workers_free() however clears the vq->worker pointers and immediately frees the workers, without waiting for a grace period. A caller that fetched the worker right before the pointer was cleared can therefore still be queueing work on it while it is freed. And even when the queueing itself wins the race, the work is never run, so its VHOST_WORK_QUEUED bit stays set and all future attempts to queue it are silently skipped. None of the current callers can actually hit this: net and scsi stop their virtqueues before the workers are freed, and vsock unhashes the device and does synchronize_rcu() of its own in vhost_vsock_dev_release() before the workers go away. But the upcoming VHOST_RESET_OWNER support in vhost-vsock keeps the device hashed while its workers are freed, so the lockless send/cancel paths become able to race with the teardown. Fix this by clearing the vq->worker pointers, waiting for a grace period, and then flushing the workers so any work the last readers queued runs before the workers are freed. Fixes: 228a27cf78af ("vhost: Allow worker switching while work is queueing") Suggested-by: Stefano Garzarella Signed-off-by: Andrey Drobyshev Reviewed-by: Stefano Garzarella Message-ID: <20260720102241.371610-5-andrey.drobyshev@virtuozzo.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vhost.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers') diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 269efad90369..1be9c0c53bed 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -729,6 +729,17 @@ static void vhost_workers_free(struct vhost_dev *dev) for (i = 0; i < dev->nvqs; i++) rcu_assign_pointer(dev->vqs[i]->worker, NULL); + + /* + * vhost_vq_work_queue() reads vq->worker under rcu_read_lock(), so a + * reader that fetched a worker before we cleared the pointers above + * may still be queueing work on it. Wait for those readers to + * finish, then flush so any work they queued runs (clearing + * VHOST_WORK_QUEUED) before the workers are freed. + */ + synchronize_rcu(); + vhost_dev_flush(dev); + /* * Free the default worker we created and cleanup workers userspace * created but couldn't clean up (it forgot or crashed). -- cgit v1.2.3 From c7bfb8815d83a63c974878180499c52411839ded Mon Sep 17 00:00:00 2001 From: Pavel Tikhomirov Date: Mon, 20 Jul 2026 13:22:41 +0300 Subject: vhost/vsock: add VHOST_RESET_OWNER ioctl This ioctl is needed for QEMU's CPR (checkpoint-restore) migration of the guest with vhost-vsock device. For this to work, we need to reset the device ownership on the source side by calling RESET_OWNER, and then claim it on the dest side by calling SET_OWNER. We expect not to lose any AF_VSOCK connection while this happens. To that end, unlike the release path, RESET_OWNER keeps the guest CID hashed: established connections survive, and host sends issued while the device is between owners simply stay on send_pkt_queue until the next device start drains them. Since the device stays reachable through the CID hash, the lockless send/cancel paths can race with the worker teardown in vhost_workers_free(). The previous commit ("vhost: synchronize with RCU readers when freeing workers") makes that safe. Signed-off-by: Pavel Tikhomirov Signed-off-by: Andrey Drobyshev Reviewed-by: Stefano Garzarella Message-ID: <20260720102241.371610-6-andrey.drobyshev@virtuozzo.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vsock.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'drivers') diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index d5022d21120b..86f25ff80722 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -903,6 +903,29 @@ err: return -EFAULT; } +static long vhost_vsock_reset_owner(struct vhost_vsock *vsock) +{ + struct vhost_iotlb *umem; + long err; + + mutex_lock(&vsock->dev.mutex); + err = vhost_dev_check_owner(&vsock->dev); + if (err) + goto done; + umem = vhost_dev_reset_owner_prepare(); + if (!umem) { + err = -ENOMEM; + goto done; + } + vhost_vsock_drop_backends(vsock); + vhost_vsock_flush(vsock); + vhost_dev_stop(&vsock->dev); + vhost_dev_reset_owner(&vsock->dev, umem); +done: + mutex_unlock(&vsock->dev.mutex); + return err; +} + static long vhost_vsock_dev_ioctl(struct file *f, unsigned int ioctl, unsigned long arg) { @@ -946,6 +969,8 @@ static long vhost_vsock_dev_ioctl(struct file *f, unsigned int ioctl, return -EOPNOTSUPP; vhost_set_backend_features(&vsock->dev, features); return 0; + case VHOST_RESET_OWNER: + return vhost_vsock_reset_owner(vsock); default: mutex_lock(&vsock->dev.mutex); r = vhost_dev_ioctl(&vsock->dev, ioctl, argp); -- cgit v1.2.3 From 0556300369d8fcdc0db08f8b4984a01119fd315f Mon Sep 17 00:00:00 2001 From: Peter Hilber Date: Fri, 5 Jun 2026 16:29:21 +0200 Subject: 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(-) (limited to 'drivers') 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; -- cgit v1.2.3 From 22172d588a7cfa61d347774cfc0b4419d6c6388e Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 5 Jul 2026 02:24:18 -0400 Subject: 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(+) (limited to 'drivers') diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 7c5ef4e5c879..9309f3198db0 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -1180,6 +1181,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; } -- cgit v1.2.3 From e5725e85d68541584e64e702785ce7445486df83 Mon Sep 17 00:00:00 2001 From: Linfeng Sun  Date: Sat, 20 Jun 2026 18:09:59 +0800 Subject: 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(-) (limited to 'drivers') 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); -- cgit v1.2.3 From 93a5af4ed28ff5f1a2999892e959587369746981 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 22 Jun 2026 01:52:15 -0500 Subject: 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(-) (limited to 'drivers') 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); -- cgit v1.2.3 From 18b83b68e65574cc1435132575ab943a086d8bec Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Mon, 22 Jun 2026 16:03:22 +0100 Subject: 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(-) (limited to 'drivers') 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; -- cgit v1.2.3 From aa47d9df13b7aa0b4ed81ccb58f1f9a6452235ba Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 16:08:43 +0200 Subject: 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(-) (limited to 'drivers') 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); -- cgit v1.2.3 From a6f917677af3495af3871a20a1aef4dc471667ae Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 16:08:44 +0200 Subject: 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(-) (limited to 'drivers') diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 9309f3198db0..d11d81e6b644 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -1109,26 +1109,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); -- cgit v1.2.3 From 009418c31c5a4162888ab9dfb0e44f0cbb6ad64b Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 16:08:45 +0200 Subject: 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(+) (limited to 'drivers') diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index d11d81e6b644..47b0fc63a65c 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -1148,6 +1148,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) { @@ -1220,6 +1226,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, -- cgit v1.2.3 From bb72524bca3a2aa2a422413b5c1c9b4cda0c843a Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 16:08:46 +0200 Subject: 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(-) (limited to 'drivers') diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 47b0fc63a65c..1c55e3c3e14c 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, -- cgit v1.2.3 From 46d447d703a8ce3f46352c71c7b281c99e416bc3 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Wed, 24 Jun 2026 17:40:01 +0200 Subject: 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(-) (limited to 'drivers') diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 1c55e3c3e14c..5cc73dd1363c 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); } -- cgit v1.2.3 From d914650e946d137fb58dbb75fe476de295595097 Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Fri, 26 Jun 2026 15:04:38 +0800 Subject: 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(-) (limited to 'drivers') 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: -- cgit v1.2.3 From db09a0f62f888da11bca2f4aba65a7b60b4eb6f5 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Fri, 26 Jun 2026 10:05:44 +0800 Subject: 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(+) (limited to 'drivers') 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; -- cgit v1.2.3 From e50521dcc5864356ae1933753bc96887eda160c7 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Fri, 26 Jun 2026 10:05:45 +0800 Subject: 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(-) (limited to 'drivers') 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); -- cgit v1.2.3 From c2ed2f2a6415a84673485e5125be08ed6f732489 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Tue, 30 Jun 2026 12:59:52 +0800 Subject: 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(-) (limited to 'drivers') 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) -- cgit v1.2.3 From d57a0ea6b521435bb688b067a0f112df1d63191b Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Mon, 29 Jun 2026 11:35:38 +0800 Subject: 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(-) (limited to 'drivers') 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)) { -- cgit v1.2.3 From 1d04623fb8d96fa0c2539239faf9c394d8cdf7c0 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:26 +0800 Subject: 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(-) (limited to 'drivers') 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; } -- cgit v1.2.3 From 42c5c8634aae258eb1a1cce8429de581d7046079 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:27 +0800 Subject: 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(-) (limited to 'drivers') 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) -- cgit v1.2.3 From 0957511b09c37fd52400741ddce0423ff4026116 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:28 +0800 Subject: 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(-) (limited to 'drivers') 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); -- cgit v1.2.3 From d330252c32cfaf8ace0d7f93aaa25f0c4f3698ca Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:29 +0800 Subject: 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(-) (limited to 'drivers') 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 @@ out_nd: 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 @@ out_err: 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; -- cgit v1.2.3 From 5ba542f94526fcc38efa4dff7cde93caa0ff5e0e Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:30 +0800 Subject: 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(-) (limited to 'drivers') 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; -- cgit v1.2.3 From 5915b4ca83967fd6daf521c8599acefe62c0a80b Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:31 +0800 Subject: 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(-) (limited to 'drivers') 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); } -- cgit v1.2.3 From be595885fc537a0ac0cb83538593cccfaf7f7b71 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:32 +0800 Subject: 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(-) (limited to 'drivers') 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); } -- cgit v1.2.3 From 37ac728bd77557c7fec841345ee098407d081e5f Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:33 +0800 Subject: 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 | 34 +++++++++++++++++++++++++++++----- drivers/nvdimm/virtio_pmem.h | 2 ++ 2 files changed, 31 insertions(+), 5 deletions(-) (limited to 'drivers') 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) { - - dev_info(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n"); + 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; + } + + 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; -- cgit v1.2.3 From 47c6063b6e3225d2165e4bcfffeac49ba68a3fb6 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:34 +0800 Subject: 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(-) (limited to 'drivers') 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); } -- cgit v1.2.3 From 926e727970a9e95d31cb5ad83d55de5754d2be84 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:35 +0800 Subject: 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(-) (limited to 'drivers') 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 { -- cgit v1.2.3 From 34a57abfd949e8e25800270321a8ebc1d8eb397e Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:36 +0800 Subject: 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(-) (limited to 'drivers') 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); - nvdimm_bus_unregister(nvdimm_bus); 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); + + nvdimm_bus_unregister(nvdimm_bus); + vdev->config->del_vqs(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 -- cgit v1.2.3 From 7150f59e1c90d676b46b8633c73e3a4a8778c733 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 30 Jun 2026 17:23:37 +0800 Subject: 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(-) (limited to 'drivers') 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 @@ out_nd: 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; } -- cgit v1.2.3 From c4f1daf41d1ef159c87e6ba75e814a2dfb1c0b4a Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Wed, 1 Jul 2026 19:36:08 +0800 Subject: 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(-) (limited to 'drivers') 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); -- cgit v1.2.3 From 8e856acae997bcdae62f68acd45388cc676efb5c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:27:32 +0800 Subject: 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(+) (limited to 'drivers') 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", -- cgit v1.2.3 From 89eca683984bbebde2516db452b3f1dac6dabc2b Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 08:25:46 +0800 Subject: 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(+) (limited to 'drivers') 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, -- cgit v1.2.3 From 77de17065aba501fba5e36ede6e6e3e4cba9a264 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Mon, 6 Jul 2026 14:09:02 +0800 Subject: 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(-) (limited to 'drivers') 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); -- cgit v1.2.3 From dffbb5120764be8812689d51d0d6f28048e08235 Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Tue, 14 Jul 2026 10:43:52 +0800 Subject: 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(-) (limited to 'drivers') 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); -- cgit v1.2.3 From 863b92f1c0b0a3a35040a5a8030df28c64f98d85 Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Tue, 14 Jul 2026 10:44:34 +0800 Subject: 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(-) (limited to 'drivers') 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) { -- cgit v1.2.3 From dd0365d2b73f7b2dd63a739819fe330ff33ab8e7 Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Tue, 14 Jul 2026 10:45:13 +0800 Subject: 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(-) (limited to 'drivers') 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 -- cgit v1.2.3 From 4b33968eaf4372706cc52228ccae2fd3542e702f Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Tue, 14 Jul 2026 10:45:27 +0800 Subject: 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(-) (limited to 'drivers') 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) -- cgit v1.2.3 From ffa8083cf37c6142afe5511f439a928468f59554 Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Tue, 14 Jul 2026 11:24:17 +0800 Subject: 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(-) (limited to 'drivers') 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). */ -- cgit v1.2.3 From 5c445c2cc50292c2e3924061b2cbfc0a83b1c339 Mon Sep 17 00:00:00 2001 From: Weimin Xiong Date: Thu, 16 Jul 2026 13:43:53 +0800 Subject: 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(-) (limited to 'drivers') 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, -- cgit v1.2.3 From e22849120e8698a1bbb5d44e95581dfe7c1f1527 Mon Sep 17 00:00:00 2001 From: Jinqian Yang Date: Thu, 16 Jul 2026 19:59:40 +0800 Subject: 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(+) (limited to 'drivers') 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); -- cgit v1.2.3 From e9660ae5f436f4af1b25ac4f6d72607f17173282 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 16 Jul 2026 22:13:43 +0800 Subject: 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(-) (limited to 'drivers') 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; -- cgit v1.2.3 From 57c53140db212722945c2e37b1dd94d73fa22c26 Mon Sep 17 00:00:00 2001 From: xiongweimin Date: Thu, 16 Jul 2026 11:02:36 +0800 Subject: 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(+) (limited to 'drivers') diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 1be9c0c53bed..a0c1d54019aa 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -1671,6 +1671,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); -- cgit v1.2.3 From 9bbe30207f300997e73fc16b4ee4b25020fd0457 Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:24:59 +0200 Subject: 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(-) (limited to 'drivers') 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; } -- cgit v1.2.3 From a503a9174044b4ace6863db5262079c50c341efd Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:25:00 +0200 Subject: 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(+) (limited to 'drivers') 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) */ /** -- cgit v1.2.3 From eefeab03e49c5183a22b30e3cf205e0d83f8071e Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:25:01 +0200 Subject: 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(+) (limited to 'drivers') 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) */ /** -- cgit v1.2.3 From f66f0b2d4486973b847451368142eb9a07bd03dc Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:25:02 +0200 Subject: 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(-) (limited to 'drivers') 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]; }; }; -- cgit v1.2.3 From 78bbdaecb2d8ac460a69175dd61c27bf77e7e2f8 Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:33:43 +0200 Subject: 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(-) (limited to 'drivers') 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, -- cgit v1.2.3 From ae864da7d762b4c692e2cdf83cad43110d5d46ee Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:33:44 +0200 Subject: 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(-) (limited to 'drivers') 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, }; /** -- cgit v1.2.3