2. 设备树文件格式:DTS、DTSI、DTB、DTC编译器、设备树源文件语法基础
设备树在嵌入式系统中以多种文件形态存在,从人类可读的源文件到内核解析的二进制块,每一层都有明确的格式与工具链。理解这些文件格式及其转换关系,是掌握设备树加载与传递的前提。
2.1 四种核心文件形态
| 文件后缀 | 全称 | 格式性质 | 用途 |
|---|---|---|---|
| .dts | Device Tree Source | 文本(ASCII/UTF-8) | 顶层设备树源文件,描述单板硬件 |
| .dtsi | Device Tree Source Include | 文本(ASCII/UTF-8) | 公共设备树头文件,被 .dts 引用 |
| .dtb | Device Tree Blob | 二进制(FDT格式) | 编译后的设备树,Bootloader加载并传递给内核 |
| .dtbo | Device Tree Blob Overlay | 二进制(FDT Overlay格式) | 运行时叠加片段,用于动态修改设备树 |
关键理解: .dts 是“源文件”,.dtsi 是“头文件”,.dtb 是“可执行二进制”,.dtbo 是“运行时补丁”。开发阶段主要操作 .dts/.dtsi,部署阶段使用 .dtb。
2.2 DTC编译器:从文本到二进制
DTC(Device Tree Compiler)是设备树的官方编译器,负责将 .dts/.dtsi 编译为 .dtb,也可反编译 .dtb 回 .dts。其核心工作流程如下:
编译命令示例:
# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o imx6ull-evk.dtb imx6ull-evk.dts
# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o imx6ull-evk-decompiled.dts imx6ull-evk.dtb
# 查看 .dtb 的详细信息
dtc -I dtb -O dts --sort imx6ull-evk.dtb | head -50
2.3 设备树源文件语法基础
.dts 文件采用树形结构描述硬件,核心语法元素包括:节点(node)、属性(property)、标签(label)、引用(phandle)、包含(include)。
2.3.1 节点与属性
每个设备树由根节点 / 开始,内部嵌套子节点。节点名称格式为 node-name@unit-address,其中 unit-address 通常对应设备寄存器基地址。
/dts-v1/;
/ {
compatible = "vendor,board-name"; // 根节点兼容性
#address-cells = <1>; // 地址宽度:32位
#size-cells = <1>; // 大小宽度:32位
memory@80000000 { // 内存节点
device_type = "memory";
reg = <0x80000000 0x20000000>; // 基地址 0x80000000,大小 512MB
};
chosen { // 内核启动参数节点
bootargs = "console=ttymxc0,115200 root=/dev/mmcblk0p2";
};
soc { // 片上系统总线节点
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart1: serial@02020000 { // UART控制器节点,带标签 uart1
compatible = "vendor,uart-ip";
reg = <0x02020000 0x4000>;
interrupts = <0 58 4>;
status = "okay";
};
};
};
2.3.2 属性数据类型
| 数据类型 | 语法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "vendor,device"; |
双引号包裹,用于匹配驱动 |
| 32位整数 | reg = <0x02020000 0x4000>; |
尖括号包裹,空格分隔 |
| 64位整数 | reg = <0x00000000 0x80000000>; |
用两个32位表示 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,十六进制字节 |
| 布尔值 | status = "disabled"; |
通过字符串"okay"/"disabled"表示 |
| 字符串列表 | compatible = "vendor,dev1", "vendor,dev2"; |
逗号分隔多个字符串 |
| 混合类型 | gpios = <&gpio1 3 0>; |
可包含引用和整数 |
2.3.3 标签与引用(phandle)
标签(label)为节点提供别名,方便在其他位置引用。引用通过 &label 语法实现,编译器自动生成 phandle(指针句柄)。
// 定义标签
uart1: serial@02020000 {
compatible = "vendor,uart-ip";
reg = <0x02020000 0x4000>;
pinctrl-0 = <&pinctrl_uart1>; // 引用 pinctrl 节点
};
// 在根节点下引用并追加属性
&uart1 {
status = "okay";
pinctrl-names = "default";
};
常见错误: 标签名不能以数字开头,不能包含特殊字符(除下划线)。引用时
&label 必须与标签定义完全匹配,否则 DTC 编译报错。
2.3.4 包含机制(.dtsi)
.dtsi 文件用于存放 SoC 公共定义、IP 核描述、时钟配置等复用内容。.dts 通过 #include 指令引入 .dtsi,支持 C 预处理器宏定义。
// imx6ull.dtsi(SoC公共定义)
/ {
soc {
uart1: serial@02020000 {
compatible = "vendor,uart-ip";
reg = <0x02020000 0x4000>;
interrupts = <0 58 4>;
status = "disabled"; // 默认关闭
};
};
};
// imx6ull-evk.dts(单板文件)
#include "imx6ull.dtsi" // 引入 SoC 定义
#include "imx6ull-evk.dtsi" // 引入板级公共定义
/ {
model = "i.MX6ULL EVK Board";
compatible = "vendor,imx6ull-evk";
};
&uart1 {
status = "okay"; // 在单板中使能 UART1
};
2.4 设备树语法结构图
2.5 编译与反编译实战
以下展示一个完整的设备树编译流程,从源文件到二进制,再到反编译验证:
# 1. 查看源文件结构
$ cat myboard.dts
/dts-v1/;
#include "soc.dtsi"
/ {
model = "MyBoard v1";
compatible = "vendor,myboard";
leds {
compatible = "gpio-leds";
led0: led@0 {
label = "heartbeat";
gpios = <&gpio1 3 0>;
linux,default-trigger = "heartbeat";
};
};
};
# 2. 编译为 DTB
$ dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 3. 查看 DTB 文件大小
$ ls -lh myboard.dtb
-rw-r--r-- 1 user user 2.3K Mar 15 10:30 myboard.dtb
# 4. 反编译验证
$ dtc -I dtb -O dts -o myboard_reverse.dts myboard.dtb
$ diff myboard.dts myboard_reverse.dts # 应无差异(除注释外)
调试技巧: 使用
dtc -I dtb -O dts --sort myboard.dtb 可以按字母顺序排序输出节点,便于对比不同版本的设备树差异。使用 fdtdump myboard.dtb 可以快速查看 DTB 的原始结构。
2.6 常见编译错误与排查
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
ERROR: Input tree has errors |
语法错误,如缺少分号或花括号 | 检查节点闭合、属性结尾分号 |
WARNING: label 'xxx' not found |
引用了未定义的标签 | 确认标签定义位置,检查拼写 |
ERROR: phandle is not contiguous |
phandle 编号冲突或溢出 | 减少节点数量或使用 -@ 选项 |
ERROR: Missing property '#address-cells' |
总线节点缺少地址描述属性 | 添加 #address-cells 和 #size-cells |
FATAL: Cannot open input file |
源文件路径错误或不存在 | 检查文件路径和权限 |
重要提示: 设备树编译错误通常会导致内核无法启动。在开发阶段,建议使用
dtc -I dts -O dtb -o /dev/null myboard.dts 进行语法检查,不生成实际文件,快速验证语法正确性。