3. 设备树节点与属性
设备树(Device Tree)是嵌入式 Linux 中描述硬件拓扑的核心数据结构。节点(Node)与属性(Property)构成了设备树的骨架与血肉。理解节点命名规则、路径寻址方式以及各类属性的语义,是编写正确设备树源文件(.dts)和设备树覆盖(.dtso)的基础。
3.1 节点命名规则
每个设备树节点必须遵循严格的命名规范,以确保编译器和内核能够正确解析。节点名称由两部分组成:节点名和可选的单元地址。
| 组成部分 | 格式 | 说明 | 示例 |
|---|---|---|---|
| 节点名 | name |
通用名称,描述设备类型,长度 1~31 字符,只能包含小写字母、数字、短横线(-)和下划线(_)。 | uart, i2c, gpio-keys |
| 单元地址 | @address |
可选,通常为设备在父总线上的寄存器地址或片选号,使用十六进制小写。 | @1f00000, @0 |
| 完整节点名 | name@address |
节点名与单元地址通过 @ 连接,同一父节点下必须唯一。 |
serial@1f00000, i2c@0 |
最佳实践:节点名应尽量使用标准化的设备类别名称(如
serial、ethernet、mmc),而非具体芯片型号。单元地址必须与 reg 属性中的第一个地址段一致,否则编译器会报警告。
3.2 节点路径
设备树采用类似文件系统的树形结构,每个节点都可以通过绝对路径或相对路径唯一标识。路径由斜杠(/)分隔的节点名序列组成。
- 根节点:用单个斜杠
/表示,是整个设备树的起点。 - 绝对路径:从根节点开始,逐级向下,如
/soc/bus@1f00000/serial@1f01000。 - 标签引用:通过
&label语法引用节点,常用于覆盖(overlay)中,如&uart1。 - 别名(aliases):在
/aliases节点中定义短名称,如serial0 = &uart1;,便于 bootloader 和内核快速查找。
路径解析规则:在设备树覆盖中,
target 属性或 target-path 属性用于指定要修改的节点。优先使用标签引用(&label),可读性更强且不易受路径变化影响。
3.3 标准属性详解
设备树规范定义了一组标准属性,用于描述设备的基本硬件特征。以下是最常用的四个标准属性。
3.3.1 compatible 属性
compatible 是设备树中最重要的属性,用于将设备节点与内核驱动程序进行匹配。它是一个字符串列表,按优先级从高到低排列。
compatible = "vendor,device", "generic,device-class";
| 字段 | 格式 | 示例 | 说明 |
|---|---|---|---|
| 厂商前缀 | vendor |
ti, nvidia, fsl |
通常与内核中 vendor-prefixes.txt 定义的厂商缩写一致。 |
| 设备型号 | device |
omap4-uart, tegra20-i2c |
具体芯片或 IP 核的型号名称。 |
| 通用兼容 | generic,device-class |
ns16550, simple-bus |
回退兼容的通用驱动类,当专用驱动不存在时使用。 |
3.3.2 reg 属性
reg 属性描述设备在总线上的地址空间,由一组地址-长度对组成。其格式由父节点的 #address-cells 和 #size-cells 属性决定。
#address-cells = <1>; // 地址占 1 个 32 位单元
#size-cells = <1>; // 长度占 1 个 32 位单元
serial@1f01000 {
reg = <0x1f01000 0x1000>; // 起始地址 0x1f01000,长度 0x1000
};
常见错误:在覆盖中修改
reg 属性时,必须确保地址-长度对的数量与父节点定义的 cells 数量一致。例如,#address-cells = <2> 时,每个地址需要两个 32 位值(如 <0x0 0x1f01000>)。
3.3.3 interrupts 属性
interrupts 属性描述设备使用的中断资源。其格式由父节点或根节点中的 #interrupt-cells 属性定义。常见的中断控制器(如 GIC)通常使用 3 个 cell:
interrupts = <0 33 4>;
// cell 0: 中断类型(0=SPI, 1=PPI)
// cell 1: 中断号(33)
// cell 2: 中断触发标志(4=高电平触发)
| 中断类型 | 值 | 说明 |
|---|---|---|
| SPI(共享外设中断) | 0 | 可被多个 CPU 处理,中断号范围 32~1019 |
| PPI(私有外设中断) | 1 | 每个 CPU 私有,中断号范围 16~31 |
| 触发标志 | 1~4 | 1=上升沿触发,2=下降沿触发,4=高电平触发,8=低电平触发 |
3.3.4 status 属性
status 属性控制设备的启用状态,是覆盖操作中最常修改的属性之一。
| 值 | 含义 | 使用场景 |
|---|---|---|
"okay" |
设备已启用且可正常操作 | 覆盖中启用默认禁用的外设 |
"disabled" |
设备禁用,驱动不会探测 | 默认关闭未使用的硬件模块 |
"reserved" |
设备保留,不由操作系统管理 | 固件或安全世界使用的设备 |
"fail" |
设备检测失败,不可操作 | 硬件初始化错误时由 bootloader 设置 |
3.4 自定义属性
设备树允许开发者定义非标准的自定义属性,用于传递板级特定参数或驱动私有数据。自定义属性必须遵循以下规则:
- 命名空间:建议使用厂商前缀作为属性名开头,如
myvendor,led-gpio,避免与未来标准属性冲突。 - 数据类型:支持
<u32>(32 位整数)、<u64>(64 位整数)、string(字符串)、byte-array(字节数组)、phandle(节点引用)等。 - 驱动读取:内核驱动通过
of_property_read_u32()、of_property_read_string()等 API 读取自定义属性。
gpio-leds {
compatible = "gpio-leds";
led-0 {
label = "heartbeat";
gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
myvendor,blink-pattern = <100 200>; // 自定义属性:亮100ms,灭200ms
};
};
3.5 节点与属性结构总览
下图展示了设备树中节点、路径、标准属性与自定义属性的完整结构关系。
3.6 属性数据类型速查表
设备树属性支持多种数据类型,在编写 .dts 或 .dtso 文件时需正确使用对应的编码语法。
| 数据类型 | 编码语法 | 示例 | 说明 |
|---|---|---|---|
| 32 位整数 | <value> |
<0x1000> |
单个 32 位无符号整数 |
| 64 位整数 | <high low> |
<0x0 0x1f01000> |
两个 32 位值拼接为 64 位 |
| 字符串 | "string" |
"okay" |
双引号包裹的 ASCII 字符串 |
| 字符串列表 | "str1", "str2" |
"ti,omap4-uart", "ns16550" |
逗号分隔的多个字符串 |
| 字节数组 | [hex bytes] |
[01 02 03 0a] |
空格分隔的十六进制字节 |
| 布尔值 | 属性存在即为 true | dma-coherent; |
无值的空属性,存在表示启用 |
| phandle | &label |
&gpio1 |
引用其他节点的句柄 |
| phandle 列表 | &l1 &l2 |
&dma &gpio2 |
空格分隔的多个 phandle |
3.7 覆盖中的节点与属性操作
在设备树覆盖(.dtso)中,对节点和属性的操作遵循增量修改原则:覆盖只添加或修改指定的属性,未提及的属性保持不变。
覆盖操作规则:
- 添加属性:在目标节点下直接定义新属性。
- 修改属性:重新定义同名属性,新值覆盖旧值。
- 删除属性:使用
/delete-property/ prop-name;语法。 - 添加子节点:在目标节点下定义新子节点。
- 删除子节点:使用
/delete-node/ &label;或/delete-node/ name@address;语法。
// 覆盖示例:启用 I2C 控制器并添加 EEPROM 子设备
/dts-v1/;
/plugin/;
&i2c1 {
status = "okay"; // 修改 status 属性
clock-frequency = <400000>; // 添加自定义时钟频率属性
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>; // 自定义属性
};
};
调试技巧:使用
dtc -I dtbo -O dts <overlay.dtbo> 反编译覆盖文件,检查节点路径和属性值是否正确。内核启动后,通过 /proc/device-tree/ 目录树可以查看实际生效的设备树结构。