diff options
-rw-r--r-- | src/device/pciexp_device.c | 161 |
1 files changed, 133 insertions, 28 deletions
diff --git a/src/device/pciexp_device.c b/src/device/pciexp_device.c index 969dbb0017..db351efd49 100644 --- a/src/device/pciexp_device.c +++ b/src/device/pciexp_device.c @@ -144,6 +144,35 @@ struct device *pcie_find_dsn(const uint64_t serial, const uint16_t vid, return from; } +static bool pcie_is_root_port(struct device *dev) +{ + unsigned int pcie_pos, pcie_type; + + pcie_pos = pci_find_capability(dev, PCI_CAP_ID_PCIE); + if (!pcie_pos) + return false; + + pcie_type = pci_read_config16(dev, pcie_pos + PCI_EXP_FLAGS) & PCI_EXP_FLAGS_TYPE; + pcie_type >>= 4; + + return (pcie_type == PCI_EXP_TYPE_ROOT_PORT); +} + +static bool pcie_is_endpoint(struct device *dev) +{ + unsigned int pcie_pos, pcie_type; + + pcie_pos = pci_find_capability(dev, PCI_CAP_ID_PCIE); + if (!pcie_pos) + return false; + + pcie_type = pci_read_config16(dev, pcie_pos + PCI_EXP_FLAGS) & PCI_EXP_FLAGS_TYPE; + pcie_type >>= 4; + + return ((pcie_type == PCI_EXP_TYPE_ENDPOINT) || (pcie_type == PCI_EXP_TYPE_LEG_END)); +} + + /* * Re-train a PCIe link */ @@ -561,26 +590,63 @@ static void pciexp_enable_aspm(struct device *root, unsigned int root_cap, printk(BIOS_INFO, "ASPM: Enabled %s\n", aspm_type_str[apmc]); } +static void pciexp_dev_set_max_payload_size(struct device *dev, unsigned int max_payload) +{ + u16 devctl; + unsigned int pcie_cap = pci_find_capability(dev, PCI_CAP_ID_PCIE); + + if (!pcie_cap) + return; + + devctl = pci_read_config16(dev, pcie_cap + PCI_EXP_DEVCTL); + devctl &= ~PCI_EXP_DEVCTL_PAYLOAD; + /* + * Should never overflow to higher bits, due to how max_payload is + * guarded in this file. + */ + devctl |= max_payload << 5; + pci_write_config16(dev, pcie_cap + PCI_EXP_DEVCTL, devctl); +} + +static unsigned int pciexp_dev_get_current_max_payload_size(struct device *dev) +{ + u16 devctl; + unsigned int pcie_cap = pci_find_capability(dev, PCI_CAP_ID_PCIE); + + if (!pcie_cap) + return 0; + + devctl = pci_read_config16(dev, pcie_cap + PCI_EXP_DEVCTL); + devctl &= PCI_EXP_DEVCTL_PAYLOAD; + return (devctl >> 5); +} + +static unsigned int pciexp_dev_get_max_payload_size_cap(struct device *dev) +{ + u16 devcap; + unsigned int pcie_cap = pci_find_capability(dev, PCI_CAP_ID_PCIE); + + if (!pcie_cap) + return 0; + + devcap = pci_read_config16(dev, pcie_cap + PCI_EXP_DEVCAP); + return (devcap & PCI_EXP_DEVCAP_PAYLOAD); +} + /* - * Set max payload size of endpoint in accordance with max payload size of root port. + * Set max payload size of a parent based on max payload size capability of the child. */ -static void pciexp_set_max_payload_size(struct device *root, unsigned int root_cap, - struct device *endp, unsigned int endp_cap) +static void pciexp_configure_max_payload_size(struct device *parent, struct device *child) { - unsigned int endp_max_payload, root_max_payload, max_payload; - u16 endp_devctl, root_devctl; - u32 endp_devcap, root_devcap; - - /* Get max payload size supported by endpoint */ - endp_devcap = pci_read_config32(endp, endp_cap + PCI_EXP_DEVCAP); - endp_max_payload = endp_devcap & PCI_EXP_DEVCAP_PAYLOAD; + unsigned int child_max_payload, parent_max_payload, max_payload; - /* Get max payload size supported by root port */ - root_devcap = pci_read_config32(root, root_cap + PCI_EXP_DEVCAP); - root_max_payload = root_devcap & PCI_EXP_DEVCAP_PAYLOAD; + /* Get max payload size supported by child */ + child_max_payload = pciexp_dev_get_current_max_payload_size(child); + /* Get max payload size configured by parent */ + parent_max_payload = pciexp_dev_get_current_max_payload_size(parent); + /* Set max payload to smaller of the reported device capability or parent config. */ + max_payload = MIN(child_max_payload, parent_max_payload); - /* Set max payload to smaller of the reported device capability. */ - max_payload = MIN(endp_max_payload, root_max_payload); if (max_payload > 5) { /* Values 6 and 7 are reserved in PCIe 3.0 specs. */ printk(BIOS_ERR, "PCIe: Max_Payload_Size field restricted from %d to 5\n", @@ -588,17 +654,11 @@ static void pciexp_set_max_payload_size(struct device *root, unsigned int root_c max_payload = 5; } - endp_devctl = pci_read_config16(endp, endp_cap + PCI_EXP_DEVCTL); - endp_devctl &= ~PCI_EXP_DEVCTL_PAYLOAD; - endp_devctl |= max_payload << 5; - pci_write_config16(endp, endp_cap + PCI_EXP_DEVCTL, endp_devctl); - - root_devctl = pci_read_config16(root, root_cap + PCI_EXP_DEVCTL); - root_devctl &= ~PCI_EXP_DEVCTL_PAYLOAD; - root_devctl |= max_payload << 5; - pci_write_config16(root, root_cap + PCI_EXP_DEVCTL, root_devctl); - - printk(BIOS_INFO, "PCIe: Max_Payload_Size adjusted to %d\n", (1 << (max_payload + 7))); + if (max_payload != parent_max_payload) { + pciexp_dev_set_max_payload_size(parent, max_payload); + printk(BIOS_INFO, "%s: Max_Payload_Size adjusted to %d\n", dev_path(parent), + (1 << (max_payload + 7))); + } } /* @@ -658,19 +718,47 @@ static void pciexp_tune_dev(struct device *dev) if (CONFIG(PCIEXP_LANE_ERR_STAT_CLEAR)) clear_lane_error_status(root); - /* Adjust Max_Payload_Size of link ends. */ - pciexp_set_max_payload_size(root, root_cap, dev, cap); + /* Set the Max Payload Size to the maximum supported capability for this device */ + if (pcie_is_endpoint(dev)) + pciexp_dev_set_max_payload_size(dev, pciexp_dev_get_max_payload_size_cap(dev)); + + /* Limit the parent's Max Payload Size if needed */ + pciexp_configure_max_payload_size(root, dev); pciexp_configure_ltr(root, root_cap, dev, cap); } +static void pciexp_sync_max_payload_size(struct bus *bus, unsigned int max_payload) +{ + struct device *child; + + /* Set the max payload for children on the bus and their children, etc. */ + for (child = bus->children; child; child = child->sibling) { + if (!is_pci(child)) + continue; + + pciexp_dev_set_max_payload_size(child, max_payload); + + if (child->downstream) + pciexp_sync_max_payload_size(child->downstream, max_payload); + } +} + void pciexp_scan_bus(struct bus *bus, unsigned int min_devfn, unsigned int max_devfn) { struct device *child; + unsigned int max_payload; pciexp_enable_ltr(bus->dev); + /* + * Set the Max Payload Size to the maximum supported capability for this bridge. + * This value will be used in pciexp_tune_dev to limit the Max Payload size if needed. + */ + max_payload = pciexp_dev_get_max_payload_size_cap(bus->dev); + pciexp_dev_set_max_payload_size(bus->dev, max_payload); + pci_scan_bus(bus, min_devfn, max_devfn); for (child = bus->children; child; child = child->sibling) { @@ -682,6 +770,23 @@ void pciexp_scan_bus(struct bus *bus, unsigned int min_devfn, } pciexp_tune_dev(child); } + + /* + * Now the root port's Max Payload Size should be set to the highest + * possible value supported by all devices under a given root port. + * Propagate that value down from root port to all devices, so the Max + * Payload Size is equal on all devices, as some devices may have + * different capabilities and the programmed value depends on the + * order of device population the in the subtree. + */ + if (pcie_is_root_port(bus->dev)) { + max_payload = pciexp_dev_get_current_max_payload_size(bus->dev); + + printk(BIOS_INFO, "%s: Setting Max_Payload_Size to %d for devices under this" + " root port\n", dev_path(bus->dev), 1 << (max_payload + 7)); + + pciexp_sync_max_payload_size(bus, max_payload); + } } void pciexp_scan_bridge(struct device *dev) |