2. 设备树基础:DTS 的起源、语法结构与核心概念
设备树(Device Tree)是嵌入式 Linux 驱动开发中用于描述硬件拓扑结构的一种数据结构。它解决了传统板级文件(board-*.c)中硬件信息硬编码、代码冗余、难以维护的问题,是驱动架构分层重构的基石。
2.1 设备树的起源与设计动机
在 Linux 内核早期,ARM 架构的每个开发板都需要在 arch/arm/mach-* 下编写大量板级文件,用于注册平台设备、定义内存地址、中断号等硬件信息。这种模式导致内核代码膨胀、不同板卡间代码重复率高、维护成本急剧上升。
设备树(Flattened Device Tree, FDT)最初源于 Open Firmware(IEEE 1275)规范,由 PowerPC 架构引入,后被 ARM、RISC-V、X86 等架构广泛采用。其核心思想是:将硬件描述从内核源码中剥离,以独立的数据结构(DTS)传递给内核,驱动通过解析设备树获取硬件资源,实现“一套内核镜像,支持多个板卡”。
2.2 DTS 语法结构
设备树源文件(.dts)采用树形层次结构,以节点(node)和属性(property)描述硬件。每个节点代表一个硬件设备或总线,属性描述设备的配置参数。
2.2.1 基本语法规则
- 节点定义:
node-name@unit-address { ... };,其中unit-address通常为寄存器基地址。 - 属性赋值:
property-name = value;,支持多种数据类型。 - 标签(Label):
label: node-name@addr { ... };,用于在 DTS 中引用节点。 - 包含文件:
#include "soc.dtsi",复用通用硬件描述。 - 注释: 支持 C 风格
/* */和 C++ 风格//。
2.2.2 完整 DTS 示例
// 根节点
/ {
model = "Embedded Board Rev 2.0";
compatible = "vendor,board-rev2", "vendor,board";
#address-cells = <1>;
#size-cells = <1>;
// CPU 节点
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x0>;
device_type = "cpu";
};
// 内存节点
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; // 1GB RAM
};
// UART 控制器节点
uart0: serial@1c090000 {
compatible = "vendor,uart";
reg = <0x1c090000 0x1000>;
interrupts = <0 37 4>;
clock-frequency = <24000000>;
status = "okay";
};
// I2C 控制器及子设备
i2c0: i2c@1c0a0000 {
compatible = "vendor,i2c";
reg = <0x1c0a0000 0x1000>;
interrupts = <0 38 4>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <8>;
};
};
};
2.3 节点与属性的核心概念
2.3.1 节点(Node)
节点是设备树的基本组成单元,每个节点对应一个硬件设备、总线或功能模块。节点通过路径(path)唯一标识,例如 /soc/uart@1c090000。
| 节点类型 | 说明 | 示例 |
|---|---|---|
| 根节点 | 设备树的顶层节点,用 / 表示 |
/ { ... }; |
| 设备节点 | 描述具体外设或控制器 | uart0: serial@1c090000 |
| 总线节点 | 描述总线控制器,可包含子节点 | i2c0: i2c@1c0a0000 |
| 子节点 | 挂载在总线下的从设备 | eeprom@50 |
| 别名节点 | 通过 aliases 提供快捷引用 |
aliases { uart0 = &uart0; }; |
2.3.2 属性(Property)
属性是节点的配置参数,驱动通过解析属性获取硬件资源。常用属性包括:
| 属性名 | 数据类型 | 说明 | 示例 |
|---|---|---|---|
compatible |
字符串列表 | 设备兼容性标识,驱动匹配的关键 | "vendor,uart" |
reg |
u32/u64 数组 | 寄存器地址和长度 | <0x1c090000 0x1000> |
interrupts |
u32 数组 | 中断号、触发类型等 | <0 37 4> |
status |
字符串 | 设备状态:okay / disabled |
"okay" |
#address-cells |
u32 | 子节点 reg 中地址字段的 cell 数 |
<1> |
#size-cells |
u32 | 子节点 reg 中长度字段的 cell 数 |
<1> |
clock-frequency |
u32 | 时钟频率(Hz) | <24000000> |
2.4 设备树层次结构与寻址
设备树的树形结构反映了硬件的物理拓扑。根节点下通常包含 cpus、memory、soc(片上系统)等顶层节点。soc 节点下再挂载各类外设控制器,形成层次化描述。
2.5 设备树在驱动架构中的角色
设备树是驱动架构分层重构的“硬件描述层”。它将硬件配置信息从驱动代码中解耦,驱动只需通过标准 API 读取设备树属性,即可获取寄存器地址、中断号、时钟频率等资源。
驱动与设备树的交互流程如下:
- 设备树编译:
.dts文件通过 DTC 编译为.dtb二进制文件。 - Bootloader 传递: U-Boot 等 bootloader 将 DTB 加载到内存,并将地址传递给内核。
- 内核解析: 内核在启动时解析 DTB,构建
device_node树结构。 - 驱动匹配: 驱动通过
compatible属性与设备节点匹配,触发probe函数。 - 资源获取: 驱动使用
of_iomap()、irq_of_parse_and_map()等 API 获取寄存器地址和中断号。
2.6 本章小结
- 设备树将硬件描述从内核源码中解耦,是驱动架构分层重构的硬件描述层。
- DTS 采用树形结构,节点代表设备,属性描述配置参数。
compatible属性是驱动匹配的关键,reg和interrupts是获取硬件资源的核心属性。- 驱动应通过
of_*API 读取设备树,禁止硬编码寄存器地址和中断号。