[PATCH v2] PCI: Max Payload Size BIOS workaround

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is required to work around buggy BIOS revisions found on various
whitebox motherboards which do not configure mps beyond one level below
the root port.  Ensure that the max payload size of children bridges and
devices are the same as the parent bus.

Signed-off-by: Jon Mason <mason@xxxxxxxx>
---
 drivers/pci/bus.c                |   63 ++++++++++++++++++++++++++++++++++++++
 drivers/pci/hotplug/pcihp_slot.c |   45 ++-------------------------
 2 files changed, 66 insertions(+), 42 deletions(-)

diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
index 1e2ad92..7dd3d24 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -169,6 +169,59 @@ int pci_bus_add_child(struct pci_bus *bus)
 	return retval;
 }
 
+/* Program PCIE MaxPayload setting on device:
+ * ensure parent maxpayload <= device
+ */
+int pci_set_payload(struct pci_dev *dev, bool hotplug)
+{
+	int pos, ppos;
+	u16 pctl, psz;
+	u16 dctl, dsz, dcap, dmax;
+	struct pci_dev *parent;
+
+	parent = dev->bus->self;
+	if (!parent)
+		return 0;
+
+	pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
+	if (!pos)
+		return 0;
+
+	/* Read Device MaxPayload capability and setting */
+	pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, &dctl);
+	pci_read_config_word(dev, pos + PCI_EXP_DEVCAP, &dcap);
+	dsz = (dctl & PCI_EXP_DEVCTL_PAYLOAD) >> 5;
+	dmax = (dcap & PCI_EXP_DEVCAP_PAYLOAD);
+
+	/* Read Parent MaxPayload setting */
+	ppos = pci_find_capability(parent, PCI_CAP_ID_EXP);
+	if (!ppos)
+		return 0;
+	pci_read_config_word(parent, ppos + PCI_EXP_DEVCTL, &pctl);
+	psz = (pctl & PCI_EXP_DEVCTL_PAYLOAD) >> 5;
+
+	/* If parent payload > device max payload -> error
+	 * If parent payload > device payload -> set speed
+	 * If parent payload <= device payload -> do nothing
+	 */
+	if (psz > dmax)
+		return -1;
+	else if (psz > dsz) {
+		if (hotplug)
+			dev_info(&dev->dev, "Setting MaxPayload to %d\n",
+				 128 << psz);
+		else
+			dev_warn(&dev->dev, "Child MPS of %d != Parent MPS of "
+				 "%d!  Most likely caused by bad BIOS.  Setting"
+				 " Child MPS to %d\n", 128 << dsz, 128 << psz,
+				 128 << psz);
+
+		pci_write_config_word(dev, pos + PCI_EXP_DEVCTL, (psz << 5) |
+				      (dctl & ~PCI_EXP_DEVCTL_PAYLOAD));
+	}
+	return 0;
+}
+
 /**
  * pci_bus_add_devices - insert newly discovered PCI devices
  * @bus: bus to check for new devices
@@ -188,6 +241,16 @@ void pci_bus_add_devices(const struct pci_bus *bus)
 	int retval;
 
 	list_for_each_entry(dev, &bus->devices, bus_list) {
+
+		/*
+		 * This is required to work around buggy BIOS revisions found on
+		 * various whitebox motherboards which do not configure mps
+		 * beyond one level below the root port.  Ensure that the max
+		 * payload size of children bridges and devices are the same as
+		 * the parent bus.
+		 */
+		pci_set_payload(dev, false);
+
 		/* Skip already-added devices */
 		if (dev->is_added)
 			continue;
diff --git a/drivers/pci/hotplug/pcihp_slot.c b/drivers/pci/hotplug/pcihp_slot.c
index 749fdf0..0038c40 100644
--- a/drivers/pci/hotplug/pcihp_slot.c
+++ b/drivers/pci/hotplug/pcihp_slot.c
@@ -26,6 +26,8 @@
 #include <linux/pci.h>
 #include <linux/pci_hotplug.h>
 
+extern int pci_set_payload(struct pci_dev *dev, bool hotplug);
+
 static struct hpp_type0 pci_default_type0 = {
 	.revision = 1,
 	.cache_line_size = 8,
@@ -158,47 +160,6 @@ static void program_hpp_type2(struct pci_dev *dev, struct hpp_type2 *hpp)
 	 */
 }
 
-/* Program PCIE MaxPayload setting on device: ensure parent maxpayload <= device */
-static int pci_set_payload(struct pci_dev *dev)
-{
-       int pos, ppos;
-       u16 pctl, psz;
-       u16 dctl, dsz, dcap, dmax;
-       struct pci_dev *parent;
-
-       parent = dev->bus->self;
-       pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
-       if (!pos)
-               return 0;
-
-       /* Read Device MaxPayload capability and setting */
-       pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, &dctl);
-       pci_read_config_word(dev, pos + PCI_EXP_DEVCAP, &dcap);
-       dsz = (dctl & PCI_EXP_DEVCTL_PAYLOAD) >> 5;
-       dmax = (dcap & PCI_EXP_DEVCAP_PAYLOAD);
-
-       /* Read Parent MaxPayload setting */
-       ppos = pci_find_capability(parent, PCI_CAP_ID_EXP);
-       if (!ppos)
-               return 0;
-       pci_read_config_word(parent, ppos + PCI_EXP_DEVCTL, &pctl);
-       psz = (pctl &  PCI_EXP_DEVCTL_PAYLOAD) >> 5;
-
-       /* If parent payload > device max payload -> error
-        * If parent payload > device payload -> set speed
-        * If parent payload <= device payload -> do nothing
-        */
-       if (psz > dmax)
-               return -1;
-       else if (psz > dsz) {
-               dev_info(&dev->dev, "Setting MaxPayload to %d\n", 128 << psz);
-               pci_write_config_word(dev, pos + PCI_EXP_DEVCTL,
-                                     (dctl & ~PCI_EXP_DEVCTL_PAYLOAD) +
-                                     (psz << 5));
-       }
-       return 0;
-}
-
 void pci_configure_slot(struct pci_dev *dev)
 {
 	struct pci_dev *cdev;
@@ -210,7 +171,7 @@ void pci_configure_slot(struct pci_dev *dev)
 			(dev->class >> 8) == PCI_CLASS_BRIDGE_PCI)))
 		return;
 
-       ret = pci_set_payload(dev);
+       ret = pci_set_payload(dev, true);
        if (ret)
                dev_warn(&dev->dev, "could not set device max payload\n");
 
-- 
1.7.4.1

--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux