设备树作为描述芯片以及单板架构、组成的一种 ABI 形式载体,其自身的构成、传输、展开与应用都遵循一套固定规则,才能做到独立于 kernel 代码、并具备更强的适配能力。作为一种独立于 kernel 镜像的 ABI 载体,它需要单独编写并生成二进制文件,再传递给 kernel;kernel 按既定规则解析所需数据,供驱动代码使用。
摘抄 kernel: usage-model.rst 的描述如下:
The "Open Firmware Device Tree", or simply Device Tree (DT), is a datastructure and language for describing hardware. More specifically, it is a description of hardware that is readable by an operating system so that the operating system doesn't need to hard code details of the machine.
设备树生命周期

设备树文件经历源码阶段、编译阶段、复制传输阶段、解析增添阶段,最终变成可用的 Expanded Device Tree(EDT)供驱动代码使用。
源码阶段
设备树源文件有两种尾缀:.dts 与 .dtsi。通常意义上:
- dtsi:定义 SoC 级别的硬件信息
- dts:定义 board 级别的硬件信息
- dts 通过
#include 的方式包含 dtsi 文件
设备树的代码实现遵循 device tree 协议以及各厂商规定的兼容 binding 规则,device tree 协议官网(纯 URL 前后加空格提升可读性):
https://www.devicetree.org/

各厂商规定的兼容 binding 规则需要在社区提交审核,最终出现在 release kernel 的 documents 中。

编译阶段
kernel 编译时使用 scripts 下的 dtc 工具(宿主机同样也有 dtc 工具),根据 arch/arm/boot/dts/Makefile 中的规则,将设备树源码编译成 dtb 格式的 ABI 文件。

复制传输阶段
dtb 文件可独立于 kernel 镜像,也可以追加到 zImage 镜像之后,再由 U-Boot 将其复制加载到 memory。
当使用前者进行复制加载时,通过 U-Boot 提供的 fdt 命令可对设备树文件进行局部修改,修改后再传递给 kernel。

当使用后者将 dtb 追加到 zImage 文件之后时,kernel 未提供相关的工具链。需要 kernel 打开 CONFIG_ARM_APPENDED_DTB,将 dtb 文件 cat 到 zImage 文件之后即可。


解析阶段
当 U-Boot 通过 ARM 通用寄存器 r2 将设备树 dtb 文件 memory 地址传递给 kernel 之后,kernel 会将“平铺”的 FDT 文件解析成 EDT 文件,提取 root、chosen、aliases、nodes 等信息供驱动代码使用。
相关的系统与内核启动话题,也可以在 网络/系统 里找到更多实战讨论与案例分析。
设备树的源码实现
设备树基本组成元素
以 vexpress-v2p-ca9.dts 为例,设备树文件的基本组成元素包括:根节点、设备类型、chosen 结点、aliases 结点、某 bus 类型的各种 node。

model
在设备树应用于 ARM 平台前,U-Boot 通过给 ARM 通用寄存器 r1 赋值来给 kernel 传递 machine type;在设备树文件应用于 ARM 平台后,kernel 通过解析设备树文件的 model 来识别并匹配对应设备,以进行适配与初始化硬件。
chosen
这个结点不描述具体硬件信息,主要用于设备树与 kernel 之间传递信息,通常传递的是 bootargs。
在设备树中可以将该节点置空,也可以预先定义一些 bootargs,然后在 kernel 的 boot 阶段填充。比如当前设备树文件中 chosen 结点是空的,但 kernel 启动后再去查看 chosen 结点,就会发现其中已经被填充了 bootargs。

aliases
kernel 通常按照绝对路径查询一个设备结点,而 aliases 会给普通设备结点的 label 取一个别名,方便后续定位与查看设备。
例如 vexpress-v2p-ca9.dts 文件中将串口和 i2c 做了别名处理,那么在查看 dtb 的反汇编文件时,别名会按绝对路径展开。

使用了别名的串口、i2c 与未使用别名的 watchdog 对比很明显,也更容易看清串口设备的编号关系。

nodes
普通设备结点是设备树文件中占比最多的内容。这些结点必须从属于某一种总线类型,例如 simple-bus 或 amba-bus。普通设备结点下包含硬件描述信息,常见字段包括:
- 设备的适配名(compatible 等)
- 寄存器(reg)
- 中断(interrupts)
- 时钟(clocks)
- DMA 通道
- 引脚复用(pinctrl)等
不同设备需要按其实际特性描述。对于 USB、PCI、Graph 等总线结构的设备,还需要在设备树中体现拓扑结构、中断映射 map 等特性。你是否也遇到过“设备节点写了但驱动就是 probe 不到”的情况?很多时候问题就藏在这些属性与绑定规则里。
设备树的头文件
设备树头文件包含是一种“覆盖/插入”的实现方式:xxx.dts 包含 xxx.dtsi 文件,当 dts 与 dtsi 都包含同一个结点 node_A 但结点信息不同时,编译器会将 dtsi 中 node_A 的硬件信息条目插入到 dts 的 node_A 结点。
设备树编译
前文已经提到 dtc。这里再补充一点:dtc 具有反汇编功能。当手头只有一个 dtb 文件,希望查看其 dts 内容时,反汇编会非常实用。
如果 kernel 启动后文件系统中具备 dtc 工具,通过反汇编还可以验证 U-Boot 是否对 dtb 文件做过修改。下面是一个反汇编实例:

设备树编写参考文档
如需获取更多 kernel 文档解读、源码解析与实践踩坑记录,可在 云栈社区 的知识库与讨论区继续延伸阅读。