Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4723d0f2 authored by Bjorn Helgaas's avatar Bjorn Helgaas Committed by Jesse Barnes
Browse files

x86/PCI: coalesce overlapping host bridge windows

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: default avatarAnisse Astier <anisse@astier.eu>
Reported-and-tested-by: default avatarPramod Dematagoda <pmd.lotr.gandalf@gmail.com>
Signed-off-by: default avatarBjorn Helgaas <bjorn.helgaas@hp.com>
Signed-off-by: default avatarJesse Barnes <jbarnes@virtuousgeek.org>
parent ac3abf2c
Loading
Loading
Loading
Loading
+83 −20
Original line number Diff line number Diff line
@@ -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,14 +169,6 @@ 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 "
@@ -187,10 +176,83 @@ setup_resource(struct acpi_resource *acpi_res, void *data)
			 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 %pR\n", res);
					 "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
			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: