linux PCI/PCIe驱动之assign_requested_resources_sorted的理解
1 assign_requested_resources_sorted
该函数是把要申请的资源, 按照对齐要求排序,最后从region中分配资源,配置到对应的PCI/PCIe设备的BAR配置空间中。 assign_requested_resources_sorted函数会调用pci_assign_resource函数,在pci_assign_resource函数中有两个需要关注的重要的函数,_pci_assign_resource和pci_update_resource,其调用过程,和对这两个函数的简单解释如下所示:
1.1 从初始化到资源配置的调用过程
|- pci_bus_size_bridges(bus);
|- pci_bus_assign_resources(bus);
|- __pci_bus_assign_resources
|- pbus_assign_resources_sorted
/* pci_dev->resource[]里记录有想申请的资源的大小,
* 把这些资源按对齐的要求排序
* 比如资源A要求1K地址对齐,资源B要求32地址对齐
* 那么资源A排在资源B前面, 优先分配资源A
*/
list_for_each_entry(dev, &bus->devices, bus_list)
__dev_sort_resources(dev, &head);
// 分配资源
|- __assign_resources_sorted
|- assign_requested_resources_sorted(head, &local_fail_head);
|- pci_assign_resource
|- ret = _pci_assign_resource(dev, resno, size, align);
// 分配地址空间
|- __pci_assign_resource
|- pci_bus_alloc_resource
|- pci_bus_alloc_from_region
/* Ok, try it out.. */
|- ret = allocate_resource(r, res, size, ...);
|- err = find_resource(root, new, size,...);
|- __find_resource
// 从资源链表中分配地址空间
// 设置pci_dev->resource[]
new->start = alloc.start;
new->end = alloc.end;
// 把对应的PCI地址写入BAR
|- pci_update_resource(dev, resno);
|- pci_std_update_resource
/* 把CPU地址转换为PCI地址: PCI地址 = CPU地址 - offset
* 写入BAR
*/
pcibios_resource_to_bus(dev->bus, ®ion, res);
new = region.start;
reg = PCI_BASE_ADDRESS_0 + 4 * resno;
pci_write_config_dword(dev, reg, new);
1.2 _pci_assign_resource
在_pci_assign_resource函数中,最终要的函数是__find_resource,该函数在资源树中找到具有给定范围和对齐约束的资源。
1.2.1 调用过程
assign_requested_resources_sorted(head, &local_fail_head);
pci_assign_resource
ret = _pci_assign_resource(dev, resno, size, align);
// 分配地址空间
__pci_assign_resource
pci_bus_alloc_resource
pci_bus_alloc_from_region
/* Ok, try it out.. */
ret = allocate_resource(r, res, size, ...);
err = find_resource(root, new, size,...);
__find_resource
// 从资源链表中分配地址空间
// 设置pci_dev->resource[]
new->start = alloc.start;
new->end = alloc.end;
1.2.2 __find_resource函数实现
static int __find_resource(struct resource *root, struct resource *old,
struct resource *new,
resource_size_t size,
struct resource_constraint *constraint)
{
struct resource *this = root->child;
struct resource tmp = *new, avail, alloc;
tmp.start = root->start;
if (this && this->start == root->start) {
tmp.start = (this == old) ? old->start : this->end + 1;
this = this->sibling;
}
for(;;) {
if (this)
tmp.end = (this == old) ? this->end : this->start - 1;
else
tmp.end = root->end;
if (tmp.end < tmp.start)
goto next;
resource_clip(&tmp, constraint->min, constraint->max);
arch_remove_reservations(&tmp);
avail.start = ALIGN(tmp.start, constraint->align);
avail.end = tmp.end;
avail.flags = new->flags & ~IORESOURCE_UNSET;
if (avail.start >= tmp.start) {
alloc.flags = avail.flags;
alloc.start = constraint->alignf(constraint->alignf_data, &avail,
size, constraint->align);
alloc.end = alloc.start + size - 1;
if (alloc.start <= alloc.end &&
resource_contains(&avail, &alloc)) {
new->start = alloc.start;
new->end = alloc.end;
return 0;
}
}
next: if (!this || this->end == root->end)
break;
if (this != old)
tmp.start = this->end + 1;
this = this->sibling;
}
return -EBUSY;
}
1.3 pci_update_resource
pci_update_resource 函数最重要的功能是把前面在_pci_assign_resource 函数处理流程中最终通过调用pci_std_update_resource 函数把给设备分配的资源配置到对应的设备BAR空间中。
1.3.1 调用过程
assign_requested_resources_sorted(head, &local_fail_head);
pci_assign_resource
pci_update_resource(dev, resno);
pci_std_update_resource
pcibios_resource_to_bus(dev->bus, ®ion, res);
new = region.start;
reg = PCI_BASE_ADDRESS_0 + 4 * resno;
pci_write_config_dword(dev, reg, new);
1.3.2 pci_std_update_resource函数实现
在pci_std_update_resource函数中,需要重点关注两部分
- pcibios_resource_to_bus该函数是在region资源中查找和分配的资源匹配类型并且包含设备需要的资源。
- 将前面分配的资源的起始地址写入设备对应的BAR寄存器空间中
- reg = PCI_BASE_ADDRESS_0 + 4 * resno;
- pci_write_config_dword(dev, reg, new);
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;
if (dev->is_virtfn)
return;
if (!res->flags)
return;
if (res->flags & IORESOURCE_UNSET)
return;
if (res->flags & IORESOURCE_PCI_FIXED)
return;
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) {
if (!(res->flags & IORESOURCE_ROM_ENABLE))
return;
reg = dev->rom_base_reg;
new |= PCI_ROM_ADDRESS_ENABLE;
} else
return;
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);
}
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);
}
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);
}
|