x86/PCI: coalesce overlapping host bridge windows
authorBjorn Helgaas <bjorn.helgaas@hp.com>
Wed, 22 Sep 2010 17:09:19 +0000 (11:09 -0600)
committerJesse Barnes <jbarnes@virtuousgeek.org>
Thu, 11 Nov 2010 17:34:31 +0000 (09:34 -0800)
Some BIOSes provide PCI host bridge windows that overlap, e.g.,

    pci_root PNP0A03:00: host bridge window [mem 0xb0000000-0xffffffff]
    pci_root PNP0A03:00: host bridge window [mem 0xafffffff-0xdfffffff]
    pci_root PNP0A03:00: host bridge window [mem 0xf0000000-0xffffffff]

If we simply insert these as children of iomem_resource, the second window
fails because it conflicts with the first, and the third is inserted as a
child of the first, i.e.,

    b0000000-ffffffff PCI Bus 0000:00
      f0000000-ffffffff PCI Bus 0000:00

When we claim PCI device resources, this can cause collisions like this
if we put them in the first window:

    pci 0000:00:01.0: address space collision: [mem 0xff300000-0xff4fffff] conflicts with PCI Bus 0000:00 [mem 0xf0000000-0xffffffff]

Host bridge windows are top-level resources by definition, so it doesn't
make sense to make the third window a child of the first.  This patch
coalesces any host bridge windows that overlap.  For the example above,
the result is this single window:

    pci_root PNP0A03:00: host bridge window [mem 0xafffffff-0xffffffff]

This fixes a 2.6.34 regression.

Reference: https://bugzilla.kernel.org/show_bug.cgi?id=17011
Reported-and-tested-by: Anisse Astier <anisse@astier.eu>
Reported-and-tested-by: Pramod Dematagoda <pmd.lotr.gandalf@gmail.com>
Signed-off-by: Bjorn Helgaas <bjorn.helgaas@hp.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
arch/x86/pci/acpi.c

index 15466c0..0972315 100644 (file)
@@ -138,7 +138,6 @@ setup_resource(struct acpi_resource *acpi_res, void *data)
        struct acpi_resource_address64 addr;
        acpi_status status;
        unsigned long flags;
-       struct resource *root, *conflict;
        u64 start, end;
 
        status = resource_to_addr(acpi_res, &addr);
@@ -146,12 +145,10 @@ setup_resource(struct acpi_resource *acpi_res, void *data)
                return AE_OK;
 
        if (addr.resource_type == ACPI_MEMORY_RANGE) {
-               root = &iomem_resource;
                flags = IORESOURCE_MEM;
                if (addr.info.mem.caching == ACPI_PREFETCHABLE_MEMORY)
                        flags |= IORESOURCE_PREFETCH;
        } else if (addr.resource_type == ACPI_IO_RANGE) {
-               root = &ioport_resource;
                flags = IORESOURCE_IO;
        } else
                return AE_OK;
@@ -172,25 +169,90 @@ setup_resource(struct acpi_resource *acpi_res, void *data)
                return AE_OK;
        }
 
-       conflict = insert_resource_conflict(root, res);
-       if (conflict) {
-               dev_err(&info->bridge->dev,
-                       "address space collision: host bridge window %pR "
-                       "conflicts with %s %pR\n",
-                       res, conflict->name, conflict);
-       } else {
-               pci_bus_add_resource(info->bus, res, 0);
-               info->res_num++;
-               if (addr.translation_offset)
-                       dev_info(&info->bridge->dev, "host bridge window %pR "
-                                "(PCI address [%#llx-%#llx])\n",
-                                res, res->start - addr.translation_offset,
-                                res->end - addr.translation_offset);
+       info->res_num++;
+       if (addr.translation_offset)
+               dev_info(&info->bridge->dev, "host bridge window %pR "
+                        "(PCI address [%#llx-%#llx])\n",
+                        res, res->start - addr.translation_offset,
+                        res->end - addr.translation_offset);
+       else
+               dev_info(&info->bridge->dev, "host bridge window %pR\n", res);
+
+       return AE_OK;
+}
+
+static bool resource_contains(struct resource *res, resource_size_t point)
+{
+       if (res->start <= point && point <= res->end)
+               return true;
+       return false;
+}
+
+static void coalesce_windows(struct pci_root_info *info, int type)
+{
+       int i, j;
+       struct resource *res1, *res2;
+
+       for (i = 0; i < info->res_num; i++) {
+               res1 = &info->res[i];
+               if (!(res1->flags & type))
+                       continue;
+
+               for (j = i + 1; j < info->res_num; j++) {
+                       res2 = &info->res[j];
+                       if (!(res2->flags & type))
+                               continue;
+
+                       /*
+                        * I don't like throwing away windows because then
+                        * our resources no longer match the ACPI _CRS, but
+                        * the kernel resource tree doesn't allow overlaps.
+                        */
+                       if (resource_contains(res1, res2->start) ||
+                           resource_contains(res1, res2->end) ||
+                           resource_contains(res2, res1->start) ||
+                           resource_contains(res2, res1->end)) {
+                               res1->start = min(res1->start, res2->start);
+                               res1->end = max(res1->end, res2->end);
+                               dev_info(&info->bridge->dev,
+                                        "host bridge window expanded to %pR; %pR ignored\n",
+                                        res1, res2);
+                               res2->flags = 0;
+                       }
+               }
+       }
+}
+
+static void add_resources(struct pci_root_info *info)
+{
+       int i;
+       struct resource *res, *root, *conflict;
+
+       if (!pci_use_crs)
+               return;
+
+       coalesce_windows(info, IORESOURCE_MEM);
+       coalesce_windows(info, IORESOURCE_IO);
+
+       for (i = 0; i < info->res_num; i++) {
+               res = &info->res[i];
+
+               if (res->flags & IORESOURCE_MEM)
+                       root = &iomem_resource;
+               else if (res->flags & IORESOURCE_IO)
+                       root = &ioport_resource;
                else
-                       dev_info(&info->bridge->dev,
-                                "host bridge window %pR\n", res);
+                       continue;
+
+               conflict = insert_resource_conflict(root, res);
+               if (conflict)
+                       dev_err(&info->bridge->dev,
+                               "address space collision: host bridge window %pR "
+                               "conflicts with %s %pR\n",
+                               res, conflict->name, conflict);
+               else
+                       pci_bus_add_resource(info->bus, res, 0);
        }
-       return AE_OK;
 }
 
 static void
@@ -224,6 +286,7 @@ get_current_resources(struct acpi_device *device, int busnum,
        acpi_walk_resources(device->handle, METHOD_NAME__CRS, setup_resource,
                                &info);
 
+       add_resources(&info);
        return;
 
 name_alloc_fail: