2. 设备树简介:设备树起源、语法基础与内核作用
2.1 设备树的起源:从“硬编码”到“描述性”的演进
在早期的 Linux 内核(如 2.4/2.6 时代),嵌入式板级支持包(BSP)中充斥着大量 arch/arm/mach-xxx 下的 C 语言代码,用于静态注册平台设备、定义内存地址、中断号和 GPIO 配置。这种“硬编码”方式导致内核源码与特定硬件板卡高度耦合,每更换一款开发板就需要修改内核源码并重新编译,严重阻碍了代码复用与社区维护。
设备树(Device Tree,DT)的引入彻底改变了这一局面。它起源于 Open Firmware(IEEE 1275)规范,被 PowerPC 架构率先采用,随后在 ARM 架构社区的大力推动下,成为嵌入式 Linux 平台的标准硬件描述语言。其核心思想是:将硬件配置信息从内核源码中剥离,以树形结构的数据文件(.dts)独立描述,由内核在启动时解析并动态生成平台设备。
关键转折点: Linux 3.x 内核开始强制要求 ARM 架构新平台必须使用设备树,从此告别了“每板一源码”的混乱时代。
类比理解: 设备树就像一张“硬件清单 + 连接图”,告诉内核“我有哪些外设、它们挂在哪里、怎么配置”,而不是让内核自己去猜或硬编码。
2.2 设备树语法基础:.dts / .dtsi / .dtb 与节点结构
设备树使用文本格式的源文件(.dts)和头文件(.dtsi),通过 dtc(Device Tree Compiler)编译为二进制格式的 .dtb(Device Tree Blob),由 bootloader 传递给内核。其语法简洁,采用树形层级结构,核心元素包括:
| 文件类型 | 扩展名 | 作用 | 编译产物 |
|---|---|---|---|
| 源文件 | .dts | 描述单板硬件,包含根节点与所有外设节点 | .dtb |
| 头文件 | .dtsi | SoC 公共定义、复用节点,通过 #include 包含 |
— |
| 二进制 | .dtb | 内核启动时解析的二进制硬件描述 | — |
| 叠加层 | .dtbo | 运行时动态修改设备树(如 FPGA 加载) | .dtbo |
基本节点语法:
/dts-v1/;
#include "imx6ull.dtsi"
/ {
model = "Freescale i.MX6 ULL EVK Board";
compatible = "fsl,imx6ull-evk", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
&ecspi1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
cs-gpios = <&gpio4 9 GPIO_ACTIVE_LOW>;
status = "okay";
spidev@0 {
compatible = "spidev";
reg = <0>;
spi-max-frequency = <20000000>;
};
};
};
常见陷阱: 节点名称中的
@ 后面跟的是地址(如 memory@80000000),用于区分同一层级下相同名称的节点,并非设备地址的完整定义。reg 属性才是真正的地址与长度描述。
核心语法要素速查表:
| 语法元素 | 示例 | 说明 |
|---|---|---|
| 根节点 | / { }; |
设备树的起始点,所有设备节点都是其子节点 |
| 节点名 | cpu@0 |
节点名称 + 可选地址单元,地址用于区分同名节点 |
| 属性 | compatible = "vendor,device"; |
驱动匹配的关键字符串,格式为“厂商,设备型号” |
| 地址属性 | reg = <0x10000000 0x1000>; |
描述设备地址空间:起始地址 + 长度 |
| 中断属性 | interrupts = <0 29 4>; |
中断号、触发类型等,依赖 interrupt-parent |
| 标签引用 | &uart1 { ... }; |
通过标签(label)引用并扩展已定义的节点 |
| 包含文件 | #include "imx6ull.dtsi" |
引入 SoC 级公共定义,支持 C 预处理宏 |
2.3 设备树在 Linux 内核中的作用
设备树在内核启动流程中扮演着“硬件配置数据库”的角色,其核心作用贯穿于内核初始化的多个阶段:
- 启动阶段硬件识别: 内核启动早期,通过解析
compatible属性匹配 machine_desc(板级描述符),确定当前运行的硬件平台。 - 设备节点动态创建: 内核遍历设备树中的每个节点,根据
compatible属性查找对应的 platform_driver,匹配成功后调用probe()函数,动态创建 platform_device。 - 资源自动分配: 内核从设备树节点中读取
reg、interrupts、gpios、clocks等属性,自动完成 I/O 内存映射、中断注册、GPIO 申请和时钟使能。 - 总线与从设备枚举: 对于 I2C、SPI 等总线控制器节点,内核解析其子节点,自动在对应总线上注册从设备,无需手动调用
i2c_new_device()或spi_new_device()。 - 电源管理与休眠支持: 设备树中的
power-domains、wakeup-source等属性,为内核电源管理框架提供设备级功耗控制依据。
核心价值总结: 设备树实现了“硬件描述”与“内核驱动”的完全解耦。更换硬件板卡时,只需更换 .dtb 文件,无需修改内核源码,极大提升了嵌入式 Linux 的可移植性和维护效率。
设备树与内核各子系统的交互关系:
| 内核子系统 | 设备树提供的属性/节点 | 作用 |
|---|---|---|
| 平台设备驱动 | compatible、reg |
驱动匹配与 I/O 资源获取 |
| 中断子系统 | interrupts、interrupt-parent |
中断控制器层级与中断号映射 |
| GPIO 子系统 | gpios、gpio-controller |
GPIO 引脚申请与配置 |
| 时钟框架 | clocks、clock-names |
时钟源选择与频率配置 |
| 引脚控制 | pinctrl-* |
引脚复用功能与电气特性设置 |
| DMA 引擎 | dmas、dma-names |
DMA 通道分配与传输配置 |
| 电源管理 | power-domains、wakeup-source |
设备级电源域与唤醒能力控制 |
学习建议: 在阅读内核源码时,可以重点关注
drivers/of/ 目录下的 of_device.c、of_platform.c 和 of_irq.c,它们实现了设备树节点到内核设备对象的完整转换逻辑。