From 6e484b870b71eb6acb3fa7a10b8e71098e242033 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 8 Sep 2026 07:38:12 +0200 Subject: virtio_ccw: fix synchronize_cbs() after interrupt fallback virtio_ccw_synchronize_cbs() assumes the device uses adapter interrupts when vcdev->airq_info is not NULL. If registering adapter indicators fails, virtio_ccw_find_vqs() falls back to classic interrupts but can leave airq_info set. With VIRTIO_HARDEN_NOTIFICATION enabled, virtio_ccw_int_handler() invokes callbacks under vcdev->irq_lock. synchronize_cbs() instead takes the indicator area lock, so it does not synchronize with those callbacks. Use vcdev->is_thinint, which the fallback clears, to select the lock. Keep the airq_info check because is_thinint can be set before the first find_vqs() call, when airq_info is still NULL. Fixes: 3a232277c1ac ("virtio-ccw: implement synchronize_cbs()") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260908053817.26065-2-kmehltretter@gmail.com> --- drivers/s390/virtio/virtio_ccw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/s390/virtio/virtio_ccw.c b/drivers/s390/virtio/virtio_ccw.c index bab6cad3fd5c..b90299205698 100644 --- a/drivers/s390/virtio/virtio_ccw.c +++ b/drivers/s390/virtio/virtio_ccw.c @@ -1062,7 +1062,7 @@ static void virtio_ccw_synchronize_cbs(struct virtio_device *vdev) struct virtio_ccw_device *vcdev = to_vc_device(vdev); struct airq_info *info = vcdev->airq_info; - if (info) { + if (vcdev->is_thinint && info) { /* * This device uses adapter interrupts: synchronize with * vring_interrupt() called by virtio_airq_handler() -- cgit v1.2.3 From 48a355cf5b382570975e60857450ff3d42f268e9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 8 Sep 2026 07:38:13 +0200 Subject: virtio_ccw: always take irq_lock in the classic interrupt handler virtio_ccw_int_handler() takes vcdev->irq_lock around its vring_interrupt() calls only when VIRTIO_HARDEN_NOTIFICATION is enabled. The virtio shutdown path calls synchronize_cbs() even when notification hardening is disabled. virtio_reset_device() now does so after the transport reset too. For classic interrupts, synchronize_cbs() takes vcdev->irq_lock for writing, but the interrupt handler does not take the matching read lock in that configuration. synchronize_cbs() can return while a callback is still running. Take the read lock unconditionally so the write side can synchronize with the callbacks. Fixes: 8bd2fa086a04 ("virtio: break and reset virtio devices on device_shutdown()") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260908053817.26065-3-kmehltretter@gmail.com> --- drivers/s390/virtio/virtio_ccw.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/s390/virtio/virtio_ccw.c b/drivers/s390/virtio/virtio_ccw.c index b90299205698..552d77998012 100644 --- a/drivers/s390/virtio/virtio_ccw.c +++ b/drivers/s390/virtio/virtio_ccw.c @@ -1204,13 +1204,11 @@ static void virtio_ccw_int_handler(struct ccw_device *cdev, vcdev->err = -EIO; } virtio_ccw_check_activity(vcdev, activity); -#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION /* * Paired with virtio_ccw_synchronize_cbs() and interrupts are * disabled here. */ read_lock(&vcdev->irq_lock); -#endif for_each_set_bit(i, indicators(vcdev), sizeof(*indicators(vcdev)) * BITS_PER_BYTE) { /* The bit clear must happen before the vring kick. */ @@ -1219,9 +1217,7 @@ static void virtio_ccw_int_handler(struct ccw_device *cdev, vq = virtio_ccw_vq_by_ind(vcdev, i); vring_interrupt(0, vq); } -#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION read_unlock(&vcdev->irq_lock); -#endif if (test_bit(0, indicators2(vcdev))) { virtio_config_changed(&vcdev->vdev); clear_bit(0, indicators2(vcdev)); -- cgit v1.2.3 From ffe14d610cb0169f607e95f994c49d089c507952 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 8 Sep 2026 07:38:14 +0200 Subject: remoteproc: implement synchronize_cbs() for virtio devices Platform drivers invoke rproc_vq_interrupt() from hard-IRQ handlers, threaded handlers, and work items. Because rpmsg callbacks may sleep, the virtio core's synchronize_rcu() fallback does not synchronize with callbacks across all these contexts. A device reset can therefore complete while a callback is still running. Add an SRCU domain per rproc. Protect both the queue lookup and vring_interrupt() with it, and synchronize the domain in the new hook. __rproc_virtio_del_vqs() can race with rproc_vq_interrupt() too. Clear all queue pointers and synchronize the SRCU domain before freeing the queues, so callers that already found a queue can finish using it. Read rvring->vq once to avoid a second load after deletion starts. The SRCU domain has the same lifetime as struct rproc. Its cleanup can sleep, so document that rproc_free() and rproc_put() must not drop the last reference from atomic context. Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260908053817.26065-4-kmehltretter@gmail.com> --- drivers/remoteproc/remoteproc_core.c | 12 +++++++++++ drivers/remoteproc/remoteproc_virtio.c | 37 +++++++++++++++++++++++++++------- include/linux/remoteproc.h | 3 +++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 1ed406714849..d131b82af320 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -2410,6 +2410,7 @@ static void rproc_type_release(struct device *dev) dev_info(&rproc->dev, "releasing %s\n", rproc->name); + cleanup_srcu_struct(&rproc->vq_srcu); idr_destroy(&rproc->notifyids); if (rproc->index >= 0) @@ -2507,6 +2508,11 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, if (!rproc) return NULL; + if (init_srcu_struct(&rproc->vq_srcu)) { + kfree(rproc); + return NULL; + } + rproc->priv = &rproc[1]; rproc->auto_boot = true; rproc->elf_class = ELFCLASSNONE; @@ -2571,6 +2577,9 @@ EXPORT_SYMBOL(rproc_alloc); * * If no one holds any reference to rproc anymore, then its refcount would * now drop to zero, and it would be freed. + * + * Context: Any context, but the last reference must not be dropped from + * atomic context. */ void rproc_free(struct rproc *rproc) { @@ -2586,6 +2595,9 @@ EXPORT_SYMBOL(rproc_free); * * If no one holds any reference to rproc anymore, then its refcount would * now drop to zero, and it would be freed. + * + * Context: Any context, but the last reference must not be dropped from + * atomic context. */ void rproc_put(struct rproc *rproc) { diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c index d5e9ff045a28..7fefb4bd7adc 100644 --- a/drivers/remoteproc/remoteproc_virtio.c +++ b/drivers/remoteproc/remoteproc_virtio.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "remoteproc_internal.h" @@ -88,15 +89,23 @@ static bool rproc_virtio_notify(struct virtqueue *vq) */ irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid) { + irqreturn_t ret = IRQ_NONE; struct rproc_vring *rvring; + struct virtqueue *vq; + int idx; dev_dbg(&rproc->dev, "vq index %d is interrupted\n", notifyid); + idx = srcu_read_lock(&rproc->vq_srcu); + rvring = idr_find(&rproc->notifyids, notifyid); - if (!rvring || !rvring->vq) - return IRQ_NONE; + vq = rvring ? READ_ONCE(rvring->vq) : NULL; + if (vq) + ret = vring_interrupt(0, vq); - return vring_interrupt(0, rvring->vq); + srcu_read_unlock(&rproc->vq_srcu, idx); + + return ret; } EXPORT_SYMBOL(rproc_vq_interrupt); @@ -153,7 +162,7 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev, vq->num_max = num; - rvring->vq = vq; + WRITE_ONCE(rvring->vq, vq); vq->priv = rvring; /* Update vring in resource table */ @@ -165,14 +174,20 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev, static void __rproc_virtio_del_vqs(struct virtio_device *vdev) { + struct rproc *rproc = vdev_to_rproc(vdev); struct virtqueue *vq, *n; struct rproc_vring *rvring; - list_for_each_entry_safe(vq, n, &vdev->vqs, list) { + list_for_each_entry(vq, &vdev->vqs, list) { rvring = vq->priv; - rvring->vq = NULL; - vring_del_virtqueue(vq); + WRITE_ONCE(rvring->vq, NULL); } + + /* Synchronize with rproc_vq_interrupt() callers that found a queue. */ + synchronize_srcu(&rproc->vq_srcu); + + list_for_each_entry_safe(vq, n, &vdev->vqs, list) + vring_del_virtqueue(vq); } static void rproc_virtio_del_vqs(struct virtio_device *vdev) @@ -242,6 +257,13 @@ static void rproc_virtio_reset(struct virtio_device *vdev) dev_dbg(&vdev->dev, "reset !\n"); } +static void rproc_virtio_synchronize_cbs(struct virtio_device *vdev) +{ + struct rproc *rproc = vdev_to_rproc(vdev); + + synchronize_srcu(&rproc->vq_srcu); +} + /* provide the vdev features as retrieved from the firmware */ static u64 rproc_virtio_get_features(struct virtio_device *vdev) { @@ -330,6 +352,7 @@ static const struct virtio_config_ops rproc_virtio_config_ops = { .find_vqs = rproc_virtio_find_vqs, .del_vqs = rproc_virtio_del_vqs, .reset = rproc_virtio_reset, + .synchronize_cbs = rproc_virtio_synchronize_cbs, .set_status = rproc_virtio_set_status, .get_status = rproc_virtio_get_status, .get = rproc_virtio_get, diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index a44368737b39..ff0ad1e12c01 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -228,6 +229,7 @@ enum rproc_features { * @mappings: list of iommu mappings we initiated, needed on shutdown * @bootaddr: address of first instruction to boot rproc with (optional) * @rvdevs: list of remote virtio devices + * @vq_srcu: SRCU domain for the virtqueue callbacks of @rvdevs * @subdevs: list of subdevices, to following the running state * @notifyids: idr for dynamically assigning rproc-wide unique notify ids * @index: index of this rproc device @@ -274,6 +276,7 @@ struct rproc { struct list_head mappings; u64 bootaddr; struct list_head rvdevs; + struct srcu_struct vq_srcu; struct list_head subdevs; struct idr notifyids; int index; -- cgit v1.2.3 From 96be137130c0a21b888bbfbac1808e5c22eca36f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 8 Sep 2026 07:38:15 +0200 Subject: um: virtio_uml: implement synchronize_cbs() virtio_uml invokes virtqueue callbacks from vu_interrupt() and vu_req_interrupt(), which share vu_dev->irq. The core's synchronize_rcu() fallback already covers these IRQ handlers. Implement synchronize_cbs() with synchronize_irq() to synchronize with that IRQ instead of waiting for an RCU grace period. Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260908053817.26065-5-kmehltretter@gmail.com> --- arch/um/drivers/virtio_uml.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/um/drivers/virtio_uml.c b/arch/um/drivers/virtio_uml.c index 7425a8548141..1f2e62e29a27 100644 --- a/arch/um/drivers/virtio_uml.c +++ b/arch/um/drivers/virtio_uml.c @@ -20,6 +20,7 @@ * * Based on Virtio MMIO driver by Pawel Moll, copyright 2011-2014, ARM Ltd. */ +#include #include #include #include @@ -869,6 +870,14 @@ static void vu_reset(struct virtio_device *vdev) vu_dev->status = 0; } +static void vu_synchronize_cbs(struct virtio_device *vdev) +{ + struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); + + if (vu_dev->irq != UM_IRQ_ALLOC) + synchronize_irq(vu_dev->irq); +} + static void vu_del_vq(struct virtqueue *vq) { struct virtio_uml_vq_info *info = vq->priv; @@ -1121,6 +1130,7 @@ static const struct virtio_config_ops virtio_uml_config_ops = { .reset = vu_reset, .find_vqs = vu_find_vqs, .del_vqs = vu_del_vqs, + .synchronize_cbs = vu_synchronize_cbs, .get_features = vu_get_features, .finalize_features = vu_finalize_features, .bus_name = vu_bus_name, -- cgit v1.2.3 From 961d62a3c153d161ab5f617a49f409deb55d3111 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 8 Sep 2026 07:38:16 +0200 Subject: platform/mellanox: mlxbf-tmfifo: implement synchronize_cbs() TmFIFO invokes virtqueue callbacks from its FIFO work item under spin_lock_irqsave(). The core's synchronize_rcu() fallback already covers those sections. Implement synchronize_cbs() using the existing per-direction locks instead of waiting for an RCU grace period. fifo->spin_lock[0] covers the transmit callbacks, including the callback from the timeout handler. fifo->spin_lock[1] covers the receive callbacks. Take and release each lock in turn to synchronize with callbacks running under that lock. Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260908053817.26065-6-kmehltretter@gmail.com> --- drivers/platform/mellanox/mlxbf-tmfifo.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/platform/mellanox/mlxbf-tmfifo.c b/drivers/platform/mellanox/mlxbf-tmfifo.c index 3c6408581373..664bec20a7ae 100644 --- a/drivers/platform/mellanox/mlxbf-tmfifo.c +++ b/drivers/platform/mellanox/mlxbf-tmfifo.c @@ -1135,6 +1135,20 @@ static void mlxbf_tmfifo_virtio_reset(struct virtio_device *vdev) tm_vdev->status = 0; } +/* Synchronize with callbacks running in the FIFO work item. */ +static void mlxbf_tmfifo_virtio_synchronize_cbs(struct virtio_device *vdev) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + struct mlxbf_tmfifo *fifo = tm_vdev->vrings[0].fifo; + unsigned long flags; + int i; + + for (i = 0; i < ARRAY_SIZE(fifo->spin_lock); i++) { + spin_lock_irqsave(&fifo->spin_lock[i], flags); + spin_unlock_irqrestore(&fifo->spin_lock[i], flags); + } +} + /* Read the value of a configuration field. */ static void mlxbf_tmfifo_virtio_get(struct virtio_device *vdev, unsigned int offset, @@ -1179,6 +1193,7 @@ static const struct virtio_config_ops mlxbf_tmfifo_virtio_config_ops = { .find_vqs = mlxbf_tmfifo_virtio_find_vqs, .del_vqs = mlxbf_tmfifo_virtio_del_vqs, .reset = mlxbf_tmfifo_virtio_reset, + .synchronize_cbs = mlxbf_tmfifo_virtio_synchronize_cbs, .set_status = mlxbf_tmfifo_virtio_set_status, .get_status = mlxbf_tmfifo_virtio_get_status, .get = mlxbf_tmfifo_virtio_get, -- cgit v1.2.3 From dd886653d2038a510a21bff476fa1be0422a4fb9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 8 Sep 2026 07:38:17 +0200 Subject: virtio_vdpa: implement synchronize_cbs() virtio_vdpa relies on the core's synchronize_rcu() fallback for callback synchronization. That covers IRQ handlers and sections with interrupts, preemption or bottom halves disabled. virtio_vdpa does not enforce any of those contexts when a vDPA driver invokes a callback. Add an SRCU domain per device and enter it around the virtqueue and config callbacks. Implement synchronize_cbs() with synchronize_srcu() so it covers the callbacks regardless of the calling context. SRCU uses per-CPU reader accounting, avoiding a single callback lock shared by all queues. Cover the config callback too, as virtio-pci does for its config vector. Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260908053817.26065-7-kmehltretter@gmail.com> --- drivers/virtio/virtio_vdpa.c | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/drivers/virtio/virtio_vdpa.c b/drivers/virtio/virtio_vdpa.c index 6bcf4567a929..f9559b6b2e0b 100644 --- a/drivers/virtio/virtio_vdpa.c +++ b/drivers/virtio/virtio_vdpa.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ struct virtio_vdpa_device { struct virtio_device vdev; struct vdpa_device *vdpa; + struct srcu_struct cb_srcu; u64 features; }; @@ -114,8 +116,11 @@ static bool virtio_vdpa_notify_with_data(struct virtqueue *vq) static irqreturn_t virtio_vdpa_config_cb(void *private) { struct virtio_vdpa_device *vd_dev = private; + int idx; + idx = srcu_read_lock(&vd_dev->cb_srcu); virtio_config_changed(&vd_dev->vdev); + srcu_read_unlock(&vd_dev->cb_srcu, idx); return IRQ_HANDLED; } @@ -123,8 +128,22 @@ static irqreturn_t virtio_vdpa_config_cb(void *private) static irqreturn_t virtio_vdpa_virtqueue_cb(void *private) { struct virtqueue *vq = private; + struct virtio_vdpa_device *vd_dev = to_virtio_vdpa_device(vq->vdev); + irqreturn_t ret; + int idx; + + idx = srcu_read_lock(&vd_dev->cb_srcu); + ret = vring_interrupt(0, vq); + srcu_read_unlock(&vd_dev->cb_srcu, idx); + + return ret; +} + +static void virtio_vdpa_synchronize_cbs(struct virtio_device *vdev) +{ + struct virtio_vdpa_device *vd_dev = to_virtio_vdpa_device(vdev); - return vring_interrupt(0, vq); + synchronize_srcu(&vd_dev->cb_srcu); } static struct virtqueue * @@ -440,6 +459,7 @@ static const struct virtio_config_ops virtio_vdpa_config_ops = { .reset = virtio_vdpa_reset, .find_vqs = virtio_vdpa_find_vqs, .del_vqs = virtio_vdpa_del_vqs, + .synchronize_cbs = virtio_vdpa_synchronize_cbs, .get_features = virtio_vdpa_get_features, .finalize_features = virtio_vdpa_finalize_features, .bus_name = virtio_vdpa_bus_name, @@ -454,6 +474,7 @@ static void virtio_vdpa_release_dev(struct device *_d) struct virtio_vdpa_device *vd_dev = container_of(vdev, struct virtio_vdpa_device, vdev); + cleanup_srcu_struct(&vd_dev->cb_srcu); kfree(vd_dev); } @@ -467,6 +488,11 @@ static int virtio_vdpa_probe(struct vdpa_device *vdpa) if (!vd_dev) return -ENOMEM; + if (init_srcu_struct(&vd_dev->cb_srcu)) { + kfree(vd_dev); + return -ENOMEM; + } + vd_dev->vdev.dev.parent = vdpa->map ? &vdpa->dev : vdpa_get_map(vdpa).dma_dev; vd_dev->vdev.dev.release = virtio_vdpa_release_dev; @@ -489,10 +515,12 @@ static int virtio_vdpa_probe(struct vdpa_device *vdpa) return 0; err: - if (reg_dev) + if (reg_dev) { put_device(&vd_dev->vdev.dev); - else + } else { + cleanup_srcu_struct(&vd_dev->cb_srcu); kfree(vd_dev); + } return ret; } -- cgit v1.2.3 From dd75508e6fe77116589c49658d0d18897688dc0b Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sat, 12 Sep 2026 06:30:14 -0400 Subject: virtio: synchronize callbacks after device reset virtio_reset_device says: Note: this guarantees that vq callbacks are not in progress but in practice, only virtio pci correctly synchronizes the cbs. On other transports, a callback that is already executing, keeps running while the driver tears down the state it uses. Add virtio_synchronize_cbs to virtio_reset_device fixing this for all transports that correctly implement synchronize_cbs(). Reported-by: Karl Mehltretter Link: https://lore.kernel.org/all/20260818040433.66986-1-kmehltretter@gmail.com/ Fixes: c46eccdaadab ("virtio: document virtio_reset_device") Tested-by: Karl Mehltretter Acked-by: Karl Mehltretter Assisted-by: LLM Signed-off-by: Michael S. Tsirkin Message-ID: <8ae2995ee649adcfe1398f7f1c4fc9f7028c3db1.1789204858.git.mst@redhat.com> --- drivers/virtio/virtio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/virtio/virtio.c b/drivers/virtio/virtio.c index b6c9e927bef5..a8588d7ad109 100644 --- a/drivers/virtio/virtio.c +++ b/drivers/virtio/virtio.c @@ -264,6 +264,8 @@ void virtio_reset_device(struct virtio_device *dev) #endif dev->config->reset(dev); + /* Flush pending VQ/configuration callbacks. */ + virtio_synchronize_cbs(dev); } EXPORT_SYMBOL_GPL(virtio_reset_device); -- cgit v1.2.3 From 2a12d96dd863cb23eadf07d8845e961580b5fcdf Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sat, 12 Sep 2026 06:30:18 -0400 Subject: virtio_pci_modern: move avq cleanup from reset to del_vqs vp_modern_avq_cleanup() detaches unused buffers from the admin virtqueue. Calling it from vp_reset() is incorrect: virtqueue_get_buf in the avq interrupt handler can race with virtqueue_detach_unused_buf in cleanup, and get_buf after detach is not documented as valid. The root cause is that detaching buffers does not belong in reset at all - reset quiesces the device, while cleanup belongs where the virtqueue is about to be destroyed, from del_vqs. Reported-by: Sashiko Link: https://lore.kernel.org/virtualization/20260911125745.E0A2F1F00899@smtp.kernel.org/ Fixes: 4c3b54af907e ("virtio_pci_modern: use completion instead of busy loop to wait on admin cmd result") Cc: Jiri Pirko Assisted-by: LLM Signed-off-by: Michael S. Tsirkin Message-ID: <21fe35ad7b66e2c30dbdba6fe4df4824d1c0db66.1789204858.git.mst@redhat.com> --- drivers/virtio/virtio_pci_modern.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/virtio/virtio_pci_modern.c b/drivers/virtio/virtio_pci_modern.c index 6d8ae2a6a8ca..b4249afd7f58 100644 --- a/drivers/virtio/virtio_pci_modern.c +++ b/drivers/virtio/virtio_pci_modern.c @@ -364,6 +364,12 @@ static void vp_modern_avq_cleanup(struct virtio_device *vdev) } } +static void vp_modern_del_vqs(struct virtio_device *vdev) +{ + vp_modern_avq_cleanup(vdev); + vp_del_vqs(vdev); +} + static void vp_transport_features(struct virtio_device *vdev, u64 features) { struct virtio_pci_device *vp_dev = to_vp_device(vdev); @@ -558,8 +564,6 @@ static void vp_reset(struct virtio_device *vdev) while (vp_modern_get_status(mdev)) msleep(1); - vp_modern_avq_cleanup(vdev); - /* Flush pending VQ/configuration callbacks. */ vp_synchronize_vectors(vdev); } @@ -1232,7 +1236,7 @@ static const struct virtio_config_ops virtio_pci_config_nodev_ops = { .set_status = vp_set_status, .reset = vp_reset, .find_vqs = vp_modern_find_vqs, - .del_vqs = vp_del_vqs, + .del_vqs = vp_modern_del_vqs, .synchronize_cbs = vp_synchronize_vectors, .get_extended_features = vp_get_features, .finalize_features = vp_finalize_features, @@ -1252,7 +1256,7 @@ static const struct virtio_config_ops virtio_pci_config_ops = { .set_status = vp_set_status, .reset = vp_reset, .find_vqs = vp_modern_find_vqs, - .del_vqs = vp_del_vqs, + .del_vqs = vp_modern_del_vqs, .synchronize_cbs = vp_synchronize_vectors, .get_extended_features = vp_get_features, .finalize_features = vp_finalize_features, -- cgit v1.2.3 From ac7360d48607622cd3b1889e2d642a47e458d9e3 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sat, 12 Sep 2026 06:30:21 -0400 Subject: virtio_pci: drop callback sync on reset The virtio core now synchronizes callbacks after reset, so the PCI transports no longer need to do it themselves. Assisted-by: LLM Signed-off-by: Michael S. Tsirkin Message-ID: <64421afa76075c2e0b66ef5c2ea18948b861ab73.1789204858.git.mst@redhat.com> --- drivers/virtio/virtio_pci_legacy.c | 2 -- drivers/virtio/virtio_pci_modern.c | 3 --- 2 files changed, 5 deletions(-) diff --git a/drivers/virtio/virtio_pci_legacy.c b/drivers/virtio/virtio_pci_legacy.c index d9cbb02b35a1..8115aa39e01e 100644 --- a/drivers/virtio/virtio_pci_legacy.c +++ b/drivers/virtio/virtio_pci_legacy.c @@ -98,8 +98,6 @@ static void vp_reset(struct virtio_device *vdev) /* Flush out the status write, and flush in device writes, * including MSi-X interrupts, if any. */ vp_legacy_get_status(&vp_dev->ldev); - /* Flush pending VQ/configuration callbacks. */ - vp_synchronize_vectors(vdev); } static u16 vp_config_vector(struct virtio_pci_device *vp_dev, u16 vector) diff --git a/drivers/virtio/virtio_pci_modern.c b/drivers/virtio/virtio_pci_modern.c index b4249afd7f58..922e2df027de 100644 --- a/drivers/virtio/virtio_pci_modern.c +++ b/drivers/virtio/virtio_pci_modern.c @@ -563,9 +563,6 @@ static void vp_reset(struct virtio_device *vdev) */ while (vp_modern_get_status(mdev)) msleep(1); - - /* Flush pending VQ/configuration callbacks. */ - vp_synchronize_vectors(vdev); } static int vp_active_vq(struct virtqueue *vq, u16 msix_vec) -- cgit v1.2.3 From 01b8354fa2bc33f411837ac2f6dccfc8dceda96c Mon Sep 17 00:00:00 2001 From: Pavel Tikhomirov Date: Fri, 24 Jul 2026 14:45:38 +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 Acked-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-ID: <20260724114542.734623-2-andrey.drobyshev@virtuozzo.com> --- drivers/vhost/vsock.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index abed1fbcf66c..0835f942684f 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 a29f2afa2d73c28f2290c7b3232e467d703035b5 Mon Sep 17 00:00:00 2001 From: Andrey Drobyshev Date: Fri, 24 Jul 2026 14:45:39 +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 Acked-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-ID: <20260724114542.734623-3-andrey.drobyshev@virtuozzo.com> --- drivers/vhost/vsock.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 0835f942684f..b106e11d31fa 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 bb52825eddec2801ae9ace88f53dfee01d22b9bd Mon Sep 17 00:00:00 2001 From: Andrey Drobyshev Date: Fri, 24 Jul 2026 14:45:40 +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 Acked-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-ID: <20260724114542.734623-4-andrey.drobyshev@virtuozzo.com> --- drivers/vhost/vsock.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index b106e11d31fa..30d4b16a5e50 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 5efd97e6f0c745d5cb34e4e3b709200e23d4d7b7 Mon Sep 17 00:00:00 2001 From: Andrey Drobyshev Date: Fri, 24 Jul 2026 14:45:41 +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 threading a bool 'sync' param through the chain: vhost_dev_reset_owner() vhost_dev_cleanup() vhost_workers_free() When set, after clearing the vq->worker pointers, wait for a grace period and flush the workers so any work the last RCU readers queued runs (clearing VHOST_WORK_QUEUED) before the workers are freed. Only the vsock RESET_OWNER path (added in the next patch) passes sync=true; every other teardown has already quiesced and passes false, so they do not need to wait the grace period. Suggested-by: Stefano Garzarella Suggested-by: Michael S. Tsirkin Signed-off-by: Andrey Drobyshev Reviewed-by: Stefano Garzarella Acked-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-ID: <20260724114542.734623-5-andrey.drobyshev@virtuozzo.com> --- drivers/vhost/net.c | 4 ++-- drivers/vhost/scsi.c | 2 +- drivers/vhost/test.c | 4 ++-- drivers/vhost/vdpa.c | 4 ++-- drivers/vhost/vhost.c | 26 +++++++++++++++++++++----- drivers/vhost/vhost.h | 5 +++-- drivers/vhost/vsock.c | 2 +- 7 files changed, 32 insertions(+), 15 deletions(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 2cc730729e08..4570a4ffbab2 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1452,7 +1452,7 @@ static int vhost_net_release(struct inode *inode, struct file *f) vhost_net_stop(n, &tx_sock, &rx_sock); vhost_net_flush(n); vhost_dev_stop(&n->dev); - vhost_dev_cleanup(&n->dev); + vhost_dev_cleanup(&n->dev, false); vhost_net_vq_reset(n); if (tx_sock) sockfd_put(tx_sock); @@ -1661,7 +1661,7 @@ static long vhost_net_reset_owner(struct vhost_net *n) vhost_net_stop(n, &tx_sock, &rx_sock); vhost_net_flush(n); vhost_dev_stop(&n->dev); - vhost_dev_reset_owner(&n->dev, umem); + vhost_dev_reset_owner(&n->dev, umem, false); vhost_net_vq_reset(n); done: mutex_unlock(&n->dev.mutex); diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 4f8c0260bc9e..885fac5ed042 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2390,7 +2390,7 @@ static int vhost_scsi_release(struct inode *inode, struct file *f) mutex_unlock(&vs->dev.mutex); vhost_scsi_clear_endpoint(vs, &t); vhost_dev_stop(&vs->dev); - vhost_dev_cleanup(&vs->dev); + vhost_dev_cleanup(&vs->dev, false); kfree(vs->dev.vqs); kvfree(vs->vqs); kfree(vs->old_inflight); diff --git a/drivers/vhost/test.c b/drivers/vhost/test.c index 24514c8fdee4..84ed9ea81219 100644 --- a/drivers/vhost/test.c +++ b/drivers/vhost/test.c @@ -163,7 +163,7 @@ static int vhost_test_release(struct inode *inode, struct file *f) vhost_test_stop(n, &private); vhost_test_flush(n); vhost_dev_stop(&n->dev); - vhost_dev_cleanup(&n->dev); + vhost_dev_cleanup(&n->dev, false); kfree(n->dev.vqs); kfree(n); return 0; @@ -238,7 +238,7 @@ static long vhost_test_reset_owner(struct vhost_test *n) vhost_test_stop(n, &priv); vhost_test_flush(n); vhost_dev_stop(&n->dev); - vhost_dev_reset_owner(&n->dev, umem); + vhost_dev_reset_owner(&n->dev, umem, false); done: mutex_unlock(&n->dev.mutex); return err; diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index a31786796d4c..ff7d90719171 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -948,7 +948,7 @@ static long vhost_vdpa_unlocked_ioctl(struct file *filep, case VHOST_SET_OWNER: r = vhost_vdpa_bind_mm(v); if (r) - vhost_dev_reset_owner(d, NULL); + vhost_dev_reset_owner(d, NULL, false); break; } out: @@ -1453,7 +1453,7 @@ static void vhost_vdpa_cleanup(struct vhost_vdpa *v) } vhost_vdpa_free_domain(v); - vhost_dev_cleanup(&v->vdev); + vhost_dev_cleanup(&v->vdev, false); kfree(v->vdev.vqs); v->vdev.vqs = NULL; } diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 44cac11b68d2..4d5d32e68d35 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -731,7 +731,7 @@ static void vhost_worker_destroy(struct vhost_dev *dev, kfree(worker); } -static void vhost_workers_free(struct vhost_dev *dev) +static void vhost_workers_free(struct vhost_dev *dev, bool sync) { struct vhost_worker *worker; unsigned long i; @@ -741,6 +741,21 @@ static void vhost_workers_free(struct vhost_dev *dev) for (i = 0; i < dev->nvqs; i++) rcu_assign_pointer(dev->vqs[i]->worker, NULL); + + /* + * This path can be reached by lockless work queuers. In this case + * vhost_vq_work_queue() reads vq->worker under rcu_read_lock(), so a + * RCU 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. Notably, that is the case for the + * vsock RESET_OWNER path. + */ + if (sync) { + 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). @@ -1163,11 +1178,12 @@ struct vhost_iotlb *vhost_dev_reset_owner_prepare(void) EXPORT_SYMBOL_GPL(vhost_dev_reset_owner_prepare); /* Caller should have device mutex */ -void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *umem) +void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *umem, + bool sync) { int i; - vhost_dev_cleanup(dev); + vhost_dev_cleanup(dev, sync); dev->fork_owner = fork_from_owner_default; dev->umem = umem; @@ -1227,7 +1243,7 @@ void vhost_clear_msg(struct vhost_dev *dev) } EXPORT_SYMBOL_GPL(vhost_clear_msg); -void vhost_dev_cleanup(struct vhost_dev *dev) +void vhost_dev_cleanup(struct vhost_dev *dev, bool sync) { int i; @@ -1251,7 +1267,7 @@ void vhost_dev_cleanup(struct vhost_dev *dev) dev->iotlb = NULL; vhost_clear_msg(dev); wake_up_interruptible_poll(&dev->wait, EPOLLIN | EPOLLRDNORM); - vhost_workers_free(dev); + vhost_workers_free(dev, sync); vhost_detach_mm(dev); } EXPORT_SYMBOL_GPL(vhost_dev_cleanup); diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 39e6121f7525..5a684ba5e389 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -219,8 +219,9 @@ long vhost_dev_set_owner(struct vhost_dev *dev); bool vhost_dev_has_owner(struct vhost_dev *dev); long vhost_dev_check_owner(struct vhost_dev *); struct vhost_iotlb *vhost_dev_reset_owner_prepare(void); -void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *iotlb); -void vhost_dev_cleanup(struct vhost_dev *); +void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *iotlb, + bool sync); +void vhost_dev_cleanup(struct vhost_dev *dev, bool sync); void vhost_dev_stop(struct vhost_dev *); long vhost_dev_ioctl(struct vhost_dev *, unsigned int ioctl, void __user *argp); long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *argp); diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 30d4b16a5e50..e6ac9b41c2a5 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -824,7 +824,7 @@ static int vhost_vsock_dev_release(struct inode *inode, struct file *file) virtio_vsock_skb_queue_purge(&vsock->send_pkt_queue); - vhost_dev_cleanup(&vsock->dev); + vhost_dev_cleanup(&vsock->dev, false); put_net_track(vsock->net, &vsock->ns_tracker); kfree(vsock->dev.vqs); vhost_vsock_free(vsock); -- cgit v1.2.3 From 8f2c2fb94a01320e5136c6e89a47fb12355ad27c Mon Sep 17 00:00:00 2001 From: Pavel Tikhomirov Date: Fri, 24 Jul 2026 14:45:42 +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 Acked-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-ID: <20260724114542.734623-6-andrey.drobyshev@virtuozzo.com> --- drivers/vhost/vsock.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index e6ac9b41c2a5..0cb87971d749 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -905,6 +905,36 @@ 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); + + /* + * vsock keeps the CID hashed across RESET_OWNER, so a lockless sender + * can still reach the device while its workers are freed. Pass + * sync=true so vhost_workers_free() fences RCU readers and flushes + * before freeing them. + */ + vhost_dev_reset_owner(&vsock->dev, umem, true); +done: + mutex_unlock(&vsock->dev.mutex); + return err; +} + static long vhost_vsock_dev_ioctl(struct file *f, unsigned int ioctl, unsigned long arg) { @@ -948,6 +978,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 c69023b5e16e5ffe9368603dca76d85bd544ee57 Mon Sep 17 00:00:00 2001 From: Longjun Tang Date: Wed, 9 Sep 2026 17:20:00 +0800 Subject: tools/virtio: add missing device-id/virtio.h wrapper header Since commit ecca1d63c1ea ("Replace by more specific (headers)"), includes instead of the old . However, tools/virtio/ only had a wrapper for the old mod_devicetable.h, causing the build to fail with: ./../../include/linux/virtio.h:10:10: fatal error: linux/device-id/virtio.h: No such file or directory Add the missing wrapper header that redirects to ../../include/linux/device-id/virtio.h Signed-off-by: Longjun Tang Message-ID: <20260909092000.107827-1-lange_tang@163.com> Signed-off-by: Michael S. Tsirkin --- tools/virtio/linux/device-id/virtio.h | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tools/virtio/linux/device-id/virtio.h diff --git a/tools/virtio/linux/device-id/virtio.h b/tools/virtio/linux/device-id/virtio.h new file mode 100644 index 000000000000..3c3e8c514c8d --- /dev/null +++ b/tools/virtio/linux/device-id/virtio.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <../../include/linux/device-id/virtio.h> -- cgit v1.2.3