PCI: Add failed link recovery for device reset events
authorMaciej W. Rozycki <macro@orcam.me.uk>
Sun, 11 Jun 2023 17:20:06 +0000 (18:20 +0100)
committerBjorn Helgaas <bhelgaas@google.com>
Tue, 20 Jun 2023 15:58:53 +0000 (10:58 -0500)
Request failed link recovery with any upstream PCIe bridge where a device
has not come back after reset within PCI_RESET_WAIT time.  Reset the
polling interval if recovery succeeded, otherwise continue as usual.

[bhelgaas: inline pcie_parent_link_retrain()]
Link: https://lore.kernel.org/r/alpine.DEB.2.21.2306111631050.64925@angie.orcam.me.uk
Signed-off-by: Maciej W. Rozycki <macro@orcam.me.uk>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/pci.c

index f599d32..64f1a87 100644 (file)
@@ -1156,7 +1156,14 @@ void pci_resume_bus(struct pci_bus *bus)
 static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout)
 {
        int delay = 1;
-       u32 id;
+       bool retrain = false;
+       struct pci_dev *bridge;
+
+       if (pci_is_pcie(dev)) {
+               bridge = pci_upstream_bridge(dev);
+               if (bridge)
+                       retrain = true;
+       }
 
        /*
         * After reset, the device should not silently discard config
@@ -1170,21 +1177,33 @@ static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout)
         * Command register instead of Vendor ID so we don't have to
         * contend with the CRS SV value.
         */
-       pci_read_config_dword(dev, PCI_COMMAND, &id);
-       while (PCI_POSSIBLE_ERROR(id)) {
+       for (;;) {
+               u32 id;
+
+               pci_read_config_dword(dev, PCI_COMMAND, &id);
+               if (!PCI_POSSIBLE_ERROR(id))
+                       break;
+
                if (delay > timeout) {
                        pci_warn(dev, "not ready %dms after %s; giving up\n",
                                 delay - 1, reset_type);
                        return -ENOTTY;
                }
 
-               if (delay > PCI_RESET_WAIT)
+               if (delay > PCI_RESET_WAIT) {
+                       if (retrain) {
+                               retrain = false;
+                               if (pcie_failed_link_retrain(bridge)) {
+                                       delay = 1;
+                                       continue;
+                               }
+                       }
                        pci_info(dev, "not ready %dms after %s; waiting\n",
                                 delay - 1, reset_type);
+               }
 
                msleep(delay);
                delay *= 2;
-               pci_read_config_dword(dev, PCI_COMMAND, &id);
        }
 
        if (delay > PCI_RESET_WAIT)