[PATCH] PCI: Try to find two continuous regions for child resource

Kai-Heng Feng kai.heng.feng at canonical.com
Mon Mar 29 19:47:59 AEDT 2021


Built-in grahpics on HP EliteDesk 805 G6 doesn't work because graphics
can't get the BAR it needs:
[    0.611504] pci_bus 0000:00: root bus resource [mem 0x10020200000-0x100303fffff window]
[    0.611505] pci_bus 0000:00: root bus resource [mem 0x10030400000-0x100401fffff window]
...
[    0.638083] pci 0000:00:08.1:   bridge window [mem 0xd2000000-0xd23fffff]
[    0.638086] pci 0000:00:08.1:   bridge window [mem 0x10030000000-0x100401fffff 64bit pref]
[    0.962086] pci 0000:00:08.1: can't claim BAR 15 [mem 0x10030000000-0x100401fffff 64bit pref]: no compatible bridge window
[    0.962086] pci 0000:00:08.1: [mem 0x10030000000-0x100401fffff 64bit pref] clipped to [mem 0x10030000000-0x100303fffff 64bit pref]
[    0.962086] pci 0000:00:08.1:   bridge window [mem 0x10030000000-0x100303fffff 64bit pref]
[    0.962086] pci 0000:07:00.0: can't claim BAR 0 [mem 0x10030000000-0x1003fffffff 64bit pref]: no compatible bridge window
[    0.962086] pci 0000:07:00.0: can't claim BAR 2 [mem 0x10040000000-0x100401fffff 64bit pref]: no compatible bridge window

However, the root bus has two continuous regions that can contain the
child resource requested.

So try to find another parent region if two regions are continuous and
can contain child resource. This change makes the grahpics works on the
system in question.

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=212013
Signed-off-by: Kai-Heng Feng <kai.heng.feng at canonical.com>
---
 arch/microblaze/pci/pci-common.c |  4 +--
 arch/powerpc/kernel/pci-common.c |  8 ++---
 arch/sparc/kernel/pci.c          |  4 +--
 drivers/pci/pci.c                | 60 +++++++++++++++++++++++++++-----
 drivers/pci/setup-res.c          | 21 +++++++----
 drivers/pcmcia/rsrc_nonstatic.c  |  4 +--
 include/linux/pci.h              |  6 ++--
 7 files changed, 80 insertions(+), 27 deletions(-)

