pci,virtio: report surprise removal event
Subsystems (2)
Michael S. Tsirkin
maintainerv4 cover pci,virtio: report surprise removal event
Lukas, Keith, Bjorn, others, would very much appreciate your comments on whether the pci core changes are acceptable.
Parav, I expect this to be integrated into your work on fixing surprise removal. As such, I am not queueing these patches yet - please include with your other patches fixing these issues.
==========
This is an attempt to fix the following race in virtio:
when device removal is initiated by a user
action, such as driver unbind, it in turn initiates driver cleanup and is then waiting for an interrupt from the device. If the device is now surprise-removed, that interrupt never arrives and the remove callback hangs forever.
For example, this was reported for virtio-blk:
1. the graceful removal is ongoing in the remove() callback, where disk deletion del_gendisk() is ongoing, which waits for the requests +to complete,
2. Now few requests are yet to complete, and surprise removal started.
At this point, virtio block driver will not get notified by the driver core layer, because it is likely serializing remove() happening by +user/driver unload and PCI hotplug driver-initiated device removal. So vblk driver doesn't know that device is removed, block layer is waiting for requests completions to arrive which it never gets. So del_gendisk() gets stuck.
We could add timeouts to handle that, but given virtio blk devices are implemented in userspace, this makes the device flaky.
Instead, this adds pci core infrastructure, and virtio core infrastructure, for drivers to be notified of device disconnect.
Actual cleanup in virtio-blk is still TBD.
Compile-tested only.
==========
Notes on the design:
Care was taken to avoid re-introducing the bug fixed by
commit 74ff8864cc84 ("PCI: hotplug: Allow marking devices as disconnected during bind/unbind")
To avoid taking locks on removal path, and to avoid invoking callback with unpredictable latency, the event is reported through a WQ.
Adding APIs to enable/disable the reporting on probe/remove, helps make sure the driver won't go away in the middle of the handling, all without taking any locks.
The benefit is that the resulting API is harder than a callback to misuse, adding unpredictable latencies to unplug. The WQ can simply do the cleanup directly, taking any locks it needs for that. The cost is several extra bytes per device, which seems modest.
Previous discussion:
https://lore.kernel.org/all/11cfcb55b5302999b0e58b94018f92a379196698.1751136072.git.mst@redhat.com
Changes from v3:
added documentation to address comments by Parav support in virtio core
Changes from v2:
v2 was corrupted, fat fingers :(
Changes from v1:
switched to a WQ, with APIs to enable/disable added motivation
==========
Michael S. Tsirkin (5):
pci: report surprise removal event
virtio: fix comments, readability
virtio: pack config changed flags
virtio: allow transports to suppress config change
virtio: support device disconnect
drivers/pci/pci.h | 6 ++++ drivers/virtio/virtio.c | 23 +++++++++++++-- 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/pci.h | 45 ++++++++++++++++++++++++++++++ include/linux/virtio.h | 11 ++++++-- include/linux/virtio_config.h | 32 +++++++++++++++++++++ 9 files changed, 163 insertions(+), 6 deletions(-)
Signature · 1 line
MST
Michael S. Tsirkin
maintainerRFC PATCH v4 1/5 pci: report surprise removal event
At the moment, in case of a surprise removal, the regular remove callback is invoked, exclusively. This works well, because mostly, the cleanup would be the same.
However, there's a race: imagine device removal was initiated by a user action, such as driver unbind, and it in turn initiated some cleanup and is now waiting for an interrupt from the device. If the device is now surprise-removed, that never arrives and the remove callback hangs forever.
For example, this was reported for virtio-blk:
1. the graceful removal is ongoing in the remove() callback, where disk deletion del_gendisk() is ongoing, which waits for the requests +to complete,
2. Now few requests are yet to complete, and surprise removal started.
At this point, virtio block driver will not get notified by the driver core layer, because it is likely serializing remove() happening by +user/driver unload and PCI hotplug driver-initiated device removal. So vblk driver doesn't know that device is removed, block layer is waiting for requests completions to arrive which it never gets. So del_gendisk() gets stuck.
Drivers can artificially add timeouts to handle that, but it can be flaky.
Instead, let's add a way for the driver to be notified about the disconnect. It can then do any necessary cleanup, knowing that the device is inactive.
Since cleanups can take a long time, this takes an approach of a work struct that the driver initiates and enables on probe, and tears down on remove.
--- drivers/pci/pci.h | 6 ++++++ include/linux/pci.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+)
drivers/pci/pci.h +6 -0
@@ -549,6 +549,12 @@ static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused)pci_dev_set_io_state (dev, pci_channel_io_perm_failure);pci_doe_disconnected (dev); +if (READ_ONCE (dev->disconnect_work_enable)) { +/* Make sure work is up to date. */ +smp_rmb (); +schedule_work (&dev->disconnect_work); + } +return 0 ; }
include/linux/pci.h +45 -0
57 lines not on this page · raw message
Signature · 1 line
MST
Michael S. Tsirkin
maintainerRFC PATCH v4 2/5 virtio: fix comments, readability
In reply to Michael S. TsirkinFix a couple of comments to match reality. Initialize config_driver_disabled to be consistent with other fields (note: the structure is already zero initialized, so this is not a bugfix as such).
--- drivers/virtio/virtio.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/virtio/virtio.c +3 -2
@@ -147,7 +147,7 @@ EXPORT_SYMBOL_GPL(virtio_config_changed);/** * virtio_config_driver_disable - disable config change reporting by drivers -* @dev: the device to reset +* @dev: the device to disable * * This is only allowed to be called by a driver and disabling can't * be nested.
@@ -162,7 +162,7 @@ EXPORT_SYMBOL_GPL(virtio_config_driver_disable);/** * virtio_config_driver_enable - enable config change reporting by drivers -* @dev: the device to reset +* @dev: the device to enable * * This is only allowed to be called by a driver and enabling can't * be nested.
@@ -530,6 +530,7 @@ int register_virtio_device(struct virtio_device *dev) goto out_ida_remove;spin_lock_init (&dev->config_lock); + dev->config_driver_disabled = false; dev->config_core_enabled = false; dev->config_change_pending = false;
Signature · 1 line
MST
Re: [PATCH RFC v4 2/5] virtio: fix comments, readability
Quoted 7 lines · Michael S. Tsirkin
Fix a couple of comments to match reality. Initialize
config_driver_disabledto be consistent with other fields (note: the structure is already zero initialized, so this is not a bugfix as such).Signed-off-by Michael S. Tsirkin <[email protected]>
Thanks
Quoted 37 lines
--- drivers/virtio/virtio.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)drivers/virtio/virtio.c +3
-3@@ -147,7 +147,7 @@ EXPORT_SYMBOL_GPL(virtio_config_changed);/** * virtio_config_driver_disable - disable config change reporting by drivers -* @dev: the device to reset +* @dev: the device to disable * * This is only allowed to be called by a driver and disabling can't * be nested. @@ -162,7 +162,7 @@ EXPORT_SYMBOL_GPL(virtio_config_driver_disable);/** * virtio_config_driver_enable - enable config change reporting by drivers -* @dev: the device to reset +* @dev: the device to enable * * This is only allowed to be called by a driver and enabling can't * be nested. @@ -530,6 +530,7 @@ int register_virtio_device(struct virtio_device *dev) goto out_ida_remove;spin_lock_init (&dev->config_lock); + dev->config_driver_disabled = false; dev->config_core_enabled = false; dev->config_change_pending = false; --MST
Michael S. Tsirkin
maintainerRFC PATCH v4 3/5 virtio: pack config changed flags
In reply to Michael S. TsirkinIn anticipation of adding more, use bit-fields instead of bool.
--- include/linux/virtio.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
include/linux/virtio.h +3 -3
@@ -149,9 +149,9 @@ struct virtio_admin_cmd {struct virtio_device {int index;bool failed; -bool config_core_enabled; -bool config_driver_disabled; -bool config_change_pending; +u8 config_core_enabled:1 ; +u8 config_driver_disabled:1 ; +u8 config_change_pending:1 ;spinlock_t config_lock;spinlock_t vqs_list_lock;struct device dev;
Signature · 1 line
MST
Michael S. Tsirkin
maintainerRFC PATCH v4 4/5 virtio: allow transports to suppress config change
In reply to Michael S. TsirkinWill be used on surprise removal, so we don't need to re-check.
--- drivers/virtio/virtio.c | 18 +++++++++++++++++- include/linux/virtio.h | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-)
drivers/virtio/virtio.c +17 -1
@@ -127,7 +127,8 @@ static void __virtio_config_changed(struct virtio_device *dev) {struct virtio_driver *drv =drv_to_virtio (dev->dev.driver); -if (!dev->config_core_enabled || dev->config_driver_disabled) +if (!dev->config_core_enabled || dev->config_driver_disabled || + dev->config_transport_disabled) dev->config_change_pending = true;else if (drv && drv->config_changed) { drv->config_changed (dev);
@@ -193,6 +194,20 @@ static void virtio_config_core_enable(struct virtio_device *dev)spin_unlock_irq (&dev->config_lock); } +/** +* virtio_config_transport_disable - disable config change reporting by transport +* @dev: the device in question +* +* This must only be called by transport and enabling is not allowed. +*/ +void virtio_config_transport_disable (struct virtio_device *dev) +{ +spin_lock_irq (&dev->config_lock); + dev->config_transport_disabled = true; +spin_unlock_irq (&dev->config_lock); +} +EXPORT_SYMBOL_GPL (virtio_config_transport_disable); +void virtio_add_status (struct virtio_device *dev,unsigned int status) {might_sleep ();
@@ -530,6 +545,7 @@ int register_virtio_device(struct virtio_device *dev) goto out_ida_remove;spin_lock_init (&dev->config_lock); + dev->config_transport_disabled = false; dev->config_driver_disabled = false; dev->config_core_enabled = false; dev->config_change_pending = false;
include/linux/virtio.h +2 -0
@@ -151,6 +151,7 @@ struct virtio_device {bool failed; u8 config_core_enabled:1; u8 config_driver_disabled:1; + u8 config_transport_disabled:1; u8 config_change_pending:1;spinlock_t config_lock;spinlock_t vqs_list_lock;
@@ -185,6 +186,7 @@ void virtio_config_changed(struct virtio_device *dev);void virtio_config_driver_disable (struct virtio_device *dev);void virtio_config_driver_enable (struct virtio_device *dev); +void virtio_config_transport_disable (struct virtio_device *dev);#ifdef CONFIG_PM_SLEEP int virtio_device_freeze (struct virtio_device *dev);int virtio_device_restore (struct virtio_device *dev);
Signature · 1 line
MST
Michael S. Tsirkin
maintainerRFC PATCH v4 5/5 virtio: support device disconnect
In reply to Michael S. TsirkinThis 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.
--- 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(+)
drivers/virtio/virtio_pci_common.c +45 -0
@@ -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) {
drivers/virtio/virtio_pci_common.h +3 -0
@@ -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 \
drivers/virtio/virtio_pci_legacy.c +2 -0
@@ -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,
drivers/virtio/virtio_pci_modern.c +2 -0
@@ -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,
include/linux/virtio.h +3 -0
@@ -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);
include/linux/virtio_config.h +32 -0
@@ -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) {
Signature · 1 line
MST