2. 设备树文件结构:DTS、DTSI、DTB、DTC工具的关系与编译流程

2.1 设备树文件家族:四种核心文件类型

设备树(Device Tree)是嵌入式Linux系统中用于描述硬件拓扑结构的数据结构,它由一系列文本源文件和二进制目标文件组成。理解这四种文件类型及其关系,是掌握设备树配置的第一步。

文件类型 扩展名 格式 作用 是否可读
设备树源文件 .dts 文本(C风格) 描述单板级硬件,是编译入口 是
设备树头文件 .dtsi 文本(C风格) 描述SoC级公共硬件,供.dts包含 是
设备树二进制 .dtb 二进制(FDT格式) Bootloader传递给内核的硬件描述 否
设备树编译工具 dtc 可执行程序 将.dts/.dtsi编译为.dtb —
💡 关键概念:.dtsi 类似于 C 语言中的 .h 头文件,用于定义 SoC 级别的公共节点(如 CPU、中断控制器、时钟控制器、GPIO 控制器等),而 .dts 则专注于单板级别的差异化配置(如外设引脚、时钟频率、内存大小等)。

2.2 编译流程:从DTS到DTB的完整链路

设备树的编译流程是一个典型的“预处理 → 编译 → 输出”过程,与C语言的编译过程高度相似。DTC(Device Tree Compiler)是这一流程的核心工具。

🔧 编译流程四步骤:
  1. 预处理(CPP):C预处理器处理 #include、#define、#ifdef 等指令,将 .dtsi 文件展开到 .dts 中,生成展开后的临时文件 .dts.pre。
  2. 词法/语法分析(dtc):DTC 对预处理后的文件进行词法分析和语法解析,构建内部节点树。
  3. 节点合并与覆盖(dtc):DTC 根据节点路径和标签(label)执行节点覆盖规则,合并同名节点属性。
  4. 二进制输出(dtc):DTC 将最终的设备树结构序列化为扁平设备树(Flattened Device Tree, FDT)格式,生成 .dtb 文件。
DTS → DTB 编译流程 .dts 源文件 单板级描述 .dtsi 头文件 SoC级公共描述 CPP 预处理 展开 #include 处理 #define 输出 .dts.pre DTC 编译 词法/语法分析 节点合并与覆盖 属性检查 FDT序列化 .dtb 二进制 FDT格式 📌 编译命令示例: dtc -I dts -O dtb -o myboard.dtb myboard.dts # 或通过内核构建系统:make myboard.dtb

2.3 Include机制:如何组织多层级设备树

设备树通过 #include 指令实现模块化组织,这与C语言的预处理机制完全一致。合理的 #include 层次结构可以大幅提高代码复用率,减少重复定义。

2.3.1 典型的三层包含结构

// 第一层:SoC公共定义(芯片厂商提供)
#include "soc.dtsi"                    // CPU、中断控制器、时钟控制器等

// 第二层:SoC系列公共定义(芯片厂商提供)
#include "soc-pinctrl.dtsi"            // pinmux复用配置

// 第三层:单板级定义(板级开发者编写)
#include "myboard-common.dtsi"         // 板级公共外设(如UART、I2C)

/ {
    model = "MyBoard v2";
    compatible = "vendor,myboard-v2", "vendor,myboard";

    &uart1 {
        status = "okay";
        pinctrl-0 = &uart1_pins_a;
    };
};
⚠️ 注意事项:
  • #include 路径相对于 DTC 的 -i 参数指定的搜索路径,或相对于源文件所在目录。
  • 内核构建系统(Kbuild)会自动将 arch/<arch>/boot/dts/ 加入搜索路径。
  • 不要使用 /include/ 旧语法(DTC v1.4.0 之前),统一使用 #include 以获得CPP宏支持。

2.4 节点覆盖规则:同名节点的合并与覆盖

设备树最强大的特性之一就是节点覆盖(Node Overlay)机制。当多个文件或同一个文件中出现同名节点时,DTC 会按照特定规则进行合并或覆盖,而不是简单报错。

2.4.1 覆盖规则详解

场景 规则 示例
同名节点,不同属性 属性合并:两个节点的属性集合取并集,同名属性以后者为准 node { a=1; } + node { b=2; } → node { a=1; b=2; }
同名节点,相同属性 后者覆盖前者:后出现的属性值覆盖先出现的 node { speed=<100>; } + node { speed=<200>; } → node { speed=<200>; }
同名子节点 递归合并:子节点也遵循相同的合并规则 父节点合并时,子节点也逐层合并
使用 /delete-node/ 删除节点:显式删除之前定义的节点 /delete-node/ &uart2;
使用 /delete-property/ 删除属性:显式删除之前定义的属性 /delete-property/ unused-flag;

