2. 设备树语法入门:节点、属性、标签与路径
设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设(如SPI控制器、I2C控制器、GPIO等)组织起来。本章将系统讲解设备树源文件(.dts)的核心语法要素:节点(node)、属性(property)、标签(label)与路径(path),为后续编写SPI与I2C设备节点打下坚实基础。
学习提示:设备树语法与C语言结构体初始化类似,但更强调层次嵌套和地址映射。掌握节点与属性的关系是理解设备树的关键。
2.1 节点(Node):设备树的骨架
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或控制器。节点通过花括号 { } 定义,内部可以包含属性(描述设备特征)和子节点(描述下级设备)。
节点定义的基本格式:
节点名 {
属性名 = 属性值;
子节点名 {
子节点属性;
};
};
节点命名规则:
- 节点名由小写字母、数字、连字符(
-)和下划线(_)组成。 - 节点名应反映设备类型,如
spi-controller、i2c-bus、gpio-keys。 - 同一层级下节点名必须唯一。
- 根节点固定为
/,所有设备节点都挂在根节点下。
示例:一个简单的设备树根节点与SPI控制器节点
/ {
model = "Embedded Development Board";
compatible = "vendor,board-v1";
spi@ff110000 {
compatible = "vendor,spi-controller";
reg = <0xff110000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
spi-device@0 {
compatible = "vendor,spi-sensor";
reg = <0>;
spi-max-frequency = <5000000>;
};
};
};
2.2 属性(Property):描述设备特征
属性是附着在节点上的键值对,用于描述设备的硬件特征、配置参数和驱动绑定信息。每个属性由属性名和属性值组成,属性值可以是多种数据类型。
属性值的数据类型:
| 数据类型 | 语法格式 | 说明 | 示例 |
|---|---|---|---|
| 字符串 | "string" |
双引号包裹的文本 | compatible = "vendor,device"; |
| 32位整数 | <value> |
尖括号包裹,支持十进制/十六进制 | reg = <0xff110000 0x1000>; |
| 64位整数 | <0x00000000 0xvalue> |
两个32位整数拼接表示64位 | reg = <0x00000000 0x80000000>; |
| 字节数组 | [hex bytes] |
方括号包裹的十六进制字节序列 | local-mac-address = [00 11 22 33 44 55]; |
| 布尔值 | 仅属性名,无值 | 存在即为真,不存在为假 | status = "disabled"; 或 status; |
| 字符串列表 | "str1", "str2" |
逗号分隔的多个字符串 | compatible = "vendor,spi-device", "generic,spi"; |
常用标准属性:
compatible:驱动匹配的关键属性,格式为"厂商,设备型号"。reg:描述设备地址空间,格式为<基地址 长度>。#address-cells和#size-cells:定义子节点中reg属性的地址和长度字段宽度。status:设备状态,可选"okay"、"disabled"、"reserved"。interrupts:描述设备使用的中断号。
常见错误:忘记设置
#address-cells 和 #size-cells 会导致子节点 reg 解析错误。对于SPI总线,通常设置为 <1> 和 <0>;对于I2C总线,通常设置为 <1> 和 <0>。
2.3 标签(Label):节点的别名引用
标签是节点的别名,用于在设备树的其他位置引用该节点。标签定义在节点名之前,使用冒号 : 分隔。标签的主要用途包括:
- 在设备树包含文件(
.dtsi)中覆盖节点属性。 - 在驱动代码中通过
of_find_node_by_label()查找节点。 - 在
phandle引用中作为目标标识。
标签定义语法:
标签名: 节点名 {
属性;
};
示例:
spi0: spi@ff110000 {
compatible = "vendor,spi-controller";
reg = <0xff110000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c0: i2c@ff120000 {
compatible = "vendor,i2c-controller";
reg = <0xff120000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
};
标签引用方式:
- 直接引用:使用
&标签名在另一个节点中引用。 - 覆盖属性:在
.dts文件中通过&标签名 { ... }覆盖.dtsi中的节点属性。
// 在 board.dts 中覆盖 spi0 的属性
&spi0 {
status = "okay";
max-frequency = <50000000>;
};
// 在驱动代码中引用
struct device_node *np = of_find_node_by_label("spi0");
2.4 路径(Path):节点的唯一地址
路径是从根节点到目标节点的完整层级描述,使用斜杠 / 分隔各级节点名。路径是设备树中定位节点的绝对地址,类似于文件系统中的文件路径。
路径格式:
/根节点名/中间节点名/目标节点名
路径示例:
/soc/spi@ff110000/spi-device@0
/soc/i2c@ff120000/i2c-device@1a
路径的用途:
- 在设备树源文件中:使用
&{/路径}语法引用节点。 - 在驱动代码中:使用
of_find_node_by_path()函数查找节点。 - 在设备树覆盖(DT Overlay)中:通过路径指定目标节点。
路径与标签的对比:
| 特性 | 路径(Path) | 标签(Label) |
|---|---|---|
| 定位方式 | 绝对层级定位 | 别名引用 |
| 可读性 | 路径较长,可读性一般 | 简短易记,可读性好 |
| 唯一性 | 全局唯一 | 全局唯一(需避免重复) |
| 适用场景 | 驱动代码查找、覆盖文件 | 属性覆盖、phandle引用 |
| 语法示例 | &{/soc/spi@ff110000} |
&spi0 |
最佳实践:在
.dts 文件中优先使用标签进行属性覆盖,因为标签更简洁且不易出错。在驱动代码中,如果节点没有标签,则使用路径查找。
2.5 节点地址单元与大小单元
#address-cells 和 #size-cells 是总线节点中至关重要的属性,它们定义了子节点 reg 属性的地址和长度字段的宽度(以32位字为单位)。
规则说明:
#address-cells:指定子节点reg中地址字段占用的32位字数量。#size-cells:指定子节点reg中长度字段占用的32位字数量。- 如果子节点没有
reg属性,则这两个属性可以省略。
常见配置:
| 总线类型 | #address-cells | #size-cells | reg 格式示例 |
|---|---|---|---|
| SPI 总线 | 1 | 0 | reg = <片选号>; |
| I2C 总线 | 1 | 0 | reg = <设备地址>; |
| 内存映射总线 | 1 | 1 | reg = <基地址 长度>; |
| 64位地址总线 | 2 | 2 | reg = <高32位 低32位 高32位 低32位>; |
SPI与I2C设备节点示例:
// SPI 控制器节点
spi0: spi@ff110000 {
compatible = "vendor,spi-controller";
reg = <0xff110000 0x1000>;
#address-cells = <1>; // 地址字段占1个32位字(片选号)
#size-cells = <0>; // 长度字段为0(无长度信息)
status = "okay";
// SPI 从设备:片选号0
adc@0 {
compatible = "vendor,spi-adc";
reg = <0>; // 片选号0
spi-max-frequency = <10000000>;
};
// SPI 从设备:片选号1
sensor@1 {
compatible = "vendor,spi-sensor";
reg = <1>; // 片选号1
spi-max-frequency = <5000000>;
};
};
// I2C 控制器节点
i2c0: i2c@ff120000 {
compatible = "vendor,i2c-controller";
reg = <0xff120000 0x1000>;
#address-cells = <1>; // 地址字段占1个32位字(7位或10位I2C地址)
#size-cells = <0>; // 长度字段为0
status = "okay";
// I2C 设备:地址0x1a
eeprom@1a {
compatible = "vendor,i2c-eeprom";
reg = <0x1a>; // I2C 从设备地址
pagesize = <64>;
};
// I2C 设备:地址0x48
temperature@48 {
compatible = "vendor,i2c-temp";
reg = <0x48>; // I2C 从设备地址
};
};
2.6 节点合并与覆盖规则
设备树编译器(DTC)在编译时会自动合并相同路径的节点。这一特性允许在 .dtsi 文件中定义基础节点,在 .dts 文件中通过相同路径或标签引用进行属性补充和覆盖。
合并规则:
- 相同路径的节点自动合并:如果两个节点路径完全相同,编译器会将它们的属性合并到一个节点中。
- 属性覆盖:如果两个节点定义了同名属性,后定义的属性值覆盖先定义的属性值。
- 子节点合并:子节点也会按照路径规则递归合并。
- 标签引用覆盖:使用
&标签名 { ... }语法,编译器会将花括号内的内容合并到标签对应的节点中。
覆盖示例:
// soc.dtsi - 基础定义
spi0: spi@ff110000 {
compatible = "vendor,spi-controller";
reg = <0xff110000 0x1000>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
// board.dts - 板级覆盖
#include "soc.dtsi"
&spi0 {
status = "okay";
max-frequency = <50000000>;
adc@0 {
compatible = "vendor,spi-adc";
reg = <0>;
spi-max-frequency = <10000000>;
};
};
// 编译后等效于:
spi0: spi@ff110000 {
compatible = "vendor,spi-controller";
reg = <0xff110000 0x1000>;
status = "okay"; // 被覆盖
#address-cells = <1>;
#size-cells = <0>;
max-frequency = <50000000>; // 新增属性
adc@0 { // 新增子节点
compatible = "vendor,spi-adc";
reg = <0>;
spi-max-frequency = <10000000>;
};
};
开发技巧:将SoC级别的通用定义放在
.dtsi 文件中,板级特定的配置(如SPI设备、I2C设备、GPIO引脚复用)放在 .dts 文件中。这样可以在不同板卡之间复用SoC定义,只需修改板级文件即可。
2.7 本章小结
本章详细介绍了设备树语法的四个核心要素:
- 节点(Node):设备树的骨架,通过树形层次结构组织硬件设备。
- 属性(Property):描述设备特征的键值对,支持多种数据类型。
- 标签(Label):节点的别名,用于简化引用和属性覆盖。
- 路径(Path):节点的绝对地址,用于精确定位。