Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI: Fix Resizable BAR restore order

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

authored by

Ilpo Järvinen and committed by
Bjorn Helgaas
5528fd38 08d9eae7

+19 -19
+1 -17
drivers/pci/rebar.c
··· 295 295 int exclude_bars) 296 296 { 297 297 struct pci_host_bridge *host; 298 - int old, ret; 299 298 300 299 /* Check if we must preserve the firmware's resource assignment */ 301 300 host = pci_find_host_bridge(dev->bus); ··· 307 308 if (!pci_rebar_size_supported(dev, resno, size)) 308 309 return -EINVAL; 309 310 310 - old = pci_rebar_get_current_size(dev, resno); 311 - if (old < 0) 312 - return old; 313 - 314 - ret = pci_rebar_set_size(dev, resno, size); 315 - if (ret) 316 - return ret; 317 - 318 - ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars); 319 - if (ret) 320 - goto error_resize; 321 - return 0; 322 - 323 - error_resize: 324 - pci_rebar_set_size(dev, resno, old); 325 - return ret; 311 + return pci_do_resource_release_and_resize(dev, resno, size, exclude_bars); 326 312 } 327 313 EXPORT_SYMBOL(pci_resize_resource);
+18 -2
drivers/pci/setup-bus.c
··· 2504 2504 struct resource *b_win, *r; 2505 2505 LIST_HEAD(saved); 2506 2506 unsigned int i; 2507 - int ret = 0; 2507 + int old, ret; 2508 2508 2509 2509 b_win = pbus_select_window(bus, res); 2510 2510 if (!b_win) 2511 2511 return -EINVAL; 2512 + 2513 + old = pci_rebar_get_current_size(pdev, resno); 2514 + if (old < 0) 2515 + return old; 2516 + 2517 + ret = pci_rebar_set_size(pdev, resno, size); 2518 + if (ret) 2519 + return ret; 2512 2520 2513 2521 pci_dev_for_each_resource(pdev, r, i) { 2514 2522 if (i >= PCI_BRIDGE_RESOURCES) ··· 2550 2542 return ret; 2551 2543 2552 2544 restore: 2553 - /* Revert to the old configuration */ 2545 + /* 2546 + * Revert to the old configuration. 2547 + * 2548 + * BAR Size must be restored first because it affects the read-only 2549 + * bits in BAR (the old address might not be restorable otherwise 2550 + * due to low address bits). 2551 + */ 2552 + pci_rebar_set_size(pdev, resno, old); 2553 + 2554 2554 list_for_each_entry(dev_res, &saved, list) { 2555 2555 struct resource *res = dev_res->res; 2556 2556 struct pci_dev *dev = dev_res->dev;