[PATCH] PCI: release empty sibling bridge resources during window resize

Geramy Loveless
2026-04-10T19:10:40Z

pbus_reassign_bridge_resources() refuses to release bridge windows that
have child resources. On multi-port PCIe switches (e.g. Thunderbolt
docks), empty sibling downstream ports hold small reservations that
prevent the parent window from being freed and re-sized for rebar.

Add a bottom-up recursive release that saves and frees each empty bridge
window individually, so the parent window can then be released and grown.

Suggested-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Geramy Loveless <gloveless@jqluv.com>
---
drivers/pci/setup-bus.c | 63 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 61 insertions(+), 2 deletions(-)

diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c
index 4cf120ebe5ad..0c1fb654c9cc 100644
--- a/drivers/pci/setup-bus.c
+++ b/drivers/pci/setup-bus.c
@@ -2292,6 +2292,60 @@ void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge)
}
EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources);

+/*
+ * pci_bus_release_empty_bridge_resources - Bottom-up release of bridge
+ * window resources in empty subtrees.
+ * @bus: PCI bus whose child bridges to process
+ * @b_win: Ancestor bridge window; only children of this resource are released
+ * @saved: List to save released resources for rollback
+ *
+ * Recurses depth-first into subordinate buses, then releases bridge windows
+ * on the way back up. Each resource is individually saved before release so
+ * the entire operation can be rolled back.
+ */
+static void pci_bus_release_empty_bridge_resources(struct pci_bus *bus,
+ struct resource *b_win,
+ struct list_head *saved)
+{
+ struct pci_dev *dev;
+
+ list_for_each_entry(dev, &bus->devices, bus_list) {
+ struct resource *r;
+ unsigned int i;
+
+ if (!dev->subordinate)
+ continue;
+
+ /* Recurse first — release deepest resources before parents */
+ pci_bus_release_empty_bridge_resources(dev->subordinate,
+ b_win, saved);
+
+ pci_dev_for_each_resource(dev, r, i) {
+ if (!pci_resource_is_bridge_win(i))
+ continue;
+
+ if (!resource_assigned(r))
+ continue;
+
+ if (r->parent != b_win)
+ continue;
+
+ if (r->child) {
+ const char *res_name = pci_resource_name(dev, i);
+
+ pci_info(dev, "%s %pR: not released, children still present\n",
+ res_name, r);
+ continue;
+ }
+
+ if (pci_dev_res_add_to_list(saved, dev, r, 0, 0))
+ continue;
+
+ pci_release_resource(dev, i);
+ }
+ }
+}
+
/*
* Walk to the root bus, find the bridge window relevant for @res and
* release it when possible. If the bridge window contains assigned
@@ -2316,7 +2370,12 @@ static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *

i = pci_resource_num(bridge, res);

- /* Ignore BARs which are still in use */
+ /* Release empty sibling bridge windows bottom-up */
+ if (bridge->subordinate)
+ pci_bus_release_empty_bridge_resources(
+ bridge->subordinate, res, saved);
+
+ /* Ignore bridge windows which are still in use */
if (!res->child) {
ret = pci_dev_res_add_to_list(saved, bridge, res, 0, 0);
if (ret)
@@ -2327,7 +2386,7 @@ static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *
const char *res_name = pci_resource_name(bridge, i);

pci_warn(bridge,
- "%s %pR: was not released (still contains assigned resources)\n",
+ "%s %pR: not released, active children present\n",
res_name, res);
}

--
2.51.0