3、设备树文件结构:.dts、.dtsi、.dtb、.dtbo文件的区别与编译流程
设备树源文件(DTS)是嵌入式 Linux 驱动开发中描述硬件拓扑的核心载体。理解不同后缀文件(.dts、.dtsi、.dtb、.dtbo)的定位、差异以及从源文件到二进制镜像的编译流程,是驱动工程师必须掌握的基础技能。本节将系统梳理这些文件类型及其转换关系。
3.1 四种文件类型概述
设备树体系包含四种主要文件格式,分别对应开发阶段的不同角色:
| 文件后缀 | 全称 | 角色定位 | 是否可读 | 典型用途 |
|---|---|---|---|---|
| .dts | Device Tree Source | 顶层源文件 | 是(文本) | 描述单板级硬件,包含根节点和板级外设 |
| .dtsi | Device Tree Source Include | 公共头文件 | 是(文本) | 存放 SoC 公共定义、IP 核模板、复用节点 |
| .dtb | Device Tree Blob | 二进制镜像 | 否(二进制) | Bootloader 加载并传递给内核的硬件描述 |
| .dtbo | Device Tree Blob Overlay | 叠加层二进制 | 否(二进制) | 运行时动态修改或扩展设备树(如 FPGA 加载) |
.dts 是单板级完整描述,.dtsi 是 SoC 级公共描述,二者通过 #include 机制组合;.dtb 是编译后的最终产物,.dtbo 是用于运行时热插拔的增量补丁。
3.2 文件结构对比与包含关系
一个典型的设备树源文件体系呈现“SoC 公共层 → 单板层”的层次结构。.dtsi 文件定义 SoC 内部 IP 核、时钟、中断控制器等通用节点,.dts 文件通过 #include 引入 .dtsi,然后补充或覆盖板级特有的外设(如 GPIO 按键、LED、特定 I2C 设备)。
.dtsi 与 .dts 的典型组合关系。
// imx6ull.dtsi(SoC 公共定义)
/ {
#address-cells = <1>;
#size-cells = <1>;
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart1: serial@02020000 {
compatible = "fsl,imx6ul-uart", "fsl,imx-uart";
reg = <0x02020000 0x4000>;
interrupts = <0 26 4>;
status = "disabled";
};
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <0 66 4>;
gpio-controller;
#gpio-cells = <2>;
};
};
};
// myboard.dts(单板级文件)
#include "imx6ull.dtsi"
/ {
model = "MyBoard i.MX6ULL";
compatible = "myboard,imx6ull", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
leds {
compatible = "gpio-leds";
led0: led-0 {
label = "heartbeat";
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
};
};
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
.dtsi 中的节点通常将 status = "disabled",由 .dts 通过 &uart1 引用语法将其改为 "okay",实现“SoC 默认关闭,单板按需开启”的设计模式。
3.3 编译流程:从 .dts 到 .dtb
设备树编译工具链的核心是 DTC(Device Tree Compiler)。其工作流程分为预处理、编译、二进制生成三个阶段:
- 预处理阶段: C 预处理器(cpp)处理
#include、#define、#ifdef等宏指令,将.dts和所有引用的.dtsi合并为一个完整的临时源文件。 - 编译阶段: DTC 解析合并后的源文件,检查语法、节点标签、属性值合法性,生成中间表示。
- 二进制生成阶段: DTC 将中间表示序列化为扁平化设备树(FDT)二进制格式,输出
.dtb文件。
# 完整编译(包含预处理)
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 手动执行预处理后再编译
cpp -nostdinc -I include -undef -x assembler-with-cpp myboard.dts myboard_preprocessed.dts
dtc -I dts -O dtb -o myboard.dtb myboard_preprocessed.dts
# 反编译 .dtb 为可读的 .dts(调试用)
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
以下 SVG 图展示了从源文件到二进制镜像的完整编译流水线:
3.4 .dtbo 叠加层与动态编译
.dtbo(Device Tree Blob Overlay)是设备树叠加层技术,允许在系统运行时动态添加或修改设备节点,无需重新编译整个 .dtb。典型应用场景包括 FPGA 动态加载、扩展板热插拔、外设模块使能。
叠加层源文件使用 .dts 格式编写,但必须包含 /plugin/; 声明和 &node 引用语法:
// fpga_accelerator.dts(叠加层源文件)
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = &amba;
__overlay__ {
#address-cells = <1>;
#size-cells = <1>;
fpga_acc: acc@43c00000 {
compatible = "xlnx,axi-acc-1.0";
reg = <0x43c00000 0x10000>;
interrupts = <0 55 4>;
};
};
};
};
编译叠加层时需添加 -@ 选项以生成符号表:
# 编译叠加层 .dtbo
dtc -@ -I dts -O dtb -o fpga_accelerator.dtbo fpga_accelerator.dts
# 运行时加载叠加层(通过 configfs)
mkdir /sys/kernel/config/device-tree/overlays/fpga
cat fpga_accelerator.dtbo > /sys/kernel/config/device-tree/overlays/fpga/dtbo
3.5 文件类型对比总结
下表从多个维度对比四种文件类型,帮助快速决策在开发中应使用哪种格式:
| 对比维度 | .dts | .dtsi | .dtb | .dtbo |
|---|---|---|---|---|
| 文件格式 | 文本 | 文本 | 二进制 | 二进制 |
| 是否包含根节点 | 是 | 否(仅片段) | 是 | 否(仅片段) |
| 编译输入/输出 | 输入 | 输入(被包含) | 输出 | 输出 |
| 加载时机 | — | — | Bootloader → 内核启动 | 内核运行时 |
| 修改影响范围 | 全板 | 所有引用该 .dtsi 的单板 | 全系统 | 仅叠加节点 |
| 调试难度 | 低(文本可读) | 低 | 中(需反编译) | 中(需反编译) |
3.6 常见编译错误与排查
设备树编译过程中常见的错误类型及解决方法:
- 语法错误: 缺少分号、花括号不匹配、属性值格式错误。DTC 会提示行号,使用
dtc -I dts -O dtb -o output.dtb input.dts 2>&1查看详细错误。 - 标签未定义: 引用了
&node_label但.dtsi中未定义该标签。检查#include路径是否正确,或标签名是否拼写错误。 - 地址/大小单元不匹配:
#address-cells和#size-cells设置与reg属性长度不一致。确保父节点与子节点的 cell 数量对齐。 - 叠加层符号缺失: 编译
.dtbo时未加-@选项,导致运行时无法解析叠加目标。务必使用dtc -@编译叠加层。
.dtsi 的嵌套包含容易导致节点重复定义。建议使用 #ifndef 保护或保持 SoC 厂商提供的 .dtsi 文件不被修改,所有板级定制放在 .dts 中通过 &node 引用覆盖。
以下 SVG 图展示四种文件类型在开发与运行阶段的关系:
3.7 内核中的设备树编译集成
在实际的 Linux 内核构建系统中,设备树编译被集成到 Kbuild 框架中。开发者只需在 arch/<arch>/boot/dts/Makefile 中声明目标 .dtb 文件,内核构建系统会自动处理依赖关系和编译流程:
# arch/arm/boot/dts/Makefile 示例片段
dtb-$(CONFIG_SOC_IMX6ULL) += \
imx6ull-14x14-evk.dtb \
imx6ull-myboard.dtb
# 编译命令(内核顶层目录执行)
make ARCH=arm dtbs
执行 make dtbs 时,Kbuild 会:
- 扫描 Makefile 中所有
dtb-y目标 - 自动查找对应的
.dts源文件 - 解析
#include依赖,确保.dtsi文件路径正确 - 调用 DTC 编译生成
.dtb文件 - 支持
DTC_FLAGS传递额外参数(如-@用于叠加层)
.dts 和 .dtsi 文件统一存放在 arch/<arch>/boot/dts/ 目录下。不要手动调用 dtc 编译,而是通过内核的 Kbuild 系统管理,以确保依赖关系和编译选项的一致性。