一、设备树文件家族:dts、dtsi、dtb、dtbo 到底有啥区别?

做嵌入式 Linux 开发,天天跟设备树打交道。但你有没有想过,为什么会有这么多后缀名?dts、dtsi、dtb、dtbo…… 说实话,我刚入行那会儿也被搞晕过。今天咱们就把这几个文件掰开揉碎了讲清楚。

1.1 先看一张图,理清关系

我习惯用一张图来理解它们之间的关系。你看下面这个流程,基本就明白了:

设备树文件编译与使用流程 .dts 设备树源文件 .dtsi 设备树头文件/包含文件 #include 包含 dtc 编译 .dtb 编译后的二进制文件 Bootloader 加载 内核 .dtbo 设备树覆盖文件 运行时覆盖 关键点: • .dtsi 是公共部分,被 .dts 通过 #include 包含 • .dts 经过 dtc 编译器生成 .dtb,这是内核最终使用的文件 • .dtbo 用于运行时动态覆盖,不修改原始 .dtb • 整个流程:.dtsi → .dts → .dtb → 内核解析

1.2 .dtsi —— 设备树的“头文件”

.dtsi 全称是 Device Tree Source Include。说白了,它就是设备树的公共部分。

我在项目中遇到过这样的情况:一个芯片平台有多个开发板,每个板子外设不同,但 CPU、内存控制器、时钟这些底层配置是一样的。如果每个板子都写一份完整的 .dts,那维护起来简直是噩梦。

所以就有了 .dtsi。它存放的是:

  • SoC 公共外设:比如 CPU 核、中断控制器、定时器、时钟控制器
  • 芯片厂商提供的默认配置:像 I2C、SPI、UART 这些控制器的寄存器地址
  • 可复用的节点定义:比如某个 IP 核的默认参数

举个例子:你打开 imx6ull.dtsi,里面定义了整个 i.MX6ULL 芯片的所有外设。然后不同的板子(比如野火、正点原子)各自写一个 .dts,通过 #include "imx6ull.dtsi" 把公共部分拉进来,再修改或添加自己板子特有的内容。

1.3 .dts —— 真正的设备树源文件

.dts 是 Device Tree Source 的缩写。它才是你真正要写的文件。

每个开发板对应一个 .dts 文件。它通过 #include 引入对应的 .dtsi,然后做三件事:

  1. 使能或禁用外设:比如 &uart1 { status = "okay"; };
  2. 修改默认参数:比如调整时钟频率、修改引脚复用
  3. 添加板级特有设备:比如板子上的 LED、按键、外部传感器

我个人习惯:把 .dts 写得尽量简洁。能用 &引用 方式修改的,绝不重新定义整个节点。这样后期维护起来,一眼就能看出哪些是板子特有的改动。

1.4 .dtb —— 内核真正吃进去的东西

.dtb 是 Device Tree Blob 的缩写。它是 .dts 经过 dtc 编译器编译后生成的二进制文件。

你想想看,内核在启动时,Bootloader(比如 U-Boot)会把 .dtb 文件加载到内存中,然后把地址传给内核。内核解析这个二进制数据,就知道板子上有哪些硬件了。

为什么不用 .dts 直接给内核?因为内核是 C 语言写的,它不认识文本格式的 .dts,只认二进制格式的 .dtb。这就好比你把一份 Word 文档发给别人,对方电脑没装 Word,你得先转成 PDF。

我曾经踩过一个坑:编译完内核后忘了重新编译设备树,结果烧录的还是旧版 .dtb。板子启动后,新加的 SPI 设备死活不工作。查了半天才发现是 .dtb 没更新。所以我现在每次编译内核,都会顺手执行一下 make dtbs

1.5 .dtbo —— 运行时“打补丁”

.dtbo 是 Device Tree Blob Overlay 的缩写。这个功能是后来才加的,但非常实用。

它的作用是什么?允许你在系统运行时,动态地添加或修改设备树节点,而不需要重新编译整个 .dtb 或重启系统。

举个例子:你的板子有一个 FPGA 扩展接口。FPGA 加载不同的 bit 文件,会呈现不同的外设。如果用传统方式,你得为每种 FPGA 配置准备一个 .dtb,然后重启切换。有了 .dtbo,你可以在 FPGA 加载后,动态地 overlay 对应的设备树片段,内核就能识别新硬件了。

文件类型 格式 用途 是否直接给内核用
.dtsi 文本 公共头文件,被 .dts 包含
.dts 文本 板级设备树源文件
.dtb 二进制 编译后的设备树,内核启动时加载
.dtbo 二进制 运行时覆盖片段,动态修改设备树 是(通过 configfs 接口)

1.6 它们之间的编译关系

搞清楚了各自的作用,咱们再看看编译命令。其实很简单:

# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 编译 .dts 为 .dtbo(需要 -@ 选项保留符号)
dtc -I dts -O dtb -@ -o myoverlay.dtbo myoverlay.dts

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

注意:编译 .dtbo 时一定要加 -@ 选项。不加的话,overlay 里引用的节点符号解析不了。我刚开始用 overlay 时,就因为这个选项没加,折腾了一下午。

1.7 总结一下

嗯,到这里你应该清楚了:

  • .dtsi 是公共部分,像 C 语言的 .h 头文件
  • .dts 是板级源文件,你主要写这个
  • .dtb 是编译后的二进制,内核启动时用
  • .dtbo 是运行时补丁,动态修改设备树

这四个文件构成了设备树的完整生态。理解它们的关系,是写好设备树的第一步。后面咱们会深入每个文件的语法细节,到时候你就知道怎么写出优雅的设备树了。