一、设备树基础回顾:DTS、DTC、DTB 文件格式与编译流程
说实话,很多嵌入式工程师一开始接触设备树,都觉得它挺玄乎的。我当年刚转做 Linux 驱动时,也被这玩意儿绕晕过。但说白了,设备树就是一套描述硬件信息的“说明书”。它告诉内核:你有哪些外设、它们挂在哪条总线上、地址是多少、中断怎么配。
今天咱们就把 DTS、DTC、DTB 这三兄弟彻底捋清楚。嗯,先从文件格式说起。
1.1 DTS:人类可读的硬件描述
DTS 是 Device Tree Source 的缩写。它是文本文件,用树形结构描述硬件。我习惯把 DTS 比作一张“硬件清单”,每个节点就是一个设备。
来看一个最简单的例子:
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
cpus {
cpu@0 {
compatible = "arm,cortex-a8";
reg = <0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; /* 256 MB */
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
};
这里有几个关键点:
- 根节点:
/是整个树的起点,相当于硬件系统的根 - 节点名:比如
cpu@0,@后面是地址或单元号 - 属性:
compatible、reg、interrupts这些都是属性,描述设备特性 - 标签:
uart0:是标签,方便在其他地方引用这个节点
/* UART0, J1-4 */。调试时能省一半时间。
1.2 DTC:把文本变成二进制
DTC 是 Device Tree Compiler 的缩写。它的工作很简单——把 DTS 编译成 DTB。我刚开始用的时候,总觉得这步可有可无,直到有一次我手动改了 DTS 却忘了重新编译……嗯,那次调试花了我整整一个下午。
编译命令长这样:
dtc -I dts -O dtb -o am335x-boneblack.dtb am335x-boneblack.dts
参数说明:
-I dts:输入格式是 DTS-O dtb:输出格式是 DTB-o:指定输出文件名
反过来,你也可以把 DTB 反编译成 DTS:
dtc -I dtb -O dts -o am335x-boneblack.dts am335x-boneblack.dtb
1.3 DTB:内核真正吃的东西
DTB 是 Device Tree Blob 的二进制格式。它被烧录到 Flash 里,或者由 bootloader 加载到内存中,然后传给内核。
DTB 的结构其实挺规整的,我画了一张图帮你理解:
你看,DTB 分四个部分:
- 头部:记录魔数(0xd00dfeed)、总大小、版本号、各块的偏移量
- 内存预留块:告诉内核哪些内存区域不能碰,比如 GPU 或 DSP 用的内存
- 结构块:核心内容,节点和属性的树形结构,用 token 分隔
- 字符串块:所有属性名的字符串,结构块里只存偏移量,节省空间
为什么这么设计? 内核启动时,DTB 被映射到内存中,内核直接解析结构块和字符串块,不需要额外的内存分配。说白了,就是为嵌入式系统“省吃俭用”设计的。
1.4 编译流程:从 DTS 到 DTB 的完整链路
整个流程其实就三步,但每一步都有坑。我画个流程图:
实际项目中,DTS 文件通常不是孤立的。你会看到这样的文件结构:
arch/arm/boot/dts/
├── am33xx.dtsi # SoC 级通用定义
├── am335x-bone.dtsi # BeagleBone 通用定义
├── am335x-boneblack.dts # BeagleBone Black 具体配置
└── am335x-bonegreen.dts # BeagleBone Green 具体配置
这里用到了 .dtsi 文件,它是“设备树包含文件”。我习惯把 SoC 的通用外设定义放在 .dtsi 里,板级差异放在 .dts 里。这样复用性极高。
am33xx.dtsi 里的 UART 地址写错了,结果所有基于 AM335x 的板子串口都打不开。排查了三天才发现是 .dtsi 的问题。从那以后,我每次改 .dtsi 都会在提交信息里加个 [COMMON] 前缀,提醒自己和团队——这玩意儿会影响所有板子。
1.5 编译过程中的常见问题
| 问题 | 现象 | 原因 | 解决方法 |
|---|---|---|---|
| 语法错误 | DTC 报错,编译失败 | 漏了分号、括号不匹配 | 用 dtc -I dts -O dtb -o /dev/null 只做语法检查 |
| 地址冲突 | 两个设备用同一个地址 | reg 属性写重复了 |
检查原理图,确认地址唯一 |
| 中断号错误 | 设备不响应中断 | interrupts 属性值不对 |
查 SoC 手册,确认中断号映射 |
| 包含文件缺失 | 找不到 .dtsi 文件 |
路径或文件名写错 | 用 -I 参数指定搜索路径 |
/dts-v1/; 声明)。我建议统一使用内核源码树里的 DTC,版本跟着内核走,最保险。
好了,设备树的基础回顾就到这里。DTS、DTC、DTB 这三者的关系,说白了就是“写文本 → 编译 → 二进制加载”。理解了这个流程,后面讲多平台复用策略时,你就能明白为什么有些做法行得通,有些行不通。
记住一句话:设备树是给内核看的,不是给人看的。所以写 DTS 时,多想想内核会怎么解析它,而不是你觉得怎么好看。
核心要点总结:
- DTS 是源文件,用树形结构描述硬件
- DTC 是编译器,把 DTS 转成 DTB
- DTB 是二进制文件,内核启动时解析
.dtsi用于复用,.dts用于板级差异- 编译流程:DTS → 预处理 → DTC → DTB → 内核
可可精品资料库