mirror of https://github.com/torvalds/linux.git
329 lines
8.2 KiB
C
329 lines
8.2 KiB
C
// 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);
|