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 文件关系与编译流程

下图展示了从源文件到二进制文件的完整转换路径,以及各文件在系统启动阶段的使用位置。

设备树文件编译与使用流程 .dts 单板源文件 board.dts .dtsi 公共头文件 soc.dtsi / i2c.dtsi .dtso 叠加源文件 overlay.dtso dtc 编译器 Device Tree Compiler .dtb 设备树二进制 board.dtb .dtbo 叠加二进制 overlay.dtbo Bootloader加载.dtb → 内核解析 → 驱动匹配;系统运行时通过configfs加载.dtbo

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解析后的节点树,而不是源文件。