2. DTS、DTC、DTB、DTBO文件格式详解

设备树从人类可读的源文件到机器可执行的二进制文件,经历了一条清晰的转换链路。理解这四种文件格式及其转换流程,是编写和调试设备树的基础。

核心概念速览: 设备树的开发流程本质上是“文本 → 编译 → 二进制”的经典嵌入式开发范式。DTS 是源码,DTC 是编译器,DTB 是产物,DTBO 是运行时补丁。

2.1 四种文件格式的定义与作用

文件后缀 全称 本质 核心作用 是否直接运行
.dts Device Tree Source 文本源文件 描述单板级硬件拓扑,是开发者编写的主要对象 否
.dtsi Device Tree Source Include 文本头文件 存放 SoC 公共定义、IP 核模板,供 .dts 引用 否
.dtb Device Tree Blob 二进制扁平数据 Bootloader 传递给内核的硬件描述,内核直接解析 是(由内核解析)
.dtbo Device Tree Blob Overlay 二进制叠加片段 运行时动态修改或扩展基设备树,无需重新编译 是(由内核叠加)
💡 开发视角: 你日常编辑的是 .dts 和 .dtsi;调试时查看的是 .dtb;做模块热插拔或外设动态加载时,才会接触 .dtbo。

2.2 源文件体系:.dts 与 .dtsi 的协作关系

.dtsi 文件通过 #include 预编译指令被 .dts 文件引用。这种设计实现了 SoC 级复用与板级定制的分离。

// 典型结构:imx6ull.dtsi 定义 SoC 公共部分
// imx6ull-myboard.dts 引用并定制板级差异

#include "imx6ull.dtsi"

/ {
    model = "MyBoard i.MX6ULL Board";
    compatible = "myboard,imx6ull", "fsl,imx6ull";

    &uart1 {
        status = "okay";
        pinctrl-0 = &pinctrl_uart1;
    };
};
  • .dtsi 负责定义:CPU 核心、中断控制器、时钟树、外设 IP 核模板、默认 pinmux 配置。
  • .dts 负责定义:板级型号、内存大小、外设使能、GPIO 复用、自定义节点。
  • 引用语法:使用 &label 语法在 .dts 中引用 .dtsi 中定义的节点并追加属性。
⚠️ 常见错误: 初学者容易在 .dtsi 中直接写板级参数(如内存地址),导致多个板子共用同一 .dtsi 时产生冲突。正确的做法是:.dtsi 只放 SoC 级定义,板级差异留在 .dts 中覆盖。

2.3 编译器 DTC:从文本到二进制的桥梁

DTC(Device Tree Compiler)是设备树编译工具,源码位于内核 scripts/dtc/ 目录下。它完成以下工作:

  1. 预处理:展开 #include 和 #define 宏。
  2. 语法解析:检查节点、属性、值的合法性。
  3. 扁平化:将树形结构转换为线性二进制格式(FDT)。
  4. 打包:生成带头部校验的 .dtb 文件。
# 常用编译命令
# 编译 .dts → .dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 反编译 .dtb → .dts(调试用)
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb

# 编译叠加层 .dtso → .dtbo
dtc -I dts -O dtb -@ -o myoverlay.dtbo myoverlay.dtso
DTC 选项 含义 典型场景
-I 指定输入格式(dts/dtb/fs) 编译或反编译
-O 指定输出格式(dts/dtb) 生成目标文件
-o 输出文件名 指定产物路径
-@ 生成叠加层所需的符号节点 编译 .dtso 时必须加
-H 指定 FDT 格式版本 兼容旧版 bootloader

2.4 二进制文件 DTB:内核眼中的硬件地图

DTB 是内核启动早期唯一能读取的硬件描述。它采用 FDT(Flattened Device Tree)格式,结构紧凑,无需动态内存分配即可解析。

DTB 二进制文件内部结构 FDT 头部(40 字节) magic(0xd00dfeed) | totalsize | off_dt_struct | off_dt_strings | version 内存预留块(Memory Reservation Block) 结构块(Structure Block) 节点树:FDT_BEGIN_NODE → 属性(FDT_PROP)→ FDT_END_NODE 所有节点和属性以 Token 标记序列存储 字符串块(Strings Block) 每个块起始地址按 4 字节对齐,保证 ARM 等平台上的高效访问
🔍 调试技巧: 使用 fdtdump myboard.dtb 命令可以查看 DTB 的原始结构块和字符串块内容,帮助定位编译或链接问题。

2.5 叠加层 DTBO:运行时动态修改

DTBO 允许在不重新编译整个设备树的情况下,动态添加或修改节点。典型应用场景包括:FPGA 动态加载、扩展板热插拔、外设模块参数调整。

// myoverlay.dtso 示例:叠加一个 I2C 温度传感器
/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = &i2c2;
        __overlay__ {
            status = "okay";
            temp_sensor: tmp102@48 {
                compatible = "ti,tmp102";
                reg = <0x48>;
            };
        };
    };
};
  • 编译要求:必须使用 -@ 选项生成符号节点。
  • 加载方式:通过 configfs 接口写入 /sys/kernel/config/device-tree/overlays/。
  • 生命周期:叠加层可以动态加载和卸载,不影响基设备树。

2.6 完整转换流程

从源文件到最终内核解析,设备树经历了以下标准流水线:

设备树文件转换与加载流程 .dts / .dtsi 文本源文件 DTC 编译器 dtc -I dts -O dtb .dtb 二进制文件 扁平设备树 Bootloader U-Boot 传递 Linux 内核解析 of_* API 驱动模型 .dtso → .dtbo DTBO 通过 configfs 在运行时动态叠加到内核设备树,无需重启系统
流程要点总结:
  • 编译阶段:.dts + .dtsi → DTC → .dtb(或 .dtbo)
  • 加载阶段:Bootloader 将 .dtb 放入内存 → 内核启动时解析 → 驱动模型匹配
  • 运行时阶段:.dtbo 通过 configfs 叠加 → 内核动态更新设备树 → 新设备被识别

2.7 常见问题与避坑指南

问题现象 根本原因 解决方案
内核启动时 panic,提示 FDT 错误 DTB 与内核版本不匹配,或 DTC 版本过旧 使用内核源码自带的 DTC 重新编译
叠加层加载失败,提示符号找不到 编译 .dtso 时未加 -@ 选项 编译命令加上 -@ 重新生成 .dtbo
反编译的 .dts 与源文件不一致 DTB 中丢失了注释和宏定义信息 反编译仅用于调试,不要作为修改依据
设备树节点属性未生效 属性名拼写错误或值格式不对 对照内核文档 Documentation/devicetree/bindings/ 检查
⚠️ 版本兼容性: DTC 编译器版本与内核版本强相关。建议始终使用当前内核源码 scripts/dtc/ 目录下的 DTC 编译设备树,避免使用系统自带的独立 DTC 版本。