3. 设备树源文件(.dts)与编译:dts文件结构、dtsi包含文件、dtc编译器使用、生成dtb文件
设备树源文件(.dts)是描述硬件拓扑结构的文本文件,经过设备树编译器(dtc)编译后生成二进制设备树 Blob(.dtb),供内核在启动时解析。本章深入剖析 .dts 文件结构、.dtsi 包含机制、dtc 编译器的使用以及生成 .dtb 的完整流程。
3.1 dts 文件结构
一个标准的 .dts 文件由根节点、若干子节点以及属性组成。其基本语法结构如下:
/dts-v1/;
/ {
model = "MyBoard RevA";
compatible = "vendor,myboard-reva", "vendor,myboard";
#address-cells = <1>;
#size-cells = <1>;
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
stdout-path = &uart0;
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <0 5 4>;
clock-frequency = <24000000>;
status = "okay";
};
gpio0: gpio@10001000 {
compatible = "vendor,gpio-ip";
reg = <0x10001000 0x1000>;
interrupts = <0 6 4>;
gpio-controller;
#gpio-cells = <2>;
status = "disabled";
};
};
};
/dts-v1/;必须位于文件头部,声明使用设备树 v1 格式。- 根节点用
/表示,所有硬件设备都是根节点的子节点。 - 节点名称格式:
node-name@unit-address,其中unit-address通常为设备基地址。 - 属性值类型:字符串(双引号)、32位整数(尖括号)、字节序列(方括号)、字符串列表(逗号分隔)。
#address-cells和#size-cells定义子节点reg属性中地址和长度的字数。
3.2 dtsi 包含文件
.dtsi 文件是设备树的包含文件,类似于 C 语言中的头文件。它用于存放 SoC 公共部分、IP 核定义、通用外设配置等,被多个 .dts 文件引用,实现代码复用。
| 文件类型 | 用途 | 示例 |
|---|---|---|
.dtsi |
SoC 级公共定义,包含 CPU 核、中断控制器、总线、通用外设 IP | soc.dtsi, imx6ull.dtsi |
.dts |
板级具体描述,包含内存大小、板载外设、引脚复用、设备状态 | myboard.dts |
包含语法使用 #include 指令,与 C 预处理器兼容:
// myboard.dts
/dts-v1/;
#include "soc.dtsi"
#include "myboard-pinfunc.h"
/ {
model = "MyBoard RevB";
compatible = "vendor,myboard-revb";
memory@80000000 {
reg = <0x80000000 0x20000000>; // 512MB
};
&uart0 {
status = "okay";
pinctrl-0 = <&pinctrl_uart0>;
};
&gpio0 {
status = "okay";
};
};
&label 语法可以引用 .dtsi 中定义的节点标签,在板级文件中覆盖或追加属性,无需重写整个节点。
下图展示了 .dts 与 .dtsi 之间的包含关系与编译流程:
3.3 dtc 编译器使用
设备树编译器(dtc)是 Linux 内核源码中提供的工具,用于将 .dts 源文件编译为 .dtb 二进制文件,也支持反编译操作。dtc 源码位于内核源码树的 scripts/dtc/ 目录下。
3.3.1 基本编译命令
# 编译 dts 为 dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译 dtb 为 dts
dtc -I dtb -O dts -o myboard.dts myboard.dtb
# 编译并输出依赖关系
dtc -I dts -O dtb -o myboard.dtb myboard.dts -d myboard.d
# 编译时指定包含路径
dtc -I dts -O dtb -o myboard.dtb myboard.dts -i ./include
| 选项 | 说明 | 常用值 |
|---|---|---|
-I |
输入格式 | dts(源文件)、dtb(二进制)、fs(文件系统) |
-O |
输出格式 | dtb(二进制)、dts(源文件)、yaml(YAML) |
-o |
输出文件名 | myboard.dtb |
-i |
添加包含搜索路径 | -i ./include |
-d |
生成依赖文件(Makefile 友好) | -d myboard.d |
-@ |
生成插件节点(overlay 支持) | 无参数 |
-W |
启用特定警告 | -W no-unit_address_vs_reg |
- “FATAL ERROR: Unable to parse input tree”:检查
.dts文件语法,确认分号、花括号、引号是否匹配。 - “WARNING: unit_address_vs_reg”:节点名中的
unit-address与reg属性首地址不匹配,需对齐。 - “Error: soc.dtsi: No such file or directory”:使用
-i指定包含路径,或将.dtsi放在当前目录。
3.3.2 编译流程详解
dtc 编译 .dts 到 .dtb 的内部流程分为四个阶段:
- 预处理阶段: 处理
#include、#define等 C 预处理器指令,展开宏定义,合并所有包含的.dtsi文件。 - 解析阶段: 将预处理后的文本解析为抽象语法树(AST),检查节点、属性、标签的语法正确性。
- 语义检查阶段: 验证
#address-cells/#size-cells一致性、中断映射有效性、属性值类型正确性。 - 二进制生成阶段: 将 AST 序列化为扁平化设备树(FDT)二进制格式,生成
.dtb文件。
3.4 生成 dtb 文件
生成 .dtb 文件是设备树裁剪的最终输出目标。在实际嵌入式开发中,通常通过内核构建系统(Kbuild)自动完成编译,也可以手动调用 dtc 进行编译。
3.4.1 使用内核 Kbuild 编译
在内核源码树中,将 .dts 文件放入 arch/<arch>/boot/dts/ 目录,然后在 Makefile 中添加目标:
# arch/arm/boot/dts/Makefile
dtb-$(CONFIG_SOC_MYBOARD) += myboard.dtb
执行内核编译命令:
make ARCH=arm myboard.dtb
Kbuild 会自动处理依赖关系、包含路径、预处理器调用,并调用 dtc 生成 .dtb 文件。
3.4.2 手动编译与验证
手动编译适用于快速原型验证或调试场景:
# 编译
dtc -I dts -O dtb -o myboard.dtb myboard.dts -i ./dtsi
# 验证 dtb 文件内容
dtc -I dtb -O dts myboard.dtb | head -50
# 检查 dtb 文件大小
ls -lh myboard.dtb
# 使用 fdtdump 查看原始结构
fdtdump myboard.dtb
- 始终通过内核 Kbuild 系统编译
.dtb,确保依赖关系和版本一致性。 - 在
.dts文件中使用#include而非/include/,以兼容 C 预处理器。 - 编译时开启
-W警告选项,提前发现节点地址不匹配、属性缺失等问题。 - 将生成的
.dtb与 bootloader(如 U-Boot)使用的设备树分开管理,避免版本冲突。
3.4.3 dtb 文件结构
编译生成的 .dtb 文件采用扁平化设备树(FDT)格式,其内部结构包含以下主要部分:
| 段名称 | 说明 | 大小(典型值) |
|---|---|---|
| FDT 头部 | 魔数、总大小、各段偏移、版本号、引导 CPU ID | 40 字节 |
| 内存保留映射 | 保留内存区域的物理地址和大小列表 | 16 字节 × 保留区数量 |
| 结构块 | 节点和属性的树形结构,使用 token 标记(BEGIN_NODE, PROP, END_NODE 等) | 数百到数千字节 |
| 字符串块 | 所有属性名的字符串表,通过偏移量引用 | 数十到数百字节 |
3.5 实战案例:从零编译一个最小设备树
以下是一个完整的实战流程,从编写 .dts 文件到生成 .dtb 并验证:
# 1. 创建最小 dts 文件
cat > minimal.dts << EOF
/dts-v1/;
/ {
model = "MinimalBoard";
compatible = "vendor,minimal";
#address-cells = <1>;
#size-cells = <1>;
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
chosen {
bootargs = "console=ttyS0,115200";
};
};
EOF
# 2. 编译为 dtb
dtc -I dts -O dtb -o minimal.dtb minimal.dts
# 3. 反编译验证
dtc -I dtb -O dts minimal.dtb
# 4. 查看 dtb 文件信息
fdtdump minimal.dtb | head -20
// 反编译输出
/dts-v1/;
/ {
model = "MinimalBoard";
compatible = "vendor,minimal";
#address-cells = <0x00000001>;
#size-cells = <0x00000001>;
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
chosen {
bootargs = "console=ttyS0,115200";
};
};
通过本章的学习,读者应掌握 .dts 文件的结构语法、.dtsi 包含机制、dtc 编译器的使用以及 .dtb 文件的生成与验证方法。这些技能是后续进行设备树裁剪与内核移植的基础。