3. 设备树文件结构:dts、dtsi、dtb、dtbo 文件的区别与作用,以及编译工具(dtc)的使用

设备树(Device Tree)是嵌入式 Linux 驱动开发中描述硬件资源的标准化数据结构。理解其文件体系与编译流程,是编写 SPI 与 I2C 设备节点的前提。本章将系统梳理四种核心文件类型(dts、dtsi、dtb、dtbo)的定位、差异与协作关系,并详解设备树编译器(dtc)的典型用法。

核心概念:设备树文件体系采用“源文件 → 中间文件 → 二进制目标文件”的编译链,其中 dtsi 提供可复用的硬件描述模板,dts 描述具体板级硬件,dtb 是内核解析的二进制对象,dtbo 则用于运行时动态覆盖。

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 动态加载、外设热插拔
开发建议:在编写 SPI 或 I2C 设备节点时,优先在 dtsi 中定义 SoC 级控制器(如 ecspi1、i2c2),然后在 dts 中通过 &ecspi1 或 &i2c2 引用并添加具体外设子节点。这种分层设计可大幅提高代码复用率。

3.2 文件层级关系与编译流程

设备树的编译流程遵循“源文件 → 预处理 → 编译 → 二进制输出”的经典路径。下图展示了 dts、dtsi、dtb、dtbo 之间的依赖关系与编译链路:

设备树文件编译流程与层级关系 设备树文件编译流程与层级关系 .dtsi SoC 公共描述 .dts 板级硬件描述 预处理(cpp) #include dtc 编译 .dtb .dtbo 编译命令示例 dtc -I dts -O dtb -o xxx.dtb xxx.dts

上图清晰地展示了:.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 典型编译命令示例

以下命令覆盖了设备树开发中最常见的四种场景:

  1. 编译 dts 为 dtb(标准流程)
    dtc -I dts -O dtb -o imx6ull-evk.dtb imx6ull-14x14-evk.dts
  2. 反编译 dtb 为 dts(调试与逆向)
    dtc -I dtb -O dts -o imx6ull-evk-decompiled.dts imx6ull-evk.dtb
  3. 编译带覆盖支持的 dtbo
    dtc -I dts -O dtb -@ -o my-overlay.dtbo my-overlay.dts
  4. 检查 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>;
    };
};
关键点:dtsi 中定义控制器节点时设置 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>;
            };
        };
    };
};
覆盖开发要点:编写覆盖 dts 时必须包含 /plugin/; 声明,并使用 fragment@N 与 target 指定要修改的父节点。编译时务必添加 -@ 选项,否则覆盖文件无法正确解析符号引用。

3.6 本章小结

  • dtsi 是 SoC 级公共描述,dts 是板级具体描述,两者通过 #include 组合。
  • dtb 是内核启动时解析的二进制设备树,dtbo 用于运行时动态覆盖。
  • dtc 是设备树编译器的核心工具,支持编译、反编译与覆盖编译。
  • 编译 SPI/I2C 设备节点时,dtsi 定义控制器,dts 添加外设子节点并设置 status = "okay"。
  • 动态覆盖场景需使用 /plugin/ 语法与 -@ 编译选项。