内核物理地址资源管理

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;
}

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值