2. 设备树语法基础:节点、属性、标签、路径
设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设、中断控制器、GPIO控制器等硬件信息组织起来。理解设备树的语法规则是编写设备树源文件(.dts)和开发设备驱动的第一步。
设备树的语法由四个核心元素构成:节点(node)、属性(property)、标签(label)和路径(path)。下面逐一讲解它们的语法规则与使用场景。
2.1 节点(Node)—— 设备树的骨架
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点采用树形嵌套结构,根节点为/,所有其他节点都是根节点的子节点或后代节点。
节点语法格式:
node-name {
property1 = <value>;
property2 = "string";
child-node {
...
};
};
- 节点名称:由字母、数字、连字符(
-)、下划线(_)组成,不能以数字开头。 - 节点体:用花括号
{ }包裹,内部包含属性和子节点。 - 节点结束:用分号
;表示节点定义结束。 - 根节点:
/ { ... };是设备树的顶层节点,所有硬件描述都从根节点开始。
节点命名规范: 推荐使用
device-name@unit-address 格式,其中 unit-address 是设备在总线上的基地址(如寄存器地址),用于区分同名的设备节点。
示例:
uart@44e09000 表示一个UART设备,其寄存器基地址为 0x44e09000。如果设备没有地址(如虚拟设备),可以省略 @unit-address 部分。
2.2 属性(Property)—— 描述设备特征
属性是附着在节点上的键值对,用于描述设备的硬件特征、配置参数、资源映射等。属性值可以是多种数据类型。
属性语法格式:
property-name = <value>;
property-name = "string";
property-name = [byte-stream];
property-name;
属性值类型对照表:
| 数据类型 | 语法表示 | 示例 | 说明 |
|---|---|---|---|
| 32位整数 | <value> |
<0x1000> |
单个32位无符号整数 |
| 整数数组 | <val1 val2 ...> |
<0x1000 0x2000 0x3000> |
多个32位整数,空格分隔 |
| 字符串 | "string" |
"ti,omap3-uart" |
双引号包裹的文本 |
| 字符串列表 | "str1", "str2" |
"okay", "disabled" |
逗号分隔的多个字符串 |
| 字节流 | [hex bytes] |
[01 02 03 0a] |
十六进制字节序列,空格分隔 |
| 布尔属性 | 仅属性名,无值 | status; |
存在即为 true,不存在为 false |
| 混合类型 | <val> "str" [bytes] |
<0x100> "reg" [00 01] |
不同类型值组合,空格分隔 |
常见陷阱: 属性值中的逗号、分号、引号、尖括号必须严格匹配。例如
<0x1000> 不能写成 <0x1000> 缺少尖括号;字符串必须使用双引号,不能使用单引号。
标准属性示例:
uart@44e09000 {
compatible = "ti,omap3-uart"; // 字符串:驱动兼容性匹配
reg = <0x44e09000 0x2000>; // 整数数组:寄存器基地址+长度
interrupts = <0x2d>; // 整数:中断号
status = "okay"; // 字符串:设备状态
clock-frequency = <48000000>; // 整数:时钟频率
dmas = <&edma 0x08>; // 引用+整数:DMA通道
};
2.3 标签(Label)—— 节点的引用别名
标签是为节点定义的引用别名,可以在设备树的其他位置通过 &label 语法引用该节点。标签极大简化了节点路径的书写,尤其在覆盖(overlay)和引用其他节点时非常有用。
标签语法格式:
label: node-name {
// 节点内容
};
标签引用语法:
&label {
// 追加或覆盖属性
};
标签使用示例:
// 定义带标签的节点
uart0: uart@44e09000 {
compatible = "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
status = "okay";
};
// 在其他地方引用标签,追加属性
&uart0 {
pinctrl-0 = <&uart0_pins>;
clock-frequency = <48000000>;
};
标签命名规则: 标签名必须以字母或下划线开头,后跟字母、数字、下划线。标签名不能与节点名、属性名冲突。推荐使用有意义的名称,如
uart0、i2c1、mmc0。
2.4 路径(Path)—— 定位节点的地址
路径是从根节点到目标节点的完整地址描述,类似于文件系统中的绝对路径。路径用于在设备树中唯一标识一个节点,常用于 phandle 引用、设备树覆盖(overlay)和驱动代码中的节点查找。
路径语法格式:
/node-name/child-node/grandchild-node
- 以斜杠
/开头,表示从根节点开始。 - 各级节点名称用斜杠
/分隔。 - 路径中不包含
@unit-address部分也可以匹配,但推荐包含以精确指定。
路径示例:
/soc/i2c@44e0b000/eeprom@50
/soc/uart@44e09000
/cpus/cpu@0
路径与标签的对比:
| 特性 | 路径(Path) | 标签(Label) |
|---|---|---|
| 语法 | /soc/i2c@44e0b000 |
&i2c0 |
| 可读性 | 冗长,但精确 | 简洁,易读 |
| 唯一性 | 全局唯一 | 需保证标签名不重复 |
| 使用场景 | 驱动代码中查找节点(of_find_node_by_path) |
设备树源文件内引用、覆盖、overlay |
| 灵活性 | 固定路径,不易重构 | 标签名不变即可,节点可移动 |
实践建议: 在设备树源文件(
.dts)中优先使用标签引用,便于维护和覆盖。在驱动代码中,使用路径或 phandle 查找节点,因为驱动无法直接使用标签名。
2.5 节点名称中的 unit-address
当多个同类型设备挂载在同一总线上时,unit-address 用于区分它们。它通常对应设备的寄存器基地址、片选号或端口号。
语法:
device-type@unit-address
示例:
i2c@44e0b000 {
eeprom@50 { // 7位I2C地址 0x50
compatible = "atmel,24c02";
reg = <0x50>;
};
rtc@68 { // 7位I2C地址 0x68
compatible = "ds1307";
reg = <0x68>;
};
};
注意:
unit-address 必须与 reg 属性中的第一个地址值一致,否则设备树编译器(DTC)会报错。例如 eeprom@50 节点的 reg = <0x50> 必须匹配。
2.6 节点与属性的完整示例
下面是一个完整的设备树片段,综合展示了节点、属性、标签、路径的语法:
/dts-v1/;
/ {
model = "TI AM335x Board";
compatible = "ti,am335x";
cpus {
cpu@0 {
compatible = "arm,cortex-a8";
device_type = "cpu";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB
};
leds {
compatible = "gpio-leds";
led0: led@0 {
label = "heartbeat";
gpios = <&gpio1 0x10 0x0>;
linux,default-trigger = "heartbeat";
};
};
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c";
reg = <0x44e0b000 0x1000>;
interrupts = <0x46>;
status = "okay";
clock-frequency = <100000>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
};
// 使用标签引用追加属性
&i2c0 {
pinctrl-0 = <&i2c0_pins>;
pinctrl-names = "default";
};
// 使用标签引用覆盖 led0 属性
&led0 {
linux,default-trigger = "timer";
};
关键点总结:
- 节点:树形结构,
/为根,子节点用{ }包裹。 - 属性:键值对,支持整数、字符串、字节流、布尔等多种类型。
- 标签:
label: node-name定义,&label引用,用于覆盖和追加。 - 路径:
/node/child格式,用于驱动代码中定位节点。 - unit-address:节点名中
@后的地址,必须与reg属性匹配。