mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Maciej Grochowski <Maciej.Grochowski@sony.com>
To: bhelgaas@google.com, ilpo.jarvinen@linux.intel.com
Cc: Maciej Grochowski <maciej.grochowski@sony.com>,
	linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org,
	Jonathan Corbet <corbet@lwn.net>,
	linux-doc@vger.kernel.org
Subject: [RFC PATCH v2 2/2] PCI: Allow hpbussize for specific hotplug bridges
Date: Tue,  3 Nov 2026 10:27:20 -0800	[thread overview]
Message-ID: <4ba0c23099e4a4f5ade2b4754871dfbcf896f7dc.1793724065.git.Maciej.Grochowski@sony.com> (raw)
In-Reply-To: <cover.1793724065.git.Maciej.Grochowski@sony.com>

From: Maciej Grochowski <maciej.grochowski@sony.com>

Spare bus numbers below a switch are divided between its hotplug ports.
A nested switch that appears later can need more than its port's share.
After a top-switch reset on our PM50052 hierarchy, five downstream ports
split 15 bus numbers equally, leaving the nested switch's port three
buses although its hierarchy needs 11.

A global hpbussize of 11 asks for the same minimum on all five ports, so
earlier siblings consume the range before the selected port is reached.

Allow hpbussize to take "<count>@<pci_dev>" entries, separated by
semicolons, using the lookup added by the memory-window patch:

  pci=hpbussize=11@0000:80:01.1/00.0/03.0

Recompute each unconfigured hotplug bridge's share from the bus numbers
still free and the remaining minima. The selected bridge's count replaces
its global minimum; the other bridges share the remainder. In this case,
the split becomes 1/1/1/11/1 instead of 3/3/3/3/3.

Include the global minimum for unselected bridges in that calculation.
Clamp scoped counts to 255 and stop before looking up a secondary bus
number above 255, so a large request cannot wrap the lookup to bus zero.

Apply the selected minimum when scanning the bridge's own child bus too.
Parent capacity still limits the reservation, and configured firmware
ranges are kept. A bare option keeps its existing global meaning.

Assisted-by: Opus 5.5 (implementation/testing), GPT-6.1 (review)
Signed-off-by: Maciej Grochowski <maciej.grochowski@sony.com>
---
 .../admin-guide/kernel-parameters.txt         | 16 ++++
 drivers/pci/pci.c                             | 34 ++++++--
 drivers/pci/probe.c                           | 87 ++++++++++++++++++-
 3 files changed, 126 insertions(+), 11 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 19defbe2a1d6..f07ee41f7feb 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5253,6 +5253,22 @@ Kernel parameters
 		hpbussize=nn	The minimum amount of additional bus numbers
 				reserved for buses below a hotplug bridge.
 				Default is 1.
+		hpbussize=nn@<pci_dev>[; ...]
+				The minimum amount of bus numbers for the
+				hotplug bridges specified (in the format
+				described above) instead of the default
+				amount above.  When the kernel numbers the
+				buses below the hotplug bridges on a bus,
+				e.g. after a switch is hot-added, these
+				bridges request at least nn bus numbers where
+				space permits; other hotplug bridges share
+				what is left.
+				Bridges numbered by firmware are kept.  For
+				example,
+				  pci=hpbussize=11@0000:80:01.1/00.0/03.0
+				gives 11 bus numbers to the downstream port
+				at devfn 03.0 of the switch behind root port
+				0000:80:01.1.
 		realloc=	Enable/disable reallocating PCI bridge resources
 				if allocations done by BIOS are too small to
 				accommodate resources required by all child
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 7a6ce1d3360d..a02220e5aaf2 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -413,11 +413,13 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p,
 }
 
 /*
- * "<size>@<pci_dev>" entries of "pci=hpmmiosize=" and "pci=hpmmioprefsize=",
- * set in pci_setup() and copied in pci_realloc_setup_params().
+ * "<size>@<pci_dev>" entries of "pci=hpmmiosize=", "pci=hpmmioprefsize=" and
+ * "pci=hpbussize=", set in pci_setup() and copied in
+ * pci_realloc_setup_params().
  */
 static const char *hotplug_mmio_size_param;
 static const char *hotplug_mmio_pref_size_param;
