Merge branch 'pci/resource'

- Prevent resource tree corruption when BAR resize fails (Ilpo Järvinen)

- Restore BARs to the original size if a BAR resize fails (Ilpo Järvinen)

- Remove BAR release from BAR resize attempts by the xe, i915, and amdgpu
  drivers so the PCI core can restore BARs if the resize fails (Ilpo
  Järvinen)

- Move Resizable BAR code to rebar.c (Ilpo Järvinen)

- Add pci_rebar_size_supported() and use it in i915 and xe (Ilpo Järvinen)

- Add pci_rebar_get_max_size() and use it in xe and amdgpu (Ilpo Järvinen)

* pci/resource:
  PCI: Validate pci_rebar_size_supported() input
  PCI: Convert BAR sizes bitmasks to u64
  drm/amdgpu: Use pci_rebar_get_max_size()
  drm/xe/vram: Use pci_rebar_get_max_size()
  PCI: Add pci_rebar_get_max_size()
  drm/xe/vram: Use PCI rebar helpers in resize_vram_bar()
  drm/i915/gt: Use pci_rebar_size_supported()
  PCI: Add pci_rebar_size_supported() helper
  PCI: Improve Resizable BAR functions kernel doc
  PCI: Move pci_rebar_size_to_bytes() and export it
  PCI: Move pci_rebar_bytes_to_size() and clean it up
  PCI: Move Resizable BAR code to rebar.c
  PCI: Prevent restoring assigned resources
  drm/amdgpu: Remove driver side BAR release before resize
  drm/i915: Remove driver side BAR release before resize
  drm/xe: Remove driver side BAR release before resize
  PCI: Add kerneldoc for pci_resize_resource()
  PCI: Fix restoring BARs on BAR resize rollback path
  PCI: Free saved list without holding pci_bus_sem
  PCI: Try BAR resize even when no window was released
  PCI: Change pci_dev variable from 'bridge' to 'dev'
  PCI/IOV: Adjust ->barsz[] when changing BAR size
  PCI: Prevent resource tree corruption when BAR resize fails
This commit is contained in:
Bjorn Helgaas 2025-12-03 14:18:32 -06:00
commit 13571584e1
14 changed files with 475 additions and 363 deletions

View File

@ -37,6 +37,9 @@ PCI Support Library
.. kernel-doc:: drivers/pci/slot.c
:export:
.. kernel-doc:: drivers/pci/rebar.c
:export:
.. kernel-doc:: drivers/pci/rom.c
:export:

View File

@ -1673,9 +1673,9 @@ int amdgpu_device_resize_fb_bar(struct amdgpu_device *adev)
int rbar_size = pci_rebar_bytes_to_size(adev->gmc.real_vram_size);
struct pci_bus *root;
struct resource *res;
int max_size, r;
unsigned int i;
u16 cmd;
int r;
if (!IS_ENABLED(CONFIG_PHYS_ADDR_T_64BIT))
return 0;
@ -1721,30 +1721,28 @@ int amdgpu_device_resize_fb_bar(struct amdgpu_device *adev)
return 0;
/* Limit the BAR size to what is available */
rbar_size = min(fls(pci_rebar_get_possible_sizes(adev->pdev, 0)) - 1,
rbar_size);
max_size = pci_rebar_get_max_size(adev->pdev, 0);
if (max_size < 0)
return 0;
rbar_size = min(max_size, rbar_size);
/* Disable memory decoding while we change the BAR addresses and size */
pci_read_config_word(adev->pdev, PCI_COMMAND, &cmd);
pci_write_config_word(adev->pdev, PCI_COMMAND,
cmd & ~PCI_COMMAND_MEMORY);
/* Free the VRAM and doorbell BAR, we most likely need to move both. */
/* Tear down doorbell as resizing will release BARs */
amdgpu_doorbell_fini(adev);
if (adev->asic_type >= CHIP_BONAIRE)
pci_release_resource(adev->pdev, 2);
pci_release_resource(adev->pdev, 0);
r = pci_resize_resource(adev->pdev, 0, rbar_size);
r = pci_resize_resource(adev->pdev, 0, rbar_size,
(adev->asic_type >= CHIP_BONAIRE) ? 1 << 5
: 1 << 2);
if (r == -ENOSPC)
dev_info(adev->dev,
"Not enough PCI address space for a large BAR.");
else if (r && r != -ENOTSUPP)
dev_err(adev->dev, "Problem resizing BAR0 (%d).", r);
pci_assign_unassigned_bus_resources(adev->pdev->bus);
/* When the doorbell or fb BAR isn't available we have no chance of
* using the device.
*/

