3. 设备树编译与加载:DTC编译器、DTB二进制格式、内核解析流程
设备树从文本形式的 .dts 源文件到内核可识别的二进制 .dtb,再到内核解析并生成 struct device_node 树,经历了三个关键阶段:编译、加载、解析。理解这一完整链路,是进行寄存器配置分层重构的基础。
3.1 DTC编译器:从 .dts 到 .dtb
DTC(Device Tree Compiler)是设备树的官方编译器,负责将人类可读的 .dts 文本文件编译为紧凑的二进制 .dtb 文件。编译过程包含词法分析、语法解析、符号展开和二进制编码四个阶段。
关键命令示例:
# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o my_board.dtb my_board.dts
# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o my_board_decompiled.dts my_board.dtb
# 生成 .dtb 时保留符号信息(用于调试)
dtc -I dts -O dtb -@ -o my_board.dtb my_board.dts
编译优化提示: 生产环境中建议使用
-@ 选项保留符号,便于后续 overlay 动态加载;调试阶段可使用 -H epapr 指定头部格式。
DTC 编译流程的核心步骤:
- 预处理:展开
#include和/include/指令,合并所有包含的.dtsi文件。 - 词法/语法分析:解析节点、属性、值、标签等语法元素,构建抽象语法树(AST)。
- 符号解析:处理
&label引用、phandle分配、aliases节点。 - 二进制编码:将 AST 转换为 DTB 格式,计算偏移量、填充对齐、生成 FDT 头部。
3.2 DTB二进制格式:结构解剖
DTB 文件采用 Flattened Device Tree(FDT)格式,由四个主要区域组成。理解其二进制布局,有助于在底层调试寄存器映射问题。
| 区域名称 | 偏移量 | 大小 | 内容说明 |
|---|---|---|---|
| FDT Header | 0x00 | 40 字节 | 魔数、总大小、各区域偏移量、版本号、boot_cpuid_phys |
| Memory Reservation Block | header.off_mem_rsvmap | 动态 | 保留内存区域列表(地址 + 大小),用于告知内核不要使用 |
| Structure Block | header.off_dt_struct | 动态 | 节点树的核心数据:token(FDT_BEGIN_NODE / FDT_PROP / FDT_END_NODE 等)+ 内容 |
| Strings Block | header.off_dt_strings | 动态 | 所有属性名的字符串池,属性值通过偏移量引用此处 |
对齐陷阱: Structure Block 中的每个 token 和属性值都必须 4 字节对齐。如果属性值长度不是 4 的倍数,DTC 会自动填充 0x00 字节。在手动构造或修改 DTB 时,忽略对齐会导致内核解析崩溃。
FDT Header 的关键字段:
magic:固定为0xD00DFEED(大端序),用于校验文件合法性。totalsize:DTB 文件总字节数。off_dt_struct:Structure Block 相对于文件起始的偏移量。off_dt_strings:Strings Block 的偏移量。version与last_comp_version:当前版本(通常为 17)与兼容的最低版本(通常为 16)。boot_cpuid_phys:引导 CPU 的物理 ID,SMP 系统使用。
3.3 内核解析流程:从 DTB 到 device_node
内核在启动早期(arch/arm/kernel/setup.c 或 drivers/of/fdt.c)完成 DTB 解析,主要分为三个阶段:
- 早期扫描(early_init_dt_scan):在 MMU 未开启或仅开启恒等映射时,直接扫描 DTB 内存镜像,提取
chosen节点(bootargs、initrd)、memory节点、reserved-memory节点,用于建立早期内存映射。 - unflatten 展开(unflatten_device_tree):将扁平的 Structure Block 递归展开为内核的
struct device_node树,分配内存、建立父子/兄弟链表、解析phandle为指针引用。 - 驱动匹配(of_platform_populate):遍历
device_node树,对compatible属性匹配的节点创建struct platform_device,触发驱动 probe。
内核关键函数调用链:
start_kernel()
└─ setup_arch()
└─ early_init_dt_scan_nodes()
├─ early_init_dt_scan_chosen() // 解析 bootargs
├─ early_init_dt_scan_root() // 获取 #address-cells / #size-cells
└─ early_init_dt_scan_memory() // 解析 memory 节点
└─ unflatten_device_tree()
└─ __unflatten_device_tree()
├─ unflatten_dt_nodes() // 递归展开节点
└─ of_alias_scan() // 扫描 aliases 节点
└─ arch_initcall(of_platform_default_populate_init)
└─ of_platform_populate()
└─ of_platform_bus_create() // 递归创建 platform_device
3.4 设备树编译与加载全流程 SVG 架构图
3.5 内核解析关键数据结构
内核 OF(Open Firmware)子系统使用以下核心数据结构表示设备树:
| 数据结构 | 定义位置 | 作用 | 关键字段 |
|---|---|---|---|
struct device_node |
include/linux/of.h | 表示一个设备树节点 | name, full_name, phandle, parent, children, sibling, properties |
struct property |
include/linux/of.h | 表示节点的一个属性 | name, value, length, next |
struct platform_device |
include/linux/platform_device.h | 驱动匹配后的设备实例 | dev, num_resources, resource, id |
struct resource |
include/linux/ioport.h | 表示寄存器地址范围 | start, end, flags(IORESOURCE_MEM / IORESOURCE_IRQ) |
寄存器配置提取示例:
// 驱动 probe 函数中获取寄存器基址和中断号
static int my_driver_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
int irq;
// 获取第 0 个寄存器区域(reg 属性的第一个条目)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 获取中断号(interrupts 属性的第一个条目)
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 读取寄存器值示例
u32 val = readl(base + 0x10);
dev_info(&pdev->dev, "REG[0x10] = 0x%08x\n", val);
return 0;
}
3.6 常见问题与调试方法
常见问题清单:
- DTB 魔数错误:内核打印
"OF: fdt: magic: 0x%08x",检查 Bootloader 是否正确加载 DTB。 - 版本不兼容:内核只支持
last_comp_version <= 16的 DTB,使用dtc -V检查版本。 - 属性对齐错误:内核在
of_find_property时崩溃,检查 DTB 中属性值是否 4 字节对齐。 - phandle 冲突:两个节点使用相同
phandle,内核在of_phandle_cache_update时覆盖。 - reg 格式不匹配:
#address-cells/#size-cells与父节点不一致,导致地址解析错误。
调试命令速查:
# 查看 DTB 结构(内核启动后)
cat /proc/device-tree/ # 遍历所有节点
ls /sys/firmware/devicetree/base/ # sysfs 接口
# 反编译运行时 DTB
dtc -I fs -O dts -o runtime.dts /sys/firmware/devicetree/base/
# 内核 DTB 解析日志(开启 DEBUG)
echo 'file drivers/of/fdt.c +p' > /sys/kernel/debug/dynamic_debug/control
echo 'file drivers/of/base.c +p' > /sys/kernel/debug/dynamic_debug/control
3.7 本章小结
设备树的编译与加载是驱动架构中承上启下的关键环节:
- DTC 编译器将
.dts文本转换为紧凑的.dtb二进制,完成符号解析和编码。 - DTB 格式包含 Header、Memory Reservation、Structure Block、Strings Block 四个区域,内核通过魔数
0xD00DFEED校验合法性。 - 内核解析经历早期扫描、unflatten 展开、驱动匹配三个阶段,最终将寄存器配置传递给驱动。
- 理解这一流程,有助于在寄存器配置分层重构中定位 DTB 生成、加载、解析各环节的问题。