1、地址资源管理
所有的地址资源都是以struct resource的树形结构管理。
内核维护所有的IO地址空间使用ioport_resource和iomem_resource,一个表示IO PORT资源,一个表示memory资源(包括MMIO),这两个结构体会记录所有可用的资源。这两个结构体的定义就在kernel/resouce.c中。
QEMU里也是有address_space_mem和address_space_io两个全局的MemoryRegion,思路是类似的。
对这两个顶层资源的结构,是采用insert_resouce和remove_resouce,分别实现:注册资源和释放资源。
我们执行 cat /proc/iomem或者cat /proc/ioport可以看到地址资源列表,这就是从iomem_resource和ioport_resource获得的。
[root@localhost ~]# cat /proc/iomem | grep RAM
00001000-0003dfff : System RAM
0003f000-0009ffff : System RAM
00100000-5c6e6017 : System RAM
5c6e6018-5c6e9e57 : System RAM
5c6e9e58-5c6ea017 : System RAM
5c6ea018-5c6ff057 : System RAM
5c6ff058-5c700017 : System RAM
5c700018-5c708057 : System RAM
5c708058-5eadcfff : System RAM
5eade000-6a23bfff : System RAM
777ff000-777fffff : System RAM
100000000-1007fffffff : System RAM
[root@localhost ~]# lsmem
RANGE SIZE STATE REMOVABLE BLOCK
0x0000000000000000-0x000000007fffffff 2G online yes 0
0x0000000100000000-0x000001007fffffff 1022G online yes 2-512
上图是我们查看/proc/iomem中的RAM空间和lsmem看到的RAM空间。
可以看到/proc/iomem中的前2G拆分成了很多空洞的细碎空间。这个还不清楚是什么原因。后面的1022G是完全吻合的。
2、PCI设备的MMIO资源是从哪里来的呢?
首先所有的PCI设备包括需要动态申请资源的PCI设备(热插拔),都是要从root bus上获取资源。
所有的PCI设备也都是以一个树形结构管理的。
RootComplex上直接挂载的是PCI host bdridge,查看内核启动日志,可以看到这样的信息。这些打印是在pci_register_host_bridge函数中打印出来的。
pci_host_common_probe
of_device_get_match_data
devm_pci_alloc_host_bridge //申请和初始化pci host bridge结构
gen_pci_init
pci_host_probe
pci_scan_root_bus_bridge
pci_register_host_bridge //资源打印在这里
24-03-14 12:54:19.656742 [ 24.271751] pci_bus 0000:00: root bus resource [io 0x0000-0x0cf7 window]
24-03-14 12:54:19.656750 [ 24.279750] pci_bus 0000:00: root bus resource [io 0x1000-0x3fff window]
24-03-14 12:54:19.668220 [ 24.283750] pci_bus 0000:00: root bus resource [mem 0x000a0000-0x000bffff window]
24-03-14 12:54:19.668230 [ 24.295750] pci_bus 0000:00: root bus resource [mem 0x000c8000-0x000cffff window]
24-03-14 12:54:19.668233 [ 24.303750] pci_bus 0000:00: root bus resource [mem 0xfe010000-0xfe010fff window]
24-03-14 12:54:19.706467 [ 24.311750] pci_bus 0000:00: root bus resource [mem 0x90040000-0x957fffff window]
24-03-14 12:54:19.706490 [ 24.319751] pci_bus 0000:00: root bus resource [mem 0x200000000000-0x200fffffffff window]
24-03-14 12:54:19.706493 [ 24.327751] pci_bus 0000:00: root bus resource [bus 00-0b]
24-03-14 12:54:20.557167 [ 24.923751] pci_bus 0000:0c: root bus resource [io 0x4000-0x5fff window]
24-03-14 12:54:20.557169 [ 24.931750] pci_bus 0000:0c: root bus resource [mem 0x95800000-0x9f7fffff window]
24-03-14 12:54:20.566733 [ 24.939751] pci_bus 0000:0c: root bus resource [mem 0x201000000000-0x201fffffffff window]
24-03-14 12:54:20.566741 [ 24.951751] pci_bus 0000:0c: root bus resource [bus 0c-5b]
每个host bridge下面都是以一个名字为windows的list_head来维护所有的资源。所以我们需要看一下在哪里向windows链表插入了资源。
pci_host_common_probe
devm_pci_alloc_host_bridge
devm_of_pci_bridge_init
devm_of_pci_get_host_bridge_resources
of_pci_range_parser_init(&parser, dev_node); //从dev_node获取resource信息
for_each_of_pci_range //遍历所有的resource
pci_add_resource_offset(resources, res, res->start - range.pci_addr); //添加到resources链表。resources为传入的bdridge->windows指针
所以bridge->windows链表中的资源是从设备dev_node中获取的。具体的代码如下。看来dev_node是有一个属性列表的,这跟QEMU的设备实现也很像。在生成pci bridge的windows链表时,会选择名称为“ranges”的property。
static int parser_init(struct of_pci_range_parser *parser,
struct device_node *node, const char *name)
{
int rlen;
parser->node = node;
parser->pna = of_n_addr_cells(node);
parser->na = of_bus_n_addr_cells(node);
parser->ns = of_bus_n_size_cells(node);
parser->dma = !strcmp(name, "dma-ranges");
parser->bus = of_match_bus(node);
parser->range = of_get_property(node, name, &rlen); //获取"ranges"属性
if (parser->range == NULL)
return -ENOENT;
parser->end = parser->range + rlen / sizeof(__be32);
return 0;
}
int of_pci_range_parser_init(struct of_pci_range_parser *parser,
struct device_node *node)
{
return parser_init(parser, node, "ranges");
}
但是,参见《【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)_xilinx pcie驱动-CSDN博客》,devm_of_pci_get_host_bridge_resources这里并不会获取真正的资源,而是:
解析rc设备树中的bus-ranges属性和ranges属性,由于rc设备树中未定义bus-ranges属性,所以使用默认的bus范围,即0~0xff。ranges属性解析出来就是将pci地址空间0地址映射到CPU地址空间0xA0000000地址,大小为0x10000000。
3、MMIO资源分配
devm_request_pci_bus_resources
linux系统使用资源树的方式管理所有可用资源,所有的子系统使用某个资源区域之前都应向资源树申请。在devm_of_pci_get_host_bridge_resources函数中解析了RC的设备树,获取了pci bus区间和pci空间映射的CPU地址空间,在使用这两个资源区间之前,需要向资源树申请,由系统检查资源区间是否被占用,若两个资源区间未被占用,则资源能申请成功,并将这两个资源区间标记被RC占用。
pci_scan_root_bus_bridge
将RC注册为host bridge,然后扫描RC下的pcie设备和桥,为它们创建对应结构并获取它们的信息填充到结构中。注意:该函数只会给pcie桥分配bus号,而不会给pcie设备分配memory space和I/O space。
pci_assign_unassigned_bus_resources
为RC下的pcie设备分配可用的资源。
pci_bus_add_devices
让pcie设备和桥挨个匹配pci总线上的驱动,若有驱动匹配成功,则先执行pci总线的probe函数(pci_device_probe),然后再执行设备驱动的probe函数
devm_request_pci_bus_resources接口也会在devm_of_pci_get_host_bridge_resources调用的。
int devm_request_pci_bus_resources(struct device *dev,
struct list_head *resources)
{
struct resource_entry *win;
struct resource *parent, *res;
int err;
resource_list_for_each_entry(win, resources) {
res = win->res;
switch (resource_type(res)) {
case IORESOURCE_IO:
parent = &ioport_resource;
break;
case IORESOURCE_MEM:
parent = &iomem_resource;
break;
default:
continue;
}
err = devm_request_resource(dev, parent, res);
//从ioport_resource或iomem_resource中申请资源(名为申请,实为注册。判断是否conflict,然后添加到ioemu/port_resource的child链表中)
if (err)
return err;
}
return 0;
}
4、pci-root桥是通过acpi创建的
pci-root桥都是通过acpi表创建的,资源也是通过acpi表中记录的信息来使能。
添加pci root桥的接口如下acpi_pci_root_add,然后调用
acpi_pci_root_add
root->bus = pci_acpi_scan_root(root);
bus = acpi_pci_root_create(root, &acpi_pci_root_ops,
&info->common, &info->sd);
struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root,
struct acpi_pci_root_ops *ops,
struct acpi_pci_root_info *info,
void *sysdata)
{
ret = acpi_pci_probe_root_resources(info); //解析acpi表项,生成info->resources资源
pci_acpi_root_add_resources(info); //将info->resources中的资源添加到系统资源列表中iomem_resource/ioport_resource
bus = pci_create_root_bus(NULL, busnum, ops->pci_ops,
sysdata, &info->resources); //创建pci_host_bridge,传入info->resources资源
}
遍历adev->handle中的资源,添加到info->resources链表中。
acpi_pci_probe_root_resource ---->
static int __acpi_dev_get_resources(struct acpi_device *adev,
struct list_head *list,
int (*preproc)(struct acpi_resource *, void *),
void *preproc_data, char *method)
{
struct res_proc_context c;
acpi_status status;
if (!adev || !adev->handle || !list_empty(list))
return -EINVAL;
if (!acpi_has_method(adev->handle, method))
return 0;
c.list = list;
c.preproc = preproc;
c.preproc_data = preproc_data;
c.count = 0;
c.error = 0;
status = acpi_walk_resources(adev->handle, method,
acpi_dev_process_resource, &c); //从adev->handle中获取资源列表,添加到list链表中
if (ACPI_FAILURE(status)) {
acpi_dev_free_resource_list(list);
return c.error ? c.error : -EIO;
}
return c.count;
}
pci_create_root_bus则注册和初始化了一个pci_host_bridge结构,而bridge->windows链表就是从入参info->resources中获取的。
struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
struct pci_ops *ops, void *sysdata, struct list_head *resources)
{
int error;
struct pci_host_bridge *bridge;
bridge = pci_alloc_host_bridge(0);
if (!bridge)
return NULL;
bridge->dev.parent = parent;
list_splice_init(resources, &bridge->windows); //把resourses链表中的内容添加到bridge->windows,所以bridge->windows在此处生成
bridge->sysdata = sysdata;
bridge->busnr = bus;
bridge->ops = ops;
error = pci_register_host_bridge(bridge);
if (error < 0)
goto err_out;
return bridge->bus;
err_out:
put_device(&bridge->dev);
return NULL;
}