mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH v2 0/2] PCI: Reserve resources for a delayed hotplug subtree
@ 2026-11-03 18:27 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 ` [RFC PATCH v2 2/2] PCI: Allow hpbussize " Maciej Grochowski
  0 siblings, 2 replies; 3+ messages in thread
From: Maciej Grochowski @ 2026-11-03 18:27 UTC (permalink / raw)
  To: bhelgaas, ilpo.jarvinen
  Cc: Maciej Grochowski, linux-pci, linux-kernel, Jonathan Corbet, linux-doc

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

On a two-level Microchip PM50052 (Switchtec PFX) hierarchy, a hard reset
of the top switch removes both switches from the PCI tree.  The top
switch returns before the nested switch, so pciehp enumerates its five
hotplug downstream ports while they are empty.  The port leading to the
nested switch receives three bus numbers and a roughly 206 MiB
prefetchable window.  When the nested switch appears, it needs eleven
bus numbers and a 1 GiB-aligned 1032 MiB window for its NTB BARs.  The
missing resources leave part of the hierarchy unusable and the nested
switchtec function cannot bind.

Switches from other vendors can encounter the same resource shortfall
when a child appears after its parent hotplug port is numbered and
sized, if the child's requirements exceed that allocation.

V2 extends the existing pci=hpmmiosize, hpmmioprefsize and hpbussize
parameters, as Ilpo suggested, instead of adding hpreserve. Patch 1
requests a required memory window size and size-derived head alignment;
patch 2 reserves bus numbers before the remainder is shared among sibling
hotplug ports. The path form tolerates downstream bus renumbering. Bare
values retain their existing global meaning.

The v2 setting for the same port is:

  pci=hpmmioprefsize=1032M@0000:80:01.1/00.0/03.0,hpbussize=11@0000:80:01.1/00.0/03.0

A 1032 MiB minimum requests 1 GiB head alignment without a separate
alignment option. Placement and parent resources still limit allocation;
assigned firmware windows are kept.

With v1 on a v7.3-rc2 based kernel and the hpreserve setting from the
original post, we tested a cold boot, two top-switch hard resets and one
nested-switch hard reset. After each reset, the nested port retained
its eleven bus numbers and aligned 1032 MiB window, the 1 GiB NTB BAR was
assigned, and both switchtec devices responded to MRPC. These are the
previously reported v1 results; this rebased v2 tree has not been boot-tested.

The failure affects our PCIe hotplug infrastructure. The two-level test
shows that a per-bridge reservation can preserve one delayed subtree.
With three or more switch layers returning at different times, this
approach appears to need a reservation at each port sized while empty.
Each ancestor must retain enough bus numbers and aligned memory for the
eventual subtree. We have not tested that case.

Consider three top-level switches, each feeding six second-level
switches with eight NTB functions apiece. If the child links return late,
that gives 18 selected bridge paths, plus any delayed ancestors. With
separate memory-size and bus-count options, those paths normally appear
in both lists: 36 value@path entries. The NTB BARs affect resources per
bridge, not the entry count. Device-ID matching could shorten a homogeneous
case, but risks reserving space on same-ID ports without delayed children.

One possible extension, not implemented here, would let several explicit
paths with identical requirements share a value in each option. Would
that make a kernel command-line interface practical, or should firmware,
platform data or a PCI core policy describe the expected resources? We
welcome feedback on the interface and the underlying allocation problem,
including required versus optional per-device memory sizes.

Changes since v1:
  - Reuse hpmmiosize, hpmmioprefsize and hpbussize, following Ilpo's feedback.
  - Derive head alignment from the requested size instead of mmioprefalign.
  - Apply the scoped bus minimum on the plain child-bus scan path too.
  - Include small sizing/sharing safety corrections; keep the two-patch design.
  - Drop self Tested-by trailers and rebase on PCI next.

This revision passed W=1 PCI object builds at both patch boundaries on
ARM64 and i386 PAE/64-bit resources, plus focused userspace source checks.
The PCI sources emitted no warnings; one Darwin host-tool macro warning
was recorded. These checks do not replace hardware or concurrency testing.

Plain-rescan minima are now handled, but existing firmware ranges and
retained-child optional spare remain limitations. The sibling-window
allocator retry issue reported with v1 is outside this series. The
separate bus-number overflow fixes overlap a scan guard and will require
rebasing if both series are taken.

v1: https://lore.kernel.org/linux-pci/20261002193111.51637-1-maciej.grochowski@sony.com/

Maciej Grochowski (2):
  PCI: Allow hpmmiosize and hpmmioprefsize for specific hotplug bridges
  PCI: Allow hpbussize for specific hotplug bridges

 Documentation/admin-guide/kernel-parameters.txt | 41 +++++++++++
 drivers/pci/pci.c                               | 91 ++++++++++++++++++++++++-
 drivers/pci/pci.h                               |  3 +
 drivers/pci/probe.c                             | 87 ++++++++++++++++++++++-
 drivers/pci/setup-bus.c                         | 25 +++++++
 5 files changed, 243 insertions(+), 4 deletions(-)

base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [RFC PATCH v2 1/2] PCI: Allow hpmmiosize and hpmmioprefsize for specific hotplug bridges
  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 ` Maciej Grochowski
  2026-11-03 18:27 ` [RFC PATCH v2 2/2] PCI: Allow hpbussize " Maciej Grochowski
  1 sibling, 0 replies; 3+ messages in thread
From: Maciej Grochowski @ 2026-11-03 18:27 UTC (permalink / raw)
  To: bhelgaas, ilpo.jarvinen
  Cc: Maciej Grochowski, linux-pci, linux-kernel, Jonathan Corbet, linux-doc

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

A hotplug bridge can be sized before devices expected behind it appear.
After a reset of a two-level PM50052 hierarchy, the nested switch's link
returns after its downstream port has received an equal share of the
parent's window. Its 1 GiB prefetchable NTB BAR then fails assignment:

  pci 0000:8c:00.1: BAR 2 [mem size 0x40000000 64bit pref]: can't assign; no space

The global hotplug memory sizes are optional targets for every hotplug
bridge. They cannot reserve the required space and head alignment on one
specific port when the parent aperture cannot satisfy all its siblings.

Allow hpmmiosize and hpmmioprefsize to take "<size>@<pci_dev>" entries,
separated by semicolons, using the existing PCI device selectors. For
example:

  pci=hpmmioprefsize=1032M@0000:80:01.1/00.0/03.0

Use the selected size as a required total window minimum in pbus_size_mem()
and request head alignment of the largest power of two not above that
size. Apply size and alignment independently, preserving stronger existing
requirements. A 1032 MiB minimum therefore requests 1 GiB head alignment.

Reject scoped values that exceed the existing unsigned long size interface
before narrowing them. Also guard window adjustment when there is no
optional realloc entry: a required minimum equal to the global size can
leave no entry to trim.

Retain the existing global optional-space policy. Assigned firmware
windows are kept; the minimum applies when the kernel sizes a window,
such as after hot-add or resource reallocation. Allocation remains subject
to the parent aperture and normal placement rules. 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         | 25 +++++++
 drivers/pci/pci.c                             | 75 ++++++++++++++++++-
 drivers/pci/pci.h                             |  3 +
 drivers/pci/setup-bus.c                       | 25 +++++++
 4 files changed, 126 insertions(+), 2 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 0b3c140babd9..19defbe2a1d6 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5218,9 +5218,34 @@ Kernel parameters
 		hpmmiosize=nn[KMG]	The fixed amount of bus space which is
 				reserved for hotplug bridge's MMIO window.
 				Default size is 2 megabytes.
+		hpmmiosize=nn[KMG]@<pci_dev>[; ...]
+				Reserve at least nn bytes of MMIO window for
+				the hotplug bridges specified (in the format
+				described above), e.g. for a switch that is
+				expected behind the bridge but whose link
+				comes up only after the bridge windows have
+				been sized.  Unlike the default amount, which
+				is optional space that is dropped if it does
+				not fit, this is required space: the window
+				is sized like it would be for devices already
+				present below the bridge, with head alignment
+				from the largest power of two not above nn.
+				The size must fit unsigned long.  Only hotplug bridges
+				are affected, and only windows the kernel
+				sizes, e.g. after a hot-add or with
+				pci=realloc; windows set up by firmware are
+				kept.
 		hpmmioprefsize=nn[KMG]	The fixed amount of bus space which is
 				reserved for hotplug bridge's MMIO_PREF window.
 				Default size is 2 megabytes.
+		hpmmioprefsize=nn[KMG]@<pci_dev>[; ...]
+				Like hpmmiosize=nn[KMG]@<pci_dev>, for the
+				MMIO_PREF window.  For example,
+				  pci=hpmmioprefsize=1032M@0000:80:01.1/00.0/03.0
+				requests a 1032 MiB prefetchable window with
+				1 GiB head alignment below the downstream
+				port at devfn 03.0 of the switch behind root
+				port 0000:80:01.1.
 		hpmemsize=nn[KMG]	The fixed amount of bus space which is
 				reserved for hotplug bridge's MMIO and
 				MMIO_PREF window.
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 7c6f2aba8144..7a6ce1d3360d 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -412,6 +412,68 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p,
 	return 1;
 }
 
+/*
+ * "<size>@<pci_dev>" entries of "pci=hpmmiosize=" and "pci=hpmmioprefsize=",
+ * 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;
+
+/* Match @bridge against a "<size>@<pci_dev>[;...]" list; first match wins */
+static bool pci_hotplug_size_match(struct pci_dev *bridge, const char *p,
+				   unsigned long *size)
+{
+	unsigned long long val;
+	char *end;
+	int ret;
+
+	while (p && *p) {
+		val = memparse(p, &end);
+		if (val > ULONG_MAX)
+			return false;
+		p = end;
+		if (*p == '@') {
+			ret = pci_dev_str_match(bridge, p + 1, &p);
+			if (ret == 1) {
+				*size = val;
+				return true;
+			}
+			if (ret < 0) {
+				pr_err_once("PCI: Can't parse hotplug size parameter\n");
+				return false;
+			}
+		}
+
+		if (*p != ';')
+			break;
+		p++;
+	}
+
+	return false;
+}
+
+/**
+ * pci_hotplug_dev_size - get a hotplug size given for a specific bridge
+ * @bridge: the bridge
+ * @type: IORESOURCE_MEM, optionally with IORESOURCE_PREFETCH
+ * @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.
+ *
+ * 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))
+		return false;
+
+	return pci_hotplug_size_match(bridge, type & IORESOURCE_PREFETCH ?
+				      hotplug_mmio_pref_size_param :
+				      hotplug_mmio_size_param, size);
+}
+
 static u8 __pci_find_next_cap(struct pci_bus *bus, unsigned int devfn,
 			      u8 pos, int cap)
 {
@@ -6892,9 +6954,15 @@ static int __init pci_setup(char *str)
 			} else if (!strncmp(str, "hpiosize=", 9)) {
 				pci_hotplug_io_size = memparse(str + 9, &str);
 			} else if (!strncmp(str, "hpmmiosize=", 11)) {
-				pci_hotplug_mmio_size = memparse(str + 11, &str);
+				if (strchr(str + 11, '@'))
+					hotplug_mmio_size_param = str + 11;
+				else
+					pci_hotplug_mmio_size = memparse(str + 11, &str);
 			} else if (!strncmp(str, "hpmmioprefsize=", 15)) {
-				pci_hotplug_mmio_pref_size = memparse(str + 15, &str);
+				if (strchr(str + 15, '@'))
+					hotplug_mmio_pref_size_param = str + 15;
+				else
+					pci_hotplug_mmio_pref_size = memparse(str + 15, &str);
 			} else if (!strncmp(str, "hpmemsize=", 10)) {
 				pci_hotplug_mmio_size = memparse(str + 10, &str);
 				pci_hotplug_mmio_pref_size = pci_hotplug_mmio_size;
@@ -6942,6 +7010,9 @@ static int __init pci_realloc_setup_params(void)
 					   GFP_KERNEL);
 	disable_acs_redir_param = kstrdup(disable_acs_redir_param, GFP_KERNEL);
 	config_acs_param = kstrdup(config_acs_param, GFP_KERNEL);
+	hotplug_mmio_size_param = kstrdup(hotplug_mmio_size_param, GFP_KERNEL);
+	hotplug_mmio_pref_size_param = kstrdup(hotplug_mmio_pref_size_param,
+					       GFP_KERNEL);
 
 	return 0;
 }
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 77efa4c5c960..3b681653b5d9 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -419,6 +419,9 @@ extern unsigned long pci_hotplug_mmio_size;
 extern unsigned long pci_hotplug_mmio_pref_size;
 extern unsigned long pci_hotplug_bus_size;
 
+bool pci_hotplug_dev_size(struct pci_dev *bridge, unsigned long type,
+			  unsigned long *size);
+
 static inline bool pci_is_cardbus_bridge(struct pci_dev *dev)
 {
 	return dev->hdr_type == PCI_HEADER_TYPE_CARDBUS;
diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c
index ed16ef7c26fa..f3992859d90f 100644
--- a/drivers/pci/setup-bus.c
+++ b/drivers/pci/setup-bus.c
@@ -26,6 +26,7 @@
 #include <linux/ioport.h>
 #include <linux/cache.h>
 #include <linux/limits.h>
+#include <linux/log2.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
 #include <linux/acpi.h>
@@ -1285,6 +1286,7 @@ static void pbus_size_mem(struct pci_bus *bus, struct resource *b_res,
 	int order, max_order;
 	resource_size_t children_add_size = 0;
 	resource_size_t add_align = 0;
+	unsigned long hp_size;
 
 	if (!b_res)
 		return;
@@ -1349,6 +1351,27 @@ static void pbus_size_mem(struct pci_bus *bus, struct resource *b_res,
 	win_align = pci_min_window_alignment(bus, b_res->flags);
 	min_align = calculate_head_align(aligns, max_order);
 	min_align = max(min_align, win_align);
+
+	/*
+	 * A hotplug size given for this bridge is a required minimum, not
+	 * optional space like the global size: aligned for the largest BAR
+	 * that fits in it, so it is in place before the devices that need
+	 * it appear below the bridge.
+	 */
+	if (bus->self &&
+	    pci_hotplug_dev_size(bus->self, b_res->flags, &hp_size)) {
+		if (hp_size) {
+			size = max(size, hp_size);
+			min_align = max_t(resource_size_t, min_align,
+					  rounddown_pow_of_two(hp_size));
+			pci_info(bus->self, "bridge window to %pR: reserving %s size %#lx align %#llx\n",
+				 &bus->busn_res,
+				 b_res->flags & IORESOURCE_PREFETCH ?
+				 "prefetchable" : "non-prefetchable",
+				 hp_size, (unsigned long long)min_align);
+		}
+	}
+
 	size0 = calculate_memsize(size, realloc_head ? 0 : add_size,
 				  0, win_align);
 
@@ -1897,6 +1920,8 @@ static void adjust_bridge_window(struct pci_dev *bridge, struct resource *res,
 		}
 
 		dev_res = res_to_dev_res(add_list, res);
+		if (!dev_res)
+			return;
 		add_size = size - new_size;
 		if (add_size < dev_res->add_size) {
 			dev_res->add_size -= add_size;

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [RFC PATCH v2 2/2] PCI: Allow hpbussize for specific hotplug bridges
  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
  1 sibling, 0 replies; 3+ messages in thread
From: Maciej Grochowski @ 2026-11-03 18:27 UTC (permalink / raw)
  To: bhelgaas, ilpo.jarvinen
  Cc: Maciej Grochowski, linux-pci, linux-kernel, Jonathan Corbet, linux-doc

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;
 

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-03 18:27 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [RFC PATCH v2 2/2] PCI: Allow hpbussize " Maciej Grochowski

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®