3. 设备树基础:DTS/DTSI/DTB 文件格式、节点与属性定义
设备树(Device Tree)是嵌入式 Linux 内核中用于描述硬件拓扑结构的数据结构。它取代了传统架构中硬编码的 board-*.c 文件,实现了内核镜像与硬件描述的分离。本章将深入剖析设备树的三种文件格式(DTS、DTSI、DTB)、节点与属性的语法规则,以及它们在内核配置与驱动裁剪中的核心作用。
3.1 设备树文件体系:DTS / DTSI / DTB
设备树源文件采用文本格式编写,经过编译生成二进制格式供内核解析。整个流程涉及三种文件类型:
| 文件类型 | 扩展名 | 格式 | 作用 | 是否可包含 |
|---|---|---|---|---|
| 设备树源文件 | .dts |
文本(C 风格) | 描述单板级硬件,是编译入口 | 可 #include 其他文件 |
| 设备树头文件 | .dtsi |
文本(C 风格) | 存放 SoC 公共定义、外设 IP 模板 | 被 .dts 或其它 .dtsi 包含 |
| 设备树二进制 | .dtb |
二进制(FDT 格式) | Bootloader 传递给内核的硬件描述 | 不可包含 |
.dtsi 是“硬件 IP 库”,.dts 是“单板实例”。驱动裁剪时,修改 .dtsi 会影响所有使用该 SoC 的单板;修改 .dts 只影响当前单板。
3.2 从 DTS 到 DTB 的编译流程
设备树编译器(dtc)将 .dts 及其包含的 .dtsi 文件解析为扁平设备树(Flattened Device Tree, FDT),最终生成 .dtb 文件。内核在启动早期通过 unflatten_device_tree() 将 DTB 恢复为内存中的树状结构。
dtc -I dts -O dtb -o board.dtb board.dts。内核构建系统(Kbuild)会自动处理 arch/arm/boot/dts/ 或 arch/riscv/boot/dts/ 下的设备树编译。
3.3 节点(Node)定义与层级结构
设备树的核心是节点,每个节点代表一个硬件设备或总线。节点采用树形层级组织,根节点为 /。节点名称格式为 node-name@unit-address,其中 unit-address 通常为寄存器基地址或设备编号。
/dts-v1/;
/ {
model = "Embedded Board v2.0";
compatible = "vendor,board-v2", "vendor,board";
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x0>;
device_type = "cpu";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; /* 1GB RAM */
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
status = "okay";
};
i2c@10002000 {
compatible = "vendor,i2c-ip";
reg = <0x10002000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <8>;
};
};
};
};
3.4 属性(Property)定义与数据类型
属性是节点的“键值对”,用于描述设备的特性。设备树规范定义了多种属性数据类型:
| 数据类型 | 语法示例 | 说明 | 典型用途 |
|---|---|---|---|
| 字符串 | compatible = "vendor,device"; |
双引号包裹的 ASCII 字符串 | 驱动匹配标识 |
| 32 位整数 | reg = <0x1000 0x200>; |
尖括号包裹,空格分隔 | 地址、大小、时钟频率 |
| 64 位整数 | reg = <0x00000000 0x80000000>; |
使用两个 32 位单元表示 | 64 位地址空间 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,十六进制字节 | MAC 地址、UUID |
| 布尔值 | dma-coherent; |
仅属性名,无值 | 开关型特性 |
| 字符串列表 | compatible = "a", "b", "c"; |
逗号分隔的多个字符串 | 驱动兼容性回退 |
3.5 核心属性详解
3.5.1 compatible —— 驱动匹配的关键
compatible 是设备树中最关键的属性。内核通过该属性查找对应的 platform_driver。格式为 "厂商,设备型号",支持多级回退:
compatible = "ti,am335x-uart", "ns16550a";
内核优先匹配 ti,am335x-uart,若未找到则回退到 ns16550a。
3.5.2 reg —— 地址与大小描述
reg 属性描述设备的寄存器地址范围。其格式由父节点的 #address-cells 和 #size-cells 决定:
#address-cells = <1>; /* 地址用 1 个 32 位单元 */
#size-cells = <1>; /* 大小用 1 个 32 位单元 */
uart@10000000 {
reg = <0x10000000 0x1000>; /* 基址 0x10000000,长度 0x1000 */
};
3.5.3 interrupts —— 中断描述
中断属性通常包含三个字段:中断控制器编号、中断号、触发类型。触发类型定义在 include/dt-bindings/interrupt-controller/irq.h 中:
interrupts = <0 33 4>;
/* 字段含义:中断控制器 0,中断号 33,触发类型 4(高电平触发) */
3.5.4 status —— 设备启用控制
status 属性是驱动裁剪的“开关”。在 .dtsi 中默认设为 disabled,在 .dts 中按需改为 okay:
/* soc.dtsi 中默认关闭 */
uart@10000000 {
status = "disabled";
};
/* board.dts 中启用 */
#include "soc.dtsi"
&uart@10000000 {
status = "okay";
};
status 设为 disabled 并不会减少内核镜像大小。驱动代码仍会被编译进内核,只是不执行 probe。真正的裁剪需要配合 Kconfig 的 CONFIG_ 选项,从编译层面移除驱动。
3.6 节点与属性的层级关系图
以下 SVG 图展示了一个典型嵌入式系统的设备树层级,包含根节点、SoC 总线、外设以及子设备:
3.7 设备树在驱动裁剪中的实践要点
- 模块化拆分原则: 将 SoC 公共 IP 定义放入
.dtsi,单板特有外设和引脚复用放入.dts。裁剪时优先在.dts中status = "disabled"关闭不需要的外设。 - 覆盖(Override)机制: 使用
&label语法引用.dtsi中定义的节点,在.dts中修改属性值,无需复制整个节点。 - 裁剪粒度: 设备树只能控制“设备是否被内核识别”,无法控制驱动代码是否编译。真正的二进制大小优化需要配合 Kconfig 的
CONFIG_选项。 - 调试手段: 使用
dtc -I dtb -O dts board.dtb反编译 DTB 验证修改是否正确;内核启动参数earlycon和of_platform_dump可打印设备树解析过程。