pci,virtio: report surprise removal event
Subsystems (3)
Michael S. Tsirkin
maintainerv5 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 v4:
fix !CONFIG_PCI, reported by Stephen
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 v5 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
@@ -553,6 +553,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
Re: [PATCH RFC v5 1/5] pci: report surprise removal event
Housekeeping: Note subject line convention. Indent with spaces in commit log. Remove spurious plus signs.
Quoted 25 lines · Michael S. Tsirkin
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 deletiondel_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. Sodel_gendisk()gets stuck.
Quoted 6 lines
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.
This relies on somebody (typically pciehp, I guess) calling pci_dev_set_disconnected() when a surprise remove happens.
Do you think it would be practical for the driver's .remove() method to recognize that the device may stop responding at any point, even if no hotplug driver is present to call pci_dev_set_disconnected()?
Waiting forever for an interrupt seems kind of vulnerable in general. Maybe "artificially adding timeouts" is alluding to *not* waiting forever for interrupts? That doesn't seem artificial to me because it's just a fact of life that devices can disappear at arbitrary times.
It seems a little fragile to me to depend on some other part of the system to notice the surprise removal and tell you about it or schedule your work function. I think it would be more robust for the driver to check directly, i.e., assume writes to the device may be lost, check for PCI_POSSIBLE_ERROR() after reads from the device, and never wait for an interrupt without a timeout.
Quoted 93 lines
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.
Signed-off-by Michael S. Tsirkin <[email protected]>--- drivers/pci/pci.h | 6 ++++++ include/linux/pci.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+)drivers/pci/pci.h +6
-0@@ -553,6 +553,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@@ -548,6 +548,10 @@ struct pci_dev {/* These methods index pci_reset_fn_methods[] */ u8 reset_methods[PCI_NUM_RESET_METHODS ];/* In priority order */ +/* Report disconnect events. 0x0 - disable, 0x1 - enable */ +u8 disconnect_work_enable; +struct work_struct disconnect_work; +#ifdef CONFIG_PCIE_TPH u16 tph_cap;/* TPH capability offset */ u8 tph_mode;/* TPH mode */ @@ -1993,6 +1997,47 @@ pci_release_mem_regions(struct pci_dev *pdev) pci_select_bars(pdev, IORESOURCE_MEM)); } +/* +* Run this first thing after getting a disconnect work, to prevent it from +* running multiple times. +* Returns: true if disconnect was enabled, proceed. false if disabled, abort. +*/ +static inline bool pci_test_and_clear_disconnect_enable (struct pci_dev *pdev) +{ +u8 enable =0x1 ; +u8 disable =0x0 ; +return try_cmpxchg (&pdev->disconnect_work_enable, &enable, disable); +} + +/* +* Caller must initialize @pdev->disconnect_work before invoking this. +* The work function must run and check pci_test_and_clear_disconnect_enable. +* Note that device can go away right after this call. +*/ +static inline void pci_set_disconnect_work (struct pci_dev *pdev) +{ +/* Make sure WQ has been initialized already */ +smp_wmb (); + +WRITE_ONCE (pdev->disconnect_work_enable,0x1 ); + +/* check the device did not go away meanwhile. */ +mb (); + +if (!pci_device_is_present (pdev)) +schedule_work (&pdev->disconnect_work); +} + +static inline void pci_clear_disconnect_work (struct pci_dev *pdev) +{ +WRITE_ONCE (pdev->disconnect_work_enable,0x0 ); + +/* Make sure to stop using work from now on. */ +smp_wmb (); + +cancel_work_sync (&pdev->disconnect_work); +} +#else /* CONFIG_PCI is not enabled */ static inline void pci_set_flags (int flags) { }Signature · 1 line
MST
Re: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 7 lines · Bjorn Helgaas
This relies on somebody (typically pciehp, I guess) calling
pci_dev_set_disconnected()when a surprise remove happens.Do you think it would be practical for the driver's .remove() method to recognize that the device may stop responding at any point, even if no hotplug driver is present to call
pci_dev_set_disconnected()?
Quoted 5 lines
Waiting forever for an interrupt seems kind of vulnerable in general. Maybe "artificially adding timeouts" is alluding to *not* waiting forever for interrupts? That doesn't seem artificial to me because it's just a fact of life that devices can disappear at arbitrary times.
I totally agree here. Every driver's .remove() should be able to guarantee forward progress some way. I put some work in blk-mq and nvme to ensure that happens for those devices at least.
That "forward progress" can come slow though, maybe minutes, so we do have opprotunisitic short cuts sprinkled about the driver. There are still gaps when waiting for interrupt driven IO that need the longer timeouts to trigger. It'd be cool if there was a mechansim to kick in quicker, but this is still an uncommon exceptional condition, right?
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 18 lines · Keith Busch
Quoted 7 lines · Bjorn Helgaas
This relies on somebody (typically pciehp, I guess) calling
pci_dev_set_disconnected()when a surprise remove happens.Do you think it would be practical for the driver's .remove() method to recognize that the device may stop responding at any point, even if no hotplug driver is present to call
pci_dev_set_disconnected()?Quoted 5 lines
Waiting forever for an interrupt seems kind of vulnerable in general. Maybe "artificially adding timeouts" is alluding to *not* waiting forever for interrupts? That doesn't seem artificial to me because it's just a fact of life that devices can disappear at arbitrary times.
I totally agree here. Every driver's .remove() should be able to guarantee forward progress some way. I put some work in blk-mq and nvme to ensure that happens for those devices at least.
Quoted 5 lines
That "forward progress" can come slow though, maybe minutes, so we do have opprotunisitic short cuts sprinkled about the driver. There are still gaps when waiting for interrupt driven IO that need the longer timeouts to trigger. It'd be cool if there was a mechansim to kick in quicker, but this is still an uncommon exceptional condition, right?
It's uncommon, yes.
Signature · 1 line
MST
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
In reply to Bjorn HelgaasQuoted 2 lines · Bjorn Helgaas
Housekeeping: Note subject line convention. Indent with spaces in commit log. Remove spurious plus signs.
Thanks!
Quoted 46 lines · Michael S. Tsirkin
Quoted 25 lines
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 deletiondel_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. Sodel_gendisk()gets stuck.Quoted 6 lines
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.
This relies on somebody (typically pciehp, I guess) calling
pci_dev_set_disconnected()when a surprise remove happens.Do you think it would be practical for the driver's .remove() method to recognize that the device may stop responding at any point, even if no hotplug driver is present to call
pci_dev_set_disconnected()?Waiting forever for an interrupt seems kind of vulnerable in general. Maybe "artificially adding timeouts" is alluding to *not* waiting forever for interrupts? That doesn't seem artificial to me because it's just a fact of life that devices can disappear at arbitrary times.
Quoted 6 lines
It seems a little fragile to me to depend on some other part of the system to notice the surprise removal and tell you about it or schedule your work function. I think it would be more robust for the driver to check directly, i.e., assume writes to the device may be lost, check for
PCI_POSSIBLE_ERROR()after reads from the device, and never wait for an interrupt without a timeout.
virtio is ... kind of special, in that users already take it for granted that having a device as long as they want to respond still does not lead to errors and data loss.
Makes it a bit harder as our timeout would have to check for presence and retry, we can't just fail as a normal hardware device does.
And there's the overhead thing - poking at the device a lot puts a high load on the host.
So I can imagine a very long timeout (minutes?), and then something like the WQ I am trying to propose here as a shortcut.
Quoted 93 lines
Quoted 93 lines
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.
Signed-off-by Michael S. Tsirkin <[email protected]>--- drivers/pci/pci.h | 6 ++++++ include/linux/pci.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+)drivers/pci/pci.h +6
-0@@ -553,6 +553,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@@ -548,6 +548,10 @@ struct pci_dev {/* These methods index pci_reset_fn_methods[] */ u8 reset_methods[PCI_NUM_RESET_METHODS ];/* In priority order */ +/* Report disconnect events. 0x0 - disable, 0x1 - enable */ +u8 disconnect_work_enable; +struct work_struct disconnect_work; +#ifdef CONFIG_PCIE_TPH u16 tph_cap;/* TPH capability offset */ u8 tph_mode;/* TPH mode */ @@ -1993,6 +1997,47 @@ pci_release_mem_regions(struct pci_dev *pdev) pci_select_bars(pdev, IORESOURCE_MEM)); } +/* +* Run this first thing after getting a disconnect work, to prevent it from +* running multiple times. +* Returns: true if disconnect was enabled, proceed. false if disabled, abort. +*/ +static inline bool pci_test_and_clear_disconnect_enable (struct pci_dev *pdev) +{ +u8 enable =0x1 ; +u8 disable =0x0 ; +return try_cmpxchg (&pdev->disconnect_work_enable, &enable, disable); +} + +/* +* Caller must initialize @pdev->disconnect_work before invoking this. +* The work function must run and check pci_test_and_clear_disconnect_enable. +* Note that device can go away right after this call. +*/ +static inline void pci_set_disconnect_work (struct pci_dev *pdev) +{ +/* Make sure WQ has been initialized already */ +smp_wmb (); + +WRITE_ONCE (pdev->disconnect_work_enable,0x1 ); + +/* check the device did not go away meanwhile. */ +mb (); + +if (!pci_device_is_present (pdev)) +schedule_work (&pdev->disconnect_work); +} + +static inline void pci_clear_disconnect_work (struct pci_dev *pdev) +{ +WRITE_ONCE (pdev->disconnect_work_enable,0x0 ); + +/* Make sure to stop using work from now on. */ +smp_wmb (); + +cancel_work_sync (&pdev->disconnect_work); +} +#else /* CONFIG_PCI is not enabled */ static inline void pci_set_flags (int flags) { }Signature · 1 line
MST
Re: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 54 lines · Michael S. Tsirkin
Quoted 46 lines · Bjorn Helgaas
Quoted 25 lines · Michael S. Tsirkin
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 deletiondel_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. Sodel_gendisk()gets stuck.Quoted 6 lines
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.
This relies on somebody (typically pciehp, I guess) calling
pci_dev_set_disconnected()when a surprise remove happens.Do you think it would be practical for the driver's .remove() method to recognize that the device may stop responding at any point, even if no hotplug driver is present to call
pci_dev_set_disconnected()?Waiting forever for an interrupt seems kind of vulnerable in general. Maybe "artificially adding timeouts" is alluding to *not* waiting forever for interrupts? That doesn't seem artificial to me because it's just a fact of life that devices can disappear at arbitrary times.
Quoted 6 lines
It seems a little fragile to me to depend on some other part of the system to notice the surprise removal and tell you about it or schedule your work function. I think it would be more robust for the driver to check directly, i.e., assume writes to the device may be lost, check for
PCI_POSSIBLE_ERROR()after reads from the device, and never wait for an interrupt without a timeout.
Quoted 7 lines
virtio is ... kind of special, in that users already take it for granted that having a device as long as they want to respond still does not lead to errors and data loss.
Makes it a bit harder as our timeout would have to check for presence and retry, we can't just fail as a normal hardware device does.
Sorry, I don't know enough about virtio to follow what you said about "having a device as long as they want to respond".
We started with a graceful remove. That must mean the user no longer needs the device.
Quoted 2 lines
And there's the overhead thing - poking at the device a lot puts a high load on the host.
Checking for PCI_POSSIBLE_ERROR() doesn't touch the device. If you did a config read already, and the result happened to be ~0, *then* we have the problem of figuring out whether the actual data from the device was ~0, or if the read failed and the Root Complex synthesized the ~0. In many cases a driver knows that ~0 is not a possible register value. Otherwise it might have to read another register that is known not to be ~0.
Bjorn
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 63 lines · Bjorn Helgaas
Quoted 54 lines · Michael S. Tsirkin
Quoted 46 lines · Bjorn Helgaas
Quoted 25 lines · Michael S. Tsirkin
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 deletiondel_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. Sodel_gendisk()gets stuck.Quoted 6 lines
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.
This relies on somebody (typically pciehp, I guess) calling
pci_dev_set_disconnected()when a surprise remove happens.Do you think it would be practical for the driver's .remove() method to recognize that the device may stop responding at any point, even if no hotplug driver is present to call
pci_dev_set_disconnected()?Waiting forever for an interrupt seems kind of vulnerable in general. Maybe "artificially adding timeouts" is alluding to *not* waiting forever for interrupts? That doesn't seem artificial to me because it's just a fact of life that devices can disappear at arbitrary times.
Quoted 6 lines
It seems a little fragile to me to depend on some other part of the system to notice the surprise removal and tell you about it or schedule your work function. I think it would be more robust for the driver to check directly, i.e., assume writes to the device may be lost, check for
PCI_POSSIBLE_ERROR()after reads from the device, and never wait for an interrupt without a timeout.Quoted 7 lines
virtio is ... kind of special, in that users already take it for granted that having a device as long as they want to respond still does not lead to errors and data loss.
Makes it a bit harder as our timeout would have to check for presence and retry, we can't just fail as a normal hardware device does.
Quoted 5 lines
Sorry, I don't know enough about virtio to follow what you said about "having a device as long as they want to respond".
We started with a graceful remove. That must mean the user no longer needs the device.
I'll try to clarify:
Indeed, the user will not submit new requests,
but users might also not know that there are some old requests being in progress of being processed by the device.
The driver, currently, waits for that processsing to be complete. Cancelling that with a reset on a timeout might be a regression, unless the timeout is very long.
Hope this makes it clear.
Quoted 11 lines
Quoted 2 lines
And there's the overhead thing - poking at the device a lot puts a high load on the host.
Checking for
PCI_POSSIBLE_ERROR()doesn't touch the device. If you did a config read already, and the result happened to be ~0, *then* we have the problem of figuring out whether the actual data from the device was ~0, or if the read failed and the Root Complex synthesized the ~0. In many cases a driver knows that ~0 is not a possible register value. Otherwise it might have to read another register that is known not to be ~0.
Quoted 1 line
Bjorn
To clarify, virtio generally is designed to operate solely by means of DMA and interrupt, completely avoiding any PCI reads. This, due to PCI reads being very expensive in virtualized scenarios.
The extra overhead I refer to is exactly initiating such a read where there would not be one in normal operation.
Signature · 1 line
MST
Re: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 71 lines · Michael S. Tsirkin
Quoted 64 lines · Bjorn Helgaas
Quoted 55 lines · Michael S. Tsirkin
Quoted 47 lines · Bjorn Helgaas
Quoted 26 lines · Michael S. Tsirkin
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 deletiondel_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. Sodel_gendisk()gets stuck.Quoted 6 lines
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.
This relies on somebody (typically pciehp, I guess) calling
pci_dev_set_disconnected()when a surprise remove happens.Do you think it would be practical for the driver's .remove() method to recognize that the device may stop responding at any point, even if no hotplug driver is present to call
pci_dev_set_disconnected()?Waiting forever for an interrupt seems kind of vulnerable in general. Maybe "artificially adding timeouts" is alluding to *not* waiting forever for interrupts? That doesn't seem artificial to me because it's just a fact of life that devices can disappear at arbitrary times.
Quoted 6 lines
It seems a little fragile to me to depend on some other part of the system to notice the surprise removal and tell you about it or schedule your work function. I think it would be more robust for the driver to check directly, i.e., assume writes to the device may be lost, check for
PCI_POSSIBLE_ERROR()after reads from the device, and never wait for an interrupt without a timeout.Quoted 7 lines
virtio is ... kind of special, in that users already take it for granted that having a device as long as they want to respond still does not lead to errors and data loss.
Makes it a bit harder as our timeout would have to check for presence and retry, we can't just fail as a normal hardware device does.
Quoted 5 lines
Sorry, I don't know enough about virtio to follow what you said about "having a device as long as they want to respond".
We started with a graceful remove. That must mean the user no longer needs the device.
Quoted 8 lines
I'll try to clarify:
Indeed, the user will not submit new requests,
but users might also not know that there are some old requests being in progress of being processed by the device.
The driver, currently, waits for that processsing to be complete. Cancelling that with a reset on a timeout might be a regression, unless the timeout is very long.
This seems like a corner case and maybe rare enough that simply making the timeout very long would be a possibility.
Quoted 11 lines
Quoted 10 lines
Quoted 2 lines
And there's the overhead thing - poking at the device a lot puts a high load on the host.
Checking for
PCI_POSSIBLE_ERROR()doesn't touch the device. If you did a config read already, and the result happened to be ~0, *then* we have the problem of figuring out whether the actual data from the device was ~0, or if the read failed and the Root Complex synthesized the ~0. In many cases a driver knows that ~0 is not a possible register value. Otherwise it might have to read another register that is known not to be ~0.
Quoted 7 lines
To clarify, virtio generally is designed to operate solely by means of DMA and interrupt, completely avoiding any PCI reads. This, due to PCI reads being very expensive in virtualized scenarios.
The extra overhead I refer to is exactly initiating such a read where there would not be one in normal operation.
Thanks, this part is very helpful. And since config accesses are very expensive in *all* environments, I expect most drivers for high-performance devices work the same way and only do config accesses during at probe time.
If that's true, it will make this more understandable if the commit log approaches it from that direction and omits virtio specifics.
Bjorn
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 81 lines · Bjorn Helgaas
Quoted 71 lines · Michael S. Tsirkin
Quoted 64 lines · Bjorn Helgaas
Quoted 55 lines · Michael S. Tsirkin
Quoted 47 lines · Bjorn Helgaas
Quoted 26 lines · Michael S. Tsirkin
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 deletiondel_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. Sodel_gendisk()gets stuck.Quoted 6 lines
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.
This relies on somebody (typically pciehp, I guess) calling
pci_dev_set_disconnected()when a surprise remove happens.Do you think it would be practical for the driver's .remove() method to recognize that the device may stop responding at any point, even if no hotplug driver is present to call
pci_dev_set_disconnected()?Waiting forever for an interrupt seems kind of vulnerable in general. Maybe "artificially adding timeouts" is alluding to *not* waiting forever for interrupts? That doesn't seem artificial to me because it's just a fact of life that devices can disappear at arbitrary times.
Quoted 6 lines
It seems a little fragile to me to depend on some other part of the system to notice the surprise removal and tell you about it or schedule your work function. I think it would be more robust for the driver to check directly, i.e., assume writes to the device may be lost, check for
PCI_POSSIBLE_ERROR()after reads from the device, and never wait for an interrupt without a timeout.Quoted 7 lines
virtio is ... kind of special, in that users already take it for granted that having a device as long as they want to respond still does not lead to errors and data loss.
Makes it a bit harder as our timeout would have to check for presence and retry, we can't just fail as a normal hardware device does.
Quoted 5 lines
Sorry, I don't know enough about virtio to follow what you said about "having a device as long as they want to respond".
We started with a graceful remove. That must mean the user no longer needs the device.
Quoted 8 lines
I'll try to clarify:
Indeed, the user will not submit new requests,
but users might also not know that there are some old requests being in progress of being processed by the device.
The driver, currently, waits for that processsing to be complete. Cancelling that with a reset on a timeout might be a regression, unless the timeout is very long.
Quoted 2 lines
This seems like a corner case and maybe rare enough that simply making the timeout very long would be a possibility.
Indeed the timeout needs to be very long, and the average would still be reasonable, but the worst case is terrible and the user can't insert a replacement card all this time. The system is perceived as flaky.
Quoted 24 lines
Quoted 11 lines
Quoted 10 lines
Quoted 2 lines
And there's the overhead thing - poking at the device a lot puts a high load on the host.
Checking for
PCI_POSSIBLE_ERROR()doesn't touch the device. If you did a config read already, and the result happened to be ~0, *then* we have the problem of figuring out whether the actual data from the device was ~0, or if the read failed and the Root Complex synthesized the ~0. In many cases a driver knows that ~0 is not a possible register value. Otherwise it might have to read another register that is known not to be ~0.Quoted 7 lines
To clarify, virtio generally is designed to operate solely by means of DMA and interrupt, completely avoiding any PCI reads. This, due to PCI reads being very expensive in virtualized scenarios.
The extra overhead I refer to is exactly initiating such a read where there would not be one in normal operation.
Thanks, this part is very helpful. And since config accesses are very expensive in *all* environments, I expect most drivers for high-performance devices work the same way and only do config accesses during at probe time.
Quoted 4 lines
If that's true, it will make this more understandable if the commit log approaches it from that direction and omits virtio specifics.
Bjorn
Will do, thanks a lot!
Signature · 1 line
MST
Re: [PATCH RFC v5 1/5] pci: report surprise removal event
In reply to Michael S. TsirkinQuoted 9 lines · Michael S. Tsirkin
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 PCI devices in a hotplug slot, user space can initiate "safe removal" by writing "0" to the hotplug slot's "power" file in sysfs.
If the PCI device is yanked from the slot while safe removal is ongoing, there is likewise no way for the driver to know that the device is suddenly gone. That's because pciehp_unconfigure_device() only calls pci_dev_set_disconnected() in the surprise removal case, not for safe removal.
The solution proposed here is thus not a complete one: It may work if user space initiated *driver* removal, but not if it initiated *safe* removal of the entire device. For virtio, that may be sufficient.
drivers/pci/pci.h static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused) · line 563
Quoted 13 lines
+++
b/drivers/pci/pci.hdrivers/pci/pci.h +6
-0@@ -553,6 +553,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 ; }
Going through all the callers of pci_dev_set_disconnected(), I suppose the (only) one you're interested in is pciehp_unconfigure_device().
The other callers are related to runtime resume, resume from system sleep and ACPI slots.
Instead of amending pci_dev_set_disconnected(), I'd prefer an approach where pciehp_unconfigure_device() first marks all devices disconnected, then wakes up some global waitqueue, e.g.:
- if (!presence)
+ if (!presence) {
pci_walk_bus(parent, pci_dev_set_disconnected, NULL);+ wake_up_all(&pci_disconnected_wq);
+ }
The benefit is that there's no delay when marking devices disconnected. (Granted, the delay is small for smp_rmb() + schedule_work().) And just having a global waitqueue is simpler and may be useful for other use cases.
So instead of adding timeouts when waiting for interrupts, drivers would be woken via the waitqueue.
But again, it's not a complete solution as it doesn't cover the "surprise removal during safe removal" case.
I also agree with Bjorn's and Keith's comments that the driver should use timeouts for robustness, but still wanted to provide additional (hopefully constructive) thoughts.
Thanks!
Lukas
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 60 lines · Lukas Wunner
Quoted 9 lines · Michael S. Tsirkin
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 PCI devices in a hotplug slot, user space can initiate "safe removal" by writing "0" to the hotplug slot's "power" file in sysfs.
If the PCI device is yanked from the slot while safe removal is ongoing, there is likewise no way for the driver to know that the device is suddenly gone. That's because
pciehp_unconfigure_device()only callspci_dev_set_disconnected()in the surprise removal case, not for safe removal.The solution proposed here is thus not a complete one: It may work if user space initiated *driver* removal, but not if it initiated *safe* removal of the entire device. For virtio, that may be sufficient.
drivers/pci/pci.h static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused) · line 563
Quoted 13 lines
+++
b/drivers/pci/pci.hdrivers/pci/pci.h +6
-0@@ -553,6 +553,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 ; }Going through all the callers of
pci_dev_set_disconnected(), I suppose the (only) one you're interested in ispciehp_unconfigure_device().The other callers are related to runtime resume, resume from system sleep and ACPI slots.
Instead of amending
pci_dev_set_disconnected(), I'd prefer an approach wherepciehp_unconfigure_device()first marks all devices disconnected, then wakes up some global waitqueue, e.g.:(quoted diff) +1
-1- if (!presence) + if (!presence) {pci_walk_bus(parent, pci_dev_set_disconnected, NULL);(quoted diff) +2
-0+ wake_up_all(&pci_disconnected_wq); + }The benefit is that there's no delay when marking devices disconnected. (Granted, the delay is small for
smp_rmb()+schedule_work().) And just having a global waitqueue is simpler and may be useful for other use cases.
Quoted 5 lines
So instead of adding timeouts when waiting for interrupts, drivers would be woken via the waitqueue.
But again, it's not a complete solution as it doesn't cover the "surprise removal during safe removal" case.
Did not realize. Will look into addressing this, thanks!
Quoted 7 lines
I also agree with Bjorn's and Keith's comments that the driver should use timeouts for robustness, but still wanted to provide additional (hopefully constructive) thoughts.
Thanks!
Lukas
I'll address these comments in the next version.
Signature · 1 line
MST
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
In reply to Lukas WunnerQuoted 20 lines · Lukas Wunner
Quoted 9 lines · Michael S. Tsirkin
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 PCI devices in a hotplug slot, user space can initiate "safe removal" by writing "0" to the hotplug slot's "power" file in sysfs.
If the PCI device is yanked from the slot while safe removal is ongoing, there is likewise no way for the driver to know that the device is suddenly gone. That's because
pciehp_unconfigure_device()only callspci_dev_set_disconnected()in the surprise removal case, not for safe removal.
Quoted 3 lines
The solution proposed here is thus not a complete one: It may work if user space initiated *driver* removal, but not if it initiated *safe* removal of the entire device. For virtio, that may be sufficient.
No, I just missed this corner case.
Quoted 36 lines
drivers/pci/pci.h static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused) · line 563
Quoted 13 lines
+++
b/drivers/pci/pci.hdrivers/pci/pci.h +6
-0@@ -553,6 +553,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 ; }Going through all the callers of
pci_dev_set_disconnected(), I suppose the (only) one you're interested in ispciehp_unconfigure_device().The other callers are related to runtime resume, resume from system sleep and ACPI slots.
Instead of amending
pci_dev_set_disconnected(), I'd prefer an approach wherepciehp_unconfigure_device()first marks all devices disconnected, then wakes up some global waitqueue, e.g.:(quoted diff) +1
-1- if (!presence) + if (!presence) {pci_walk_bus(parent, pci_dev_set_disconnected, NULL);(quoted diff) +2
-0+ wake_up_all(&pci_disconnected_wq); + }The benefit is that there's no delay when marking devices disconnected. (Granted, the delay is small for
smp_rmb()+schedule_work().) And just having a global waitqueue is simpler and may be useful for other use cases.
Quoted 8 lines
So instead of adding timeouts when waiting for interrupts, drivers would be woken via the waitqueue.
But again, it's not a complete solution as it doesn't cover the "surprise removal during safe removal" case.
I also agree with Bjorn's and Keith's comments that the driver should use timeouts for robustness,
Yes - we can consider this an optimization, as robust timeouts are by necessity minutes.
Quoted 6 lines
but still wanted to provide additional
(hopefully constructive) thoughts.Thanks!
Lukas
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
In reply to Lukas WunnerQuoted 20 lines · Lukas Wunner
Quoted 9 lines · Michael S. Tsirkin
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 PCI devices in a hotplug slot, user space can initiate "safe removal" by writing "0" to the hotplug slot's "power" file in sysfs.
If the PCI device is yanked from the slot while safe removal is ongoing, there is likewise no way for the driver to know that the device is suddenly gone. That's because
pciehp_unconfigure_device()only callspci_dev_set_disconnected()in the surprise removal case, not for safe removal.
Quoted 3 lines
The solution proposed here is thus not a complete one: It may work if user space initiated *driver* removal, but not if it initiated *safe* removal of the entire device. For virtio, that may be sufficient.
So just as an idea, something like this can work I guess? I'm yet to test this - wrote this on the go - and also I'll need to implement for other hotplug drivers, I need it at least for ACPI additonally. WDYT?
drivers/pci/hotplug/pciehp_ctrl.c +9 -0
@@ -231,6 +231,15 @@ void pciehp_handle_disable_request(struct controller *ctrl)void pciehp_handle_presence_or_link_change (struct controller *ctrl,u32 events) {int present, link_active; +/* +* Always mark downstream devices disconnected on Presence Detect Change. +* Covers device yanked during safe removal. +*/ +if (events &PCI_EXP_SLTSTA_PDC ) { +struct pci_bus *parent = ctrl->pcie->port->subordinate; +if (parent) +pci_walk_bus (parent, pci_dev_set_disconnected,NULL ); + }/* * If the slot is on and presence or link has changed, turn it off.
Re: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 21 lines · Michael S. Tsirkin
Quoted 20 lines · Lukas Wunner
Quoted 9 lines · Michael S. Tsirkin
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 PCI devices in a hotplug slot, user space can initiate "safe removal" by writing "0" to the hotplug slot's "power" file in sysfs.
If the PCI device is yanked from the slot while safe removal is ongoing, there is likewise no way for the driver to know that the device is suddenly gone. That's because
pciehp_unconfigure_device()only callspci_dev_set_disconnected()in the surprise removal case, not for safe removal.
Quoted 6 lines
Quoted 3 lines
The solution proposed here is thus not a complete one: It may work if user space initiated *driver* removal, but not if it initiated *safe* removal of the entire device. For virtio, that may be sufficient.
So just as an idea, something like this can work I guess? I'm yet to test this - wrote this on the go -
Don't bother, it won't work:
pciehp_handle_presence_or_link_change() is called from pciehp_ist(), the IRQ thread. During safe removal the IRQ thread is busy in pciehp_unconfigure_device() and waiting for the driver to unbind from devices being safe-removed.
An IRQ thread is always single-threaded. There's no second instance of the IRQ thread being run when another interrupt is signaled. Rather, the IRQ thread is re-run when it has finished.
In *theory* what would be possible is to plumb this into pciehp_isr(). That's the hardirq handler. This one will indeed be run when an interrupt comes in while the IRQ thread is running. Normally the hardirq handler would just collect the events for later consumption by the IRQ thread. The hardirq handler could *theoretically* mark devices gone while they're being safe-removed.
I'm saying "theoretically" because in reality I don't think this is a viable approach either: pciehp_ist() contains code to *ignore* link or presence changes if they were caused by a Secondary Bus Reset or Downstream Port Containment. In that case we do *not* want to mark devices disconnected because they're only *temporarily* inaccessible. This requires waiting for the SBR or DPC to conclude, which can take several seconds. We can't wait in the hardirq handler.
So this cannot be solved with the current architecture of pciehp, at least not easily or in an elegant way. Sorry!
Thanks,
Lukas
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 29 lines · Lukas Wunner
Quoted 21 lines · Michael S. Tsirkin
Quoted 20 lines · Lukas Wunner
Quoted 9 lines · Michael S. Tsirkin
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 PCI devices in a hotplug slot, user space can initiate "safe removal" by writing "0" to the hotplug slot's "power" file in sysfs.
If the PCI device is yanked from the slot while safe removal is ongoing, there is likewise no way for the driver to know that the device is suddenly gone. That's because
pciehp_unconfigure_device()only callspci_dev_set_disconnected()in the surprise removal case, not for safe removal.Quoted 6 lines
Quoted 3 lines
The solution proposed here is thus not a complete one: It may work if user space initiated *driver* removal, but not if it initiated *safe* removal of the entire device. For virtio, that may be sufficient.
So just as an idea, something like this can work I guess? I'm yet to test this - wrote this on the go -
Quoted 6 lines
Don't bother, it won't work:
pciehp_handle_presence_or_link_change()is called frompciehp_ist(), the IRQ thread. During safe removal the IRQ thread is busy inpciehp_unconfigure_device()and waiting for the driver to unbind from devices being safe-removed.
Confused. I thought safe removal happens in the userspace thread that wrote into sysfs?
Quoted 25 lines
An IRQ thread is always single-threaded. There's no second instance of the IRQ thread being run when another interrupt is signaled. Rather, the IRQ thread is re-run when it has finished.
In *theory* what would be possible is to plumb this into
pciehp_isr(). That's the hardirq handler. This one will indeed be run when an interrupt comes in while the IRQ thread is running. Normally the hardirq handler would just collect the events for later consumption by the IRQ thread. The hardirq handler could *theoretically* mark devices gone while they're being safe-removed.I'm saying "theoretically" because in reality I don't think this is a viable approach either:
pciehp_ist()contains code to *ignore* link or presence changes if they were caused by a Secondary Bus Reset or Downstream Port Containment. In that case we do *not* want to mark devices disconnected because they're only *temporarily* inaccessible. This requires waiting for the SBR or DPC to conclude, which can take several seconds. We can't wait in the hardirq handler.So this cannot be solved with the current architecture of pciehp, at least not easily or in an elegant way. Sorry!
Thanks,
Lukas
Re: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 8 lines · Michael S. Tsirkin
Quoted 4 lines · Lukas Wunner
pciehp_handle_presence_or_link_change()is called frompciehp_ist(), the IRQ thread. During safe removal the IRQ thread is busy inpciehp_unconfigure_device()and waiting for the driver to unbind from devices being safe-removed.Confused. I thought safe removal happens in the userspace thread that wrote into sysfs?
No, the userspace thread synthesizes a DISABLE_SLOT event, calls irq_wake_thread(), then waits for the IRQ thread to finish handling that event. See pciehp_sysfs_disable_slot().
Until 2018 we indeed brought down the slot in the userspace thread, but that required locking between the workqueue fed by the interrupt handler on the one hand and the userspace thread on the other hand. It was difficult to reason about the code.
We had bug reports about slots flapping the link or presence bits on slot bringdown that we could easily address by handling everything in the IRQ thread, see 3943af9d01e9. The same was reported for slot bringup and addressed by 6c35a1ac3da6.
This wouldn't have been possible with the architecture prior to 2018, at least not this easily.
Thanks,
Lukas
Michael S. Tsirkin
maintainerRe: [PATCH RFC v5 1/5] pci: report surprise removal event
Quoted 25 lines · Lukas Wunner
Quoted 8 lines · Michael S. Tsirkin
Quoted 4 lines · Lukas Wunner
pciehp_handle_presence_or_link_change()is called frompciehp_ist(), the IRQ thread. During safe removal the IRQ thread is busy inpciehp_unconfigure_device()and waiting for the driver to unbind from devices being safe-removed.Confused. I thought safe removal happens in the userspace thread that wrote into sysfs?
No, the userspace thread synthesizes a
DISABLE_SLOTevent, callsirq_wake_thread(), then waits for the IRQ thread to finish handling that event. Seepciehp_sysfs_disable_slot().Until 2018 we indeed brought down the slot in the userspace thread, but that required locking between the workqueue fed by the interrupt handler on the one hand and the userspace thread on the other hand. It was difficult to reason about the code.
We had bug reports about slots flapping the link or presence bits on slot bringdown that we could easily address by handling everything in the IRQ thread, see
3943af9d01e9. The same was reported for slot bringup and addressed by6c35a1ac3da6.
Quoted 6 lines
This wouldn't have been possible with the architecture prior to 2018, at least not this easily.
Thanks,
Lukas
Got it, thanks!
Signature · 1 line
MST
Michael S. Tsirkin
maintainerRFC PATCH v5 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
Michael S. Tsirkin
maintainerRFC PATCH v5 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 v5 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 v5 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
cover pci_hp: fix surprise removal hang during safe removal
In reply to Michael S. TsirkinThis series is based on top of MST's RFC v5 ([email protected]) and tries to address the architectural gap identified by Lukas Wunner in that thread.
Context:
MST's RFC v5 adds disconnect_work infrastructure so drivers can be notified of surprise removal. Lukas identified a race that the series cannot address: if safe removal is already in progress when the device is yanked, pciehp_ist() is blocked and cannot deliver the disconnect event.
Potential fix:
I reproduced this with a simple edu device driver (patch 1) that blocks in remove() waiting for an interrupt. Same as blk_mq_freeze_queue_wait().
deadlock in pciehp_ist() is avoided by adding a workaround in pciehp_isr() (patch 3) where a disconnect_work is scheduled and as this is a different thread it runs and dispatches the disconnect event to the driver. The driver can then unblock and complete the remove() and thus breaking the deadlock and also overcoming the problem of not waiting in pciehp_isr().This is only schduled if the PDS is set to 0 indicating that there is no card attached at this slot in pciehp_isr().
Tested using qemu:
- Hacked the edu device to raise a delayed interrupt.
- Hacked qemu to actually act like suprise removal by adding a simple monitor cmd pcie_surprise_del to remove the device and genrate PDC=1, DLLSC = 1 and PDS=0.
1. Launched qemu with the edu device:
`-device pcie-root-port,id=rp1,chassis=1,slot=1 -device edu,bus=rp1,id=edu0`
2. Ran echo 1 > /sys/bus/pci/devices/0000:01:00.0/remove to start safe removal
3. Ran pcie_surprise_del edu0 to surprise remove the device while safe removal is in progress.
4. Verified that edu_remove() and edu_disconnect() are called.
Without the fix: remove() hangs permanently.
With the fix: pciehp_isr() fires, pciehp_disconnect_work() schedules pci_dev_set_disconnected(), edu_disconnect() completes the wait, remove() proceeds.
Would this be a viable approach?
Abhin Parekadan Jose (3):
misc: add edu_srpoc surprise removal POC driver
pciehp: add disconnect_work work_struct
pciehp_hpc: workaround to not wait in pciehp_isr on surprise removal
drivers/misc/Makefile | 1 + drivers/misc/edu_srpoc.c | 180 +++++++++++++++++++++++++++++++ drivers/pci/hotplug/pciehp.h | 1 + drivers/pci/hotplug/pciehp_hpc.c | 47 ++++++++ 4 files changed, 229 insertions(+) create mode 100644 drivers/misc/edu_srpoc.c
--
2.51.1
RFC PATCH 1/3 misc: add edu_srpoc surprise removal POC driver
From: Abhin Parekadan Jose <[email protected]>
A test driver for the QEMU edu device that reproduces the surprise removal hang described in MST's RFC v5 thread.
- hacked in a reg to the edu device on qemu to raise a delayed irq
- This driver writes to that reg in remove and waits for the irq to be handled. This simulate simulating del_gendisk() blocked in blk_mq_freeze_queue_wait()
--- drivers/misc/Makefile | 1 + drivers/misc/edu_srpoc.c | 180 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 drivers/misc/edu_srpoc.c
drivers/misc/Makefile +1 -0
@@ -10,6 +10,7 @@ obj-$(CONFIG_AD525X_DPOT_I2C) += ad525x_dpot-i2c.o obj-$(CONFIG_AD525X_DPOT_SPI) += ad525x_dpot-spi.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_DUMMY_IRQ) += dummy-irq.o +obj-y += edu_srpoc.o obj-$(CONFIG_ICS932S401) += ics932s401.o obj-$(CONFIG_LKDTM) += lkdtm/ obj-$(CONFIG_TI_FPC202) += ti_fpc202.o
drivers/misc/edu_srpoc.c new file +180 -0
180 lines not on this page · raw message
Signature · 1 line
2.51.1
[email protected]
AI reviewRe: [PATCH RFC 1/3] misc: add edu_srpoc surprise removal POC driver
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] The shared IRQ handler does not correctly handle the `0xffffffff` register read value on surprise…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The shared IRQ handler does not correctly handle the 0xffffffff register read value on surprise removal, leading to spurious IRQ_HANDLED returns and false completions.
- [Medium] Missing explicit cancel_work_sync() for disconnect_work in remove() leads to a Use-After-Free of the edu structure due to a TOCTOU race.
- [Low] The driver references a non-existent disconnect_work member in struct pci_dev and calls non-existent PCI functions, causing immediate build failures. --
--- Patch [1]: [PATCH RFC 1/3] misc: add edu_srpoc surprise removal POC driver --- commit 1e01da4d29945cd7e9e2a5dc7d46075c3af6e84b
Author: Abhin Parekadan Jose <[email protected]>
misc: add edu_srpoc surprise removal POC driver
This patch adds a test driver for the QEMU edu device to reproduce a surprise removal hang. It registers a PCI driver, requests regions, and sets up interrupts and a work queue.
Quoted 5 lines
drivers/misc/edu_srpoc.c new file +0
-0
[ ... ]
drivers/misc/edu_srpoc.c line 59
Quoted 13 lines
drivers/misc/edu_srpoc.c new file +13
-0+static irqreturn_t edu_irq_handler (int irq,void *data) +{ +struct edu_dev *edu = data; +u32 status; + + status =ioread32 (edu->regs +EDU_REG_IRQ_STATUS ); +if (!status) +return IRQ_NONE ; + +iowrite32 (status, edu->regs +EDU_REG_IRQ_LOWER ); + +if (status & (EDU_FACT_IRQ |EDU_DELAY_IRQ )) + {
[Severity: High]
When the PCI device is surprise-removed, MMIO reads (like ioread32()) typically return all-ones (0xffffffff).
Since this is a shared interrupt handler, 0xffffffff will pass the !status check and the status & (EDU_FACT_IRQ | EDU_DELAY_IRQ) check.
Will this cause the handler to spuriously complete the wait and return IRQ_HANDLED, stealing interrupts from other devices sharing the line?
drivers/misc/edu_srpoc.c line 70
Quoted 11 lines
drivers/misc/edu_srpoc.c new file +11
-0+pr_info ("complete(&edu->irq_done)\n" ); +complete (&edu->irq_done); + } + +return IRQ_HANDLED ; +} + +static void edu_disconnect (struct work_struct *work) +{ + struct pci_dev *pdev = container_of(work, struct pci_dev, + disconnect_work);
[Severity: Low]
Is disconnect_work a valid member of struct pci_dev?
It appears this member does not exist in the PCI subsystem, which would cause the module to fail to compile.
drivers/misc/edu_srpoc.c line 81
Quoted 11 lines
drivers/misc/edu_srpoc.c new file +11
-0+struct edu_dev *edu =pci_get_drvdata (pdev); + +dev_info (&pdev->dev,"edu_disconnect()\n" ); +if (!pci_test_and_clear_disconnect_enable (pdev)) +return ; + +if (!edu) +return ; + +dev_info (&pdev->dev,"disconnect_work fired — unblocking remove()\n" ); +complete (&edu->irq_done);
[Severity: Medium]
If the work function executes concurrently with edu_remove(), could it read the edu pointer, pass the if (!edu) check, and get preempted before calling complete()?
Since edu_remove() does not synchronously cancel the work (e.g. using cancel_work_sync()), and edu is allocated with devm_kzalloc(), edu could be freed by devres when remove() returns.
Could this lead to a use-after-free on edu->irq_done here?
Quoted 4 lines
drivers/misc/edu_srpoc.c new file +4
-0+} + +static int edu_probe (struct pci_dev *pdev,const struct pci_device_id *id) +{
[ ... ]
drivers/misc/edu_srpoc.c line 126
Quoted 2 lines
drivers/misc/edu_srpoc.c new file +2
-0+INIT_WORK (&pdev->disconnect_work, edu_disconnect); +pci_set_disconnect_work (pdev);
[Severity: Low]
Does pci_set_disconnect_work() exist in the kernel?
Calling this undefined function will cause build errors.
Quoted 3 lines
drivers/misc/edu_srpoc.c new file +3
-0+ +dev_info (&pdev->dev,"edu_srpoc probed\n" ); +return 0 ;
[ ... ]
Quoted 5 lines
drivers/misc/edu_srpoc.c new file +5
-0+static void edu_remove (struct pci_dev *pdev) +{ +struct edu_dev *edu =pci_get_drvdata (pdev); + +dev_info (&pdev->dev,"remove(): starting factorial — blocking for IRQ\n" );
drivers/misc/edu_srpoc.c line 156
Quoted 10 lines
drivers/misc/edu_srpoc.c new file +10
-0+ +iowrite32 (EDU_STATUS_IRQFACT , edu->regs +EDU_REG_STATUS ); + +iowrite32 (600000 , edu->regs +EDU_REG_DELAYED_IRQ ); + +wait_for_completion (&edu->irq_done); + +dev_info (&pdev->dev,"remove(): unblocked, cleaning up\n" ); + +pci_clear_disconnect_work (pdev);
[Severity: Low]
Does pci_clear_disconnect_work() exist in the kernel?
Calling this undefined function will cause build errors.
Quoted 6 lines
drivers/misc/edu_srpoc.c new file +6
-0+free_irq (pci_irq_vector (pdev,0 ), edu); +pci_free_irq_vectors (pdev); +pci_iounmap (pdev, edu->regs); +pci_release_regions (pdev); +pci_disable_device (pdev); +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
RFC PATCH 2/3 pciehp: add disconnect_work work_struct
In reply to Abhin Parekadan Jose- This work_struct is used to dispatch disconnect events directly from the pciehp_isr, as we cannot wait in pciehp_isr
--- drivers/pci/hotplug/pciehp.h | 1 + 1 file changed, 1 insertion(+)
drivers/pci/hotplug/pciehp.h +1 -0
@@ -116,6 +116,7 @@ struct controller {unsigned int ist_running;int request_result;wait_queue_head_t requester; +struct work_struct disconnect_work; };/**
Signature · 1 line
2.51.1
[email protected]
AI reviewRe: [PATCH RFC 2/3] pciehp: add disconnect_work work_struct
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 3 lines
- This
work_structis used to dispatch disconnect events directly from thepciehp_isr, as we cannot wait inpciehp_isrSigned-off-by Abhin Parekadan Jose <[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=2
RFC PATCH 3/3 pciehp_hpc: workaround to not wait in pciehp_isr on surprise removal
In reply to Abhin Parekadan Jose- Add pciehp_disconnect_work() , this function is used using schedule_work from pciehp_isr allowing us to not wait in pciehp_isr()
- pciehp_disconnect_work() is scheduled only if the card is not detected (PDS bit = 0).
The PDS bit can be used to differentiate between a safe and surprise removal.
--- drivers/pci/hotplug/pciehp_hpc.c | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+)
drivers/pci/hotplug/pciehp_hpc.c +47 -0
@@ -620,6 +620,40 @@ static void pciehp_ignore_link_change(struct controller *ctrl,up_read (&ctrl->reset_lock); } +/* +* Workaround to not wait in the isr. +*/ +static void pciehp_disconnect_work (struct work_struct *work) +{ + struct controller *ctrl = container_of(work, struct controller, + disconnect_work); +struct pci_dev *pdev =ctrl_dev (ctrl); +int events; + + events =atomic_read (&ctrl->pending_events); + +/* +* Ignore Link Down/Up events caused by Downstream Port Containment +* if recovery succeeded, or caused by Secondary Bus Reset, +* suspend to D3cold, firmware update, FPGA reconfiguration, etc. +*/ +if ((events & (PCI_EXP_SLTSTA_PDC |PCI_EXP_SLTSTA_DLLSC )) && + (pci_dpc_recovered (pdev) ||pci_hp_spurious_link_change (pdev)) && + ctrl->state ==ON_STATE ) { +// Ignore the link change events and return to normal operation. +// Could also wait here if needed. +return ; + } + +struct pci_bus *bus = ctrl->pcie->port->subordinate; + +/* the card may have returned. */ +if (!bus ||pciehp_card_present (ctrl) !=0 ) +return ; + +pci_walk_bus (bus, pci_dev_set_disconnected,NULL ); +} +static irqreturn_t pciehp_isr (int irq,void *dev_id) {struct controller *ctrl = (struct controller *)dev_id;
@@ -722,6 +756,17 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)/* Save pending events for consumption by IRQ thread. */ atomic_or (events, &ctrl->pending_events); + +// presence change events +if (events & (PCI_EXP_SLTSTA_PDC |PCI_EXP_SLTSTA_DLLSC )) { +// Get PDS bit to determine if card is present or not +int present =pciehp_card_present (ctrl); +if (!present) {// for sure the card is not present +schedule_work (&ctrl->disconnect_work); +// After this we are no longer in isr and can wait. + } + } +return IRQ_WAKE_THREAD ; }
@@ -1036,6 +1081,7 @@ struct controller *pcie_init(struct pcie_device *dev)init_waitqueue_head (&ctrl->requester);init_waitqueue_head (&ctrl->queue);INIT_DELAYED_WORK (&ctrl->button_work, pciehp_queue_pushbutton_work); +INIT_WORK (&ctrl->disconnect_work, pciehp_disconnect_work);dbg_ctrl (ctrl);down_read (&pci_bus_sem);
@@ -1096,6 +1142,7 @@ struct controller *pcie_init(struct pcie_device *dev)void pciehp_release_ctrl (struct controller *ctrl) {cancel_delayed_work_sync (&ctrl->button_work); +cancel_work_sync (&ctrl->disconnect_work);kfree (ctrl); }
Signature · 1 line
2.51.1
[email protected]
AI reviewRe: [PATCH RFC 3/3] pciehp_hpc: workaround to not wait in pciehp_isr on surprise removal
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] Concurrent calls to `pci_hp_spurious_link_change()` corrupt state flags, breaking hot-plug…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Concurrent calls to pci_hp_spurious_link_change() corrupt state flags, breaking hot-plug recovery logic.
- [High] Lockless read of pending_events races with atomic_xchg, bypassing spurious link checks and causing premature disconnections.
- [Medium] Incorrect truthiness evaluation of pciehp_card_present() breaks the disconnect workaround during severe surprise removals. --
--- Patch [3]: [PATCH RFC 3/3] pciehp_hpc: workaround to not wait in pciehp_isr on surprise removal ---
Note: The format of this review has been altered to provide a free-form
summary of the findings due to automated recitation restrictions. The original
patch diff is not quoted inline.
commit df7f3f7b5143de6a32373ccd864f5ca514636a79
Author: Abhin Parekadan Jose <[email protected]>pciehp_hpc: workaround to not wait in pciehp_isr on surprise removal
This patch adds a workqueue to handle PCIe hotplug device disconnects without waiting in the interrupt service routine. It checks the presence detect state bit to differentiate between safe and surprise removals, scheduling the disconnect work if the card is no longer present.
[Severity: High]
Could the concurrent evaluation of pci_hp_spurious_link_change() in the new disconnect workqueue corrupt state flags? The function pci_hp_spurious_link_change() is stateful and clears the PCI_LINK_CHANGED bit using test_and_clear_bit(). If the asynchronous workqueue destructively consumes this flag before the primary hot-plug IRQ thread evaluates it, won't the core handler erroneously tear down the device tree instead of safely ignoring a transient link flap?
[Severity: High]
Does the lockless read of pending_events in pciehp_disconnect_work() race with the IRQ thread? The workqueue reads ctrl->pending_events locklessly. However, the threaded IRQ handler pciehp_ist() unconditionally clears these events using atomic_xchg(&ctrl->pending_events, 0). If the workqueue executes just after pciehp_ist() clears the events, it will observe a value of 0. Wouldn't this cause the workqueue to completely bypass the spurious link down safety checks and prematurely disconnect all devices on the bus?
[Severity: Medium]
Will the truthiness checks for pciehp_card_present() fail during an actual hardware surprise removal? When a device is abruptly removed, configuration space reads typically return 0xffff, causing pciehp_card_present() to return -ENODEV. Because -ENODEV is a truthy value in C, the condition (!present) in the interrupt handler will evaluate to false, meaning the disconnect work is never scheduled. Furthermore, the check inside the workqueue evaluates pciehp_card_present(ctrl) != 0, which is true for -ENODEV, causing an early return. Does this logic inadvertently disable the workaround precisely when a surprise removal happens?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Re: [PATCH RFC 0/3] pci_hp: fix surprise removal hang during safe removal
In reply to Abhin Parekadan JoseQuoted 3 lines · Abhin Parekadan Jose
This series is based on top of MST's RFC v5 ([email protected]) and tries to address the architectural gap identified by Lukas Wunner in that thread.
This is going to get lost because it's buried in the middle of a virtio thread that's been dormant for a while. Can this be pulled out along with any dependencies from Michael's series, so it's purely PCI and posted separately? Also please observe the drivers/pci commit log and subject line conventions.