2.4.2 覆盖顺序优先级

覆盖顺序由 #include 的顺序和节点在文件中的出现顺序共同决定:

  1. 先包含的先定义:#include "soc.dtsi" 中的节点先被解析。
  2. 后出现的覆盖先出现的:在同一个文件中,后面的节点定义覆盖前面的。
  3. 根节点下的覆盖优先级最高:.dts 文件根节点下的定义覆盖 .dtsi 中的定义。
  4. 标签引用(&label)覆盖路径引用:使用 &label { } 语法可以精确覆盖指定节点。
设备树节点覆盖规则示意图 soc.dtsi(先包含) uart1: serial@1000 { clock = <1000000>; status = "disabled"; }; myboard.dts(后覆盖) &uart1 { clock = <2000000>; // 覆盖 status = "okay"; // 覆盖 }; 覆盖方向 合并后的最终节点 uart1: serial@1000 { clock = <2000000>; // 来自 myboard.dts status = "okay"; // 来自 myboard.dts };

2.5 实战:使用标签(Label)进行精确覆盖

在实际开发中,最推荐的覆盖方式是使用 标签引用(Label Reference) 语法 &label { }。这种方式比使用完整路径更简洁、更不易出错。

// soc.dtsi 中定义标签
soc {
    uart1: serial@1000 {
        compatible = "vendor,uart";
        reg = <0x1000 0x100>;
        clocks = <&clkc 16>;
        pinctrl-names = "default";
        pinctrl-0 = <&uart1_pins_a>;
        status = "disabled";
    };

    uart1_pins_a: uart1-pins {
        pins = "PA0", "PA1";
        function = "uart1";
    };
};

// myboard.dts 中通过标签覆盖
#include "soc.dtsi"

&uart1 {
    status = "okay";                    // 覆盖 status 属性
    clock-frequency = <115200>;         // 新增属性
};

&uart1_pins_a {
    bias-pull-up;                       // 新增属性:使能上拉
};
📋 标签覆盖的优势:
  • 路径无关:即使父节点路径发生变化,标签引用仍然有效。
  • 可读性强:&uart1 { } 比 &soc { serial@1000 { } } 更直观。
  • 支持增量修改:只覆盖需要修改的属性,其余保持原样。
  • 编译时检查:如果标签未定义,DTC 会报错,避免静默错误。

2.6 反编译与调试:DTB → DTS 的逆向过程

在实际调试中,经常需要将二进制 .dtb 反编译为可读的 .dts 格式,以验证编译结果是否正确,或者分析其他板卡的设备树配置。

# 反编译 DTB 为 DTS
dtc -I dtb -O dts -o decompiled.dts myboard.dtb

# 查看 DTB 的详细信息(包括大小、版本、占用空间)
dtc -I dtb -O dts -V myboard.dtb

# 使用 fdtdump 工具直接查看 DTB 的原始结构
fdtdump myboard.dtb
工具 功能 适用场景
dtc -I dtb -O dts 标准反编译,输出格式化的DTS 日常调试、代码审查
dtc -I dtb -O dts -V 显示DTB版本信息 检查DTB兼容性
fdtdump 以十六进制+ASCII方式显示DTB原始数据 底层分析、损坏检查
dtc -O asm 输出汇编格式的设备树 Bootloader集成调试
⚠️ 反编译注意事项:
  • 反编译后的 .dts 会丢失所有注释和宏定义(因为预处理阶段已展开)。
  • 反编译结果中的节点顺序可能与源文件不同,但语义等价。
  • 如果 DTB 使用了 /plugin/ 语法(设备树覆盖),反编译时需要加 -@ 参数保留符号信息。

2.7 本章小结

设备树的文件结构设计体现了嵌入式系统“分层抽象、增量覆盖”的核心思想:

  • DTSI 负责 SoC 级公共描述,由芯片厂商维护,板级开发者不应修改。
  • DTS 负责单板级差异化配置,通过 #include 引入 DTSI,并通过标签覆盖机制实现精确修改。
  • DTC 作为编译工具,将文本源文件转换为二进制 FDT 格式,供 Bootloader 和内核使用。
  • 节点覆盖规则 是设备树最灵活的特性,理解“属性合并、后者覆盖、标签优先”三大原则,是编写正确设备树的基础。