PCI: Unset resource if initial BAR value is invalid
authorKevin Hao <haokexin@gmail.com>
Sat, 25 May 2013 11:36:27 +0000 (19:36 +0800)
committerBjorn Helgaas <bhelgaas@google.com>
Sat, 25 May 2013 18:02:56 +0000 (12:02 -0600)
The initial BAR value in the following example is invalid:

  pci_bus 0000:00: root bus resource [mem 0xa0000000-0xbfffffff] (bus address [0xe0000000-0xffffffff])
  pci 0000:01:00.0: reg 10: initial BAR value: 0xa0000000
  pci 0000:01:00.0: reg 10: [mem 0xa0000000-0xa000007f 64bit]

bus_to_resource(0xa0000000) yields 0xa0000000 because there's no host
bridge window whose bus address range contains 0xa0000000.  But CPU
accesses to 0xa0000000 appear on the bus at 0xe0000000, so they will
not be claimed if the BAR contains 0xa0000000.

If we find a BAR where resource_to_bus(bus_to_resource(A)) != A, we can
work around this problem by reassigning the BAR.

[bhelgaas: changelog, comment]
Reference: https://lkml.kernel.org/r/1368536876-27307-3-git-send-email-haokexin@gmail.com
Signed-off-by: Kevin Hao <haokexin@gmail.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/probe.c

index cd7b6de9376cdbaa2548a4ced7d58ff2d00a93b3..fe5b50bd75367898d44ad04b49152bec365f102a 100644 (file)
@@ -170,7 +170,7 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type,
 {
        u32 l, sz, mask;
        u16 orig_cmd;
-       struct pci_bus_region region;
+       struct pci_bus_region region, inverted_region;
        bool bar_too_big = false, bar_disabled = false;
 
        mask = type ? PCI_ROM_ADDRESS_MASK : ~0;
@@ -266,6 +266,26 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type,
        }
 
        pcibios_bus_to_resource(dev, res, &region);
+       pcibios_resource_to_bus(dev, &inverted_region, res);
+
+       /*
+        * If "A" is a BAR value (a bus address), "bus_to_resource(A)" is
+        * the corresponding resource address (the physical address used by
+        * the CPU.  Converting that resource address back to a bus address
+        * should yield the original BAR value:
+        *
+        *     resource_to_bus(bus_to_resource(A)) == A
+        *
+        * If it doesn't, CPU accesses to "bus_to_resource(A)" will not
+        * be claimed by the device.
+        */
+       if (inverted_region.start != region.start) {
+               dev_info(&dev->dev, "reg 0x%x: initial BAR value %pa invalid; forcing reassignment\n",
+                        pos, &region.start);
+               res->flags |= IORESOURCE_UNSET;
+               res->end -= res->start;
+               res->start = 0;
+       }
 
        goto out;