]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
PCI: Fix Resizable BAR restore order
authorIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Wed, 21 Jan 2026 13:14:17 +0000 (15:14 +0200)
committerBjorn Helgaas <bhelgaas@google.com>
Thu, 22 Jan 2026 16:29:55 +0000 (10:29 -0600)
The commit 337b1b566db0 ("PCI: Fix restoring BARs on BAR resize rollback
path") changed BAR resize to layer rebar code and resource setup/restore
code cleanly. Unfortunately, it did not consider how the value of the BAR
Size field impacts the read-only bits in the Base Address Register (PCIe7
spec, sec. 7.8.6.3). That is, it very much matters in which order the BAR
Size and Base Address Register are restored.

Post-337b1b566db0 ("PCI: Fix restoring BARs on BAR resize rollback path")
during BAR resize rollback, pci_do_resource_release_and_resize() attempts
to restore the old address to the BAR that was resized, but it can fail to
setup the address correctly if the address has low bits set that collide
with the bits that are still read-only. As a result, kernel's resource and
BAR will be out-of-sync.

Fix this by restoring BAR Size before rolling back the resource changes and
restoring the BAR.

Fixes: 337b1b566db0 ("PCI: Fix restoring BARs on BAR resize rollback path")
Reported-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Link: https://lore.kernel.org/linux-pci/aW_w1oFQCzUxGYtu@intel.com/
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Tested-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260121131417.9582-3-ilpo.jarvinen@linux.intel.com
drivers/pci/rebar.c
drivers/pci/setup-bus.c

index ecdebdeb2dff099b73337932fd76bf70de7d7b0d..39f8cf3b70d5728cd76f5ab504ee43aad011c8b6 100644 (file)
@@ -295,7 +295,6 @@ int pci_resize_resource(struct pci_dev *dev, int resno, int size,
                        int exclude_bars)
 {
        struct pci_host_bridge *host;
-       int old, ret;
 
        /* Check if we must preserve the firmware's resource assignment */
        host = pci_find_host_bridge(dev->bus);
@@ -308,21 +307,6 @@ int pci_resize_resource(struct pci_dev *dev, int resno, int size,
        if (!pci_rebar_size_supported(dev, resno, size))
                return -EINVAL;
 
-       old = pci_rebar_get_current_size(dev, resno);
-       if (old < 0)
-               return old;
-
-       ret = pci_rebar_set_size(dev, resno, size);
-       if (ret)
-               return ret;
-
-       ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars);
-       if (ret)
-               goto error_resize;
-       return 0;
-
-error_resize:
-       pci_rebar_set_size(dev, resno, old);
-       return ret;
+       return pci_do_resource_release_and_resize(dev, resno, size, exclude_bars);
 }
 EXPORT_SYMBOL(pci_resize_resource);
index 9c374feafc7719351d9409962038d93444b53842..a61d38777cdc4acaedc7137741bb8672cfc48a7f 100644 (file)
@@ -2504,12 +2504,20 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size
        struct resource *b_win, *r;
        LIST_HEAD(saved);
        unsigned int i;
-       int ret = 0;
+       int old, ret;
 
        b_win = pbus_select_window(bus, res);
        if (!b_win)
                return -EINVAL;
 
+       old = pci_rebar_get_current_size(pdev, resno);
+       if (old < 0)
+               return old;
+
+       ret = pci_rebar_set_size(pdev, resno, size);
+       if (ret)
+               return ret;
+
        pci_dev_for_each_resource(pdev, r, i) {
                if (i >= PCI_BRIDGE_RESOURCES)
                        break;
@@ -2542,7 +2550,15 @@ out:
        return ret;
 
 restore:
-       /* Revert to the old configuration */
+       /*
+        * Revert to the old configuration.
+        *
+        * BAR Size must be restored first because it affects the read-only
+        * bits in BAR (the old address might not be restorable otherwise
+        * due to low address bits).
+        */
+       pci_rebar_set_size(pdev, resno, old);
+
        list_for_each_entry(dev_res, &saved, list) {
                struct resource *res = dev_res->res;
                struct pci_dev *dev = dev_res->dev;