View File

@ -18,16 +18,6 @@
#include "gt/intel_gt_regs.h"
#ifdef CONFIG_64BIT
static void _release_bars(struct pci_dev *pdev)
{
int resno;
for (resno = PCI_STD_RESOURCES; resno < PCI_STD_RESOURCE_END; resno++) {
if (pci_resource_len(pdev, resno))
pci_release_resource(pdev, resno);
}
}
static void
_resize_bar(struct drm_i915_private *i915, int resno, resource_size_t size)
{
@ -35,9 +25,7 @@ _resize_bar(struct drm_i915_private *i915, int resno, resource_size_t size)
int bar_size = pci_rebar_bytes_to_size(size);
int ret;
_release_bars(pdev);
ret = pci_resize_resource(pdev, resno, bar_size);
ret = pci_resize_resource(pdev, resno, bar_size, 0);
if (ret) {
drm_info(&i915->drm, "Failed to resize BAR%d to %dM (%pe)\n",
resno, 1 << bar_size, ERR_PTR(ret));
@ -61,16 +49,12 @@ static void i915_resize_lmem_bar(struct drm_i915_private *i915, resource_size_t
current_size = roundup_pow_of_two(pci_resource_len(pdev, GEN12_LMEM_BAR));
if (i915->params.lmem_bar_size) {
u32 bar_sizes;
rebar_size = i915->params.lmem_bar_size *
(resource_size_t)SZ_1M;
bar_sizes = pci_rebar_get_possible_sizes(pdev, GEN12_LMEM_BAR);
rebar_size = i915->params.lmem_bar_size * (resource_size_t)SZ_1M;
if (rebar_size == current_size)
return;
if (!(bar_sizes & BIT(pci_rebar_bytes_to_size(rebar_size))) ||
if (!pci_rebar_size_supported(pdev, GEN12_LMEM_BAR,
pci_rebar_bytes_to_size(rebar_size)) ||
rebar_size >= roundup_pow_of_two(lmem_size)) {
rebar_size = lmem_size;

View File

@ -24,8 +24,6 @@
#include "xe_vram.h"
#include "xe_vram_types.h"
#define BAR_SIZE_SHIFT 20
static void
_resize_bar(struct xe_device *xe, int resno, resource_size_t size)
{
@ -33,10 +31,7 @@ _resize_bar(struct xe_device *xe, int resno, resource_size_t size)
int bar_size = pci_rebar_bytes_to_size(size);
int ret;
if (pci_resource_len(pdev, resno))
pci_release_resource(pdev, resno);
ret = pci_resize_resource(pdev, resno, bar_size);
ret = pci_resize_resource(pdev, resno, bar_size, 0);
if (ret) {
drm_info(&xe->drm, "Failed to resize BAR%d to %dM (%pe). Consider enabling 'Resizable BAR' support in your BIOS\n",
resno, 1 << bar_size, ERR_PTR(ret));
@ -58,41 +53,37 @@ static void resize_vram_bar(struct xe_device *xe)
resource_size_t current_size;
resource_size_t rebar_size;
struct resource *root_res;
u32 bar_size_mask;
int max_size, i;
u32 pci_cmd;
int i;
/* gather some relevant info */
current_size = pci_resource_len(pdev, LMEM_BAR);
bar_size_mask = pci_rebar_get_possible_sizes(pdev, LMEM_BAR);
if (!bar_size_mask)
return;
if (force_vram_bar_size < 0)
return;
/* set to a specific size? */
if (force_vram_bar_size) {
u32 bar_size_bit;
rebar_size = pci_rebar_bytes_to_size(force_vram_bar_size *
(resource_size_t)SZ_1M);
rebar_size = force_vram_bar_size * (resource_size_t)SZ_1M;
bar_size_bit = bar_size_mask & BIT(pci_rebar_bytes_to_size(rebar_size));
if (!bar_size_bit) {
if (!pci_rebar_size_supported(pdev, LMEM_BAR, rebar_size)) {
drm_info(&xe->drm,
"Requested size: %lluMiB is not supported by rebar sizes: 0x%x. Leaving default: %lluMiB\n",
(u64)rebar_size >> 20, bar_size_mask, (u64)current_size >> 20);
"Requested size: %lluMiB is not supported by rebar sizes: 0x%llx. Leaving default: %lluMiB\n",
(u64)pci_rebar_size_to_bytes(rebar_size) >> 20,
pci_rebar_get_possible_sizes(pdev, LMEM_BAR),
(u64)current_size >> 20);
return;
}
rebar_size = 1ULL << (__fls(bar_size_bit) + BAR_SIZE_SHIFT);
rebar_size = pci_rebar_size_to_bytes(rebar_size);
if (rebar_size == current_size)
return;
} else {
rebar_size = 1ULL << (__fls(bar_size_mask) + BAR_SIZE_SHIFT);
max_size = pci_rebar_get_max_size(pdev, LMEM_BAR);
if (max_size < 0)
return;
rebar_size = pci_rebar_size_to_bytes(max_size);
/* only resize if larger than current */
if (rebar_size <= current_size)

View File

@ -4,7 +4,7 @@
obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \
remove.o pci.o pci-driver.o search.o \
rom.o setup-res.o irq.o vpd.o \
rebar.o rom.o setup-res.o irq.o vpd.o \
setup-bus.o vc.o mmap.o devres.o
obj-$(CONFIG_PCI) += msi/

View File

@ -158,8 +158,7 @@ resource_size_t pci_iov_resource_size(struct pci_dev *dev, int resno)
return dev->sriov->barsz[pci_resource_num_to_vf_bar(resno)];
}
void pci_iov_resource_set_size(struct pci_dev *dev, int resno,
resource_size_t size)
void pci_iov_resource_set_size(struct pci_dev *dev, int resno, int size)
{
if (!pci_resource_is_iov(resno)) {
pci_warn(dev, "%s is not an IOV resource\n",
@ -167,7 +166,8 @@ void pci_iov_resource_set_size(struct pci_dev *dev, int resno,
return;
}
dev->sriov->barsz[pci_resource_num_to_vf_bar(resno)] = size;
resno = pci_resource_num_to_vf_bar(resno);
dev->sriov->barsz[resno] = pci_rebar_size_to_bytes(size);
}
bool pci_iov_is_memory_decoding_enabled(struct pci_dev *dev)
@ -1339,29 +1339,16 @@ EXPORT_SYMBOL_GPL(pci_sriov_configure_simple);
*/
int pci_iov_vf_bar_set_size(struct pci_dev *dev, int resno, int size)
{
u32 sizes;
int ret;
if (!pci_resource_is_iov(resno))
return -EINVAL;
if (pci_iov_is_memory_decoding_enabled(dev))
return -EBUSY;
sizes = pci_rebar_get_possible_sizes(dev, resno);
if (!sizes)
return -ENOTSUPP;
if (!(sizes & BIT(size)))
if (!pci_rebar_size_supported(dev, resno, size))
return -EINVAL;
ret = pci_rebar_set_size(dev, resno, size);
if (ret)
return ret;
pci_iov_resource_set_size(dev, resno, pci_rebar_size_to_bytes(size));
return 0;
return pci_rebar_set_size(dev, resno, size);
}
EXPORT_SYMBOL_GPL(pci_iov_vf_bar_set_size);
@ -1380,7 +1367,7 @@ EXPORT_SYMBOL_GPL(pci_iov_vf_bar_set_size);
u32 pci_iov_vf_bar_get_sizes(struct pci_dev *dev, int resno, int num_vfs)
{
u64 vf_len = pci_resource_len(dev, resno);
u32 sizes;
u64 sizes;
if (!num_vfs)
return 0;

View File

@ -1587,7 +1587,7 @@ static ssize_t __resource_resize_show(struct device *dev, int n, char *buf)
pci_config_pm_runtime_get(pdev);
ret = sysfs_emit(buf, "%016llx\n",
(u64)pci_rebar_get_possible_sizes(pdev, n));
pci_rebar_get_possible_sizes(pdev, n));
pci_config_pm_runtime_put(pdev);
@ -1599,18 +1599,13 @@ static ssize_t __resource_resize_store(struct device *dev, int n,
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pci_bus *bus = pdev->bus;
struct resource *b_win, *res;
unsigned long size;
int ret, i;
int ret;
u16 cmd;
if (kstrtoul(buf, 0, &size) < 0)
return -EINVAL;
b_win = pbus_select_window(bus, pci_resource_n(pdev, n));
if (!b_win)
return -EINVAL;
device_lock(dev);
if (dev->driver || pci_num_vf(pdev)) {
ret = -EBUSY;
@ -1632,15 +1627,7 @@ static ssize_t __resource_resize_store(struct device *dev, int n,
pci_remove_resource_files(pdev);
pci_dev_for_each_resource(pdev, res, i) {
if (i >= PCI_BRIDGE_RESOURCES)
break;
if (b_win == pbus_select_window(bus, res))
pci_release_resource(pdev, i);
}
ret = pci_resize_resource(pdev, n, size);
ret = pci_resize_resource(pdev, n, size, 0);
pci_assign_unassigned_bus_resources(bus);

View File

@ -1823,32 +1823,6 @@ static void pci_restore_config_space(struct pci_dev *pdev)
}
}
static void pci_restore_rebar_state(struct pci_dev *pdev)
{
unsigned int pos, nbars, i;
u32 ctrl;
pos = pdev->rebar_cap;
if (!pos)
return;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
for (i = 0; i < nbars; i++, pos += 8) {
struct resource *res;
int bar_idx, size;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
res = pci_resource_n(pdev, bar_idx);
size = pci_rebar_bytes_to_size(resource_size(res));
ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
}
}
/**
* pci_restore_state - Restore the saved state of a PCI device
* @dev: PCI device that we're dealing with
@ -3684,125 +3658,6 @@ void pci_acs_init(struct pci_dev *dev)
pci_enable_acs(dev);
}
void pci_rebar_init(struct pci_dev *pdev)
{
pdev->rebar_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
}
/**
* pci_rebar_find_pos - find position of resize ctrl reg for BAR
* @pdev: PCI device
* @bar: BAR to find
*
* Helper to find the position of the ctrl register for a BAR.
* Returns -ENOTSUPP if resizable BARs are not supported at all.
* Returns -ENOENT if no ctrl register for the BAR could be found.
*/
static int pci_rebar_find_pos(struct pci_dev *pdev, int bar)
{
unsigned int pos, nbars, i;
u32 ctrl;
if (pci_resource_is_iov(bar)) {
pos = pci_iov_vf_rebar_cap(pdev);
bar = pci_resource_num_to_vf_bar(bar);
} else {
pos = pdev->rebar_cap;
}
if (!pos)
return -ENOTSUPP;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
for (i = 0; i < nbars; i++, pos += 8) {
int bar_idx;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
bar_idx = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, ctrl);
if (bar_idx == bar)
return pos;
}
return -ENOENT;
}
/**
* pci_rebar_get_possible_sizes - get possible sizes for BAR
* @pdev: PCI device
* @bar: BAR to query
*
* Get the possible sizes of a resizable BAR as bitmask defined in the spec
* (bit 0=1MB, bit 31=128TB). Returns 0 if BAR isn't resizable.
*/
u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar)
{
int pos;
u32 cap;
pos = pci_rebar_find_pos(pdev, bar);
if (pos < 0)
return 0;
pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap);
cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap);
/* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */
if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f &&
bar == 0 && cap == 0x700)
return 0x3f00;
return cap;
}
EXPORT_SYMBOL(pci_rebar_get_possible_sizes);
/**
* pci_rebar_get_current_size - get the current size of a BAR
* @pdev: PCI device
* @bar: BAR to set size to
*
* Read the size of a BAR from the resizable BAR config.
* Returns size if found or negative error code.
*/
int pci_rebar_get_current_size(struct pci_dev *pdev, int bar)
{
int pos;
u32 ctrl;
pos = pci_rebar_find_pos(pdev, bar);
if (pos < 0)
return pos;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
return FIELD_GET(PCI_REBAR_CTRL_BAR_SIZE, ctrl);
}
/**
* pci_rebar_set_size - set a new size for a BAR
* @pdev: PCI device
* @bar: BAR to set size to
* @size: new size as defined in the spec (0=1MB, 31=128TB)
*
* Set the new size of a BAR as defined in the spec.
* Returns zero if resizing was successful, error code otherwise.
*/
int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size)
{
int pos;
u32 ctrl;
pos = pci_rebar_find_pos(pdev, bar);
if (pos < 0)
return pos;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
return 0;
}
/**
* pci_enable_atomic_ops_to_root - enable AtomicOp requests to root port
* @dev: the PCI device

View File

@ -421,8 +421,10 @@ enum pci_bar_type {
struct device *pci_get_host_bridge_device(struct pci_dev *dev);
void pci_put_host_bridge_device(struct device *dev);
void pci_resize_resource_set_size(struct pci_dev *dev, int resno, int size);
int pci_do_resource_release_and_resize(struct pci_dev *dev, int resno, int size,
int exclude_bars);
unsigned int pci_rescan_bus_bridge_resize(struct pci_dev *bridge);
int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res);
int __must_check pci_reassign_resource(struct pci_dev *dev, int i, resource_size_t add_size, resource_size_t align);
int pci_configure_extended_tags(struct pci_dev *dev, void *ign);
@ -808,8 +810,7 @@ void pci_iov_update_resource(struct pci_dev *dev, int resno);
resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno);
void pci_restore_iov_state(struct pci_dev *dev);
int pci_iov_bus_range(struct pci_bus *bus);
void pci_iov_resource_set_size(struct pci_dev *dev, int resno,
resource_size_t size);
void pci_iov_resource_set_size(struct pci_dev *dev, int resno, int size);
bool pci_iov_is_memory_decoding_enabled(struct pci_dev *dev);
static inline u16 pci_iov_vf_rebar_cap(struct pci_dev *dev)
{
@ -851,7 +852,7 @@ static inline int pci_iov_bus_range(struct pci_bus *bus)
return 0;
}
static inline void pci_iov_resource_set_size(struct pci_dev *dev, int resno,
resource_size_t size) { }
int size) { }
static inline bool pci_iov_is_memory_decoding_enabled(struct pci_dev *dev)
{
return false;
@ -1020,12 +1021,9 @@ static inline int acpi_get_rc_resources(struct device *dev, const char *hid,
#endif
void pci_rebar_init(struct pci_dev *pdev);
void pci_restore_rebar_state(struct pci_dev *pdev);
int pci_rebar_get_current_size(struct pci_dev *pdev, int bar);
int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size);
static inline u64 pci_rebar_size_to_bytes(int size)
{
return 1ULL << (size + 20);
}
struct device_node;

328
drivers/pci/rebar.c Normal file
View File

@ -0,0 +1,328 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Resizable BAR Extended Capability handling.
*/
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/ioport.h>
#include <linux/log2.h>
#include <linux/pci.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include "pci.h"
#define PCI_REBAR_MIN_SIZE ((resource_size_t)SZ_1M)
/**
* pci_rebar_bytes_to_size - Convert size in bytes to PCI BAR Size
* @bytes: size in bytes
*
* Convert size in bytes to encoded BAR Size in Resizable BAR Capability
* (PCIe r6.2, sec. 7.8.6.3).
*
* Return: encoded BAR Size as defined in the PCIe spec (0=1MB, 31=128TB)
*/
int pci_rebar_bytes_to_size(u64 bytes)
{
int rebar_minsize = ilog2(PCI_REBAR_MIN_SIZE);
bytes = roundup_pow_of_two(bytes);
return max(ilog2(bytes), rebar_minsize) - rebar_minsize;
}
EXPORT_SYMBOL_GPL(pci_rebar_bytes_to_size);
/**
* pci_rebar_size_to_bytes - Convert encoded BAR Size to size in bytes
* @size: encoded BAR Size as defined in the PCIe spec (0=1MB, 31=128TB)
*
* Return: BAR size in bytes
*/
resource_size_t pci_rebar_size_to_bytes(int size)
{
return 1ULL << (size + ilog2(PCI_REBAR_MIN_SIZE));
}
EXPORT_SYMBOL_GPL(pci_rebar_size_to_bytes);
void pci_rebar_init(struct pci_dev *pdev)
{
pdev->rebar_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
}
/**
* pci_rebar_find_pos - find position of resize control reg for BAR
* @pdev: PCI device
* @bar: BAR to find
*
* Helper to find the position of the control register for a BAR.
*
* Return:
* * %-ENOTSUPP if resizable BARs are not supported at all,
* * %-ENOENT if no control register for the BAR could be found.
*/
static int pci_rebar_find_pos(struct pci_dev *pdev, int bar)
{
unsigned int pos, nbars, i;
u32 ctrl;
if (pci_resource_is_iov(bar)) {
pos = pci_iov_vf_rebar_cap(pdev);
bar = pci_resource_num_to_vf_bar(bar);
} else {
pos = pdev->rebar_cap;
}
if (!pos)
return -ENOTSUPP;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
for (i = 0; i < nbars; i++, pos += 8) {
int bar_idx;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
bar_idx = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, ctrl);
if (bar_idx == bar)
return pos;
}
return -ENOENT;
}
/**
* pci_rebar_get_possible_sizes - get possible sizes for Resizable BAR
* @pdev: PCI device
* @bar: BAR to query
*
* Get the possible sizes of a resizable BAR as bitmask.
*
* Return: A bitmask of possible sizes (bit 0=1MB, bit 31=128TB), or %0 if
* BAR isn't resizable.
*/
u64 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar)
{
int pos;
u32 cap;
pos = pci_rebar_find_pos(pdev, bar);
if (pos < 0)
return 0;
pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap);
cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap);
/* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */
if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f &&
bar == 0 && cap == 0x700)
return 0x3f00;
return cap;
}
EXPORT_SYMBOL(pci_rebar_get_possible_sizes);
/**
* pci_rebar_size_supported - check if size is supported for BAR
* @pdev: PCI device
* @bar: BAR to check
* @size: encoded size as defined in the PCIe spec (0=1MB, 31=128TB)
*
* Return: %true if @bar is resizable and @size is supported, otherwise
* %false.
*/
bool pci_rebar_size_supported(struct pci_dev *pdev, int bar, int size)
{
u64 sizes = pci_rebar_get_possible_sizes(pdev, bar);
if (size < 0 || size > ilog2(SZ_128T) - ilog2(PCI_REBAR_MIN_SIZE))
return false;
return BIT(size) & sizes;
}
EXPORT_SYMBOL_GPL(pci_rebar_size_supported);
/**
* pci_rebar_get_max_size - get the maximum supported size of a BAR
* @pdev: PCI device
* @bar: BAR to query
*
* Get the largest supported size of a resizable BAR as a size.
*
* Return: the encoded maximum BAR size as defined in the PCIe spec
* (0=1MB, 31=128TB), or %-NOENT on error.
*/
int pci_rebar_get_max_size(struct pci_dev *pdev, int bar)
{
u64 sizes;
sizes = pci_rebar_get_possible_sizes(pdev, bar);
if (!sizes)
return -ENOENT;
return __fls(sizes);
}
EXPORT_SYMBOL_GPL(pci_rebar_get_max_size);
/**
* pci_rebar_get_current_size - get the current size of a Resizable BAR
* @pdev: PCI device
* @bar: BAR to get the size from
*
* Read the current size of a BAR from the Resizable BAR config.
*
* Return: BAR Size if @bar is resizable (0=1MB, 31=128TB), or negative on
* error.
*/
int pci_rebar_get_current_size(struct pci_dev *pdev, int bar)
{
int pos;
u32 ctrl;
pos = pci_rebar_find_pos(pdev, bar);
if (pos < 0)
return pos;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
return FIELD_GET(PCI_REBAR_CTRL_BAR_SIZE, ctrl);
}
/**
* pci_rebar_set_size - set a new size for a Resizable BAR
* @pdev: PCI device
* @bar: BAR to set size to
* @size: new size as defined in the PCIe spec (0=1MB, 31=128TB)
*
* Set the new size of a BAR as defined in the spec.
*
* Return: %0 if resizing was successful, or negative on error.
*/
int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size)
{
int pos;
u32 ctrl;
pos = pci_rebar_find_pos(pdev, bar);
if (pos < 0)
return pos;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
if (pci_resource_is_iov(bar))
pci_iov_resource_set_size(pdev, bar, size);
return 0;
}
void pci_restore_rebar_state(struct pci_dev *pdev)
{
unsigned int pos, nbars, i;
u32 ctrl;
pos = pdev->rebar_cap;
if (!pos)
return;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
for (i = 0; i < nbars; i++, pos += 8) {
struct resource *res;
int bar_idx, size;
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
res = pci_resource_n(pdev, bar_idx);
size = pci_rebar_bytes_to_size(resource_size(res));
ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
}
}
static bool pci_resize_is_memory_decoding_enabled(struct pci_dev *dev,
int resno)
{
u16 cmd;
if (pci_resource_is_iov(resno))
return pci_iov_is_memory_decoding_enabled(dev);
pci_read_config_word(dev, PCI_COMMAND, &cmd);
return cmd & PCI_COMMAND_MEMORY;
}
void pci_resize_resource_set_size(struct pci_dev *dev, int resno, int size)
{
resource_size_t res_size = pci_rebar_size_to_bytes(size);
struct resource *res = pci_resource_n(dev, resno);
if (pci_resource_is_iov(resno))
res_size *= pci_sriov_get_totalvfs(dev);
resource_set_size(res, res_size);
}
/**
* pci_resize_resource - reconfigure a Resizable BAR and resources
* @dev: the PCI device
* @resno: index of the BAR to be resized
* @size: new size as defined in the spec (0=1MB, 31=128TB)
* @exclude_bars: a mask of BARs that should not be released
*
* Reconfigure @resno to @size and re-run resource assignment algorithm
* with the new size.
*
* Prior to resize, release @dev resources that share a bridge window with
* @resno. This unpins the bridge window resource to allow changing it.
*
* The caller may prevent releasing a particular BAR by providing
* @exclude_bars mask, but this may result in the resize operation failing
* due to insufficient space.
*
* Return: 0 on success, or negative on error. In case of an error, the
* resources are restored to their original places.
*/
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);
if (host->preserve_config)
return -ENOTSUPP;
if (pci_resize_is_memory_decoding_enabled(dev, resno))
return -EBUSY;
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;
}
EXPORT_SYMBOL(pci_resize_resource);

