[PATCH] PCI: pack oversized prefetchable windows by demoting small BARs at enumeration
From: Geramy Loveless
Date: Fri Aug 28 2026 - 17:11:55 EST
Clear PREFETCH on small control BARs beside a large prefetchable BAR at
enumeration so they are placed below 4 GiB and the prefetchable window
packs to the large BAR alone. Bounded to small BARs (<=16 MiB) beside a
big one (>=64 MiB); pci=no_bar_demote disables it.
---
.../admin-guide/kernel-parameters.txt | 1 +
drivers/pci/pci.c | 5 ++
drivers/pci/pci.h | 1 +
drivers/pci/quirks.c | 54 +++++++++++++++++++
4 files changed, 61 insertions(+)
diff --git a/Documentation/admin-guide/kernel-parameters.txt
b/Documentation/admin-guide/kernel-parameters.txt
index 82d2b340fb98..7360407dd4b8 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5246,6 +5246,7 @@ Kernel parameters
hpbussize=nn The minimum amount of additional bus numbers
reserved for buses below a hotplug bridge.
Default is 1.
+ no_bar_demote Do not demote small prefetchable BARs below 4 GiB to
pack oversized prefetchable bridge windows.
no_rebar_reserve Do not reserve prefetchable bridge-window
space for the maximum size of downstream Resizable
BARs. By default such space is reserved so a driver
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index c9901b15432a..0bb4ad570171 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -128,6 +128,9 @@ unsigned long pci_hotplug_bus_size =
DEFAULT_HOTPLUG_BUS_SIZE;
*/
bool pci_rebar_no_reserve;
+/* pci=no_bar_demote disables demoting small prefetchable BARs below 4
GiB. */
+bool pci_bar_demote_disabled;
+
/* PCIe MPS/MRRS strategy; can be overridden by kernel command-line
param */
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_DEFAULT;
@@ -6850,6 +6853,8 @@ static int __init pci_setup(char *str)
pci_hotplug_bus_size = DEFAULT_HOTPLUG_BUS_SIZE;
} else if (!strncmp(str, "no_rebar_reserve", 16)) {
pci_rebar_no_reserve = true;
+ } else if (!strncmp(str, "no_bar_demote", 13)) {
+ pci_bar_demote_disabled = true;
} else if (!strncmp(str, "pcie_bus_tune_off", 17)) {
pcie_bus_config = PCIE_BUS_TUNE_OFF;
} else if (!strncmp(str, "pcie_bus_safe", 13)) {
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 6eddcb492425..0f21972b674a 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -410,6 +410,7 @@ extern unsigned long pci_hotplug_mmio_size;
extern unsigned long pci_hotplug_mmio_pref_size;
extern unsigned long pci_hotplug_bus_size;
extern bool pci_rebar_no_reserve;
+extern bool pci_bar_demote_disabled;
static inline bool pci_is_cardbus_bridge(struct pci_dev *dev)
{
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index de9bbccda21f..57a5926a273c 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -6423,3 +6423,57 @@ static void pci_mask_replay_timer_timeout(struct
pci_dev *pdev)
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750,
pci_mask_replay_timer_timeout);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755,
pci_mask_replay_timer_timeout);
#endif
+
+/*
+ * Pack oversized prefetchable windows.
+ *
+ * A small prefetchable BAR (e.g. a 2 MiB doorbell) sharing a bridge's
single
+ * prefetchable window with a much larger one (e.g. a 32 GiB
framebuffer) pushes
+ * the window past the large BAR's alignment, so siblings behind a
switch waste
+ * an alignment each. A prefetchable BAR may legally be assigned from the
+ * non-prefetchable window (PCI-to-PCI Bridge spec r1.2 sec 3.2.5);
clear PREFETCH
+ * on such small BARs so they are placed below 4 GiB and the
prefetchable window
+ * is sized to the large BAR alone. Run at enumeration -- before
resources are
+ * sized -- so the reservation and assignment see the final layout.
Only small
+ * "control" BARs beside a large one are touched, bounding the extra
below-4G use.
+ * pci=no_bar_demote disables this.
+ */
+#define PCI_BAR_PACK_MIN SZ_64M /* device must have a pref BAR at least
this big */
+#define PCI_BAR_PACK_CAP SZ_16M /* only demote small pref BARs up to
this size */
+
+static void quirk_pci_pack_prefetch(struct pci_dev *dev)
+{
+ resource_size_t max_align = 0;
+ struct resource *r;
+ int i;
+
+ if (pci_bar_demote_disabled)
+ return;
+
+ for (i = 0; i < PCI_STD_NUM_BARS; i++) {
+ r = pci_resource_n(dev, i);
+ if ((r->flags & (IORESOURCE_MEM | IORESOURCE_PREFETCH)) ==
+ (IORESOURCE_MEM | IORESOURCE_PREFETCH))
+ max_align = max(max_align, pci_resource_alignment(dev, r));
+ }
+ if (max_align < PCI_BAR_PACK_MIN)
+ return;
+
+ for (i = 0; i < PCI_STD_NUM_BARS; i++) {
+ r = pci_resource_n(dev, i);
+ if ((r->flags & (IORESOURCE_MEM | IORESOURCE_PREFETCH)) !=
+ (IORESOURCE_MEM | IORESOURCE_PREFETCH))
+ continue;
+ if (r->flags & IORESOURCE_PCI_FIXED)
+ continue;
+ if (pci_resource_alignment(dev, r) >= max_align)
+ continue; /* an alignment-setting BAR, keep it */
+ if (resource_size(r) > PCI_BAR_PACK_CAP)
+ continue; /* not a small control BAR */
+
+ r->flags &= ~IORESOURCE_PREFETCH;
+ pci_info(dev, "%pR: assigned from the non-prefetchable window to pack
the prefetchable window\n",
+ r);
+ }
+}
+DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, quirk_pci_pack_prefetch);
base-commit: 45c13f3f9e3bb15fd89ff2864c6f627a3b4b4229
prerequisite-patch-id: 73634e017e2770ecd47fe4fe434973e280b5d3d8
prerequisite-patch-id: c397a97535c26763ed6104deba168b0f5e456250
prerequisite-patch-id: 24c5bab9f84760d15064b6ea7d73720ce33eb5bc
prerequisite-patch-id: df6cdb5d432ed50ffffc5b31e3df32711c0b98c2
--
2.53.0