2. DTS文件结构:DTS、DTSI、DTB、DTC的关系与编译流程
设备树(Device Tree)在Linux内核驱动开发中扮演着硬件描述的核心角色。理解DTS、DTSI、DTB、DTC这四个关键概念及其转换流程,是掌握设备树编程的第一步。本章将系统梳理这些文件类型的功能定位、语法差异以及从源文件到二进制文件的完整编译链路。
2.1 核心概念与文件类型
设备树体系包含四种主要文件形态,它们分别处于开发、编译、分发和运行的不同阶段:
| 文件后缀 | 全称 | 本质 | 可读性 | 典型用途 |
|---|---|---|---|---|
| .dts | Device Tree Source | 文本源文件 | 人类可读 | 板级设备树主文件 |
| .dtsi | Device Tree Source Include | 文本头文件 | 人类可读 | SoC公共定义、外设复用 |
| .dtb | Device Tree Blob | 二进制文件 | 机器可读 | Bootloader传递给内核 |
| .dtc | Device Tree Compiler | 编译工具 | 可执行程序 | 将.dts/.dtsi编译为.dtb |
关键区分: .dtsi 与 .dts 在语法上完全一致,区别仅在于用途约定。.dtsi 通常被 .dts 通过
#include 包含,用于存放SoC级通用描述,避免重复定义。
2.2 文件关系与包含机制
一个典型的板级设备树项目,通常由一个主 .dts 文件通过 #include 预处理器指令引入多个 .dtsi 文件,形成层次化的描述结构:
// 主文件:imx6ull-myboard.dts
#include "imx6ull.dtsi" // SoC 级定义
#include "imx6ull-pinfunc.h" // 引脚复用宏定义
#include "myboard-lcd.dtsi" // 板级外设模块
/ {
model = "MyBoard i.MX6ULL";
compatible = "myboard,imx6ull", "fsl,imx6ull";
memory {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB RAM
};
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};
};
包含路径解析: DTC 在编译时会调用 C 预处理器(cpp),因此 .dts 中可以使用
#include、#define、#ifdef 等预处理指令。头文件搜索路径通过 -I 参数指定,通常包括内核源码的 arch/arm/boot/dts/ 目录。
2.3 编译流程:从源文件到二进制文件
DTC 编译器将可读的 .dts 源文件转换为紧凑的 .dtb 二进制文件,整个过程分为三个阶段:
- 预处理阶段: C预处理器(cpp)处理
#include、#define、#ifdef等指令,展开所有宏和头文件,生成一个完整的、无依赖的临时 .dts 文件。 - 解析与语法检查阶段: DTC 解析器读取预处理后的 .dts,构建内部节点树,检查语法错误、属性合法性、节点唯一性等。
- 二进制输出阶段: DTC 将验证通过的设备树结构序列化为 FDT(Flattened Device Tree)格式的 .dtb 文件,包含节点、属性、字符串表、内存保留区等。
常见编译错误: 节点名称重复、属性值类型不匹配、引用的 &label 未定义、
reg 属性地址长度不匹配等。使用 dtc -I dts -O dtb -o output.dtb input.dts 编译时,务必检查终端输出的错误信息。
2.4 编译与反编译命令
DTC 工具支持正向编译和反向反编译,方便开发者调试和验证设备树内容:
# 正向编译:.dts → .dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译:.dtb → .dts(还原为可读文本)
dtc -I dtb -O dts -o myboard.dts myboard.dtb
# 查看 .dtb 内部结构(不生成文件)
dtc -I dtb -O dts /sys/firmware/devicetree/base | head -100
# 编译时指定头文件搜索路径
dtc -I dts -O dtb -o myboard.dtb -I /path/to/include myboard.dts
调试技巧: 在Linux系统运行时,可以通过
/sys/firmware/devicetree/base 目录直接查看当前内核使用的设备树结构,该目录以文件系统形式呈现了 .dtb 的节点和属性。
2.5 编译流程SVG架构图
下图展示了从 .dts/.dtsi 源文件到 .dtb 二进制文件的完整编译链路,以及反编译和运行时访问的路径:
2.6 编译产物与内核加载
编译生成的 .dtb 文件在系统启动过程中由 Bootloader(如 U-Boot)加载到内存,并将起始地址传递给内核。内核在启动早期解析 FDT 格式,构建设备树结构,驱动框架据此完成设备枚举和资源分配:
# U-Boot 中加载设备树的典型命令序列
load mmc 0:1 ${fdt_addr_r} imx6ull-myboard.dtb
load mmc 0:1 ${kernel_addr_r} zImage
bootz ${kernel_addr_r} - ${fdt_addr_r}
多板支持策略: 同一份内核镜像可以搭配不同的 .dtb 文件运行在不同硬件上。内核通过
compatible 属性匹配板级设备树,实现“一次编译,多处运行”的灵活部署。
2.7 常见问题与避坑指南
- 头文件路径错误: 编译时未指定
-I参数导致#include找不到 .dtsi 文件。解决方案:确保包含路径指向内核源码的arch/arm/boot/dts/目录。 - 节点标签重复: 多个 .dtsi 中定义了相同名称的标签(label),导致编译冲突。解决方案:使用
&label引用时确保标签唯一,或通过/delete-node/删除冲突节点。 - 属性值类型不匹配: 如
reg属性中地址长度与#address-cells不一致。解决方案:严格遵循#address-cells和#size-cells的定义。 - 反编译后无法重新编译: 反编译生成的 .dts 可能包含
__symbols__等内部节点,直接编译会报错。解决方案:使用-f强制编译,或手动清理无关节点。
生产环境注意: 切勿在运行时直接修改
/sys/firmware/devicetree/base 下的文件,该目录是内核内存中设备树的只读映射。修改设备树必须重新编译 .dtb 并更新 Bootloader 加载的文件。