11 Linux pci子系统之pci资源分配
·
背景
PCIe设备枚举完成后,PCI总线号已经分配,PCIe ecam的映射、PCIe设备信息、bar的个数以及大小等已经ready,但是此时并没有给PCI device的bar、IO、mem分配资源。
这时就需要走到PCIe的资源分配流程,整个资源分配的过程就是从系统的总资源里给每个PCI device的bar分配资源。给每个PCI桥的base、limit的寄存器分配资源。其资源分配的入口是pci_bus_assign_resources()。
资源分配流程
dw_pcie_host_init
|--->pci_bus_size_bridges(__pci_bus_size_bridges)
| |--->pci_bus_size_cardbus
| |--->__pci_bus_size_bridges
| |--->pci_bridge_check_ranges
| |--->pbus_size_io
| |--->pbus_size_mem
|--->pci_bus_assign_resources ---> __pci_bus_assign_resources
__pci_bus_assign_resources
|--->pbus_assign_resources_sorted
| |--->__dev_sort_resources
| | |--->pdev_sort_resources
| |--->__assign_resources_sorted
| |--->assign_requested_resources_sorted
| |--->pci_assign_resource
| |--->_pci_assign_resource
| |--->__pci_assign_resource
| |--->pci_bus_alloc_resource
| |--->pci_bus_alloc_from_region
| |--->allocate_resource
| |--->find_resource
| |--->__request_resource
|--->__request_resource
|--->__pci_bus_assign_resources(递归回调)
|--->pci_setup_bridge
__pci_bus_assign_resources
void __pci_bus_assign_resources(const struct pci_bus *bus,
struct list_head *realloc_head,
struct list_head *fail_head)
{
struct pci_bus *b;
struct pci_dev *dev;
/* bus:0, 会对该bus上的所有device分别调用__dev_sort_resource
* 然后统一调用一个__assign_resources_sorted()。
* 之后程序进入下面的list中,又会嵌套进入__pci_bus_assign_resources,
* 这时bus:1。重复上面的过程。
* 在bus:1是__pci_bus_assign_resources
* 在处理处理完pbus_assign_resources_sorted后不回往下执行,返回上层。
* 这时bus:0, 进入pci_setup_bridge执行。
* 其中,会在pbus_assign_resources_sorted中配置BAR,
* 在__pci_setup_bridge中配mem, I/O, prefetch mem的base/limit
*/
// 最后调用pci_update_resource()将初始化pcie bar寄存器,
// 将更新的资源区间写到寄存器
// __assign_resources_sorted
// |--->assign_requested_resources_sorted
// |--->pci_assign_resource
// |--->pci_update_resource
pbus_assign_resources_sorted(bus, realloc_head, fail_head);
list_for_each_entry(dev, &bus->devices, bus_list) {
// 当前pci设备分配pcie地址空间 --- request_resource
pdev_assign_fixed_resources(dev);
b = dev->subordinate;
if (!b)
continue;
// 此处的一个小细节:
// pbus_assign_resources_sorted 函数是在此函数递归调用之前执行,
// 而 pci_setup_bridge 函数 在递归调用之后执行
// Linux PCI采用这种方式,可以保证PCI设备BAR寄存器初始化
// 是从上游 PCI总线 到 下游 PCI总线,
// 而PCI桥Base、Limit寄存器的初始化是从下游PCI总线到上游PCI总线
__pci_bus_assign_resources(b, realloc_head, fail_head);
switch (dev->hdr_type) {
case PCI_HEADER_TYPE_BRIDGE:
if (!pci_is_enabled(dev))
pci_setup_bridge(b);
break;
case PCI_HEADER_TYPE_CARDBUS:
pci_setup_cardbus(b);
break;
default:
pci_info(dev, "not setting up bridge for bus %04x:%02x\n",
pci_domain_nr(b), b->number);
break;
}
}
}
pbus_assign_resources_sorted
static void pbus_assign_resources_sorted(const struct pci_bus *bus,
struct list_head *realloc_head,
struct list_head *fail_head)
{
struct pci_dev *dev;
LIST_HEAD(head);
/*
* pci_dev->resource[]里记录有想申请的资源的大小,
* 把这些资源按对齐的要求排序
* 比如资源A要求1K地址对齐,资源B要求32地址对齐
* 那么资源A排在资源B前面, 优先分配资源A
*/
list_for_each_entry(dev, &bus->devices, bus_list) {
// 将pci设备使用的资源进行对齐和排序,然后加入到head流程中
__dev_sort_resources(dev, &head);
}
// 分配资源
__assign_resources_sorted(&head, realloc_head, fail_head);
}
__dev_sort_resources
static void __dev_sort_resources(struct pci_dev *dev, struct list_head *head)
{
u16 class = dev->class >> 8;
/* Don't touch classless devices or host bridges or IOAPICs */
if (class == PCI_CLASS_NOT_DEFINED || class == PCI_CLASS_BRIDGE_HOST)
return;
/* Don't touch IOAPIC devices already enabled by firmware */
if (class == PCI_CLASS_SYSTEM_PIC) {
u16 command;
pci_read_config_word(dev, PCI_COMMAND, &command);
if (command & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY))
return;
}
/* 把pci_dev中的resource[]从大到小排序, 存入链表head中 */
pdev_sort_resources(dev, head);
}
__assign_resources_sorted
static void __assign_resources_sorted(struct list_head *head,
struct list_head *realloc_head,
struct list_head *fail_head)
{
/*
* Should not assign requested resources at first. They could be
* adjacent, so later reassign can not reallocate them one by one in
* parent resource window.
*
* Try to assign requested + add_size at beginning. If could do that,
* could get out early. If could not do that, we still try to assign
* requested at first, then try to reassign add_size for some resources.
*
* Separate three resource type checking if we need to release
* assigned resource after requested + add_size try.
*
* 1. If IO port assignment fails, will release assigned IO
* port.
* 2. If pref MMIO assignment fails, release assigned pref
* MMIO. If assigned pref MMIO's parent is non-pref MMIO
* and non-pref MMIO assignment fails, will release that
* assigned pref MMIO.
* 3. If non-pref MMIO assignment fails or pref MMIO
* assignment fails, will release assigned non-pref MMIO.
*/
LIST_HEAD(save_head);
LIST_HEAD(local_fail_head);
struct pci_dev_resource *save_res;
struct pci_dev_resource *dev_res, *tmp_res, *dev_res2;
unsigned long fail_type;
resource_size_t add_align, align;
/* Check if optional add_size is there */
/* 因为realloc_head为空链表,所以直接到requested_and_reassign */
if (!realloc_head || list_empty(realloc_head))
goto requested_and_reassign;
/* Save original start, end, flags etc at first */
list_for_each_entry(dev_res, head, list) {
if (add_to_list(&save_head, dev_res->dev, dev_res->res, 0, 0)) {
free_list(&save_head);
goto requested_and_reassign;
}
}
......
requested_and_reassign:
/* Satisfy the must-have resource requests */
assign_requested_resources_sorted(head, fail_head);
/* Try to satisfy any additional optional resource requests */
if (realloc_head)
reassign_resources_sorted(realloc_head, head);
free_list(head);
}
assign_requested_resources_sorted
/**
* assign_requested_resources_sorted() - Satisfy resource requests
*
* @head: Head of the list tracking requests for resources
* @fail_head: Head of the list tracking requests that could not be
* allocated
*
* Satisfy resource requests of each element in the list. Add requests that
* could not be satisfied to the failed_list.
*/
static void assign_requested_resources_sorted(struct list_head *head,
struct list_head *fail_head)
{
struct resource *res;
struct pci_dev_resource *dev_res;
int idx;
/*
* dev_res(pci_dev_resource)存储一个device中一个配置空间
* 的资源(一个设备可有多个mem或I/O配置空间)
*/
list_for_each_entry(dev_res, head, list) {
res = dev_res->res;
idx = res - &dev_res->dev->resource[0];
if (resource_size(res) &&
pci_assign_resource(dev_res->dev, idx)) {
if (fail_head) {
/*
* If the failed resource is a ROM BAR and
* it will be enabled later, don't add it
* to the list.
*/
if (!((idx == PCI_ROM_RESOURCE) &&
(!(res->flags & IORESOURCE_ROM_ENABLE))))
add_to_list(fail_head,
dev_res->dev, res,
0 /* don't care */,
0 /* don't care */);
}
reset_resource(res);
}
}
}
pci_assign_resource
int pci_assign_resource(struct pci_dev *dev, int resno)
{
struct resource *res = dev->resource + resno;
resource_size_t align, size;
int ret;
if (res->flags & IORESOURCE_PCI_FIXED)
return 0;
res->flags |= IORESOURCE_UNSET;
align = pci_resource_alignment(dev, res);
if (!align) {
pci_info(dev, "BAR %d: can't assign %pR (bogus alignment)\n",
resno, res);
return -EINVAL;
}
size = resource_size(res);
// 分配地址空间
//_pci_assign_resource
// |--->__pci_assign_resource
// |--->pci_bus_alloc_resource
// |--->pci_bus_alloc_from_region
// |--->allocate_resource
// |--->find_resource
// |--->__find_resource
ret = _pci_assign_resource(dev, resno, size, align);
/*
* If we failed to assign anything, let's try the address
* where firmware left it. That at least has a chance of
* working, which is better than just leaving it disabled.
*/
if (ret < 0) {
pci_info(dev, "BAR %d: no space for %pR\n", resno, res);
ret = pci_revert_fw_address(res, dev, resno, size);
}
if (ret < 0) {
pci_info(dev, "BAR %d: failed to assign %pR\n", resno, res);
return ret;
}
res->flags &= ~IORESOURCE_UNSET;
res->flags &= ~IORESOURCE_STARTALIGN;
pci_info(dev, "BAR %d: assigned %pR\n", resno, res);
// 将初始化pcie bar寄存器,将更新的资源区间写到寄存器,把对应的PCI地址写入BAR
if (resno < PCI_BRIDGE_RESOURCES)
pci_update_resource(dev, resno);
return 0;
}
_pci_assign_resource
static int _pci_assign_resource(struct pci_dev *dev, int resno,
resource_size_t size, resource_size_t min_align)
{
struct pci_bus *bus;
int ret;
bus = dev->bus;
while ((ret = __pci_assign_resource(bus, dev, resno, size, min_align))) {
if (!bus->parent || !bus->self->transparent)
break;
bus = bus->parent;
}
return ret;
}
static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev,
int resno, resource_size_t size, resource_size_t align)
{
struct resource *res = dev->resource + resno;
resource_size_t min;
int ret;
min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM;
/*
* First, try exact prefetching match. Even if a 64-bit
* prefetchable bridge window is below 4GB, we can't put a 32-bit
* prefetchable resource in it because pbus_size_mem() assumes a
* 64-bit window will contain no 32-bit resources. If we assign
* things differently than they were sized, not everything will fit.
*/
ret = pci_bus_alloc_resource(bus, res, size, align, min,
IORESOURCE_PREFETCH | IORESOURCE_MEM_64,
pcibios_align_resource, dev);
if (ret == 0)
return 0;
/*
* If the prefetchable window is only 32 bits wide, we can put
* 64-bit prefetchable resources in it.
*/
if ((res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) ==
(IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) {
ret = pci_bus_alloc_resource(bus, res, size, align, min,
IORESOURCE_PREFETCH,
pcibios_align_resource, dev);
if (ret == 0)
return 0;
}
/*
* If we didn't find a better match, we can put any memory resource
* in a non-prefetchable window. If this resource is 32 bits and
* non-prefetchable, the first call already tried the only possibility
* so we don't need to try again.
*/
if (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64))
ret = pci_bus_alloc_resource(bus, res, size, align, min, 0,
pcibios_align_resource, dev);
return ret;
}
/**
* pci_bus_alloc_resource - allocate a resource from a parent bus
* @bus: PCI bus
* @res: resource to allocate
* @size: size of resource to allocate
* @align: alignment of resource to allocate
* @min: minimum /proc/iomem address to allocate
* @type_mask: IORESOURCE_* type flags
* @alignf: resource alignment function
* @alignf_data: data argument for resource alignment function
*
* Given the PCI bus a device resides on, the size, minimum address,
* alignment and type, try to find an acceptable resource allocation
* for a specific device resource.
*/
int pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res,
resource_size_t size, resource_size_t align,
resource_size_t min, unsigned long type_mask,
resource_size_t (*alignf)(void *,
const struct resource *,
resource_size_t,
resource_size_t),
void *alignf_data)
{
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
int rc;
if (res->flags & IORESOURCE_MEM_64) {
rc = pci_bus_alloc_from_region(bus, res, size, align, min,
type_mask, alignf, alignf_data,
&pci_high);
if (rc == 0)
return 0;
return pci_bus_alloc_from_region(bus, res, size, align, min,
type_mask, alignf, alignf_data,
&pci_64_bit);
}
#endif
return pci_bus_alloc_from_region(bus, res, size, align, min,
type_mask, alignf, alignf_data,
&pci_32_bit);
}
static int pci_bus_alloc_from_region(struct pci_bus *bus, struct resource *res,
resource_size_t size, resource_size_t align,
resource_size_t min, unsigned long type_mask,
resource_size_t (*alignf)(void *,
const struct resource *,
resource_size_t,
resource_size_t),
void *alignf_data,
struct pci_bus_region *region)
{
int i, ret;
struct resource *r, avail;
resource_size_t max;
type_mask |= IORESOURCE_TYPE_BITS;
pci_bus_for_each_resource(bus, r, i) {
resource_size_t min_used = min;
if (!r)
continue;
/* type_mask must match */
if ((res->flags ^ r->flags) & type_mask)
continue;
/* We cannot allocate a non-prefetching resource
from a pre-fetching area */
if ((r->flags & IORESOURCE_PREFETCH) &&
!(res->flags & IORESOURCE_PREFETCH))
continue;
avail = *r;
pci_clip_resource_to_region(bus, &avail, region);
/*
* "min" is typically PCIBIOS_MIN_IO or PCIBIOS_MIN_MEM to
* protect badly documented motherboard resources, but if
* this is an already-configured bridge window, its start
* overrides "min".
*/
if (avail.start)
min_used = avail.start;
max = avail.end;
/* Ok, try it out.. */
ret = allocate_resource(r, res, size, min_used, max,
align, alignf, alignf_data);
if (ret == 0)
return 0;
}
return -ENOMEM;
}
/**
* allocate_resource - allocate empty slot in the resource tree given range & alignment.
* The resource will be reallocated with a new size if it was already allocated
* @root: root resource descriptor
* @new: resource descriptor desired by caller
* @size: requested resource region size
* @min: minimum boundary to allocate
* @max: maximum boundary to allocate
* @align: alignment requested, in bytes
* @alignf: alignment function, optional, called if not NULL
* @alignf_data: arbitrary data to pass to the @alignf function
*/
int allocate_resource(struct resource *root, struct resource *new,
resource_size_t size, resource_size_t min,
resource_size_t max, resource_size_t align,
resource_size_t (*alignf)(void *,
const struct resource *,
resource_size_t,
resource_size_t),
void *alignf_data)
{
int err;
struct resource_constraint constraint;
if (!alignf)
alignf = simple_align_resource;
constraint.min = min;
constraint.max = max;
constraint.align = align;
constraint.alignf = alignf;
constraint.alignf_data = alignf_data;
if ( new->parent ) {
/* resource is already allocated, try reallocating with
the new constraints */
return reallocate_resource(root, new, size, &constraint);
}
write_lock(&resource_lock);
err = find_resource(root, new, size, &constraint);
if (err >= 0 && __request_resource(root, new))
err = -EBUSY;
write_unlock(&resource_lock);
return err;
}
pci_update_resource
void pci_update_resource(struct pci_dev *dev, int resno)
{
if (resno <= PCI_ROM_RESOURCE)
pci_std_update_resource(dev, resno);
#ifdef CONFIG_PCI_IOV
else if (resno >= PCI_IOV_RESOURCES && resno <= PCI_IOV_RESOURCE_END)
pci_iov_update_resource(dev, resno);
#endif
}
static void pci_std_update_resource(struct pci_dev *dev, int resno)
{
struct pci_bus_region region;
bool disable;
u16 cmd;
u32 new, check, mask;
int reg;
struct resource *res = dev->resource + resno;
/* Per SR-IOV spec 3.4.1.11, VF BARs are RO zero */
if (dev->is_virtfn)
return;
/*
* Ignore resources for unimplemented BARs and unused resource slots
* for 64 bit BARs.
*/
if (!res->flags)
return;
if (res->flags & IORESOURCE_UNSET)
return;
/*
* Ignore non-moveable resources. This might be legacy resources for
* which no functional BAR register exists or another important
* system resource we shouldn't move around.
*/
if (res->flags & IORESOURCE_PCI_FIXED)
return;
/*
* 在region资源中查找和分配的资源匹配类型并且包含设备需要的资源
* 把CPU地址转换为PCI地址: PCI地址 = CPU地址 - offset 写入BAR
*/
pcibios_resource_to_bus(dev->bus, ®ion, res);
new = region.start;
if (res->flags & IORESOURCE_IO) {
mask = (u32)PCI_BASE_ADDRESS_IO_MASK;
new |= res->flags & ~PCI_BASE_ADDRESS_IO_MASK;
} else if (resno == PCI_ROM_RESOURCE) {
mask = PCI_ROM_ADDRESS_MASK;
} else {
mask = (u32)PCI_BASE_ADDRESS_MEM_MASK;
new |= res->flags & ~PCI_BASE_ADDRESS_MEM_MASK;
}
if (resno < PCI_ROM_RESOURCE) {
reg = PCI_BASE_ADDRESS_0 + 4 * resno;
} else if (resno == PCI_ROM_RESOURCE) {
/*
* Apparently some Matrox devices have ROM BARs that read
* as zero when disabled, so don't update ROM BARs unless
* they're enabled. See https://lkml.org/lkml/2005/8/30/138.
*/
if (!(res->flags & IORESOURCE_ROM_ENABLE))
return;
reg = dev->rom_base_reg;
new |= PCI_ROM_ADDRESS_ENABLE;
} else
return;
/*
* We can't update a 64-bit BAR atomically, so when possible,
* disable decoding so that a half-updated BAR won't conflict
* with another device.
*/
disable = (res->flags & IORESOURCE_MEM_64) && !dev->mmio_always_on;
if (disable) {
pci_read_config_word(dev, PCI_COMMAND, &cmd);
pci_write_config_word(dev, PCI_COMMAND,
cmd & ~PCI_COMMAND_MEMORY);
}
// 更新 resource 的低 32bit 到 PCI_BASE_ADDRESS_0 + 4 * resno
pci_write_config_dword(dev, reg, new);
pci_read_config_dword(dev, reg, &check);
if ((new ^ check) & mask) {
pci_err(dev, "BAR %d: error updating (%#08x != %#08x)\n",
resno, new, check);
}
// 更新 MEM64 的高 32bit 到 PCI_BASE_ADDRESS_0 + 4 * resno + 4
if (res->flags & IORESOURCE_MEM_64) {
new = region.start >> 16 >> 16;
pci_write_config_dword(dev, reg + 4, new);
pci_read_config_dword(dev, reg + 4, &check);
if (check != new) {
pci_err(dev, "BAR %d: error updating (high %#08x != %#08x)\n",
resno, new, check);
}
}
if (disable)
pci_write_config_word(dev, PCI_COMMAND, cmd);
}
小结
总而言之,pcie的资源枚举过程可以概况如下:
- 获取上游pci 桥设备所管理的系统资源范围
- 使用DFS(深度优先搜索)对所有的pci ep device进行bar资源的分配
- 使用DFS(深度优先搜索)对当前pci桥设备的base和limit的值,并对这些寄存器进行更新。
至此,pci树中所有pci设备的BAR寄存器,以及pci桥的base、limit寄存器都已经初始化完毕。
更多推荐


所有评论(0)