1. 设备树概述:设备树(DTS)的起源、作用、基本语法结构,以及为什么需要设备树来描述GPIO控制器与中断资源
1.1 设备树的起源与背景
在早期的嵌入式Linux系统中(如ARM Linux 2.6及更早版本),板级硬件信息被硬编码在大量的 board-*.c 文件中。每引入一款新硬件平台,内核源码树中就需要增加数百行甚至上千行的平台设备注册代码、GPIO配置代码和中断映射代码。这种“代码即配置”的方式导致了以下严重问题:
- 代码冗余:同一款SoC的不同板卡,其GPIO和中断配置差异极小,却需要复制大量相似代码。
- 内核与硬件耦合:更换外设或调整引脚功能时,必须重新编译内核。
- 主线化困难:厂商的板级补丁难以被Linux内核主线社区接受,维护成本极高。
为了解决上述问题,Linux内核社区从PowerPC架构引入了设备树(Device Tree)机制。设备树是一种描述硬件资源的数据结构,它将硬件配置信息从内核代码中剥离出来,以文本形式(.dts文件)独立存在,并在内核启动时由引导加载程序传递给内核。
关键里程碑:ARM Linux在2011年(Linux 3.0左右)开始大规模采用设备树,到2014年(Linux 3.16)后,设备树成为ARM平台的标准硬件描述方式。
1.2 设备树的作用
设备树在嵌入式Linux系统中的核心作用可以归纳为以下四点:
| 作用 | 说明 | 与GPIO/中断的关联 |
|---|---|---|
| 硬件描述 | 描述CPU、内存、总线、外设的地址、中断、时钟、GPIO等资源 | 定义GPIO控制器的寄存器基址、中断控制器的中断号范围 |
| 资源解耦 | 将硬件配置与内核驱动代码分离,同一驱动可适配不同板卡 | 驱动通过设备树节点获取GPIO编号和中断号,无需硬编码 |
| 启动参数传递 | 引导程序(如U-Boot)将设备树二进制(DTB)传递给内核 | 内核解析DTB后建立GPIO和中断的映射关系 |
| 设备发现 | 内核根据设备树节点自动创建设备,匹配驱动 | GPIO控制器和中断控制器作为平台设备被注册 |
理解要点:设备树本质上是一个“硬件数据库”,内核在启动时读取这个数据库,就知道“哪个GPIO引脚连接了哪个外设”、“哪个中断号对应哪个功能模块”。
1.3 设备树的基本语法结构
设备树源文件(.dts)采用树形层次结构,以节点(node)和属性(property)为核心。以下是一个包含GPIO控制器和中断控制器的典型设备树片段:
/dts-v1/;
/ {
model = "Embedded Development Board";
compatible = "vendor,board-v1";
#address-cells = <1>;
#size-cells = <1>;
/* 中断控制器节点 */
intc: interrupt-controller@10000000 {
compatible = "vendor,soc-intc";
reg = <0x10000000 0x1000>;
interrupt-controller;
#interrupt-cells = <2>;
};
/* GPIO控制器节点 */
gpio0: gpio@20000000 {
compatible = "vendor,soc-gpio";
reg = <0x20000000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <0 31>; /* 连接到intc,类型0,中断号31 */
interrupt-parent = <&intc>;
};
/* 使用GPIO和中断的外设节点 */
uart@30000000 {
compatible = "vendor,uart";
reg = <0x30000000 0x1000>;
interrupts = <0 45>;
interrupt-parent = <&intc>;
cts-gpios = <&gpio0 5 0>; /* GPIO0的第5号引脚 */
rts-gpios = <&gpio0 6 0>;
};
};
语法要点说明:
- 节点(Node):以
节点名@地址形式定义,如gpio@20000000。 - 属性(Property):键值对,值可以是字符串、32位整数数组(
< >)、字节数组([ ])等。 - 标签(Label):如
gpio0:,用于在其它节点中引用(通过&gpio0)。 - 特殊属性:
compatible用于驱动匹配,reg描述地址空间,interrupts描述中断资源。
1.4 为什么需要设备树来描述GPIO控制器与中断资源
GPIO控制器和中断资源是嵌入式系统中最核心、最复杂的硬件资源。使用设备树来描述它们,主要基于以下三个关键原因:
- 资源映射的复杂性:一个SoC内部可能有多个GPIO控制器(每个控制器管理32个或更多引脚),以及多个中断控制器(如GIC、AINTC)。设备树通过
interrupt-parent和interrupts属性,清晰地建立了“外设 → 中断控制器 → 中断号”的层级映射关系。 - 引脚复用与配置:同一物理引脚可能被配置为GPIO、UART、I2C等不同功能。设备树中的
pinctrl子系统与GPIO控制器节点配合,可以在系统启动时自动完成引脚功能切换,避免驱动代码中硬编码寄存器操作。 - 板级差异隔离:同一款SoC可以用于数十种不同的板卡,每块板卡的GPIO分配和中断连接可能完全不同。设备树允许厂商只提供一个SoC的
.dtsi文件(包含所有内部控制器节点),然后为每块板卡编写一个极小的.dts文件(仅描述板级差异),极大减少了重复工作。
常见误区:不要试图在设备树中描述所有硬件细节。设备树只描述“硬件拓扑”和“资源分配”,不描述“驱动行为”或“初始化时序”。例如,GPIO控制器的驱动初始化顺序由内核驱动模型管理,而不是由设备树控制。
1.5 GPIO控制器与中断资源的设备树关系图
下图展示了设备树中GPIO控制器节点、中断控制器节点以及外设节点之间的资源关联关系:
1.6 本章小结
设备树作为嵌入式Linux硬件描述的标准语言,其核心价值在于将GPIO控制器、中断控制器等硬件资源的配置信息从内核代码中分离出来。通过本章的学习,你应该掌握:
- 设备树解决了早期ARM Linux中“代码即配置”导致的维护难题。
- 设备树通过节点和属性描述硬件拓扑,
interrupts、gpios等属性建立了资源关联。 - GPIO控制器和中断资源的设备树描述,是实现驱动与硬件解耦、支持板级差异化的基础。