aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYinghai Lu <yinghai@kernel.org>2012-09-17 22:24:30 -0700
committerYinghai Lu <yinghai@kernel.org>2012-09-17 22:24:30 -0700
commitf4f26d7257040ff14c9b27f2777b1e3096656637 (patch)
tree1ef6ae075e00e8d60c18e2f4209075a2062d50ca
parent719e863587dbc1acc601d420084059170370579e (diff)
downloadlinux-yinghai-f4f26d7257040ff14c9b27f2777b1e3096656637.tar.gz
PCI: More strict checking of valid range for bridge
Found one sick system with two sibling bridge have overlaping range from BIOS. 00:02.1 bus range is 0x30-0x30 00:02.2 bus range is 0x30-0x3f, and it have two children under it. RHEL 6.2 kernel, will just 00:02.1 have child bus 0x30, and bridge 00:02.2 will not have. before this patch, this patchset will have 00:02.1 to have bus 0x30, and 00:02.2 will have reallocate range bus 01. but 00:02.1 will have scaned at first, so later it will have two fake devices. To fix the problem, We need to check with unscaned sibling bridge about range overlapping. If there is overlapping found, will mark both sides to be broken. So we could prevent one side take too big range. Signed-off-by: Yinghai Lu <yinghai@kernel.org>
-rw-r--r--drivers/pci/probe.c45
1 files changed, 44 insertions, 1 deletions
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 0b38ed3e519622..7c8429ad46b6a3 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -923,6 +923,48 @@ static int __devinit pci_bridge_probe_busn_res(struct pci_bus *bus,
return ret;
}
+static int __devinit pci_bridge_check_busn_broken_with_unscaned(
+ struct pci_bus *bus,
+ struct pci_dev *dev,
+ int secondary, int subordinate)
+{
+ u32 buses2;
+ int broken = 0;
+ struct pci_dev *dev2;
+ int secondary2, subordinate2;
+ int common_start, common_end;
+
+ /* need to check with not scaned sibling bridges */
+ list_for_each_entry(dev2, &bus->devices, bus_list) {
+ if (dev2->hdr_type != PCI_HEADER_TYPE_BRIDGE &&
+ dev2->hdr_type != PCI_HEADER_TYPE_CARDBUS)
+ continue;
+ if (dev2->subordinate)
+ continue;
+ if (dev2 == dev)
+ continue;
+
+ pci_read_config_dword(dev2, PCI_PRIMARY_BUS, &buses2);
+ secondary2 = (buses2 >> 8) & 0xFF;
+ subordinate2 = (buses2 >> 16) & 0xFF;
+
+ /* overlapping between them ? */
+ common_start = max(secondary, secondary2);
+ common_end = min(subordinate, subordinate2);
+ if (common_start <= common_end) {
+ /*
+ * Temporarily disable forwarding of the
+ * configuration cycles on this sibling bridge
+ */
+ pci_write_config_dword(dev2, PCI_PRIMARY_BUS,
+ buses2 & ~0xffffff);
+ broken = 1;
+ }
+ }
+
+ return broken;
+}
+
static int __devinit pci_bridge_check_busn_broken(struct pci_bus *bus,
struct pci_dev *dev,
int secondary, int subordinate)
@@ -943,7 +985,8 @@ static int __devinit pci_bridge_check_busn_broken(struct pci_bus *bus,
release_resource(&busn_res);
- return 0;
+ return pci_bridge_check_busn_broken_with_unscaned(bus, dev,
+ secondary, subordinate);
}
static unsigned int __devinit __pci_scan_child_bus(struct pci_bus *bus,