3. 设备树(DTS)基础:设备树语法、节点、属性、reg、interrupts
设备树(Device Tree)是一种描述硬件资源的数据结构,它把嵌入式系统中不可枚举的硬件设备信息(如内存基址、中断号、GPIO引脚、时钟频率等)从内核源码中剥离出来,以文本形式独立描述。内核在启动时解析设备树二进制文件(DTB),动态创建平台设备和资源映射,从而实现“一套内核镜像支持多款硬件”的目标。
核心思想:设备树是“硬件描述语言”,不是“驱动代码”。驱动通过匹配 compatible 属性找到对应设备节点,再读取节点中的 reg、interrupts、GPIO 等属性来获取硬件资源。
3.1 设备树源文件(DTS)基本语法
设备树源文件采用树形层次结构,以分号结尾,花括号表示层级。一个最小的 DTS 文件包含根节点、CPU 节点、内存节点和若干外设节点。
/dts-v1/;
/ {
compatible = "vendor,board-name";
#address-cells = <1>;
#size-cells = <1>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512MB RAM */
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
status = "okay";
};
};
};
语法要点:
/dts-v1/;— 版本标识,必须放在文件开头。/ { };— 根节点,所有设备节点都是它的子节点。node-name@unit-address— 节点名 + 单元地址(可选,用于区分同类型设备)。label: node-name— 标签,方便其他节点引用(如&uart0)。property = value;— 属性赋值,值可以是字符串、整数、字节数组等。
3.2 节点与属性
每个设备树节点代表一个硬件设备或总线,节点内部通过属性描述设备的配置信息。常用属性分类如下:
| 属性类别 | 属性名 | 说明 | 示例值 |
|---|---|---|---|
| 标识 | compatible | 驱动匹配字符串,格式 "厂商,型号" | "ti,am335x-gpio" |
| 地址 | reg | 寄存器地址范围,由 #address-cells 和 #size-cells 决定长度 | <0x4804c000 0x1000> |
| 中断 | interrupts | 中断号、触发类型,格式由中断控制器定义 | <0 29 4> |
| GPIO | gpios | GPIO 引脚描述,包含控制器句柄、引脚号、标志 | <&gpio1 15 0> |
| 状态 | status | 设备启用状态,"okay" 或 "disabled" | "okay" |
| 时钟 | clocks | 时钟源句柄 | <&clk_uart0> |
| 引脚控制 | pinctrl-0 | 引脚复用配置句柄 | <&pinctrl_uart0> |
常见错误:#address-cells 和 #size-cells 必须在父节点中定义,子节点的 reg 属性才能正确解析。如果忘记设置,内核解析 reg 时会默认使用 2 个 cell 表示地址、1 个 cell 表示长度,导致地址空间错乱。
3.3 reg 属性详解
reg 属性描述设备在总线上的地址空间,格式为 reg = <address1 length1 address2 length2 ...>。每个地址/长度对称为一个“reg 条目”。address 和 length 的 cell 数量由父节点的 #address-cells 和 #size-cells 决定。
/* 32位地址空间,1个cell表示地址,1个cell表示长度 */
#address-cells = <1>;
#size-cells = <1>;
gpio@4804c000 {
reg = <0x4804c000 0x1000>; /* 基址0x4804c000,长度0x1000 */
};
/* 64位地址空间,2个cell表示地址,1个cell表示长度 */
#address-cells = <2>;
#size-cells = <1>;
memory@80000000 {
reg = <0x0 0x80000000 0x20000000>; /* 地址0x80000000,长度0x20000000 */
};
驱动读取 reg 的 API:
platform_get_resource(pdev, IORESOURCE_MEM, index)— 获取第 index 个内存资源。of_iomap(node, index)— 直接映射第 index 个 reg 条目到虚拟地址。of_address_to_resource(node, index, &res)— 将 reg 条目转换为 resource 结构。
3.4 interrupts 属性详解
interrupts 属性描述设备的中断连接信息,其格式由中断控制器(interrupt-controller)的 #interrupt-cells 决定。最常见的是 ARM GIC(通用中断控制器),使用 3 个 cell:
| Cell 位置 | 含义 | 取值说明 |
|---|---|---|
| cell 0 | 中断类型 | 0 = SPI(共享外设中断),1 = PPI(私有外设中断) |
| cell 1 | 中断号 | SPI 中断号范围 32~1019,PPI 中断号范围 16~31 |
| cell 2 | 触发类型 | 1 = 上升沿触发,2 = 下降沿触发,4 = 高电平触发,8 = 低电平触发 |
/* 中断控制器节点 */
gic: interrupt-controller@1e00000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x1e00000 0x1000>;
};
/* 使用中断的设备节点 */
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>; /* SPI 中断号33,高电平触发 */
interrupt-parent = <&gic>; /* 指定中断控制器 */
};
interrupt-parent 的作用:当系统中有多个中断控制器时,设备节点必须通过 interrupt-parent 属性显式指定连接到哪个中断控制器。如果省略,内核会向上查找父节点的 interrupt-parent 或根节点的 interrupt-parent。
3.5 设备树层次结构与引用
设备树采用树形结构,根节点下包含 cpus、memory、soc 等子节点。通过标签和 & 引用,可以在 dtsi 文件中定义基础节点,在 dts 文件中追加或覆盖属性。
/* am335x.dtsi — SoC 级定义 */
/ {
soc {
uart0: serial@44e09000 {
compatible = "ti,am3352-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "disabled";
};
};
};
/* myboard.dts — 板级定义 */
#include "am335x.dtsi"
&uart0 {
status = "okay";
pinctrl-0 = <&pinctrl_uart0>;
pinctrl-names = "default";
};
引用机制优势:
- SoC 厂商提供 dtsi 文件,板卡厂商只需 include 并修改差异部分。
- 避免重复定义,减少错误。
- 通过
&label可以追加属性、修改 status、添加子节点。
3.6 设备树结构总览
下面这张 SVG 图展示了设备树从 DTS 源文件到内核驱动匹配的完整流程,以及节点、属性、reg、interrupts 在其中的位置。
3.7 常用工具与调试方法
| 工具/命令 | 用途 | 示例 |
|---|---|---|
| dtc | DTS 编译/反编译 | dtc -I dts -O dtb -o output.dtb input.dts |
| dtc -I dtb -O dts | 将 DTB 反编译为 DTS | dtc -I dtb -O dts -o output.dts input.dtb |
| ls /proc/device-tree | 查看内核解析后的设备树 | ls /proc/device-tree/soc/uart0 |
| of_* 调试打印 | 驱动中打印节点信息 | of_node_name(node)、of_get_property() |
| devicetree.org | 设备树规范与绑定文档 | 查看 compatible 字符串定义 |
调试建议:在驱动 probe() 函数中加入
dev_info(dev, "node name: %s\n", of_node_name(np)); 可以快速确认设备树节点是否被正确匹配。如果 probe 没有被调用,优先检查 compatible 字符串是否一致、status 是否为 "okay"。
3.8 本章小结
- 设备树是硬件描述文件,使用树形结构组织节点和属性。
- compatible 属性是驱动匹配的关键,必须与驱动中 of_match_table 的字符串完全一致。
- reg 属性描述设备地址空间,由 #address-cells 和 #size-cells 决定格式。
- interrupts 属性描述中断连接,格式由中断控制器的 #interrupt-cells 定义。
- 通过标签引用和 dtsi 包含,可以实现 SoC 级与板级设备树的分离管理。
- dtc 工具和 /proc/device-tree 是调试设备树的利器。