4. 设备树在Linux内核中的角色:内核启动流程中设备树的传递、OF API简介
设备树(Device Tree)在Linux内核中扮演着“硬件描述语言”与“平台配置契约”的双重角色。它从引导加载程序(Bootloader)传递到内核,贯穿整个启动流程,最终被OF(Open Firmware)子系统解析为内核可用的资源列表。理解这一角色,是进行设备树调试的前提。
4.1 设备树在内核启动流程中的传递路径
设备树的传递并非简单的内存拷贝,而是一个经过校验、解压、映射和解析的严格流程。以下表格总结了从引导加载程序到内核完成解析的关键阶段:
| 阶段 | 执行主体 | 关键动作 | 数据结构/寄存器 |
|---|---|---|---|
| 1. 加载与准备 | Bootloader (U-Boot) | 从存储介质读取dtb,放入内存,修改chosen节点 | dtb在内存中的物理地址 |
| 2. 传递入口 | Bootloader → 内核 | 将dtb物理地址写入寄存器x0(ARM64)或通过ATAG传递 | 寄存器x0 / r2 |
| 3. 早期解压 | 内核解压代码 | 若内核被压缩,解压后保留dtb地址;若dtb被压缩,则解压 | 解压后的dtb内存区域 |
| 4. 早期映射 | setup_arch() | 调用early_init_dt_scan(),扫描chosen、memory节点 | boot_command_line, memblock |
| 5. 完整解析 | unflatten_device_tree() | 将扁平dtb展开为内核的struct device_node树 | struct device_node, struct property |
| 6. 驱动绑定 | of_platform_populate() | 遍历device_node树,创建platform_device | platform_device, resource |
调试提示: 在启动早期,如果dtb地址传递错误或dtb损坏,内核通常会卡在
Uncompressing Linux... done, booting the kernel. 之后无任何输出。此时应检查Bootloader中 fdt_addr 或 bootm fdt 参数是否正确。
下面这张SVG流程图展示了设备树从Bootloader到内核驱动绑定的完整传递路径:
4.2 OF API 简介:内核操作设备树的接口
OF(Open Firmware)API 是内核提供给驱动开发者操作设备树的标准接口。它分为三个层次:节点查找、属性读取、资源转换。掌握这些API是编写设备树感知驱动的核心。
4.2.1 核心数据结构
内核使用两个主要结构体表示设备树:
// 表示一个设备节点
struct device_node {
const char *name; // 节点名称,如 "uart@fe000000"
const char *type; // device_type 属性值
phandle phandle; // 节点的phandle
const char *full_name; // 完整路径,如 "/soc/uart@fe000000"
struct property *properties;// 属性链表
struct device_node *parent; // 父节点
struct device_node *child; // 子节点
struct device_node *sibling;// 兄弟节点
};
// 表示一个属性
struct property {
char *name; // 属性名,如 "compatible"
int length; // 属性值长度(字节)
void *value; // 属性值指针
struct property *next; // 下一个属性
};
4.2.2 常用OF API分类
| 类别 | API函数 | 功能描述 | 典型返回值 |
|---|---|---|---|
| 节点查找 | of_find_node_by_name() |
通过节点名称查找 | struct device_node * |
of_find_node_by_type() |
通过 device_type 查找 | struct device_node * | |
of_find_compatible_node() |
通过 compatible 字符串查找 | struct device_node * | |
| 属性读取 | of_property_read_u32() |
读取32位整型属性 | 0 成功 / -EINVAL |
of_property_read_string() |
读取字符串属性 | 0 成功 / -EINVAL | |
of_get_property() |
获取原始属性值指针 | const void * | |
| 资源转换 | of_address_to_resource() |
将 reg 属性转换为 struct resource | 0 成功 / 负值错误码 |
of_irq_to_resource() |
将 interrupts 属性转换为中断资源 | 中断号 / 负值错误码 | |
| 遍历辅助 | for_each_child_of_node() |
遍历当前节点的所有子节点 | 宏,无返回值 |
实战要点: 在驱动中使用OF API时,务必检查返回值。例如
of_property_read_u32(np, "clock-frequency", &freq) 返回非0值时,不能使用freq变量,否则会导致使用未初始化的数据。
4.2.3 OF API调用关系图
下面的SVG图展示了一个典型驱动如何通过OF API从设备树节点获取资源:
4.3 设备树解析的早期调试方法
当内核在设备树解析阶段出现问题时,常规的printk可能尚未生效。此时可以使用以下方法进行调试:
- 早期printk(early_printk):在
setup_arch()之前启用,可以输出early_init_dt_scan()的执行状态。 - dtb反编译对比:使用
dtc -I dtb -O dts boot.dtb将传递的dtb反编译为dts,与源码dts对比,确认Bootloader是否修改了节点。 - ftrace function_graph:在支持早期ftrace的平台上,可以追踪
unflatten_device_tree()的子函数调用序列。 - CONFIG_OF_UNITTEST:内核内置的设备树单元测试框架,可以在启动时验证设备树解析的正确性。
常见陷阱: 设备树中的
reg 属性地址是 CPU总线地址,而非外设总线地址。在具有地址转换桥(如PCIe RC)的系统中,of_address_to_resource() 会自动进行地址映射,但若设备树缺少 ranges 属性,转换会失败,返回 -EINVAL。