View File

@ -15,6 +15,7 @@
*/
#include <linux/bitops.h>
#include <linux/bug.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
@ -135,6 +136,9 @@ static void restore_dev_resource(struct pci_dev_resource *dev_res)
{
struct resource *res = dev_res->res;
if (WARN_ON_ONCE(res->parent))
return;
res->start = dev_res->start;
res->end = dev_res->end;
res->flags = dev_res->flags;
@ -2420,18 +2424,16 @@ EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources);
* release it when possible. If the bridge window contains assigned
* resources, it cannot be released.
*/
int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res)
static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res,
struct list_head *saved)
{
unsigned long type = res->flags;
struct pci_dev_resource *dev_res;
struct pci_dev *bridge;
LIST_HEAD(saved);
struct pci_dev *bridge = NULL;
LIST_HEAD(added);
LIST_HEAD(failed);
unsigned int i;
int ret;
down_read(&pci_bus_sem);
int ret = 0;
while (!pci_is_root_bus(bus)) {
bridge = bus->self;
@ -2443,9 +2445,9 @@ int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res)
/* Ignore BARs which are still in use */
if (!res->child) {
ret = add_to_list(&saved, bridge, res, 0, 0);
ret = add_to_list(saved, bridge, res, 0, 0);
if (ret)
goto cleanup;
return ret;
pci_release_resource(bridge, i);
} else {
@ -2459,10 +2461,8 @@ int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res)
bus = bus->parent;
}
if (list_empty(&saved)) {
up_read(&pci_bus_sem);
if (!bridge)
return -ENOENT;
}
__pci_bus_size_bridges(bridge->subordinate, &added);
__pci_bridge_assign_resources(bridge, &added, &failed);
@ -2470,49 +2470,107 @@ int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res)
free_list(&added);
if (!list_empty(&failed)) {
if (pci_required_resource_failed(&failed, type)) {
if (pci_required_resource_failed(&failed, type))
ret = -ENOSPC;
goto cleanup;
}
/* Only resources with unrelated types failed (again) */
free_list(&failed);
if (ret)
return ret;
/* Only resources with unrelated types failed (again) */
}
list_for_each_entry(dev_res, &saved, list) {
list_for_each_entry(dev_res, saved, list) {
struct pci_dev *dev = dev_res->dev;
/* Skip the bridge we just assigned resources for */
if (bridge == dev_res->dev)
if (bridge == dev)
continue;
bridge = dev_res->dev;
pci_setup_bridge(bridge->subordinate);
if (!dev->subordinate)
continue;
pci_setup_bridge(dev->subordinate);
}
free_list(&saved);
up_read(&pci_bus_sem);
return 0;
}
cleanup:
/* Restore size and flags */
list_for_each_entry(dev_res, &failed, list)
restore_dev_resource(dev_res);
free_list(&failed);
int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size,
int exclude_bars)
{
struct resource *res = pci_resource_n(pdev, resno);
struct pci_dev_resource *dev_res;
struct pci_bus *bus = pdev->bus;
struct resource *b_win, *r;
LIST_HEAD(saved);
unsigned int i;
int ret = 0;
b_win = pbus_select_window(bus, res);
if (!b_win)
return -EINVAL;
pci_dev_for_each_resource(pdev, r, i) {
if (i >= PCI_BRIDGE_RESOURCES)
break;
if (exclude_bars & BIT(i))
continue;
if (b_win != pbus_select_window(bus, r))
continue;
ret = add_to_list(&saved, pdev, r, 0, 0);
if (ret)
goto restore;
pci_release_resource(pdev, i);
}
pci_resize_resource_set_size(pdev, resno, size);
if (!bus->self)
goto out;
down_read(&pci_bus_sem);
ret = pbus_reassign_bridge_resources(bus, res, &saved);
if (ret)
goto restore;
out:
up_read(&pci_bus_sem);
free_list(&saved);
return ret;
restore:
/* Revert to the old configuration */
list_for_each_entry(dev_res, &saved, list) {
struct resource *res = dev_res->res;
struct pci_dev *dev = dev_res->dev;
bridge = dev_res->dev;
i = pci_resource_num(bridge, res);
i = pci_resource_num(dev, res);
if (res->parent) {
release_child_resources(res);
pci_release_resource(dev, i);
}
restore_dev_resource(dev_res);
pci_claim_resource(bridge, i);
pci_setup_bridge(bridge->subordinate);
}
free_list(&saved);
up_read(&pci_bus_sem);
ret = pci_claim_resource(dev, i);
if (ret)
continue;
return ret;
if (i < PCI_BRIDGE_RESOURCES) {
const char *res_name = pci_resource_name(dev, i);
pci_update_resource(dev, i);
pci_info(dev, "%s %pR: old value restored\n",
res_name, res);
}
if (dev->subordinate)
pci_setup_bridge(dev->subordinate);
}
goto out;
}
void pci_assign_unassigned_bus_resources(struct pci_bus *bus)

