内核解析dtb 文件,并构建树状devide_node 的过程
本节将探究以下问题:
- dtb被编译好传给内核之后,内核是如何解析的?解析的结果是什么?
- device_node 是在哪里被alloc 的?被填充了哪些内容?
/* 在 drivers/of/base.c 中,创建了一个全局的指针*/
struct device_node *of_root;
EXPORT_SYMBOL(of_root);
start_kernel()
/*arch/arm/kernel/setup.c*/
setup_arch(char **cmdline_p)
/*
* 这里的__atags_pointer 是定义在arch/arm/kernel/head-common.S 中:
* .long processor_id @ r0
* .long __machine_arch_type @ r1
* .long __atags_pointer @ r2
* 保存了uboot传给内核的第三个参数,也就是启动参数所在物理地址
*/
mdesc = setup_machine_fdt(__atags_pointer);
dt_phys = __atags_pointer;
early_init_dt_verify(phys_to_virt(dt_phys));
params = phys_to_virt(dt_phys);
/*
* 重点!从这里可以看到initial_boot_params 就是启动参数,也就是设备树所在地址。
* 后边这个参数都是直接拿来就用
*/
initial_boot_params = params;
unflatten_device_tree()
/*
* drivers/of/fdt.c 看起来下边这个函数给of_root 赋了值
* initial_boot_params:dtb 所在地址
* of_root:解析完的设备树根节点就保存在这里
* early_init_dt_alloc_memory_arch: An allocator that provides a virtual address to memory
* for the resulting tree
* 看起来是个内存分配的函数。可能是因为此时kmalloc还不能用?
*/
__unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);
先将设备树扫描一遍,得到需要分配的内存大小,然后分配一块内存mem
/*下边这里真正开始unflattern*/
unflatten_dt_nodes(initial_boot_params, mem, NULL, &of_root);
for (offset = 0;
offset >= 0 && depth >= initial_depth;
offset = fdt_next_node(blob, offset, &depth)) {
/*
* 核心在于下边这个函数populate_node,会从blob+offset 的位置解析一个dts节点出来,创建相应的
* device_node。然后填充device_node,并赋值给 nps[depth+1]。
* 其中,新建的device_node *np:
* np->parent = dad;
* np->sibling = dad->child;
* dad->child = np;
* 即,当前创建节点(np)的大哥(sibling)指向父亲(dad)上次刚出生的孩子(dad->child),这样就把所
* 有的device_node 串了起来。其中,每个节点能直接找到的孩子(np->child)都是最晚出生(创建)的那
* 个老小,其他的孩子必须通过这个老小的sibling指针链式查找。为了直观,画了一个device_node
* 层级关系图。
*
* 值得注意的是,np的name和device_type直接是结构体device_node的属性,而compatible和其他所有
* 的属性是作为 propertie保存在device_node中的。np->name 对应dts中的name属性(而非
* compatible)。但是通常dts节点并不存在name属性,所以np->name 一般为空。device_type 也是一样
*/
if (!populate_node(blob, offset, &mem, nps[depth],
&nps[depth+1], dryrun))
return mem - base;
if (!dryrun && nodepp && !*nodepp)
*nodepp = nps[depth+1];
if (!dryrun && !root)
root = nps[depth+1];
}
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
unittest_unflatten_overlay_base();
device_node 的层级关系详解
这里把devide_node 的层级关系单独拿出来研究一下,前面已经提到“每个节点能直接找到的孩子(np->child)都是最晚出生(创建)的那个老小,其他的孩子必须通过这个老小的sibling指针链式查找。为了验证这个猜测,我们可以看一下of_platform_populate 函数中是如何遍历所有device_node的
/* root就是device_node 根节点*/
of_platform_populate(root, of_default_bus_match_table, lookup, parent);
for_each_child_of_node(root, child)
for (child = of_get_next_child(parent, NULL); child != NULL; child = of_get_next_child(parent, child))
/*
* 下边这个函数是重点,这是个递归函数,其中还存在一个遍历每个child的过程。
* 遍历个过程就是先父辈,再子辈。同辈内先年纪从小到遍历。其实就是按照dts 从上到下的顺序遍历的。
* 在drivers/of/platform.c 中定义DEBUG宏就能看到 “create child: %pOF” 的打印。
*/
of_platform_bus_create(child, matches, lookup, parent, true);
of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
for_each_child_of_node(bus, child)
pr_debug(" create child: %pOF\n", child);
of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
/*另起一行看一下of_get_next_child 的实现*/
of_get_next_child(parent, child)
node = parent, prev = child;
__of_get_next_child(node, prev)
/*
* 核心就是下边这一行了
* 1. node == np_dad,prev = NULL时,next=node->child,也就是拿到了图中的np3
* 2. 再次调用时,prev == np3,,next=np3->sibling,也就是拿到np2
* 3. 重复2,到prev == np0时,next=NULL
*/
next = prev ? prev->sibling : node->child;
for (; next; next = next->sibling)
if (of_node_get(next))
break;
of_node_put(prev);
return next;
PS: 上边的pr_debug(” create child: %pOF\n”, child); 打印把我看懵了,本以为打出来的是个地址,但实际上打出来这个:
[ 0.199217] OF: create child: /soc/aips-bus@2000000
[ 0.199580] OF: create child: /soc/aips-bus@2000000/spba-bus@2000000
查了下文档,才知道%p有这么多用法
platform_device 创建过程
在自己写的platform driver驱动中,例如下边这个
static int spi_imx_probe(struct platform_device *pdev){...}
static struct platform_driver spi_imx_driver = {
.probe = spi_imx_probe,
...
};
platform_driver的probe 函数是有一个入参struct platform_device *pdev的,这玩意儿哪来的?谁alloc出来的?其中填充了什么内容?本节将一探究竟。
start_kernel // init/main.c
rest_init();
kernel_thread(kernel_init, NULL, CLONE_FS);
kernel_init
kernel_init_freeable();
do_basic_setup();
do_initcalls();
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level); // 比如 do_initcall_level(3)
for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
do_one_initcall(initcall_from_entry(fn));//arch_initcall_sync ,即段“.init”声明的函数
/* drivers/of/platform.c */
arch_initcall_sync(of_platform_default_populate_init);
of_platform_default_populate_init(void)
/* Populate everything else. */
of_platform_default_populate(NULL, NULL, NULL);
/*
这里关注of_default_bus_match_table的定义,会发现.compatible = "simple-bus"与dts中的soc的compatible一致。
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", },
{ .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
{} /* Empty terminated list */
};
*/
of_platform_populate(root, of_default_bus_match_table, lookup, parent);
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
}
其中,of_platform_bus_create 是一个递归函数,会遍历所有child node,逐一创建dev。创建时,会忽略掉几类node,比如没有compatible 属性的等。最终调用of_platform_device_create_pdata 函数来创建节点
/* drivers/of/platform.c */
/**
* of_platform_device_create_pdata - Alloc, initialize and register an of_device
* @np: pointer to node to create device for
* @bus_id: name to assign device
* @platform_data: pointer to populate platform_data pointer with
* @parent: Linux device model parent device.
*
* Returns pointer to created platform device, or NULL if a device was not
* registered. Unavailable devices will not get registered.
*/
static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,
void *platform_data, struct device *parent)
struct platform_device *dev;
/* 这里分配pdev 的时候,就已经把pdev->dev 的of_node, res, parent等参数配置好了 */
dev = of_device_alloc(np, bus_id, parent);
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
/* 挂载到platform_bus 上,等待与platform_driver 相匹配 */
dev->dev.bus = &platform_bus_type;
/* 这里的platform_data 貌似是空的*/
dev->dev.platform_data = platform_data;
/* 貌似是这中中断:Message Signaled Interrupts */
of_msi_configure(&dev->dev, dev->dev.of_node);
/* 这里将创建好的dev 添加到内核 */
of_device_add(dev);
还是有必要单独看一下of_device_alloc 和of_device_add 这两个函数
/*
这个函数的主要工作有:
1. 分配一个空的pdev,并将一些资源赋值给它
a. 为每个寄存器地址和中断分配res
b. of_node, parent 赋值
c. 利用bus id 命名,类似spi1这样?
*/
struct platform_device *of_device_alloc(struct device_node *np, const char *bus_id, struct device *parent)
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
/* count the io and irq resources */
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
/* 这里对设备节点的每一个irq 资源都创建了一个irq_desc*/
num_irq = of_irq_count(np);
/* Populate the resource table */
if (num_irq || num_reg) {
res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
dev->num_resources = num_reg + num_irq;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++)
rc = of_address_to_resource(np, i, res);
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
np);
}
dev->dev.of_node = of_node_get(np);
dev->dev.fwnode = &np->fwnode;
dev->dev.parent = parent ? : &platform_bus;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
注:这里的 of_irq_to_resource_table 过程是 request_irq 中的irq_to_desc 的前提条件。具体可以看
这篇blog
的中断注册过程小节。