From: Manish Honap <[email protected]> A CXL port ignores the Secondary Bus Reset bit in Bridge Control unless Unmask SBR is set in its CXL Port DVSEC (CXL r4.0 sec 8.1.5.2). pci_reset_bus_function() already returns -ENOTTY in that case. pci_reset_bus(), pci_probe_reset_bus() and the slot variants do not, so they report success for a reset that reached no device below the port.
vfio-pci uses these for VFIO_DEVICE_GET_PCI_HOT_RESET_INFO and VFIO_DEVICE_PCI_HOT_RESET. A user is then told a hot reset happened while a CXL device below a masked port kept its state. Return -ENOTTY from pci_probe_reset_slot() and pci_probe_reset_bus() when the bridge above the bus is a CXL port with SBR masked. pci_reset_bus() and pci_try_reset_bridge() probe before they reset, so they refuse too. Users see this in three places: - the vfio hot reset ioctls fail (vfio reports -ENODEV when neither probe succeeds), - the reset_subordinate sysfs attribute returns -ENOTTY, - vfio-pci groups a device in a hotplug slot below such a port by bus instead of by slot. Every other pci_reset_bus() caller, including the vfio bus reset at close, is refused the same way. With one device per CXL port both groupings have the same members. Leave pci_bus_error_reset() as it is. AER and host bridge error recovery call it without saving or restoring state and only map its result to recovered or disconnect. Failing it below a masked port would turn recovery of every CXL device there, memory expanders included, into a disconnect; that is a CXL error handling decision, not part of this fix. Assisted-by: LLM Signed-off-by: Manish Honap <[email protected]> --- drivers/pci/pci.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index cecf8e681e69..87fe9de81acf 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4984,6 +4984,20 @@ static bool cxl_sbr_masked(struct pci_dev *dev) return true; } +/* + * A secondary bus reset below a CXL port with SBR masked does not reach the + * devices on the bus, so a bus or slot reset there would report success for + * a reset that did not happen. The probes and the save/restore resets that + * pci_reset_bus() and reset_subordinate use refuse it; error recovery + * (pci_bus_error_reset()) keeps its existing behavior. + */ +static bool pci_bus_cxl_sbr_masked(struct pci_bus *bus) +{ + struct pci_dev *bridge = bus->self; + + return bridge && pcie_is_cxl(bridge) && cxl_sbr_masked(bridge); +} + static int pci_reset_bus_function(struct pci_dev *dev, bool probe) { struct pci_dev *bridge = pci_upstream_bridge(dev); @@ -5615,6 +5629,9 @@ static int pci_slot_reset(struct pci_slot *slot, bool probe) */ int pci_probe_reset_slot(struct pci_slot *slot) { + if (slot && pci_bus_cxl_sbr_masked(slot->bus)) + return -ENOTTY; + return pci_slot_reset(slot, PCI_RESET_PROBE); } EXPORT_SYMBOL_GPL(pci_probe_reset_slot); @@ -5638,7 +5655,7 @@ static int pci_try_reset_slot(struct pci_slot *slot) { int rc; - rc = pci_slot_reset(slot, PCI_RESET_PROBE); + rc = pci_probe_reset_slot(slot); if (rc) return rc; @@ -5685,7 +5702,7 @@ static int pci_try_reset_bus(struct pci_bus *bus) { int rc; - rc = pci_bus_reset(bus, PCI_RESET_PROBE); + rc = pci_probe_reset_bus(bus); if (rc) return rc; @@ -5726,8 +5743,13 @@ static int pci_reset_bridge(struct pci_dev *bridge, bool restore) if (list_empty(&bus->slots)) goto bus_reset; + /* + * Probe without the CXL SBR mask check: error recovery keeps its + * reset path below a masked CXL port, and pci_try_reset_slot() and + * pci_try_reset_bus() refuse the restore path there. + */ list_for_each_entry(slot, &bus->slots, list) - if (pci_probe_reset_slot(slot)) + if (pci_slot_reset(slot, PCI_RESET_PROBE)) goto bus_reset; list_for_each_entry(slot, &bus->slots, list) { @@ -5773,6 +5795,9 @@ int pci_try_reset_bridge(struct pci_dev *bridge) */ int pci_probe_reset_bus(struct pci_bus *bus) { + if (pci_bus_cxl_sbr_masked(bus)) + return -ENOTTY; + return pci_bus_reset(bus, PCI_RESET_PROBE); } EXPORT_SYMBOL_GPL(pci_probe_reset_bus); -- 2.53.0

