PCI: Warn if unsafe MPS settings detected

If a BIOS configures MPS incorrectly, devices may not work normally.
For example, if a bridge has MPS set larger than an endpoint below it,
the endpoint may discard packets.

To help diagnose this issue, print a warning if we find an endpoint
MPS setting different than that of the upstream bridge.

[bhelgaas: changelog, "bridge" temporary, warning text]
Reference: https://bugzilla.kernel.org/show_bug.cgi?id=60799
Reported-by: Joe Jin <joe.jin@oracle.com>
Signed-off-by: Yijing Wang <wangyijing@huawei.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Cc: Jon Mason <jdmason@kudzu.us>
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 94c5a77c..cd5c4ac 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1582,6 +1582,22 @@
 			"with pci=pcie_bus_safe.\n");
 }
 
+static void pcie_bus_detect_mps(struct pci_dev *dev)
+{
+	struct pci_dev *bridge = dev->bus->self;
+	int mps, p_mps;
+
+	if (!bridge)
+		return;
+
+	mps = pcie_get_mps(dev);
+	p_mps = pcie_get_mps(bridge);
+
+	if (mps != p_mps)
+		dev_warn(&dev->dev, "Max Payload Size %d, but upstream %s set to %d; if necessary, use \"pci=pcie_bus_safe\" and report a bug\n",
+			 mps, pci_name(bridge), p_mps);
+}
+
 static int pcie_bus_configure_set(struct pci_dev *dev, void *data)
 {
 	int mps, orig_mps;
@@ -1589,6 +1605,11 @@
 	if (!pci_is_pcie(dev))
 		return 0;
 
+	if (pcie_bus_config == PCIE_BUS_TUNE_OFF) {
+		pcie_bus_detect_mps(dev);
+		return 0;
+	}
+
 	mps = 128 << *(u8 *)data;
 	orig_mps = pcie_get_mps(dev);
 
@@ -1616,9 +1637,6 @@
 	if (!pci_is_pcie(bus->self))
 		return;
 
-	if (pcie_bus_config == PCIE_BUS_TUNE_OFF)
-		return;
-
 	/* FIXME - Peer to peer DMA is possible, though the endpoint would need
 	 * to be aware of the MPS of the destination.  To work around this,
 	 * simply force the MPS of the entire system to the smallest possible.