View File

@ -431,84 +431,6 @@ int pci_release_resource(struct pci_dev *dev, int resno)
}
EXPORT_SYMBOL(pci_release_resource);
static bool pci_resize_is_memory_decoding_enabled(struct pci_dev *dev,
int resno)
{
u16 cmd;
if (pci_resource_is_iov(resno))
return pci_iov_is_memory_decoding_enabled(dev);
pci_read_config_word(dev, PCI_COMMAND, &cmd);
return cmd & PCI_COMMAND_MEMORY;
}
static void pci_resize_resource_set_size(struct pci_dev *dev, int resno,
int size)
{
resource_size_t res_size = pci_rebar_size_to_bytes(size);
struct resource *res = pci_resource_n(dev, resno);
if (!pci_resource_is_iov(resno)) {
resource_set_size(res, res_size);
} else {
resource_set_size(res, res_size * pci_sriov_get_totalvfs(dev));
pci_iov_resource_set_size(dev, resno, res_size);
}
}
int pci_resize_resource(struct pci_dev *dev, int resno, int size)
{
struct resource *res = pci_resource_n(dev, resno);
struct pci_host_bridge *host;
int old, ret;
u32 sizes;
/* Check if we must preserve the firmware's resource assignment */
host = pci_find_host_bridge(dev->bus);
if (host->preserve_config)
return -ENOTSUPP;
/* Make sure the resource isn't assigned before resizing it. */
if (!(res->flags & IORESOURCE_UNSET))
return -EBUSY;
if (pci_resize_is_memory_decoding_enabled(dev, resno))
return -EBUSY;
sizes = pci_rebar_get_possible_sizes(dev, resno);
if (!sizes)
return -ENOTSUPP;
if (!(sizes & BIT(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;
pci_resize_resource_set_size(dev, resno, size);
/* Check if the new config works by trying to assign everything. */
if (dev->bus->self) {
ret = pbus_reassign_bridge_resources(dev->bus, res);
if (ret)
goto error_resize;
}
return 0;
error_resize:
pci_rebar_set_size(dev, resno, old);
pci_resize_resource_set_size(dev, resno, old);
return ret;
}
EXPORT_SYMBOL(pci_resize_resource);
int pci_enable_resources(struct pci_dev *dev, int mask)
{
u16 cmd, old_cmd;

View File

@ -1421,16 +1421,16 @@ void pcibios_reset_secondary_bus(struct pci_dev *dev);
void pci_update_resource(struct pci_dev *dev, int resno);
int __must_check pci_assign_resource(struct pci_dev *dev, int i);
int pci_release_resource(struct pci_dev *dev, int resno);
static inline int pci_rebar_bytes_to_size(u64 bytes)
{
bytes = roundup_pow_of_two(bytes);
/* Return BAR size as defined in the resizable BAR specification */
return max(ilog2(bytes), 20) - 20;
}
/* Resizable BAR related routines */
int pci_rebar_bytes_to_size(u64 bytes);
resource_size_t pci_rebar_size_to_bytes(int size);
u64 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar);
bool pci_rebar_size_supported(struct pci_dev *pdev, int bar, int size);
int pci_rebar_get_max_size(struct pci_dev *pdev, int bar);
int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size,
int exclude_bars);
u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar);
int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size);
int pci_select_bars(struct pci_dev *dev, unsigned long flags);
bool pci_device_is_present(struct pci_dev *pdev);
void pci_ignore_hotplug(struct pci_dev *dev);

View File

@ -67,5 +67,6 @@
#define SZ_16T _AC(0x100000000000, ULL)
#define SZ_32T _AC(0x200000000000, ULL)
#define SZ_64T _AC(0x400000000000, ULL)
#define SZ_128T _AC(0x800000000000, ULL)
#endif /* __LINUX_SIZES_H__ */