2、设备树基础概念:DTS、DTC、DTB、DTBO文件格式与关系

好,咱们今天来聊聊设备树里最基础、也最容易搞混的几个概念——DTS、DTC、DTB、DTBO。说实话,我刚入行那会儿,看着这几个缩写也是一头雾水。后来在项目里被坑过几次,才真正搞明白它们之间的关系。

你想想看,设备树本质上就是一个描述硬件信息的“数据文件”。但硬件描述不能直接给内核用,得经过编译、转换。这中间就涉及到了四种文件格式。咱们一个一个说。

2.1 DTS:设备树源文件

DTS 全称 Device Tree Source,就是设备树的源代码。它用文本形式描述硬件信息,比如 CPU 数量、内存地址、外设寄存器、中断号等等。

我个人习惯把 DTS 文件比作“硬件的身份证”。里面写的都是板上钉钉的东西——哪个 GPIO 接了 LED,哪个 I2C 总线上挂了传感器,清清楚楚。

一个典型的 DTS 文件长这样:

/dts-v1/;

/ {
    compatible = "vendor,board-name";
    #address-cells = <1>;
    #size-cells = <1>;

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;
    };

    chosen {
        bootargs = "console=ttyS0,115200";
    };
};

嗯,这里要注意:DTS 文件里可以包含 #include 预处理指令,就像 C 语言一样。所以实际项目中,我们通常不会把整个板子的描述写在一个文件里,而是拆成多个 .dtsi 文件(设备树包含文件),然后通过 #include 组合起来。

我的经验: 在维护多款同系列产品时,把公共部分提取到 .dtsi 里,差异部分放在各自的 .dts 里。这样改起来特别方便,不容易出错。

2.2 DTC:设备树编译器

DTC 就是 Device Tree Compiler,负责把 DTS 编译成 DTB。它就像 C 语言里的 gcc,是连接源码和二进制文件的桥梁。

DTC 的用法很简单:

dtc -I dts -O dtb -o output.dtb input.dts

反过来,你也可以把 DTB 反编译成 DTS:

dtc -I dtb -O dts -o output.dts input.dtb

我曾经在调试一个启动问题的时候,手头只有别人给的 DTB 文件,没有源码。就是用这个反编译命令,把 DTB 转成 DTS,才找到了问题所在。所以这个技能你一定要掌握。

注意: 反编译出来的 DTS 会丢失注释和宏定义信息,只能看到最终的展开结果。所以它只能作为调试参考,不能直接拿回去修改。

2.3 DTB:设备树二进制文件

DTB 是 Device Tree Blob 的缩写,就是编译后的设备树二进制文件。Bootloader(比如 U-Boot)在启动内核时,会把 DTB 加载到内存中,然后把它的地址传给内核。

说白了,内核启动时看到的硬件信息,就是从这个 DTB 里读出来的。它包含了所有硬件节点的地址、大小、中断号、时钟频率等关键数据。

DTB 文件的结构是扁平的,由一个个节点(node)和属性(property)组成。每个节点都有一个偏移量,内核通过遍历这些节点来获取硬件信息。

文件类型 格式 用途
DTS 文本 人类可读的硬件描述源码
DTB 二进制 内核直接使用的硬件描述数据
DTBO 二进制 可叠加的设备树片段

2.4 DTBO:设备树叠加文件

DTBO 是 Device Tree Blob Overlay 的缩写。它是个相对较新的概念,我接触它是在做 FPGA 动态加载项目的时候。

为什么需要 DTBO?你想想看,如果一块板子上有多个可插拔的模块,每个模块都有自己的设备树描述。总不能为每一种组合都编译一个完整的 DTB 吧?那太麻烦了。

DTBO 就是解决这个问题的。它只描述“增量”部分,然后通过叠加(overlay)的方式,合并到基础 DTB 上。

举个例子,基础 DTB 描述了一个 I2C 控制器,而 DTBO 描述了这个 I2C 总线上挂载的一个温度传感器。加载 DTBO 后,内核就能识别到这个传感器了。

编译 DTBO 的命令:

dtc -I dts -O dtb -@ -o overlay.dtbo overlay.dts

注意这里的 -@ 选项,它会在 DTB 中生成符号表,这样叠加时才能找到对应的节点。

核心关系总结:
  • DTS 是源码,DTC 是编译器,DTB 是编译产物
  • DTBO 是 DTB 的“补丁”,用于动态添加硬件
  • 内核启动时只认 DTB,不认 DTS
  • 调试时可以用 DTC 在 DTS 和 DTB 之间来回转换

2.5 它们之间的流转关系

咱们捋一下整个流程:

  1. 工程师编写 .dts 和 .dtsi 文件
  2. 构建系统调用 DTC 编译器,生成 .dtb 文件
  3. Bootloader 加载 .dtb 到内存,传给内核
  4. 内核解析 DTB,初始化硬件驱动
  5. 如果支持设备树叠加,可以在运行时加载 .dtbo 文件

我曾经遇到过一个坑:某次升级内核后,板子死活起不来。后来发现是 DTB 没更新,新内核里某个驱动改了设备树节点的命名规则,旧的 DTB 匹配不上。从那以后,我每次升级内核都会同步编译新的 DTB,再也不敢偷懒了。

避坑指南: 如果你在调试时修改了 DTS,一定要重新编译 DTB 并烧写到板子上。很多人改了 DTS 后忘了编译,折腾半天发现没生效——嗯,我也干过这种事。

好了,关于 DTS、DTC、DTB、DTBO 的基本概念和关系,咱们就聊到这里。下一节我会深入讲解 DTS 的语法细节,包括节点、属性、标签、引用这些内容。到时候咱们再细聊。