virtio: fix callback synchronization and avq cleanup on reset
Michael S. Tsirkin
maintainerv4 cover virtio: fix callback synchronization and avq cleanup on reset
Two issues with virtio device reset:
1. Karl Mehltretter reported that virtio_reset_device() promises callbacks are not in progress after reset, but only PCI transports actually synchronize callbacks - other transports leave a window where a handler already executing keeps running while the driver tears down state.
2. sashiko reported a race in virtio_pci_modern: the avq interrupt handler calls virtqueue_get_buf concurrently with virtqueue_detach_unused_buf in vp_modern_avq_cleanup, and there is no synchronize_irq between reset and cleanup.
Fix 1 by adding virtio_synchronize_cbs in the core after reset, then dropping the now-redundant per-transport sync calls. Fix 2 by moving avq cleanup from vp_reset to a modern-specific del_vqs wrapper, which runs after callbacks have been synchronized - and is where buffer teardown conceptually belongs.
Changes v3->v4:
patch 1: add Tested-by and Acked-by from Karl patch 2: was patch 3 in v3; instead of moving
vp_modern_avq_cleanup() to the common vp_del_vqs(), add a vp_modern_del_vqs() wrapper in virtio_pci_modern.c. Split out callback sync removal into a separate patch.
patch 3: was patch 2 in v3 (legacy only); now includes
modern transport too.
Changes v2->v3:
patch 1: unchanged
patch 2: split from v2 patch 2 - legacy part only patch 3: new in v3
Michael S. Tsirkin (3):
virtio: synchronize callbacks after device reset
virtio_pci_modern: move avq cleanup from reset to del_vqs
virtio_pci: drop callback sync on reset
drivers/virtio/virtio.c | 2 ++ drivers/virtio/virtio_pci_legacy.c | 2 -- drivers/virtio/virtio_pci_modern.c | 15 ++++++++------- 3 files changed, 10 insertions(+), 9 deletions(-)
Signature · 1 line
MST
Michael S. Tsirkin
maintainerPATCH v4 1/3 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().
---
Notes (changelog):
unchanged since v2
drivers/virtio/virtio.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/virtio/virtio.c +2 -0
@@ -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);
Signature · 1 line
MST
[email protected]
AI reviewRe: [PATCH v4 1/3] virtio: synchronize callbacks after device reset
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines
virtio_reset_devicesays:
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_cbstovirtio_reset_devicefixing this for all transports that correctly implementsynchronize_cbs().
Quoted 7 lines
Reported-by Karl Mehltretter <[email protected]>Fixes c46eccdaadab ("virtio: document virtio_reset_device")Tested-by Karl Mehltretter <[email protected]>Acked-by Karl Mehltretter <[email protected]>Assisted-by LLMSigned-off-by Michael S. Tsirkin <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Michael S. Tsirkin
maintainerPATCH v4 2/3 virtio_pci_modern: move avq cleanup from reset to del_vqs
In reply to Michael S. Tsirkinvp_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.
---
Notes (changelog):
v3->v4:
split patch 3: avq cleanup move is now a separate patch from callback sync removal. Callback sync removal (previously modern-only in patch 3) merged with legacy removal into a single patch.
v2->v3:
new in v3
drivers/virtio/virtio_pci_modern.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
drivers/virtio/virtio_pci_modern.c +8 -4
@@ -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,
Signature · 1 line
MST
[email protected]
AI reviewRe: [PATCH v4 2/3] virtio_pci_modern: move avq cleanup from reset to del_vqs
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Surprise removal of the PF device while a VF is executing an admin command leads to an unresolvable deadlock. --
commit 77107fdcf13b9589fc326075af29c2e63cc3478d
Author: Michael S. Tsirkin <[email protected]>
virtio_pci_modern: move avq cleanup from reset to del_vqs
This commit moves the admin virtqueue cleanup from the device reset path to the virtqueue deletion path to prevent a race condition with the interrupt handler.
Quoted 4 lines
drivers/virtio/virtio_pci_modern.c +0
-0
[ ... ]
drivers/virtio/virtio_pci_modern.c static void vp_modern_avq_cleanup(struct virtio_device *vdev) · line 369
Quoted 7 lines
drivers/virtio/virtio_pci_modern.c +3
-0@@ -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);
[Severity: High]
This isn't a bug introduced by this patch, but could this architecture still lead to an unresolvable deadlock during a surprise removal of the PF device?
If a VF driver executes an admin command, the thread blocks uninterruptibly in virtqueue_exec_admin_cmd() waiting for the command to complete.
If a surprise removal of the PF occurs while the VF is blocked, the PF driver calls virtio_pci_remove(), which then calls pci_disable_sriov() to wait for all VFs to unbind.
However, the VF unbind is blocked because the VF thread is stuck in wait_for_completion(). Because pci_disable_sriov() never returns, the PF removal never reaches unregister_virtio_device() and thus never calls this vp_modern_del_vqs() function to detach the unused buffers and unblock the VF thread.
Is there a way to abort the pending admin commands earlier in the PF surprise removal path, before it blocks on disabling SRIOV?
Quoted 3 lines
drivers/virtio/virtio_pci_modern.c +3
-0+vp_del_vqs (vdev); +} +
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Michael S. Tsirkin
maintainerPATCH v4 3/3 virtio_pci: drop callback sync on reset
In reply to Michael S. TsirkinThe virtio core now synchronizes callbacks after reset, so the PCI transports no longer need to do it themselves.
---
Notes (changelog):
v3->v4:
now includes modern transport (previously only legacy in patch 2, modern was in patch 3)
v2->v3:
split from v2 patch 2 - legacy part only
drivers/virtio/virtio_pci_legacy.c | 2 -- drivers/virtio/virtio_pci_modern.c | 3 --- 2 files changed, 5 deletions(-)
drivers/virtio/virtio_pci_legacy.c +0 -2
@@ -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)
drivers/virtio/virtio_pci_modern.c +0 -3
@@ -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)
Signature · 1 line
MST
[email protected]
AI reviewRe: [PATCH v4 3/3] virtio_pci: drop callback sync on reset
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Removal of callback synchronization from `vp_reset()` introduces a race condition during system…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Removal of callback synchronization from vp_reset() introduces a race condition during system shutdown, as virtio_device_shutdown() fails to synchronize callbacks after device reset. --
commit a9d5f056fe92ddf169045b3557dd34b627d1eb0b
Author: Michael S. Tsirkin <[email protected]>
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.
drivers/virtio/virtio_pci_legacy.c static void vp_reset(struct virtio_device *vdev) · line 102
Quoted 10 lines
drivers/virtio/virtio_pci_legacy.c +0
-2@@ -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);
[Severity: High]
Does removing this synchronization introduce a race condition during system shutdown?
During system shutdown, virtio_device_shutdown() in drivers/virtio/virtio.c calls dev->config->reset(dev) directly without calling virtio_reset_device():
drivers/virtio/virtio.c:virtio_device_shutdown() {
...
dev->config->reset(dev);
/* Missing virtio_synchronize_cbs(dev) here */
}The vp_reset() implementation flushes pending MSI-X interrupts, which can cause a configuration interrupt to fire. Because this patch removes the internal synchronization from vp_reset(), virtio_device_shutdown() returns immediately.
The configuration interrupt (vp_config_changed()) could then run concurrently on another CPU, invoking driver callbacks while the system is tearing down the IOMMU or jumping into a kexec kernel.
Quoted 8 lines
drivers/virtio/virtio_pci_legacy.c +0
-0}static u16 vp_config_vector (struct virtio_pci_device *vp_dev,u16 vector)drivers/virtio/virtio_pci_modern.c +0
-0
[ ... ]
drivers/virtio/virtio_pci_modern.c static void vp_reset(struct virtio_device *vdev) · line 568
Quoted 7 lines
drivers/virtio/virtio_pci_modern.c +0
-3@@ -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);
[Severity: High]
Does removing the synchronization here present the same issue?
This breaks the guarantee that interrupts flushed during the reset loop have finished before returning, which virtio_device_shutdown() relies upon to safely stop the device.
Quoted 3 lines
drivers/virtio/virtio_pci_modern.c +0
-0}static int vp_active_vq (struct virtqueue *vq,u16 msix_vec)
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3