PCI: Check for PCIe Link downtraining
authorAlexandru Gagniuc <mr.nuke.me@gmail.com>
Mon, 6 Aug 2018 23:25:35 +0000 (18:25 -0500)
committerBjorn Helgaas <bhelgaas@google.com>
Fri, 10 Aug 2018 17:29:04 +0000 (12:29 -0500)
When both ends of a PCIe Link are capable of a higher bandwidth than is
currently in use, the Link is said to be "downtrained".  A downtrained Link
may indicate hardware or configuration problems in the system, but it's
hard to identify such Links from userspace.

Refactor pcie_print_link_status() so it continues to always print PCIe
bandwidth information, as several NIC drivers desire.

Add a new internal __pcie_print_link_status() to emit a message only when a
device's bandwidth is constrained by the fabric and call it from the PCI
core for all devices, which identifies all downtrained Links.  It also
emits messages for a few cases that are technically not downtrained, such
as a x4 device in an open-ended x1 slot.

Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
[bhelgaas: changelog, move __pcie_print_link_status() declaration to
drivers/pci/, rename pcie_check_upstream_link() to
pcie_report_downtraining()]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/pci.c
drivers/pci/pci.h
drivers/pci/probe.c

index 97acba712e4e7f7191df5fd7ae7800597dc94fd5..a84d341504a5383ff62c4138cab6cdfd960a150b 100644 (file)
@@ -5264,14 +5264,16 @@ u32 pcie_bandwidth_capable(struct pci_dev *dev, enum pci_bus_speed *speed,
 }
 
 /**
- * pcie_print_link_status - Report the PCI device's link speed and width
+ * __pcie_print_link_status - Report the PCI device's link speed and width
  * @dev: PCI device to query
+ * @verbose: Print info even when enough bandwidth is available
  *
- * Report the available bandwidth at the device.  If this is less than the
- * device is capable of, report the device's maximum possible bandwidth and
- * the upstream link that limits its performance to less than that.
+ * If the available bandwidth at the device is less than the device is
+ * capable of, report the device's maximum possible bandwidth and the
+ * upstream link that limits its performance.  If @verbose, always print
+ * the available bandwidth, even if the device isn't constrained.
  */
-void pcie_print_link_status(struct pci_dev *dev)
+void __pcie_print_link_status(struct pci_dev *dev, bool verbose)
 {
        enum pcie_link_width width, width_cap;
        enum pci_bus_speed speed, speed_cap;
@@ -5281,11 +5283,11 @@ void pcie_print_link_status(struct pci_dev *dev)
        bw_cap = pcie_bandwidth_capable(dev, &speed_cap, &width_cap);
        bw_avail = pcie_bandwidth_available(dev, &limiting_dev, &speed, &width);
 
-       if (bw_avail >= bw_cap)
+       if (bw_avail >= bw_cap && verbose)
                pci_info(dev, "%u.%03u Gb/s available PCIe bandwidth (%s x%d link)\n",
                         bw_cap / 1000, bw_cap % 1000,
                         PCIE_SPEED2STR(speed_cap), width_cap);
-       else
+       else if (bw_avail < bw_cap)
                pci_info(dev, "%u.%03u Gb/s available PCIe bandwidth, limited by %s x%d link at %s (capable of %u.%03u Gb/s with %s x%d link)\n",
                         bw_avail / 1000, bw_avail % 1000,
                         PCIE_SPEED2STR(speed), width,
@@ -5293,6 +5295,17 @@ void pcie_print_link_status(struct pci_dev *dev)
                         bw_cap / 1000, bw_cap % 1000,
                         PCIE_SPEED2STR(speed_cap), width_cap);
 }
+
+/**
+ * pcie_print_link_status - Report the PCI device's link speed and width
+ * @dev: PCI device to query
+ *
+ * Report the available bandwidth at the device.
+ */
+void pcie_print_link_status(struct pci_dev *dev)
+{
+       __pcie_print_link_status(dev, true);
+}
 EXPORT_SYMBOL(pcie_print_link_status);
 
 /**
index 70808c168fb9a56de1702b3b7e0e2d68b65eaf6a..ce880dab5bc8198078c601298d194423b9f2fc4a 100644 (file)
@@ -263,6 +263,7 @@ enum pci_bus_speed pcie_get_speed_cap(struct pci_dev *dev);
 enum pcie_link_width pcie_get_width_cap(struct pci_dev *dev);
 u32 pcie_bandwidth_capable(struct pci_dev *dev, enum pci_bus_speed *speed,
                           enum pcie_link_width *width);
+void __pcie_print_link_status(struct pci_dev *dev, bool verbose);
 
 /* Single Root I/O Virtualization */
 struct pci_sriov {
index 7c0c8ab94bcfc7924408fa5ad17ebec423f6ec8b..71412db3cbeb7d090416b5835d8d0ebbe577c424 100644 (file)
@@ -2223,6 +2223,25 @@ static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
        return dev;
 }
 
+static void pcie_report_downtraining(struct pci_dev *dev)
+{
+       if (!pci_is_pcie(dev))
+               return;
+
+       /* Look from the device up to avoid downstream ports with no devices */
+       if ((pci_pcie_type(dev) != PCI_EXP_TYPE_ENDPOINT) &&
+           (pci_pcie_type(dev) != PCI_EXP_TYPE_LEG_END) &&
+           (pci_pcie_type(dev) != PCI_EXP_TYPE_UPSTREAM))
+               return;
+
+       /* Multi-function PCIe devices share the same link/status */
+       if (PCI_FUNC(dev->devfn) != 0 || dev->is_virtfn)
+               return;
+
+       /* Print link status only if the device is constrained by the fabric */
+       __pcie_print_link_status(dev, false);
+}
+
 static void pci_init_capabilities(struct pci_dev *dev)
 {
        /* Enhanced Allocation */
@@ -2258,6 +2277,8 @@ static void pci_init_capabilities(struct pci_dev *dev)
        /* Advanced Error Reporting */
        pci_aer_init(dev);
 
+       pcie_report_downtraining(dev);
+
        if (pci_probe_reset_function(dev) == 0)
                dev->reset_fn = 1;
 }