+static const char *hotplug_bus_size_param;
 
 /* Match @bridge against a "<size>@<pci_dev>[;...]" list; first match wins */
 static bool pci_hotplug_size_match(struct pci_dev *bridge, const char *p,
@@ -455,23 +457,33 @@ static bool pci_hotplug_size_match(struct pci_dev *bridge, const char *p,
 /**
  * pci_hotplug_dev_size - get a hotplug size given for a specific bridge
  * @bridge: the bridge
- * @type: IORESOURCE_MEM, optionally with IORESOURCE_PREFETCH
+ * @type: IORESOURCE_MEM, optionally with IORESOURCE_PREFETCH, or IORESOURCE_BUS
  * @size: set to the size given for @bridge
  *
- * Look up @bridge in the "<size>@<pci_dev>" entries of "pci=hpmmiosize=" or
- * "pci=hpmmioprefsize=".  Only hotplug bridges are considered.
+ * Look up @bridge in the "<size>@<pci_dev>" entries of "pci=hpmmiosize=",
+ * "pci=hpmmioprefsize=" or "pci=hpbussize=".  Only hotplug bridges are
+ * considered.
  *
  * Return: true if a size was given for @bridge.
  */
 bool pci_hotplug_dev_size(struct pci_dev *bridge, unsigned long type,
 			  unsigned long *size)
 {
-	if (!bridge->is_hotplug_bridge || !(type & IORESOURCE_MEM))
+	const char *p;
+
+	if (!bridge->is_hotplug_bridge)
+		return false;
+
+	if (type & IORESOURCE_BUS)
+		p = hotplug_bus_size_param;
+	else if (type & IORESOURCE_PREFETCH)
+		p = hotplug_mmio_pref_size_param;
+	else if (type & IORESOURCE_MEM)
+		p = hotplug_mmio_size_param;
+	else
 		return false;
 
-	return pci_hotplug_size_match(bridge, type & IORESOURCE_PREFETCH ?
-				      hotplug_mmio_pref_size_param :
-				      hotplug_mmio_size_param, size);
+	return pci_hotplug_size_match(bridge, p, size);
 }
 
 static u8 __pci_find_next_cap(struct pci_bus *bus, unsigned int devfn,
@@ -6966,6 +6978,9 @@ static int __init pci_setup(char *str)
 			} else if (!strncmp(str, "hpmemsize=", 10)) {
 				pci_hotplug_mmio_size = memparse(str + 10, &str);
 				pci_hotplug_mmio_pref_size = pci_hotplug_mmio_size;
+			} else if (!strncmp(str, "hpbussize=", 10) &&
+				   strchr(str + 10, '@')) {
+				hotplug_bus_size_param = str + 10;
 			} else if (!strncmp(str, "hpbussize=", 10)) {
 				pci_hotplug_bus_size =
 					simple_strtoul(str + 10, &str, 0);
@@ -7013,6 +7028,7 @@ static int __init pci_realloc_setup_params(void)
 	hotplug_mmio_size_param = kstrdup(hotplug_mmio_size_param, GFP_KERNEL);
 	hotplug_mmio_pref_size_param = kstrdup(hotplug_mmio_pref_size_param,
 					       GFP_KERNEL);
+	hotplug_bus_size_param = kstrdup(hotplug_bus_size_param, GFP_KERNEL);
 
 	return 0;
 }
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 9a178e0e5f7f..b79a931edce5 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1519,6 +1519,9 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
 		else
 			next_busnr = max + 1;
 
+		if (next_busnr > 0xff)
+			goto out;
+
 		/*
 		 * Prevent assigning a bus number that already exists.
 		 * This can happen when a bridge is hot-plugged, so in this
@@ -3076,6 +3079,62 @@ void __weak pcibios_fixup_bus(struct pci_bus *bus)
        /* nothing to do, expected to be removed in the future */
 }
 
+/* Bus numbers given for @bridge with "pci=hpbussize=<n>@<pci_dev>", or 0 */
+static unsigned int pci_hotplug_bus_reserve(struct pci_dev *bridge)
+{
+	unsigned long size;
+
+	if (!pci_hotplug_dev_size(bridge, IORESOURCE_BUS, &size))
+		return 0;
+
+	return min(size, 255UL);
+}
+
+/*
+ * Return the bus numbers for the next hotplug bridge on @bus that is not
+ * configured yet, out of the @left that the next bridge can still take.
+ * The secondary bus of each such bridge is already counted as used, so
+ * together they have @left plus one less than their count.  A bridge with
+ * its own "pci=hpbussize=" gets at least that many; the others share the
+ * rest equally.
+ */
+static unsigned int pci_hotplug_bus_share(struct pci_bus *bus,
+					  struct pci_dev *bridge,
+					  unsigned int left)
+{
+	unsigned int n, sum, share = UINT_MAX, prev, reserve;
+	struct pci_dev *dev;
+
+	n = 0;
+	for_each_pci_bridge(dev, bus)
+		if (dev->is_hotplug_bridge && !dev->subordinate)
+			n++;
+	if (!n)
+		return 0;
+	left += n - 1;
+
+	/* Each bridge above the share lowers it for the others */
+	do {
+		prev = share;
+		n = 0;
+		sum = 0;
+		for_each_pci_bridge(dev, bus) {
+			if (!dev->is_hotplug_bridge || dev->subordinate)
+				continue;
+
+			reserve = pci_hotplug_bus_reserve(dev) ?: pci_hotplug_bus_size;
+			if (reserve > prev)
+				sum += reserve;
+			else
+				n++;
+		}
+		share = n && left > sum ? (left - sum) / n : 0;
+	} while (share < prev);
+
+	reserve = pci_hotplug_bus_reserve(bridge) ?: pci_hotplug_bus_size;
+	return max(reserve, share);
+}
+
 /**
  * pci_scan_child_bus_extend() - Scan devices below a bus
  * @bus: Bus to scan for devices
@@ -3094,6 +3153,8 @@ static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
 	unsigned int used_buses, normal_bridges = 0, hotplug_bridges = 0;
 	unsigned int start = bus->busn_res.start;
 	unsigned int devnr, cmax, max = start;
+	bool hotplug_reserve = false;
+	unsigned int reserve;
 	struct pci_dev *dev;
 
 	dev_dbg(&bus->dev, "scanning bus\n");
@@ -3126,6 +3187,8 @@ static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
 			hotplug_bridges++;
 		else
 			normal_bridges++;
+		if (pci_hotplug_bus_reserve(dev))
+			hotplug_reserve = true;
 	}
 
 	/*
@@ -3150,6 +3213,8 @@ static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
 	for_each_pci_bridge(dev, bus) {
 		unsigned int buses = 0;
 
+		reserve = 0;
+
 		if (!hotplug_bridges && normal_bridges == 1) {
 			/*
 			 * There is only one bridge on the bus (upstream
@@ -3163,7 +3228,16 @@ static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
 			 * Distribute the extra buses between hotplug
 			 * bridges if any.
 			 */
-			buses = available_buses / hotplug_bridges;
+			if (hotplug_reserve && available_buses) {
+				if (!dev->subordinate) {
+					reserve = pci_hotplug_bus_reserve(dev);
+					buses = available_buses + 1 > used_buses ?
+						available_buses + 1 - used_buses : 0;
+					buses = pci_hotplug_bus_share(bus, dev, buses);
+				}
+			} else {
+				buses = available_buses / hotplug_bridges;
+			}
 			buses = min(buses, available_buses - used_buses + 1);
 		}
 
