入口
内核启动调用 setup_arch() -> unflatten_device_tree() -> of_alias_scan() ->
of_find_node_by_path() -> of_property_read_string() -> for_each_property_of_node() -> of_alias_add() ->
unflatten_device_tree
主要职责是将固件(如 U-Boot)传递的扁平化设备树二进制块(Flat Device Tree Blob, FDT/DTB)解析并展开为内核可操作的层级化 struct device_node 链表树。
函数原型
c
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
unittest_unflatten_overlay_base();
}
函数 __unflatten_device_tree
- initial_boot_params:指向 Bootloader 传递的扁平设备树(DTB)在内存中的起始地址。
- NULL:通常用于指定特定的节点过滤或父节点,此处为 NULL 表示从根节点开始完整展开。
- &of_root:关键输出。展开后生成的根节点 struct device_node 指针将被赋值给全局变量 of_root。此后,内核中所有基于设备树的操作(如 of_find_node_by_name)都以此为基础。
- early_init_dt_alloc_memory_arch:内存分配回调函数。由于此时内核常规内存管理器(如 Slab)尚未就绪,必须使用架构特定的早期内存分配器(通常基于 Memblock 或 Bootmem)。
- false:标志位,通常指示是否仅进行大小计算而不实际分配内存(第一遍扫描常设为 true 以计算所需空间,第二遍设为 false 进行实际分配)。
of_alias_scan
主要任务是扫描设备树根节点下的 /aliases 子节点,解析其中的属性,建立"简短别名"到"完整设备路径"的映射表
函数原型
c
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
{
struct property *pp;
of_aliases = of_find_node_by_path("/aliases");
of_chosen = of_find_node_by_path("/chosen");
if (of_chosen == NULL)
of_chosen = of_find_node_by_path("/chosen@0");
if (of_chosen) {
/* linux,stdout-path and /aliases/stdout are for legacy compatibility */
const char *name = NULL;
if (of_property_read_string(of_chosen, "stdout-path", &name))
of_property_read_string(of_chosen, "linux,stdout-path",
&name);
if (IS_ENABLED(CONFIG_PPC) && !name)
of_property_read_string(of_aliases, "stdout", &name);
if (name)
of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
}
if (!of_aliases)
return;
//核心循环
for_each_property_of_node(of_aliases, pp) {
const char *start = pp->name;
const char *end = start + strlen(start);
struct device_node *np;
struct alias_prop *ap;
int id, len;
/* Skip those we do not want to proceed */
if (!strcmp(pp->name, "name") ||
!strcmp(pp->name, "phandle") ||
!strcmp(pp->name, "linux,phandle"))
continue;
np = of_find_node_by_path(pp->value);
if (!np)
continue;
/* walk the alias backwards to extract the id and work out
* the 'stem' string */
while (isdigit(*(end-1)) && end > start)
end--;
len = end - start;
if (kstrtoint(end, 10, &id) < 0)
continue;
/* Allocate an alias_prop with enough space for the stem */
ap = dt_alloc(sizeof(*ap) + len + 1, __alignof__(*ap));
if (!ap)
continue;
memset(ap, 0, sizeof(*ap) + len + 1);
ap->alias = start;
of_alias_add(ap, np, id, start, len); //注册
}
}
举例说明
假设设备树中有:
bash
dts
aliases {
serial0 = &uart0;
serial1 = &uart1;
i2c0 = &i2c1;
};
of_alias_scan 执行后:
全局链表 aliases_lookup 包含三个条目:
{ stem="serial", id=0, np=uart0 }
{ stem="serial", id=1, np=uart1 }
{ stem="i2c", id=0, np=i2c1 }
- 调用 of_alias_get_id(uart0, "serial") 将返回 0,
- 调用 of_alias_get_id(node, "i2c") 在链表中查找,将返回 0,
- 用户空间看到的设备节点(如 /dev/i2c-0)始终保持一致,实现了硬件描述与驱动逻辑的解耦。
函数 of_find_node_by_path
Linux 内核设备树(Device Tree)子系统中用于根据绝对路径查找节点的核心 API。它允许驱动程序或内核子系统通过字符串形式的路径,直接定位到设备树中的特定节点
c
struct device_node *of_find_node_by_path(const char *path)
参数与返回值
path:指向必须以 / 开头的绝对路径字符串。
例如:"/soc/i2c@12340000" 或 "/chosen"。
路径必须与设备树中的节点层级完全匹配。
返回值:
成功:返回指向对应 struct device_node 的指针。
失败:如果路径不存在或格式错误,返回 NULL。