1. 设备树基础概念:DTS 的起源、作用与基本结构
1.1 设备树的起源:从“硬编码”到“描述”
在早期 Linux 内核(2.6 及之前)中,板级硬件信息(如内存地址、中断号、GPIO 映射)被直接硬编码在 arch/arm/mach-xxx 下的 C 语言板文件中。每增加一款新硬件平台,就需要新增或修改大量 C 代码,导致内核源码膨胀、维护困难,且无法实现“同一内核镜像支持多款设备”。
设备树(Device Tree,DT)正是为解决此问题而生。它起源于 PowerPC 架构(Open Firmware),后被 ARM、RISC-V、x86 等架构广泛采用。其核心思想是:将硬件描述从内核代码中剥离,以结构化文本(DTS)的形式独立存在,内核在启动时解析该描述,动态创建平台设备。
关键转变: 从“代码定义硬件” → “数据描述硬件”。设备树是数据,不是代码。
1.2 设备树的作用:硬件拓扑的“数据清单”
设备树在 Linux 系统中扮演三个核心角色:
| 角色 | 说明 | 典型内容 |
|---|---|---|
| 硬件拓扑描述 | 描述 CPU、内存、总线、外设的层级连接关系 | CPU 核心数、内存基地址与大小、总线类型 |
| 资源清单 | 声明每个设备使用的硬件资源 | 中断号、寄存器地址范围、DMA 通道、时钟频率 |
| 配置参数 | 提供设备运行所需的非通用参数 | GPIO 默认电平、SPI 模式、I2C 地址 |
内核启动时,Bootloader(如 U-Boot)将编译后的设备树二进制(DTB)传递给内核;内核的 Device Tree Blob (DTB) 解析器 遍历节点,为每个匹配的节点创建 platform_device 或 i2c_client 等设备实例。
1.3 设备树的基本结构:节点、属性与层级
设备树源文件(.dts)采用树形层级结构,每个节点代表一个硬件设备或总线,节点内包含若干属性(键值对)。
// 最小设备树示例
/dts-v1/;
/ {
model = "Example ARM Board";
compatible = "vendor,example-board";
#address-cells = <1>;
#size-cells = <1>;
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; // 1GB RAM
};
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
};
};
};
核心语法要素:
- 根节点:
/,整个设备树的起点,包含model、compatible等全局属性。 - 节点名:
uart0: serial@10000000,格式为[标签:]名称[@地址]。标签(label)用于引用,地址用于区分同类型设备。 - 属性:
compatible = "vendor,uart";,驱动匹配的关键字符串;reg描述地址空间;interrupts描述中断资源。 - 地址单元:
#address-cells和#size-cells定义子节点reg属性的地址和长度字段宽度(32位或64位)。
1.4 设备树与平台设备(Platform Device)的关系
平台设备(Platform Device)是 Linux 内核中用于“非可枚举总线”上设备的抽象模型。设备树与平台设备的关系可概括为:设备树是“硬件描述源”,平台设备是“内核设备实例”。
| 维度 | 设备树(DTS/DTB) | 平台设备(Platform Device) |
|---|---|---|
| 存在位置 | 源码目录 arch/arm/boot/dts/,编译为 DTB |
内核运行时动态创建,挂载在 platform_bus 上 |
| 生命周期 | 编译时生成,启动时由 Bootloader 加载 | 内核启动时由 of_platform_populate() 创建 |
| 匹配机制 | 节点中的 compatible 属性 |
驱动中的 of_match_table 与节点 compatible 匹配 |
| 资源传递 | 通过 reg、interrupts 等属性声明 |
内核解析 DTB 后填充 platform_device->resource 数组 |
| 典型场景 | 描述 SoC 内部外设(UART、SPI、I2C、DMA) | 驱动绑定后提供 probe() 回调,完成设备初始化 |
调试要点: 当你在
/sys/devices/platform/ 下看到某个设备节点时,说明设备树解析成功并创建了对应的平台设备。如果设备未出现,请检查 DTB 是否被正确加载、compatible 是否匹配。
1.5 常见误区与避坑
- 误区一: 认为设备树是内核驱动的一部分。实际上设备树是独立的数据文件,驱动通过
of_match_table引用它,但两者编译和发布是分离的。 - 误区二: 所有设备都必须出现在设备树中。对于 PCI、USB 等可枚举总线上的设备,设备树通常只描述总线控制器本身,子设备由总线枚举自动发现。
- 误区三: 修改 DTS 后必须重新编译整个内核。实际上只需重新编译 DTB(
make dtbs),然后替换启动分区中的 DTB 文件即可。
调试警告: 如果 DTB 与内核版本不匹配(例如使用旧内核解析新 DTB),可能导致
compatible 字符串无法识别,设备不会被创建。始终确保 DTB 与内核源码树同步编译。