From 2187be212179243f33da1271248571aca3e99a3f Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Tue, 10 Mar 2026 09:40:46 +0100 Subject: virtio: Add ID for virtio media Add VIRTIO_ID_MEDIA definition for virtio-media. Signed-off-by: Albert Esteve Message-ID: <20260310-virtio-media-id-v1-1-be211bcf682b@redhat.com> Signed-off-by: Michael S. Tsirkin --- include/uapi/linux/virtio_ids.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index 6c12db16faa3..f9056af0c622 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -69,6 +69,7 @@ #define VIRTIO_ID_BT 40 /* virtio bluetooth */ #define VIRTIO_ID_GPIO 41 /* virtio gpio */ #define VIRTIO_ID_SPI 45 /* virtio spi */ +#define VIRTIO_ID_MEDIA 48 /* virtio media */ /* * Virtio Transitional IDs -- cgit From 0d8aebe089b4ba887e792884cf041ce9f1040ff4 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 'include') 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 From 656662dc53e6431fbfabb5f39103cb940838bf82 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Mon, 29 Jun 2026 11:31:46 +0800 Subject: virtio_dma_buf: fix typo in kdoc comment: get_uid -> get_uuid The @get_uid tag in the virtio_dma_buf_ops kdoc comment is a typo; the actual field name is get_uuid. Fixes: a0308938ec81 ("virtio: add dma-buf support for exported objects") Signed-off-by: Li RongQing Signed-off-by: Michael S. Tsirkin Message-ID: <20260629033146.2209-1-lirongqing@baidu.com> --- include/linux/virtio_dma_buf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/virtio_dma_buf.h b/include/linux/virtio_dma_buf.h index a2fdf217ac62..545ac5f17a54 100644 --- a/include/linux/virtio_dma_buf.h +++ b/include/linux/virtio_dma_buf.h @@ -17,7 +17,7 @@ * @ops: the base dma_buf_ops. ops.attach MUST be virtio_dma_buf_attach. * @device_attach: [optional] callback invoked by virtio_dma_buf_attach during * all attach operations. - * @get_uid: [required] callback to get the uuid of the exported object. + * @get_uuid: [required] callback to get the uuid of the exported object. */ struct virtio_dma_buf_ops { struct dma_buf_ops ops; -- cgit From 40f356e610df95728074b1fc2e2ccb54ca1b5659 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 'include') 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 From a596238c2ab6944861404dc597133ffd4ad062d5 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 'include') 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 From 3b441820cb61ac1137dd4b336adfb4892cda4233 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 'include') 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 From 4c318d91cc60a0c2c94bcb6db2630baed43e9387 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 'include') 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 From b282418bc366194677eafd1dad180d92254586ac 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 'include') 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