2. 设备树基础:设备树(DTS)语法入门、节点与属性、标准属性详解
设备树(Device Tree,DT)是嵌入式 Linux 系统中用于描述硬件拓扑结构和资源分配的一种数据结构。它取代了传统架构中硬编码的板级文件(如 arch/arm/mach-xxx),实现了内核与硬件描述的分离。本章将系统讲解设备树的语法、节点与属性,以及三个最核心的标准属性:compatible、reg 和 interrupts。
2.1 设备树语法入门
设备树源文件(.dts)采用树形结构描述硬件,其基本语法规则如下:
- 节点(Node):以
node-name@unit-address形式定义,花括号{ }包裹子节点和属性。 - 属性(Property):键值对,值可以是字符串、整数、字节数组、字符串列表等。
- 根节点:
/表示设备树的根,所有设备节点都挂载在根下。 - 标签(Label):
label: node-name用于引用节点,方便在.dtsi中覆盖。 - 包含文件:
#include引入.dtsi头文件,实现 SoC 级描述复用。
💡 关键点:设备树是“扁平化”的硬件描述,不包含任何内核逻辑。它只回答“硬件有什么、在哪里、怎么访问”,不回答“驱动怎么用”。
2.1.1 一个完整的 DTS 示例
/dts-v1/;
#include "imx6ul.dtsi"
/ {
model = "Embedded Device Board";
compatible = "vendor,embedded-board", "vendor,imx6ul";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
leds {
compatible = "gpio-leds";
led0: led-red {
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
label = "red-led";
};
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
};
2.2 节点与属性详解
设备树的每个节点代表一个硬件设备或总线,属性则描述该设备的配置参数。节点可以嵌套,形成层次化的总线拓扑。
| 概念 | 语法示例 | 说明 |
|---|---|---|
| 节点定义 | spi@1c00000 { ... }; |
节点名 spi,单元地址 1c00000(基址) |
| 属性赋值 | reg = <0x1c00000 0x1000>; |
键 reg,值 <地址 长度> |
| 字符串 | compatible = "vendor,device"; |
双引号包裹,用于驱动匹配 |
| 字符串列表 | compatible = "a", "b", "c"; |
逗号分隔,优先级从高到低 |
| 32位整数数组 | reg = <0x1000 0x2000 0x3000>; |
尖括号包裹,空格分隔 |
| 64位整数 | reg = <0x00000000 0x80000000 0x0 0x20000000>; |
两个 32 位单元拼接 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,十六进制字节 |
| 布尔属性 | status = "disabled"; |
存在即真,常用 okay/disabled |
| 引用(phandle) | &gpio1 |
& 引用标签,编译器替换为整数句柄 |
2.3 标准属性详解
设备树规范定义了一系列标准属性,用于统一描述设备资源。其中 compatible、reg 和 interrupts 是驱动开发中最核心的三个属性。
2.3.1 compatible 属性:驱动匹配的“身份证”
compatible 是设备树中最重要的属性,它建立了设备节点与内核驱动之间的匹配桥梁。其值是一个字符串列表,格式通常为 "厂商,设备型号"。
- 匹配优先级:列表中的第一个字符串优先级最高,内核按顺序尝试匹配。
- 通配匹配:如果设备与多个驱动兼容,可以列出多个字符串,如
"ti,am3352-uart", "ns16550"。 - 驱动侧对应:驱动通过
of_match_table声明支持的 compatible 值。
✅ 最佳实践:compatible 字符串应遵循“厂商前缀+设备名”的命名规范,避免使用过于通用的名称(如
"uart"),防止匹配冲突。
2.3.2 reg 属性:描述地址空间
reg 属性用于描述设备在总线上的地址范围,其格式由父节点的 #address-cells 和 #size-cells 决定。
- #address-cells:表示地址字段占用的 32 位单元数(1 为 32 位,2 为 64 位)。
- #size-cells:表示长度字段占用的 32 位单元数。
- reg 格式:
reg = <地址1 长度1 地址2 长度2 ...>;
/* 32位地址空间 */
spi@1c00000 {
#address-cells = <1>;
#size-cells = <1>;
reg = <0x1c00000 0x1000>; /* 基址0x1c00000,长度0x1000 */
};
/* 64位地址空间 */
memory@80000000 {
#address-cells = <2>;
#size-cells = <2>;
reg = <0x0 0x80000000 0x0 0x20000000>; /* 基址0x80000000,长度0x20000000 */
};
2.3.3 interrupts 属性:中断资源描述
interrupts 属性描述设备使用的中断线,其解析依赖于父节点 interrupt-controller 和 #interrupt-cells。
- interrupt-parent:指定设备连接的中断控制器(phandle 引用)。
- interrupts:中断描述符,格式由中断控制器的
#interrupt-cells决定。 - 常见格式:
<中断号 触发类型>,触发类型如IRQ_TYPE_EDGE_RISING(1)、IRQ_TYPE_LEVEL_HIGH(4)。
/* 中断控制器定义 */
intc: interrupt-controller@a01000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xa01000 0x1000>;
};
/* 使用中断的设备 */
uart@21c0000 {
compatible = "vendor,uart";
reg = <0x21c0000 0x1000>;
interrupt-parent = <&intc>;
interrupts = <0 33 4>; /* SPI 33, 高电平触发 */
};
2.4 设备树节点与标准属性关系图
下图展示了设备树中节点、标准属性与内核驱动之间的核心关系:
2.5 标准属性对比总结
| 属性 | 作用 | 驱动解析 API | 典型值示例 |
|---|---|---|---|
compatible |
驱动匹配标识 | of_match_device() |
"vendor,uart" |
reg |
地址/内存资源 | of_iomap() / platform_get_resource() |
<0x21c0000 0x1000> |
interrupts |
中断资源 | platform_get_irq() / irq_of_parse_and_map() |
<0 33 4> |
2.6 常见陷阱与避坑指南
⚠️ 常见错误:
- compatible 拼写错误:驱动侧和设备树侧字符串必须完全一致,包括大小写和标点。
- reg 的 address-cells 不匹配:父节点与子节点的地址单元数必须一致,否则解析偏移。
- 中断号混淆:SPI(共享外设中断)和 PPI(私有外设中断)的编号范围不同,GIC 中 SPI 从 32 开始。
- 缺少 interrupt-parent:如果设备不在中断控制器子节点下,必须显式指定 interrupt-parent。
2.7 设备树编译与反编译
设备树源文件(.dts)需要编译为二进制设备树 blob(.dtb),内核在启动时解析 .dtb 并构建设备树。常用工具如下:
- 编译:
dtc -I dts -O dtb -o output.dtb input.dts - 反编译:
dtc -I dtb -O dts -o output.dts input.dtb - 查看设备树:
dtc -I fs -O dts /proc/device-tree(运行时查看)
💡 调试技巧:在驱动 probe 函数中添加
dev_info(dev, "compatible: %s\n", of_get_property(dev->of_node, "compatible", NULL)); 可以验证设备树解析是否正确。