[RFC PATCH 2/5] PCI: Add pci_reset_bus_cond() for a caller-gated slot or bus reset
Alex Williamson <[email protected]>
| Newsgroups | org.kernel.vger.linux-pci,org.kernel.vger.kvm,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
pci_reset_bus() locks every device affected by a slot or bus reset, performs the reset, and unlocks the devices, all internally. A caller has no way to evaluate what the reset would actually touch. Add pci_reset_bus_cond(), which takes a callback invoked on each affected device once they are all locked. A nonzero return value from the callback aborts the reset and the value is returned to the caller. This allows, for instance, the caller to validate that SR-IOV is not enabled on any affected device under device_lock, where the value is known stable across the reset. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Alex Williamson <[email protected]> --- drivers/pci/pci.c | 97 ++++++++++++++++++++++++++++++++++++++------- include/linux/pci.h | 3 ++ 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index b40b00c0c0c9..06728137c407 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5567,9 +5567,36 @@ int pci_probe_reset_slot(struct pci_slot *slot) } EXPORT_SYMBOL_GPL(pci_probe_reset_slot); +/* Call @cb on every device a slot or bus reset affects, stopping on error. */ +static int pci_walk_reset_check(struct pci_bus *bus, struct pci_slot *slot, + int (*cb)(struct pci_dev *dev, void *data), + void *data) +{ + struct pci_dev *dev; + int rc; + + list_for_each_entry(dev, &bus->devices, bus_list) { + if (slot && (!dev->slot || dev->slot != slot)) + continue; + rc = cb(dev, data); + if (rc) + return rc; + if (dev->subordinate) { + rc = pci_walk_reset_check(dev->subordinate, + NULL, cb, data); + if (rc) + return rc; + } + } + + return 0; +} + /** * pci_try_reset_slot - Try to reset a PCI slot * @slot: PCI slot to reset + * @check: optional per-device callback that can abort the reset + * @data: opaque argument for @check * * A PCI bus may host multiple slots, each slot may support a reset mechanism * independent of other slots. For instance, some slots may support slot power @@ -5582,7 +5609,9 @@ EXPORT_SYMBOL_GPL(pci_probe_reset_slot); * * Same as above except return -EAGAIN if the slot cannot be locked */ -static int pci_try_reset_slot(struct pci_slot *slot) +static int pci_try_reset_slot(struct pci_slot *slot, + int (*check)(struct pci_dev *dev, void *data), + void *data) { int rc; @@ -5591,10 +5620,14 @@ static int pci_try_reset_slot(struct pci_slot *slot) return rc; if (pci_slot_trylock(slot)) { - pci_slot_save_and_disable_locked(slot); - might_sleep(); - rc = pci_reset_hotplug_slot(slot->hotplug, PCI_RESET_DO_RESET); - pci_slot_restore_locked(slot); + rc = check ? pci_walk_reset_check(slot->bus, slot, check, data) : 0; + if (!rc) { + pci_slot_save_and_disable_locked(slot); + might_sleep(); + rc = pci_reset_hotplug_slot(slot->hotplug, + PCI_RESET_DO_RESET); + pci_slot_restore_locked(slot); + } pci_slot_unlock(slot); } else rc = -EAGAIN; @@ -5626,10 +5659,14 @@ static int pci_bus_reset(struct pci_bus *bus, bool probe) /** * pci_try_reset_bus - Try to reset a PCI bus * @bus: top level PCI bus to reset + * @check: optional per-device callback that can abort the reset + * @data: opaque argument for @check * * Same as above except return -EAGAIN if the bus cannot be locked */ -static int pci_try_reset_bus(struct pci_bus *bus) +static int pci_try_reset_bus(struct pci_bus *bus, + int (*check)(struct pci_dev *dev, void *data), + void *data) { int rc; @@ -5638,10 +5675,13 @@ static int pci_try_reset_bus(struct pci_bus *bus) return rc; if (pci_bus_trylock(bus)) { - pci_bus_save_and_disable_locked(bus); - might_sleep(); - rc = pci_bridge_secondary_bus_reset(bus->self); - pci_bus_restore_locked(bus); + rc = check ? pci_walk_reset_check(bus, NULL, check, data) : 0; + if (!rc) { + pci_bus_save_and_disable_locked(bus); + might_sleep(); + rc = pci_bridge_secondary_bus_reset(bus->self); + pci_bus_restore_locked(bus); + } pci_bus_unlock(bus); } else rc = -EAGAIN; @@ -5680,7 +5720,7 @@ static int pci_reset_bridge(struct pci_dev *bridge, bool restore) list_for_each_entry(slot, &bus->slots, list) { if (restore) - ret = pci_try_reset_slot(slot); + ret = pci_try_reset_slot(slot, NULL, NULL); else ret = pci_slot_reset(slot, PCI_RESET_DO_RESET); @@ -5694,7 +5734,7 @@ static int pci_reset_bridge(struct pci_dev *bridge, bool restore) mutex_unlock(&pci_slot_mutex); if (restore) - return pci_try_reset_bus(bus); + return pci_try_reset_bus(bus, NULL, NULL); return pci_bus_reset(bridge->subordinate, PCI_RESET_DO_RESET); } @@ -5724,16 +5764,43 @@ int pci_probe_reset_bus(struct pci_bus *bus) } EXPORT_SYMBOL_GPL(pci_probe_reset_bus); +/** + * pci_reset_bus_cond - conditionally reset the slot or bus containing a device + * @pdev: top level PCI device to reset via slot/bus + * @check: optional callback invoked on each affected device before the reset + * @data: opaque argument passed to @check + * + * Reset the slot or bus containing @pdev. Once the entire physical bus/slot + * hierarchy is locked, @check (if not NULL) is called on each of those locked + * devices. A nonzero return aborts the reset and is returned to the caller, + * otherwise the reset proceeds. + * + * NB. @check runs with the full set of device_locks noted above held; callbacks + * must take these locking semantics into account. Use NULL to perform an + * unconditional reset. + * + * Return: 0 on success, -ENOTTY if @pdev is not resettable, -EAGAIN if the + * devices cannot be locked, or the value returned by @check. + */ +int pci_reset_bus_cond(struct pci_dev *pdev, + int (*check)(struct pci_dev *dev, void *data), + void *data) +{ + return !pci_probe_reset_slot(pdev->slot) ? + pci_try_reset_slot(pdev->slot, check, data) : + pci_try_reset_bus(pdev->bus, check, data); +} +EXPORT_SYMBOL_GPL(pci_reset_bus_cond); + /** * pci_reset_bus - Try to reset a PCI bus * @pdev: top level PCI device to reset via slot/bus * - * Same as above except return -EAGAIN if the bus cannot be locked + * Same as above without the conditional check. */ int pci_reset_bus(struct pci_dev *pdev) { - return (!pci_probe_reset_slot(pdev->slot)) ? - pci_try_reset_slot(pdev->slot) : pci_try_reset_bus(pdev->bus); + return pci_reset_bus_cond(pdev, NULL, NULL); } EXPORT_SYMBOL_GPL(pci_reset_bus); diff --git a/include/linux/pci.h b/include/linux/pci.h index 64b308b6e61c..ab41a49f4eb7 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1480,6 +1480,9 @@ int pci_try_reset_function(struct pci_dev *dev); int pci_probe_reset_slot(struct pci_slot *slot); int pci_probe_reset_bus(struct pci_bus *bus); int pci_reset_bus(struct pci_dev *dev); +int pci_reset_bus_cond(struct pci_dev *pdev, + int (*check)(struct pci_dev *dev, void *data), + void *data); void pci_reset_secondary_bus(struct pci_dev *dev); void pcibios_reset_secondary_bus(struct pci_dev *dev); void pci_update_resource(struct pci_dev *dev, int resno); -- 2.53.0