3. 设备树文件结构:dts、dtsi、dtb、dtbo 文件的区别与作用,以及编译工具(dtc)的使用
设备树(Device Tree)是嵌入式 Linux 驱动开发中描述硬件资源的标准化数据结构。理解其文件体系与编译流程,是编写 SPI 与 I2C 设备节点的前提。本章将系统梳理四种核心文件类型(dts、dtsi、dtb、dtbo)的定位、差异与协作关系,并详解设备树编译器(dtc)的典型用法。
3.1 四种文件类型概述
设备树文件体系包含四种扩展名,分别对应不同的开发阶段与使用场景。下表从定义、作用、生成方式与典型用途四个维度进行对比:
| 文件类型 | 全称 | 作用 | 生成方式 | 典型用途 |
|---|---|---|---|---|
| .dts | Device Tree Source | 板级设备树源文件,描述具体硬件平台的完整拓扑 | 手动编写或从 BSP 模板修改 | 单板硬件描述,如 imx6ull-14x14-evk.dts |
| .dtsi | Device Tree Source Include | 设备树头文件,定义 SoC 公共外设、时钟、中断等通用节点 | SoC 厂商提供,开发者在 dts 中通过 #include 引用 |
SoC 级描述,如 imx6ull.dtsi |
| .dtb | Device Tree Blob | 编译后的二进制设备树,供 Bootloader 传递给内核 | 由 dtc 编译 dts 生成 | 内核启动时解析硬件拓扑 |
| .dtbo | Device Tree Blob Overlay | 设备树覆盖二进制,用于运行时动态修改或扩展设备树 | 由 dtc 编译 dts 并指定 -@ 选项生成 |
FPGA 动态加载、外设热插拔 |
&ecspi1 或 &i2c2 引用并添加具体外设子节点。这种分层设计可大幅提高代码复用率。
3.2 文件层级关系与编译流程
设备树的编译流程遵循“源文件 → 预处理 → 编译 → 二进制输出”的经典路径。下图展示了 dts、dtsi、dtb、dtbo 之间的依赖关系与编译链路:
上图清晰地展示了:.dtsi 通过 #include 预处理器指令被 .dts 引用,形成完整的源文件;经过 C 预处理器(cpp)展开宏与头文件后,由 dtc 编译生成 .dtb;若编译时启用 -@ 选项,则额外生成 .dtbo 覆盖文件。
3.3 dtc 编译工具详解
设备树编译器(Device Tree Compiler,dtc)是设备树开发的核心工具。它负责将人类可读的 dts 源文件转换为内核可解析的二进制 dtb 文件,同时支持反编译与覆盖编译。
3.3.1 常用编译选项
| 选项 | 功能 | 典型用法 |
|---|---|---|
-I |
指定输入格式(dts / dtb / fs) | -I dts |
-O |
指定输出格式(dts / dtb / dtbo) | -O dtb |
-o |
指定输出文件名 | -o myboard.dtb |
-@ |
生成设备树覆盖所需的符号节点 | -@ -O dtb |
-H |
指定 dtb 头部格式(epapr / legacy) | -H epapr |
-W |
启用特定编译警告 | -W no-unit_address_vs_reg |
-E |
启用特定编译错误 | -E unit_address_vs_reg |
3.3.2 典型编译命令示例
以下命令覆盖了设备树开发中最常见的四种场景:
- 编译 dts 为 dtb(标准流程)
dtc -I dts -O dtb -o imx6ull-evk.dtb imx6ull-14x14-evk.dts - 反编译 dtb 为 dts(调试与逆向)
dtc -I dtb -O dts -o imx6ull-evk-decompiled.dts imx6ull-evk.dtb - 编译带覆盖支持的 dtbo
dtc -I dts -O dtb -@ -o my-overlay.dtbo my-overlay.dts - 检查 dtb 语法与格式
dtc -I dtb -O dts imx6ull-evk.dtb | head -50
-@ 选项,生成的 dtb 将不包含 __symbols__ 节点,导致后续无法通过 configfs 或 libfdt 进行设备树覆盖操作。对于需要动态加载 SPI 或 I2C 从设备的场景,务必在编译时添加 -@。
3.4 文件结构对比:dtsi 与 dts 的协作模式
为了更直观地理解 dtsi 与 dts 的分工,下面以 i.MX6ULL 平台的 SPI 控制器为例,展示两者的典型协作方式:
imx6ull.dtsi(SoC 级定义):
/ {
soc {
aips1: aips-bus@02000000 {
ecspi1: ecspi@02008000 {
compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
reg = <0x02008000 0x4000>;
interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ECSPI1>;
dmas = <&sdma 3 7 1>, <&sdma 4 7 2>;
dma-names = "rx", "tx";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
};
};
};
imx6ull-14x14-evk.dts(板级引用与填充):
#include "imx6ull.dtsi"
&ecspi1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>;
status = "okay";
spidev0: spi-device@0 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <20000000>;
};
};
status = "disabled",dts 中通过 &ecspi1 引用并覆盖为 status = "okay",同时添加板级特有的引脚复用(pinctrl)与片选 GPIO。这种“先禁用、后使能”的模式是设备树驱动开发的标准实践。
3.5 dtbo 覆盖机制与 SPI/I2C 动态加载
设备树覆盖(Device Tree Overlay)允许在系统运行时动态添加或修改设备节点,特别适用于 FPGA 外设、热插拔 SPI/I2C 从设备等场景。dtbo 文件通过 configfs 接口加载到内核:
# 加载覆盖文件
mkdir /sys/kernel/config/device-tree/overlays/my-spi-overlay
cat my-spi-overlay.dtbo > /sys/kernel/config/device-tree/overlays/my-spi-overlay/dtbo
# 卸载覆盖
rmdir /sys/kernel/config/device-tree/overlays/my-spi-overlay
以下是一个典型的 SPI 设备覆盖 dts 示例:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&ecspi1>;
__overlay__ {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
temperature-sensor@0 {
compatible = "maxim,max31865";
reg = <0>;
spi-max-frequency = <5000000>;
};
};
};
};
/plugin/; 声明,并使用 fragment@N 与 target 指定要修改的父节点。编译时务必添加 -@ 选项,否则覆盖文件无法正确解析符号引用。
3.6 本章小结
- dtsi 是 SoC 级公共描述,dts 是板级具体描述,两者通过
#include组合。 - dtb 是内核启动时解析的二进制设备树,dtbo 用于运行时动态覆盖。
- dtc 是设备树编译器的核心工具,支持编译、反编译与覆盖编译。
- 编译 SPI/I2C 设备节点时,dtsi 定义控制器,dts 添加外设子节点并设置
status = "okay"。 - 动态覆盖场景需使用
/plugin/语法与-@编译选项。