3、设备树文件结构:dts、dtsi、dtb、dtbo文件的区别与作用
在嵌入式Linux开发中,设备树源文件经过编译生成二进制文件,供内核解析使用。理解.dts、.dtsi、.dtb、.dtbo四种文件的作用与关系,是编写正确设备树节点的基础。
核心概念:设备树文件体系采用“源文件 → 编译 → 二进制”的流水线模型。.dts和.dtsi是文本源文件,.dtb是最终烧录的二进制文件,.dtbo是运行时动态加载的叠加文件。
3.1 四种文件类型总览
| 文件后缀 | 全称 | 类型 | 作用 | 是否可读 |
|---|---|---|---|---|
| .dts | Device Tree Source | 源文件 | 描述单板级硬件拓扑,是编译入口 | 文本可读 |
| .dtsi | Device Tree Source Include | 头文件 | 存放SoC公共定义、外设IP核模板,供.dts引用 | 文本可读 |
| .dtb | Device Tree Blob | 二进制 | 内核启动时解析的硬件描述数据 | 不可读 |
| .dtbo | Device Tree Blob Overlay | 二进制叠加 | 运行时动态修改或扩展设备树 | 不可读 |
开发流程:编写.dts和.dtsi → 使用dtc编译器生成.dtb → 烧录到固件分区。需要动态外设时,额外编译.dtbo并在系统运行时加载。
3.2 文件关系与编译流程
下图展示了从源文件到二进制文件的完整转换路径,以及各文件在系统启动阶段的使用位置。
3.3 .dtsi 文件:SoC公共描述
.dtsi 文件是设备树的“头文件”,通常由芯片厂商提供,描述SoC内部所有IP核的默认配置和地址映射。开发板.dts通过#include指令引用.dtsi,避免重复定义。
// i2c0.dtsi — I2C控制器公共定义
/ {
soc {
i2c0: i2c@ff010000 {
compatible = "snps,designware-i2c";
reg = <0x0 0xff010000 0x0 0x1000>;
interrupts = <0 56 4>;
clocks = <&i2c0_clk>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled"; // 默认关闭,板级文件按需开启
};
};
};
注意:.dtsi中定义的节点通常带有
status = "disabled",板级.dts通过&i2c0 { status = "okay"; };启用。这种设计让SoC描述与板级描述解耦。
3.4 .dts 文件:单板级描述
.dts 是设备树的编译入口,每个开发板对应一个.dts文件。它通过#include引入SoC的.dtsi,然后添加或覆盖板级特有的硬件信息:GPIO引脚、I2C从设备、时钟频率等。
// myboard.dts — 单板级设备树
/dts-v1/;
#include "soc.dtsi"
#include "i2c0.dtsi"
/ {
model = "MyBoard v1.0";
compatible = "myvendor,myboard", "myvendor,soc";
// 板级I2C从设备
&i2c0 {
status = "okay";
clock-frequency = <400000>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <8>;
};
temperature@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
};
3.5 .dtb 文件:内核解析的二进制
.dtb 是设备树的最终交付物,由dtc编译器将.dts及其包含的所有.dtsi合并编译而成。Bootloader(如U-Boot)将.dtb从存储介质加载到内存,并将地址传递给内核。内核在启动早期解析.dtb,构建struct device_node树,驱动据此匹配设备。
编译命令:
反编译查看二进制内容:
dtc -I dts -O dtb -o myboard.dtb myboard.dts反编译查看二进制内容:
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
3.6 .dtbo 文件:运行时叠加
.dtbo 是设备树叠加文件,用于在系统运行时动态添加或修改设备节点,无需重新编译整个.dtb或重启内核。典型应用场景包括:FPGA动态加载外设、扩展板热插拔、调试时临时修改设备参数。
// i2c_rtc_overlay.dtso — 叠加源文件
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = &i2c0;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
rtc@68 {
compatible = "dallas,ds1307";
reg = <0x68>;
};
};
};
};
叠加加载方式:将编译好的.dtbo复制到
/lib/firmware/目录,通过configfs接口加载:echo i2c_rtc_overlay.dtbo > /sys/kernel/config/device-tree/overlays/rtc/path
3.7 四种文件对比总结
| 对比维度 | .dts | .dtsi | .dtb | .dtbo |
|---|---|---|---|---|
| 文件性质 | 源文件(入口) | 源文件(头文件) | 二进制(固件) | 二进制(叠加) |
| 是否包含板级信息 | 是 | 否(仅SoC级) | 是(合并后) | 是(增量) |
| 编译产物 | .dtb | 被.dts包含 | 直接使用 | 直接使用 |
| 加载时机 | 编译时 | 编译时 | 内核启动时 | 系统运行时 |
| 修改影响范围 | 整个板级 | 所有引用它的板级 | 需重新烧录 | 仅当前叠加 |
| 驱动匹配方式 | — | — | 内核解析节点树 | 动态合并到树 |
3.8 实际开发建议
- 不要直接修改.dtb:二进制文件不可读,应修改对应的.dts源文件后重新编译。
- 善用.dtsi分层:将SoC公共部分、外设IP核、板级接口分别放在不同.dtsi中,提高复用性。
- 叠加文件使用/plugin/:.dtso文件第一行必须包含
/plugin/;,否则dtc会按普通.dts编译。 - 版本管理:将.dts和.dtsi纳入Git管理,.dtb和.dtbo作为构建产物不提交。
- 反编译调试:遇到.dtb相关问题时,先用
dtc -I dtb -O dts反编译查看实际内容。
关键记忆:编写设备树时,你在写.dts和.dtsi;编译后得到.dtb;需要动态修改时用.dtbo。内核驱动匹配的是.dtb解析后的节点树,而不是源文件。