@@ -3172,6 +3246,12 @@ static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
 		/* One bus is already accounted so don't add it again */
 		if (max - cmax > 1)
 			used_buses += max - cmax - 1;
+
+		if (reserve > max - cmax)
+			pci_warn(dev, "only %u of %u bus numbers available\n",
+				 max - cmax, reserve);
+		else if (reserve)
+			pci_info(dev, "reserving %u bus numbers\n", max - cmax);
 	}
 
 	/*
@@ -3180,7 +3260,10 @@ static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
 	 * bus number if there is room.
 	 */
 	if (bus->self && bus->self->is_hotplug_bridge) {
-		used_buses = max(available_buses, pci_hotplug_bus_size - 1);
+		unsigned long bus_size = pci_hotplug_bus_reserve(bus->self) ?:
+					 pci_hotplug_bus_size;
+
+		used_buses = max(available_buses, bus_size - 1);
 		if (max - start < used_buses) {
 			max = start + used_buses;
 

      parent reply	other threads:[~2026-11-03 18:27 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 18:27 [RFC PATCH v2 0/2] PCI: Reserve resources for a delayed hotplug subtree Maciej Grochowski
2026-11-03 18:27 ` [RFC PATCH v2 1/2] PCI: Allow hpmmiosize and hpmmioprefsize for specific hotplug bridges Maciej Grochowski
2026-11-03 18:27 ` Maciej Grochowski [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4ba0c23099e4a4f5ade2b4754871dfbcf896f7dc.1793724065.git.Maciej.Grochowski@sony.com \
    --to=maciej.grochowski@sony.com \
    --cc=bhelgaas@google.com \
    --cc=corbet@lwn.net \
    --cc=ilpo.jarvinen@linux.intel.com \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pci@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®