Linux -设备树(Device Tree)解析流程

入口‌

内核启动调用 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。