diff --git a/arch/microblaze/pci/pci-common.c b/arch/microblaze/pci/pci-common.c
index 557585f1be41..8e65832fb510 100644
--- a/arch/microblaze/pci/pci-common.c
+++ b/arch/microblaze/pci/pci-common.c
@@ -669,7 +669,7 @@ static void pcibios_allocate_bus_resources(struct pci_bus *bus)
 {
 	struct pci_bus *b;
 	int i;
-	struct resource *res, *pr;
+	struct resource *res, *pr = NULL;
 
 	pr_debug("PCI: Allocating bus resources for %04x:%02x...\n",
 		 pci_domain_nr(bus), bus->number);
@@ -688,7 +688,7 @@ static void pcibios_allocate_bus_resources(struct pci_bus *bus)
 			 * and as such ensure proper re-allocation
 			 * later.
 			 */
-			pr = pci_find_parent_resource(bus->self, res);
+			pci_find_parent_resource(bus->self, res, &pr, NULL);
 			if (pr == res) {
 				/* this happens when the generic PCI
 				 * code (wrongly) decides that this
diff --git a/arch/powerpc/kernel/pci-common.c b/arch/powerpc/kernel/pci-common.c
index 001e90cd8948..f865354b746d 100644
--- a/arch/powerpc/kernel/pci-common.c
+++ b/arch/powerpc/kernel/pci-common.c
@@ -1196,7 +1196,7 @@ static void pcibios_allocate_bus_resources(struct pci_bus *bus)
 {
 	struct pci_bus *b;
 	int i;
-	struct resource *res, *pr;
+	struct resource *res, *pr = NULL;
 
 	pr_debug("PCI: Allocating bus resources for %04x:%02x...\n",
 		 pci_domain_nr(bus), bus->number);
@@ -1213,7 +1213,7 @@ static void pcibios_allocate_bus_resources(struct pci_bus *bus)
 			pr = (res->flags & IORESOURCE_IO) ?
 				&ioport_resource : &iomem_resource;
 		else {
-			pr = pci_find_parent_resource(bus->self, res);
+			pci_find_parent_resource(bus->self, res, &pr, NULL);
 			if (pr == res) {
 				/* this happens when the generic PCI
 				 * code (wrongly) decides that this
@@ -1265,12 +1265,12 @@ static void pcibios_allocate_bus_resources(struct pci_bus *bus)
 
 static inline void alloc_resource(struct pci_dev *dev, int idx)
 {
-	struct resource *pr, *r = &dev->resource[idx];
+	struct resource *pr = NULL, *r = &dev->resource[idx];
 
 	pr_debug("PCI: Allocating %s: Resource %d: %pR\n",
 		 pci_name(dev), idx, r);
 
-	pr = pci_find_parent_resource(dev, r);
+	pci_find_parent_resource(dev, r, &pr, NULL);
 	if (!pr || (pr->flags & IORESOURCE_UNSET) ||
 	    request_resource(pr, r) < 0) {
 		printk(KERN_WARNING "PCI: Cannot allocate resource region %d"
diff --git a/arch/sparc/kernel/pci.c b/arch/sparc/kernel/pci.c
index 9c2b720bfd20..b4006798e4e1 100644
--- a/arch/sparc/kernel/pci.c
+++ b/arch/sparc/kernel/pci.c
@@ -621,7 +621,7 @@ static void pci_bus_register_of_sysfs(struct pci_bus *bus)
 static void pci_claim_legacy_resources(struct pci_dev *dev)
 {
 	struct pci_bus_region region;
-	struct resource *p, *root, *conflict;
+	struct resource *p, *root = NULL, *conflict;
 
 	if ((dev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
 		return;
@@ -637,7 +637,7 @@ static void pci_claim_legacy_resources(struct pci_dev *dev)
 	region.end = region.start + 0x1ffffUL;
 	pcibios_bus_to_resource(dev->bus, p, &region);
 
-	root = pci_find_parent_resource(dev, p);
+	pci_find_parent_resource(dev, p, &root, NULL);
 	if (!root) {
 		pci_info(dev, "can't claim VGA legacy %pR: no compatible bridge window\n", p);
 		goto err;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 16a17215f633..abbcd2dcdc02 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -693,20 +693,25 @@ u8 pci_find_ht_capability(struct pci_dev *dev, int ht_cap)
 EXPORT_SYMBOL_GPL(pci_find_ht_capability);
 
 /**
- * pci_find_parent_resource - return resource region of parent bus of given
+ * pci_find_parent_resource - find resource region of parent bus of given
  *			      region
  * @dev: PCI device structure contains resources to be searched
  * @res: child resource record for which parent is sought
+ * @first: the first region that contains the child resource
+ * @second: the second region that combines with the first region to fully
+ * contains the child resource
  *
  * For given resource region of given device, return the resource region of
  * parent bus the given region is contained in.
  */
-struct resource *pci_find_parent_resource(const struct pci_dev *dev,
-					  struct resource *res)
+void pci_find_parent_resource(const struct pci_dev *dev,
+					  struct resource *res,
+					  struct resource **first,
+					  struct resource **second)
 {
 	const struct pci_bus *bus = dev->bus;
 	struct resource *r;
-	int i;
+	int i, overlaps = 0;
 
 	pci_bus_for_each_resource(bus, r, i) {
 		if (!r)
@@ -718,8 +723,10 @@ struct resource *pci_find_parent_resource(const struct pci_dev *dev,
 			 * not, the allocator made a mistake.
 			 */
 			if (r->flags & IORESOURCE_PREFETCH &&
-			    !(res->flags & IORESOURCE_PREFETCH))
-				return NULL;
+			    !(res->flags & IORESOURCE_PREFETCH)) {
+				*first = NULL;
+				return;
+			}
 
 			/*
 			 * If we're below a transparent bridge, there may
@@ -729,10 +736,47 @@ struct resource *pci_find_parent_resource(const struct pci_dev *dev,
 			 * on pci_bus_for_each_resource() giving us those
 			 * first.
 			 */
-			return r;
+			*first = r;
+			return;
 		}
 	}
-	return NULL;
+
+	if (!second)
+		return;
+
+	pci_bus_for_each_resource(bus, r, i) {
+		if (!r)
+			continue;
+		if (resource_overlaps(r, res)) {
+			if (r->flags & IORESOURCE_PREFETCH &&
+			    !(res->flags & IORESOURCE_PREFETCH))
+				continue;
+
+			if (!overlaps++)
+				*first = r;
+			else {
+				*second = r;
+				break;
+			}
+		}
+	}
+
+	if (overlaps != 2)
+		goto out;
+
+	if ((*first)->start > (*second)->start)
+		swap(*first, *second);
+
+	if ((*first)->end + 1 != (*second)->start)
+		goto out;
+
+	if ((*first)->start <= res->start && (*second)->end >= res->end)
+		return;
+out:
+
+	*first = NULL;
+	*second = NULL;
+	return;
 }
 EXPORT_SYMBOL(pci_find_parent_resource);
 
diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c
index 7f1acb3918d0..e39615321d81 100644
--- a/drivers/pci/setup-res.c
+++ b/drivers/pci/setup-res.c
@@ -131,7 +131,7 @@ void pci_update_resource(struct pci_dev *dev, int resno)
 int pci_claim_resource(struct pci_dev *dev, int resource)
 {
 	struct resource *res = &dev->resource[resource];
-	struct resource *root, *conflict;
+	struct resource *first = NULL, *second = NULL, *conflict;
 
 	if (res->flags & IORESOURCE_UNSET) {
 		pci_info(dev, "can't claim BAR %d %pR: no address assigned\n",
@@ -147,21 +147,28 @@ int pci_claim_resource(struct pci_dev *dev, int resource)
 	if (res->flags & IORESOURCE_ROM_SHADOW)
 		return 0;
 
-	root = pci_find_parent_resource(dev, res);
-	if (!root) {
+	pci_find_parent_resource(dev, res, &first, &second);
+	if (!first) {
 		pci_info(dev, "can't claim BAR %d %pR: no compatible bridge window\n",
 			 resource, res);
 		res->flags |= IORESOURCE_UNSET;
 		return -EINVAL;
 	}
 
-	conflict = request_resource_conflict(root, res);
+	if (second)
+		first->end = second->end;
+
+	conflict = request_resource_conflict(first, res);
 	if (conflict) {
+		if (second)
+			first->end = second->start - 1;
+
 		pci_info(dev, "can't claim BAR %d %pR: address conflict with %s %pR\n",
 			 resource, res, conflict->name, conflict);
 		res->flags |= IORESOURCE_UNSET;
 		return -EBUSY;
-	}
+	} else if (second)
+		second->start = second->end = 0;
 
 	return 0;
 }
@@ -195,7 +202,7 @@ resource_size_t __weak pcibios_retrieve_fw_addr(struct pci_dev *dev, int idx)
 static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev,
 		int resno, resource_size_t size)
 {
-	struct resource *root, *conflict;
+	struct resource *root = NULL, *conflict;
 	resource_size_t fw_addr, start, end;
 
 	fw_addr = pcibios_retrieve_fw_addr(dev, resno);
@@ -208,7 +215,7 @@ static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev,
 	res->end = res->start + size - 1;
 	res->flags &= ~IORESOURCE_UNSET;
 
-	root = pci_find_parent_resource(dev, res);
+	pci_find_parent_resource(dev, res, &root, NULL);
 	if (!root) {
 		if (res->flags & IORESOURCE_IO)
 			root = &ioport_resource;
diff --git a/drivers/pcmcia/rsrc_nonstatic.c b/drivers/pcmcia/rsrc_nonstatic.c
index 3b05760e69d6..2fba42d7486e 100644
--- a/drivers/pcmcia/rsrc_nonstatic.c
+++ b/drivers/pcmcia/rsrc_nonstatic.c
@@ -73,7 +73,7 @@ static struct resource *
 claim_region(struct pcmcia_socket *s, resource_size_t base,
 		resource_size_t size, int type, char *name)
 {
-	struct resource *res, *parent;
+	struct resource *res, *parent = NULL;
 
 	parent = type & IORESOURCE_MEM ? &iomem_resource : &ioport_resource;
 	res = pcmcia_make_resource(base, size, type | IORESOURCE_BUSY, name);
@@ -81,7 +81,7 @@ claim_region(struct pcmcia_socket *s, resource_size_t base,
 	if (res) {
 #ifdef CONFIG_PCI
 		if (s && s->cb_dev)
-			parent = pci_find_parent_resource(s->cb_dev, res);
+			pci_find_parent_resource(s->cb_dev, res, &parent, NULL);
 #endif
 		if (!parent || request_resource(parent, res)) {
 			kfree(res);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 86c799c97b77..dd1455be5247 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1049,8 +1049,10 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus);
 unsigned int pci_scan_child_bus(struct pci_bus *bus);
 void pci_bus_add_device(struct pci_dev *dev);
 void pci_read_bridge_bases(struct pci_bus *child);
-struct resource *pci_find_parent_resource(const struct pci_dev *dev,
-					  struct resource *res);
+void pci_find_parent_resource(const struct pci_dev *dev,
+					  struct resource *res,
+					  struct resource **first,
+					  struct resource **second);
 u8 pci_swizzle_interrupt_pin(const struct pci_dev *dev, u8 pin);
 int pci_get_interrupt_pin(struct pci_dev *dev, struct pci_dev **bridge);
 u8 pci_common_swizzle(struct pci_dev *dev, u8 *pinp);
-- 
2.30.2



More information about the Linuxppc-dev mailing list