[PATCH RFC v4 5/5] virtio: support device disconnect
From: Michael S. Tsirkin
Date: Thu Jul 03 2025 - 05:28:00 EST
This adds support for device disconnect:
upon device surprise removal, virtio core makes sure
to no callbacks are running, and then notifies the driver.
At the moment, virtio pci is the only transport with this
functionality enabled, it does nothing for other transports.
Signed-off-by: Michael S. Tsirkin <mst@xxxxxxxxxx>
---
drivers/virtio/virtio_pci_common.c | 45 ++++++++++++++++++++++++++++++
drivers/virtio/virtio_pci_common.h | 3 ++
drivers/virtio/virtio_pci_legacy.c | 2 ++
drivers/virtio/virtio_pci_modern.c | 2 ++
include/linux/virtio.h | 3 ++
include/linux/virtio_config.h | 32 +++++++++++++++++++++
6 files changed, 87 insertions(+)
diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c
index d6d79af44569..a475f47052eb 100644
--- a/drivers/virtio/virtio_pci_common.c
+++ b/drivers/virtio/virtio_pci_common.c
@@ -594,6 +594,51 @@ const struct cpumask *vp_get_vq_affinity(struct virtio_device *vdev, int index)
vp_dev->vqs[index]->msix_vector);
}
+/* Report disconnect to the driver. */
+static void virtio_pci_disconnect_work(struct work_struct *work)
+{
+ struct pci_dev *pci_dev = container_of(work, struct pci_dev,
+ disconnect_work);
+ struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
+ struct virtio_device *vdev = &vp_dev->vdev;
+ struct virtio_driver *drv = drv_to_virtio(vdev->dev.driver);
+
+ if (!pci_test_and_clear_disconnect_enable(pci_dev))
+ return;
+
+ virtio_config_transport_disable(vdev);
+ virtio_break_device(vdev);
+
+ vp_synchronize_vectors(vdev);
+
+ drv->disconnect(&vp_dev->vdev);
+}
+
+void virtio_pci_enable_disconnect(struct virtio_device *vdev)
+{
+ struct virtio_pci_device *vp_dev = to_vp_device(vdev);
+ struct pci_dev *pci_dev = vp_dev->pci_dev;
+ struct virtio_driver *drv = drv_to_virtio(vdev->dev.driver);
+
+ if (!drv->disconnect)
+ return;
+
+ INIT_WORK(&pci_dev->disconnect_work, virtio_pci_disconnect_work);
+ pci_set_disconnect_work(pci_dev);
+}
+
+void virtio_pci_disable_disconnect(struct virtio_device *vdev)
+{
+ struct virtio_pci_device *vp_dev = to_vp_device(vdev);
+ struct pci_dev *pci_dev = vp_dev->pci_dev;
+ struct virtio_driver *drv = drv_to_virtio(vdev->dev.driver);
+
+ if (!drv->disconnect)
+ return;
+
+ pci_clear_disconnect_work(pci_dev);
+}
+
#ifdef CONFIG_PM_SLEEP
static int virtio_pci_freeze(struct device *dev)
{
diff --git a/drivers/virtio/virtio_pci_common.h b/drivers/virtio/virtio_pci_common.h
index 8cd01de27baf..982c4c8aabc8 100644
--- a/drivers/virtio/virtio_pci_common.h
+++ b/drivers/virtio/virtio_pci_common.h
@@ -161,6 +161,9 @@ static inline void virtio_pci_legacy_remove(struct virtio_pci_device *vp_dev)
int virtio_pci_modern_probe(struct virtio_pci_device *);
void virtio_pci_modern_remove(struct virtio_pci_device *);
+void virtio_pci_enable_disconnect(struct virtio_device *);
+void virtio_pci_disable_disconnect(struct virtio_device *);
+
struct virtio_device *virtio_pci_vf_get_pf_dev(struct pci_dev *pdev);
#define VIRTIO_LEGACY_ADMIN_CMD_BITMAP \
diff --git a/drivers/virtio/virtio_pci_legacy.c b/drivers/virtio/virtio_pci_legacy.c
index d9cbb02b35a1..cd424f619b47 100644
--- a/drivers/virtio/virtio_pci_legacy.c
+++ b/drivers/virtio/virtio_pci_legacy.c
@@ -191,6 +191,8 @@ static const struct virtio_config_ops virtio_pci_config_ops = {
.set = vp_set,
.get_status = vp_get_status,
.set_status = vp_set_status,
+ .enable_disconnect = virtio_pci_enable_disconnect,
+ .disable_disconnect = virtio_pci_disable_disconnect,
.reset = vp_reset,
.find_vqs = vp_find_vqs,
.del_vqs = vp_del_vqs,
diff --git a/drivers/virtio/virtio_pci_modern.c b/drivers/virtio/virtio_pci_modern.c
index 7182f43ed055..b3dfb403913f 100644
--- a/drivers/virtio/virtio_pci_modern.c
+++ b/drivers/virtio/virtio_pci_modern.c
@@ -1230,6 +1230,8 @@ static const struct virtio_config_ops virtio_pci_config_nodev_ops = {
.generation = vp_generation,
.get_status = vp_get_status,
.set_status = vp_set_status,
+ .enable_disconnect = virtio_pci_enable_disconnect,
+ .disable_disconnect = virtio_pci_disable_disconnect,
.reset = vp_reset,
.find_vqs = vp_modern_find_vqs,
.del_vqs = vp_del_vqs,
diff --git a/include/linux/virtio.h b/include/linux/virtio.h
index 072a25f6622c..a091651e3144 100644
--- a/include/linux/virtio.h
+++ b/include/linux/virtio.h
@@ -214,6 +214,8 @@ size_t virtio_max_dma_size(const struct virtio_device *vdev);
* @scan: optional function to call after successful probe; intended
* for virtio-scsi to invoke a scan.
* @remove: the function to call when a device is removed.
+ * @disconnect: the function to call on disconnect (surprise removal),
+ * before remove.
* @config_changed: optional function to call when the device configuration
* changes; may be called in interrupt context.
* @freeze: optional function to call during suspend/hibernation.
@@ -235,6 +237,7 @@ struct virtio_driver {
int (*validate)(struct virtio_device *dev);
int (*probe)(struct virtio_device *dev);
void (*scan)(struct virtio_device *dev);
+ void (*disconnect)(struct virtio_device *dev);
void (*remove)(struct virtio_device *dev);
void (*config_changed)(struct virtio_device *dev);
int (*freeze)(struct virtio_device *dev);
diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h
index b3e1d30c765b..861198a74be2 100644
--- a/include/linux/virtio_config.h
+++ b/include/linux/virtio_config.h
@@ -58,6 +58,10 @@ struct virtqueue_info {
* @set_status: write the status byte
* vdev: the virtio_device
* status: the new status byte
+ * @enable_disconnect: driver will get disconnect callbacks
+ * vdev: the virtio_device
+ * @disable_disconnect: driver will not get disconnect callbacks
+ * vdev: the virtio_device
* @reset: reset the device
* vdev: the virtio device
* After this, status and feature negotiation must be done again
@@ -113,6 +117,8 @@ struct virtio_config_ops {
u32 (*generation)(struct virtio_device *vdev);
u8 (*get_status)(struct virtio_device *vdev);
void (*set_status)(struct virtio_device *vdev, u8 status);
+ void (*enable_disconnect)(struct virtio_device *vdev);
+ void (*disable_disconnect)(struct virtio_device *vdev);
void (*reset)(struct virtio_device *vdev);
int (*find_vqs)(struct virtio_device *vdev, unsigned int nvqs,
struct virtqueue *vqs[],
@@ -299,6 +305,32 @@ void virtio_device_ready(struct virtio_device *dev)
dev->config->set_status(dev, status | VIRTIO_CONFIG_S_DRIVER_OK);
}
+/**
+ * virtio_device_enable_disconnect - enable disconnect callback
+ * @dev: the virtio device
+ *
+ * Driver must call this in the probe function.
+ */
+static inline
+void virtio_device_enable_disconnect(struct virtio_device *dev)
+{
+ if (dev->config->enable_disconnect)
+ dev->config->enable_disconnect(dev);
+}
+
+/**
+ * virtio_device_disable_disconnect - enable disconnect callback
+ * @dev: the virtio device
+ *
+ * Driver must call this in the remove function.
+ */
+static inline
+void virtio_device_disable_disconnect(struct virtio_device *dev)
+{
+ if (dev->config->disable_disconnect)
+ dev->config->disable_disconnect(dev);
+}
+
static inline
const char *virtio_bus_name(struct virtio_device *vdev)
{
--
MST