pci: Workaround Stratus broken PCIE hierarchy Stratus systems have a hierarchy that includes a PCIE Downstream bridge connected to a PCIE Upstream bridge and a PCI Downstream bridge. The system boots with this wrong hierarchy into a crippled mode (USB doesn't work, network doesn't work ...). Avoiding the Downstream bridge check in only_one_child() causes all the bridges to be enumerated and the system to function properly. Unfortunately this hardware is currently available so we should at least keep it functional. [v2] - ddutile@xxxxxxxxxx requested drivers/pci/quirks.c code - matthew@xxxxxx requested that dmi_name_in_vendors() be called only once with a static var check - added a kernel parameter to enable scanning of all PCIE devices. [v3] - matthew@xxxxxx requested a clean up of is_broken_pcie_port( - eike-kernel@xxxxxxxxx requested a clean up of the printk level and message Signed-off-by: Prarit Bhargava <prarit@xxxxxxxxxx> diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index a0c5c5f..7af458f 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -2069,6 +2069,7 @@ bytes respectively. Such letter suffixes can also be entirely omitted. on: Turn ECRC on. realloc reallocate PCI resources if allocations done by BIOS are erroneous. + pcie_scan_all Scan all possible PCIE devices. pcie_aspm= [PCIE] Forcibly enable or disable PCIe Active State Power Management. diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 6f45a73..8c96fff 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3589,6 +3589,9 @@ static int __init pci_setup(char *str) pcie_bus_config = PCIE_BUS_PERFORMANCE; } else if (!strncmp(str, "pcie_bus_peer2peer", 18)) { pcie_bus_config = PCIE_BUS_PEER2PEER; + } else if (!strncmp(str, "pcie_scan_all", 13)) { + printk(KERN_INFO HW_ERR "PCIE: User request scan of all PCIE devices. Your PCIE hardware is broken if you require this to boot.\n"); + pcie_scan_all = 1; } else { printk(KERN_ERR "PCI: Unknown option `%s'\n", str); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index b74084e..5a07895 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -309,11 +309,16 @@ struct pci_dev_reset_methods { #ifdef CONFIG_PCI_QUIRKS extern int pci_dev_specific_reset(struct pci_dev *dev, int probe); +extern int is_broken_pcie_port(void); #else static inline int pci_dev_specific_reset(struct pci_dev *dev, int probe) { return -ENOTTY; } +static inline int is_broken_pcie_port(void) +{ + return 0; +} #endif #endif /* DRIVERS_PCI_H */ diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 04e74f4..fc88a9b 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1270,13 +1270,18 @@ static unsigned no_next_fn(struct pci_dev *dev, unsigned fn) return 0; } +int pcie_scan_all = 0; /* set via pci=pcie_scan_all */ static int only_one_child(struct pci_bus *bus) { struct pci_dev *parent = bus->self; + if (pcie_scan_all) + return 0; if (!parent || !pci_is_pcie(parent)) return 0; - if (parent->pcie_type == PCI_EXP_TYPE_ROOT_PORT || - parent->pcie_type == PCI_EXP_TYPE_DOWNSTREAM) + if (parent->pcie_type == PCI_EXP_TYPE_ROOT_PORT) + return 1; + if (parent->pcie_type == PCI_EXP_TYPE_DOWNSTREAM && + !is_broken_pcie_port()) return 1; return 0; } diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 7285145..704e66d 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3092,3 +3092,17 @@ int pci_dev_specific_reset(struct pci_dev *dev, int probe) return -ENOTTY; } + +int is_broken_pcie_port(void) +{ + /* + * Stratus/NEC ftServer systems have a broken PCIE hierarchy in which + * one upstream and one downstream port are plugged into a downstream + * port. Avoiding the downstream port check in only_one_child() results + * in a functional system. + */ + if (dmi_name_in_vendors("ftServer")) + pcie_scan_all = 1; + + return pcie_scan_all; +} diff --git a/include/linux/pci.h b/include/linux/pci.h index 337df0d..745b98e 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1647,5 +1647,7 @@ static inline void pci_release_bus_of_node(struct pci_bus *bus) { } */ struct pci_dev *pci_find_upstream_pcie_bridge(struct pci_dev *pdev); +/* set via pci=pcie_scan_all in order to enumerate all possible PCIE busses */ +extern int pcie_scan_all; #endif /* __KERNEL__ */ #endif /* LINUX_PCI_H */ -- 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