2. 设备树文件结构:.dts、.dtsi、.dtb、.dtbo 文件的区别与关系

设备树在嵌入式 Linux 开发中并非单一文件,而是由四种不同格式的文件组成,分别对应源码、头文件、编译后的二进制镜像以及可叠加的覆盖片段。理解这四类文件的区别与转换关系,是进行板级硬件信息提取与配置的基础。

2.1 四类文件的核心定义与用途

文件后缀 全称 格式 核心用途
.dts Device Tree Source 文本(ASCII) 设备树源文件,描述单板硬件拓扑与资源
.dtsi Device Tree Source Include 文本(ASCII) 设备树头文件/包含文件,用于复用 SoC 公共定义
.dtb Device Tree Blob 二进制(FDT 格式) 编译后的设备树二进制,Bootloader 传递给内核
.dtbo Device Tree Blob Overlay 二进制(FDT Overlay 格式) 设备树叠加片段,运行时动态修改/扩展设备树
关键认知: .dts 和 .dtsi 是给人读的源码,.dtb 和 .dtbo 是给机器读的二进制。开发流程中,工程师修改 .dts/.dtsi,然后通过编译器生成 .dtb/.dtbo。

2.2 .dts 与 .dtsi 的层级关系

.dtsi 文件类似于 C 语言中的 .h 头文件,用于存放 SoC 厂商提供的公共设备树定义(如 CPU 核、中断控制器、时钟、GPIO 控制器等)。板级 .dts 文件通过 #include 指令引用 .dtsi,然后添加或覆盖板级特有的硬件节点。

// 典型结构:板级 .dts 引用 SoC 级 .dtsi
#include "imx6ull.dtsi"

/ {
    model = "EmbedFire i.MX6ULL Board";
    compatible = "embedfire,imx6ull", "fsl,imx6ull";

    &uart1 {
        status = "okay";
        pinctrl-0 = &pinctrl_uart1;
    };

    &i2c1 {
        clock-frequency = <100000>;
        status = "okay";

        touch: ft5426@38 {
            compatible = "edt,ft5426";
            reg = <0x38>;
            interrupt-parent = <gpio1>;
            interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
        };
    };
};
工程实践: 永远不要直接修改 SoC 厂商提供的 .dtsi 文件。正确的做法是在板级 .dts 中使用 &节点标签 语法进行覆盖或追加,这样当 SoC 升级 .dtsi 时,你的板级修改不会冲突。

2.3 .dtb 与 .dtbo 的二进制形态

.dtb 是 Flattened Device Tree(扁平设备树)的二进制表示,由 .dts 经过 DTC(Device Tree Compiler)编译生成。Bootloader(如 U-Boot)在启动内核前将 .dtb 加载到内存,并将地址通过寄存器传递给内核。

.dtbo 是设备树叠加(Overlay)的二进制格式,允许在不重新编译整个 .dtb 的情况下,动态添加或修改硬件节点。常用于 FPGA 动态配置、外设模块热插拔等场景。

兼容性注意: 内核需要开启 CONFIG_OF_OVERLAY 支持才能加载 .dtbo。Bootloader 也需要支持 Overlay 的加载与合并机制(如 U-Boot 的 fdt apply 命令)。

2.4 编译与反编译工具链

设备树的核心工具是 DTC(Device Tree Compiler),它由内核源码中的 scripts/dtc/ 目录提供。常用命令如下:

操作 命令示例 说明
编译 .dts → .dtb dtc -I dts -O dtb -o output.dtb input.dts 标准编译,生成二进制设备树
反编译 .dtb → .dts dtc -I dtb -O dts -o output.dts input.dtb 将二进制还原为可读的源码
编译 .dts → .dtbo dtc -I dts -O dtb -@ -o output.dtbo input.dts -@ 参数生成 overlay 符号表
检查 .dtb 内容 dtc -I dtb -O dts /sys/firmware/fdt 查看内核当前使用的设备树
查看 .dtb 大小 fdtdump input.dtb 打印二进制结构摘要
# 完整编译示例:从源码到二进制
# 1. 编译板级设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 2. 编译设备树叠加
dtc -I dts -O dtb -@ -o myoverlay.dtbo myoverlay.dts

# 3. 反编译验证
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb

2.5 四类文件的转换与依赖关系

设备树文件转换与依赖关系图 .dtsi SoC 公共定义 .dts 板级硬件描述 #include DTC 编译器 dtc 工具 .dtb 完整设备树二进制 .dtbo 设备树叠加片段 -O dtb -O dtb -@ Bootloader U-Boot 加载与合并 Linux 内核 OF 子系统解析 加载到内存 叠加合并 传递 dtb 地址 反编译 -I dtb -O dts 💡 开发流程总结: 编辑 .dtsi(SoC 公共)→ 编辑 .dts(板级)→ DTC 编译 → .dtb → Bootloader 加载 → 内核解析。

2.6 反编译实战:从 .dtb 恢复可读源码

在实际调试中,经常需要从运行中的系统提取设备树进行逆向分析。以下是从目标板获取 .dtb 并反编译的完整流程:

  1. 从内核导出当前设备树:
    # 方法1:通过 /sys 文件系统
    cp /sys/firmware/fdt /tmp/current.dtb
    
    # 方法2:通过 U-Boot 导出
    # 在 U-Boot 命令行执行:
    # ext4load mmc 0:1 0x80000000 /boot/myboard.dtb
    # 然后通过 tftp 或 SD 卡导出
  2. 反编译为 .dts:
    dtc -I dtb -O dts -o current.dts current.dtb
  3. 查看反编译结果:
    # 使用 less 或 vim 查看
    less current.dts
    
    # 搜索特定节点
    grep "uart1" current.dts
反编译注意事项: 反编译后的 .dts 会丢失原始 .dts 中的注释、宏定义和 #include 结构,所有节点会被展开为完整路径。此外,/include/ 指令会被替换为实际内容,导致文件体积增大。反编译结果主要用于调试和逆向分析,不适合直接作为源码修改后重新编译。

2.7 文件组织最佳实践

层级 文件类型 维护者 示例路径
SoC 级 .dtsi SoC 厂商(NXP/ST/Allwinner 等) arch/arm/boot/dts/imx6ull.dtsi
板级 .dts 板卡厂商或开发者 arch/arm/boot/dts/imx6ull-embedfire.dts
Overlay .dtso → .dtbo 模块/外设开发者 arch/arm/boot/dts/overlays/spi1-oled.dtso
二进制 .dtb / .dtbo 构建系统自动生成 arch/arm/boot/dts/imx6ull-embedfire.dtb
常见陷阱: 不要将 .dtsi 直接传递给 DTC 编译,它缺少根节点 / { }; 定义,会导致编译失败。同样,不要将 .dtbo 直接传递给内核作为主设备树,它只包含片段信息,无法独立描述完整硬件。