2. 设备树文件结构:.dts、.dtsi、.dtb、.dtbo 文件的区别与关系
设备树在嵌入式 Linux 开发中并非单一文件,而是由四种不同格式的文件组成,分别对应源码、头文件、编译后的二进制镜像以及可叠加的覆盖片段。理解这四类文件的区别与转换关系,是进行板级硬件信息提取与配置的基础。
2.1 四类文件的核心定义与用途
| 文件后缀 | 全称 | 格式 | 核心用途 |
|---|---|---|---|
| .dts | Device Tree Source | 文本(ASCII) | 设备树源文件,描述单板硬件拓扑与资源 |
| .dtsi | Device Tree Source Include | 文本(ASCII) | 设备树头文件/包含文件,用于复用 SoC 公共定义 |
| .dtb | Device Tree Blob | 二进制(FDT 格式) | 编译后的设备树二进制,Bootloader 传递给内核 |
| .dtbo | Device Tree Blob Overlay | 二进制(FDT Overlay 格式) | 设备树叠加片段,运行时动态修改/扩展设备树 |
关键认知: .dts 和 .dtsi 是给人读的源码,.dtb 和 .dtbo 是给机器读的二进制。开发流程中,工程师修改 .dts/.dtsi,然后通过编译器生成 .dtb/.dtbo。
2.2 .dts 与 .dtsi 的层级关系
.dtsi 文件类似于 C 语言中的 .h 头文件,用于存放 SoC 厂商提供的公共设备树定义(如 CPU 核、中断控制器、时钟、GPIO 控制器等)。板级 .dts 文件通过 #include 指令引用 .dtsi,然后添加或覆盖板级特有的硬件节点。
// 典型结构:板级 .dts 引用 SoC 级 .dtsi
#include "imx6ull.dtsi"
/ {
model = "EmbedFire i.MX6ULL Board";
compatible = "embedfire,imx6ull", "fsl,imx6ull";
&uart1 {
status = "okay";
pinctrl-0 = &pinctrl_uart1;
};
&i2c1 {
clock-frequency = <100000>;
status = "okay";
touch: ft5426@38 {
compatible = "edt,ft5426";
reg = <0x38>;
interrupt-parent = <gpio1>;
interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
};
};
};
工程实践: 永远不要直接修改 SoC 厂商提供的 .dtsi 文件。正确的做法是在板级 .dts 中使用
&节点标签 语法进行覆盖或追加,这样当 SoC 升级 .dtsi 时,你的板级修改不会冲突。
2.3 .dtb 与 .dtbo 的二进制形态
.dtb 是 Flattened Device Tree(扁平设备树)的二进制表示,由 .dts 经过 DTC(Device Tree Compiler)编译生成。Bootloader(如 U-Boot)在启动内核前将 .dtb 加载到内存,并将地址通过寄存器传递给内核。
.dtbo 是设备树叠加(Overlay)的二进制格式,允许在不重新编译整个 .dtb 的情况下,动态添加或修改硬件节点。常用于 FPGA 动态配置、外设模块热插拔等场景。
兼容性注意: 内核需要开启
CONFIG_OF_OVERLAY 支持才能加载 .dtbo。Bootloader 也需要支持 Overlay 的加载与合并机制(如 U-Boot 的 fdt apply 命令)。
2.4 编译与反编译工具链
设备树的核心工具是 DTC(Device Tree Compiler),它由内核源码中的 scripts/dtc/ 目录提供。常用命令如下:
| 操作 | 命令示例 | 说明 |
|---|---|---|
| 编译 .dts → .dtb | dtc -I dts -O dtb -o output.dtb input.dts |
标准编译,生成二进制设备树 |
| 反编译 .dtb → .dts | dtc -I dtb -O dts -o output.dts input.dtb |
将二进制还原为可读的源码 |
| 编译 .dts → .dtbo | dtc -I dts -O dtb -@ -o output.dtbo input.dts |
-@ 参数生成 overlay 符号表 |
| 检查 .dtb 内容 | dtc -I dtb -O dts /sys/firmware/fdt |
查看内核当前使用的设备树 |
| 查看 .dtb 大小 | fdtdump input.dtb |
打印二进制结构摘要 |
# 完整编译示例:从源码到二进制
# 1. 编译板级设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 2. 编译设备树叠加
dtc -I dts -O dtb -@ -o myoverlay.dtbo myoverlay.dts
# 3. 反编译验证
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
2.5 四类文件的转换与依赖关系
2.6 反编译实战:从 .dtb 恢复可读源码
在实际调试中,经常需要从运行中的系统提取设备树进行逆向分析。以下是从目标板获取 .dtb 并反编译的完整流程:
- 从内核导出当前设备树:
# 方法1:通过 /sys 文件系统 cp /sys/firmware/fdt /tmp/current.dtb # 方法2:通过 U-Boot 导出 # 在 U-Boot 命令行执行: # ext4load mmc 0:1 0x80000000 /boot/myboard.dtb # 然后通过 tftp 或 SD 卡导出 - 反编译为 .dts:
dtc -I dtb -O dts -o current.dts current.dtb - 查看反编译结果:
# 使用 less 或 vim 查看 less current.dts # 搜索特定节点 grep "uart1" current.dts
反编译注意事项: 反编译后的 .dts 会丢失原始 .dts 中的注释、宏定义和
#include 结构,所有节点会被展开为完整路径。此外,/include/ 指令会被替换为实际内容,导致文件体积增大。反编译结果主要用于调试和逆向分析,不适合直接作为源码修改后重新编译。
2.7 文件组织最佳实践
| 层级 | 文件类型 | 维护者 | 示例路径 |
|---|---|---|---|
| SoC 级 | .dtsi | SoC 厂商(NXP/ST/Allwinner 等) | arch/arm/boot/dts/imx6ull.dtsi |
| 板级 | .dts | 板卡厂商或开发者 | arch/arm/boot/dts/imx6ull-embedfire.dts |
| Overlay | .dtso → .dtbo | 模块/外设开发者 | arch/arm/boot/dts/overlays/spi1-oled.dtso |
| 二进制 | .dtb / .dtbo | 构建系统自动生成 | arch/arm/boot/dts/imx6ull-embedfire.dtb |
常见陷阱: 不要将 .dtsi 直接传递给 DTC 编译,它缺少根节点
/ { }; 定义,会导致编译失败。同样,不要将 .dtbo 直接传递给内核作为主设备树,它只包含片段信息,无法独立描述完整硬件。