A bus reset can trigger a presence detection change and result in a
suprise hotplug.  This is generally not what we want to happen when
trying to reset a device.  Disable the presence detection control on
on bridges around bus reset.

Signed-off-by: Alex Williamson <alex.william...@redhat.com>
---
 drivers/pci/pci.c |   29 ++++++++++++++++++++++++-----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 5cb5820..c1f7d77 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -3229,8 +3229,8 @@ static int pci_pm_reset(struct pci_dev *dev, int probe)
 
 static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
 {
-       u16 ctrl;
-       struct pci_dev *pdev;
+       u16 ctrl, flags, sltctl = 0;
+       struct pci_dev *pdev, *bridge;
 
        if (pci_is_root_bus(dev->bus) || dev->subordinate || !dev->bus->self)
                return -ENOTTY;
@@ -3242,15 +3242,34 @@ static int pci_parent_bus_reset(struct pci_dev *dev, 
int probe)
        if (probe)
                return 0;
 
-       pci_read_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, &ctrl);
+       bridge = dev->bus->self;
+
+       /*
+        * If the parent device supports a slot with presence detection
+        * change enabled, holding the bus in reset can trigger that and
+        * cause an unwanted surprise removal.  Disable presence detection
+        * around the bus reset.
+        */
+       pcie_capability_read_word(bridge, PCI_EXP_FLAGS, &flags);
+       if (flags & PCI_EXP_FLAGS_SLOT) {
+               pcie_capability_read_word(bridge, PCI_EXP_SLTCTL, &sltctl);
+               if (sltctl & PCI_EXP_SLTCTL_PDCE)
+                       pcie_capability_write_word(bridge, PCI_EXP_SLTCTL,
+                                               sltctl & ~PCI_EXP_SLTCTL_PDCE);
+       }
+
+       pci_read_config_word(bridge, PCI_BRIDGE_CONTROL, &ctrl);
        ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
-       pci_write_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, ctrl);
+       pci_write_config_word(bridge, PCI_BRIDGE_CONTROL, ctrl);
        msleep(100);
 
        ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
-       pci_write_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, ctrl);
+       pci_write_config_word(bridge, PCI_BRIDGE_CONTROL, ctrl);
        msleep(100);
 
+       if (sltctl & PCI_EXP_SLTCTL_PDCE)
+               pcie_capability_write_word(bridge, PCI_EXP_SLTCTL, sltctl);
+
        return 0;
 }
 

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to