第四节:节点(Node)与属性(Property)——设备树的“细胞”与“基因”
设备树(Device Tree)的本质是一种描述硬件拓扑与资源配置的树形数据结构。在这棵树中,节点(Node)是构成树形结构的基本单元,代表一个硬件设备、总线、控制器或抽象的功能模块;而属性(Property)则是附着在节点上的键值对,用于描述该硬件的具体特征、地址映射、中断连接、状态信息等。可以这样理解:节点是硬件在软件世界中的“化身”,属性则是这个化身的“基因序列”。本章将深入剖析节点的命名规范、地址单元(unit-address)的编码规则,以及各类属性的数据类型与常见属性的语义,帮助读者建立从硬件手册到设备树源文件(DTS)的映射能力。
4.1 节点命名规则与 unit-address 编码
设备树中每个节点都必须遵循统一的命名规范,这是内核解析设备树时的第一道门槛。节点名称由两部分组成:节点名(node-name)和可选的地址单元(unit-address),两者之间用“@”符号分隔。节点名应使用小写字母、数字、连字符和下划线,且必须以字母开头;unit-address 则用于区分同一总线或同一控制器下的多个实例,其编码规则与节点的 reg 属性中的地址部分严格对应。
4.1.1 核心命名原则
- 通用性优先:节点名应反映设备的功能类型,而非具体型号。例如使用
uart、i2c、ethernet,而不是max3100或rtl8211。具体型号由compatible属性表达。 - 地址单元必须与 reg 匹配:如果节点有
reg属性,则unit-address必须等于reg中第一个地址值(十六进制小写)。例如reg = <0x1000 0x100>对应节点名uart@1000。 - 无地址设备可省略 @:如 CPU 节点
cpu@0中的0是 CPU 编号;而像gpio-keys这类无内存映射的设备,可以省略 unit-address。 - 路径分隔符:节点路径从根节点
/开始,如/soc/uart@1000,在phandle引用中常用&uart0这样的标签(label)代替完整路径。
uart1: serial@02020000 {
compatible = "fsl,imx6ul-uart", "fsl,imx21-uart";
reg = <0x02020000 0x4000>;
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
};
这里 uart1 是标签(label),serial 是节点名,02020000 是 unit-address,与 reg 的起始地址完全一致。
4.1.2 unit-address 的编码规则
| 总线类型 | 地址编码格式 | 示例 | 说明 |
|---|---|---|---|
| 内存映射总线 | 32位或64位十六进制 | uart@1000 | 对应 reg 中的地址字段 |
| I2C 总线 | 7位从机地址 | eeprom@50 | 地址为 0x50,与 reg = <0x50> 匹配 |
| SPI 总线 | 片选编号 | flash@0 | 表示 SPI 片选 0 |
| PCIe 总线 | 域:总线:设备.功能 | pcie@1,0 | BDF 编码,需配合 reg 的 PCI 格式 |
| CPU 节点 | CPU 逻辑编号 | cpu@0 | 与 reg = <0> 对应 |
of_find_node_by_name() 和 of_find_node_by_path() 函数在匹配节点时,会严格校验地址单元。若不一致,可能导致设备驱动无法正确绑定。
4.2 属性类型详解:从 string 到 phandle
属性是设备树中承载信息的核心载体,其数据类型决定了内核如何解析这些信息。设备树规范(DT Spec)定义了五种基本数据类型,理解这些类型是编写正确 DTS 文件的基础。
4.2.1 五种基本属性类型
| 类型 | DTS 表示法 | 字节长度 | 典型用途 |
|---|---|---|---|
| string | "okay" | 可变 | 状态描述、兼容性标识 |
| u32 | <0x1000> | 4 字节 | 地址、大小、频率、标志位 |
| u64 | <0x0 0x80000000> | 8 字节 | 大地址空间(如 DDR 基址) |
| array | <0x100 0x200 0x300> | 4×N 字节 | 多地址/多数据组合 |
| phandle | <&label> | 4 字节 | 节点引用,建立设备间关联 |
boolean(布尔)类型比较特殊,它不携带值,属性存在即为 true,不存在即为 false。例如 dma-coherent; 表示设备支持 DMA 一致性,无需额外赋值。
4.2.2 复合类型与特殊编码
- 字符串列表:
compatible = "ti,am335x-uart", "ns16550a";多个字符串按顺序排列,内核按优先级匹配。 - u32 数组与 phandle 混合:
interrupts-extended = <&gpio1 5 IRQ_TYPE_EDGE_RISING>;第一个元素是 phandle,后续是参数。 - 64 位值在 32 位系统中的表示:
reg = <0x0 0x80000000 0x0 0x10000000>;表示基址 0x80000000,大小 0x10000000,需要两个 u32 组合。 - 空属性:
status = "disabled";是字符串,而status;这种写法不合法,必须赋值。
compatible 属性时,遵循“厂商,型号”的格式,且越具体的型号放在越前面。例如 "fsl,imx6ul-uart", "fsl,imx21-uart",内核会先尝试匹配第一个,失败后再尝试第二个。
4.3 常见属性深度解析
在 Linux 内核的设备驱动模型中,有几个属性几乎出现在每一个设备节点中,它们是驱动与硬件交互的“桥梁”。本节将逐一拆解这些属性的语义、格式与使用场景。
4.3.1 compatible —— 设备驱动的“身份证”
compatible 属性是设备树与驱动匹配的核心机制。内核通过 of_match_table 中的 compatible 字符串与设备节点的该属性进行匹配。其格式为 "厂商,型号",厂商名通常为芯片或 IP 核的缩写。
compatible = "ti,am335x-uart", "ns16550a";
匹配顺序是从左到右,内核优先使用第一个匹配项。这种设计允许同一驱动兼容多个硬件版本,也允许新硬件复用旧驱动。
4.3.2 reg —— 地址与内存区域的“地图”
reg 属性描述设备在总线上的地址空间。其格式为 <地址 大小 ...>,地址和大小均为 u32 或 u64。对于内存映射设备,地址是物理地址;对于 I2C/SPI 设备,地址是从机地址。
reg = <0x02020000 0x4000>; // 基址 0x02020000,大小 16KB
在 #address-cells 和 #size-cells 的配合下,内核可以正确解析 reg 中的地址和大小字段。例如父节点定义 #address-cells = <1>; #size-cells = <1>;,则 reg 中每对地址和大小各占一个 u32。
4.3.3 interrupts —— 中断连接的“神经”
interrupts 属性描述设备的中断请求线。其格式依赖于中断控制器(interrupt-controller)的 #interrupt-cells 属性。常见格式为 <中断类型 中断号 触发方式>。
interrupts = <0 29 4>; // SPI 中断 29,高电平触发
其中第一个字段 0 表示 SPI(共享外设中断),29 是中断号,4 是触发类型(4 表示高电平触发)。对于 GPIO 中断,通常使用 interrupts-extended 属性,并包含 GPIO 控制器的 phandle。
4.3.4 status —— 设备的“开关”
status 属性控制设备是否启用。可选值包括 "okay"(启用)、"disabled"(禁用)、"reserved"(保留)和 "fail"(失败)。在开发阶段,常将未调试完成的设备设为 "disabled",待验证通过后再改为 "okay"。
4.3.5 其他高频属性
| 属性名 | 类型 | 作用 | 示例 |
|---|---|---|---|
| interrupt-parent | phandle | 指定中断控制器 | interrupt-parent = <&intc>; |
| clocks | phandle+参数 | 关联时钟源 | clocks = <&clk_uart1>; |
| resets | phandle+参数 | 关联复位控制器 | resets = <&rst 10>; |
| pinctrl-0 | phandle | 引脚复用配置 | pinctrl-0 = <&pinctrl_uart1>; |
| dma-coherent | boolean | 声明 DMA 一致性 | dma-coherent; |
4.4 实战案例:从零构建一个 UART 节点
下面通过一个完整的案例,演示如何根据芯片手册和硬件原理图,编写一个符合规范的 UART 设备节点。假设我们使用 TI AM335x 平台,UART3 连接在内部总线 0x48024000 地址上,使用中断 45,且需要引脚复用配置。
4.4.1 硬件信息收集
- 芯片手册:UART3 基址 0x48024000,地址空间大小 0x1000(4KB)。
- 中断控制器:使用 GIC(通用中断控制器),SPI 中断号 45,触发方式为高电平(4)。
- 引脚复用:UART3 的 TXD/RXD 引脚复用为 Mode 1,需要配置
pinctrl子系统。 - 时钟:UART3 使用外设时钟
dpll_per_m2_ck,频率 48MHz。
4.4.2 DTS 节点编写
&uart3 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x48024000 0x1000>;
interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&dpll_per_m2_ck>;
clock-frequency = <48000000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};
4.4.3 关键点分析
| 属性 | 值 | 设计依据 |
|---|---|---|
| compatible | "ti,am3352-uart" | 精确到 SoC 型号,驱动优先匹配 |
| reg | <0x48024000 0x1000> | 与手册地址一致,大小覆盖全部寄存器 |
| interrupts | <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH> | GIC_SPI 是宏定义,值为 0;45 为中断号 |
| clocks | <&dpll_per_m2_ck> | 引用时钟节点,驱动可获取频率 |
| pinctrl-0 | <&pinctrl_uart3> | 引脚复用配置,需在 pinctrl 节点中定义 |
| status | "okay" | 确认硬件已启用 |
dmesg 查看发现驱动未 probe,进一步检查发现 pinctrl_uart3 节点中的引脚编号写错(将 PIN 编号与 GPIO 编号混淆)。修正后,UART3 正常输出日志。此案例说明,pinctrl 配置错误是 UART 类设备最常见的故障原因之一。
4.5 常见误区与设计建议
- unit-address 与 reg 不一致:导致内核无法通过地址匹配节点,驱动 probe 失败。
- compatible 字符串拼写错误:驱动匹配不到,且
dmesg中无任何提示。 - interrupts 格式错误:中断号或触发方式写错,导致设备无法响应中断。
- status 误设为 "disabled":设备被内核跳过,且不报错。
- reg 中地址大小与 #address-cells 不匹配:导致地址解析错位,访问非法内存。
- 在 DTS 中尽量使用宏定义(如
GIC_SPI、IRQ_TYPE_LEVEL_HIGH)代替魔法数字,提高可读性。 - 为每个节点添加注释,说明硬件来源(如“参考原理图第 3 页 UART3 部分”)。
- 使用
dtc -I dtb -O dts反编译 DTB,验证编译结果是否符合预期。 - 在驱动中通过
of_property_read_u32()等 API 读取属性时,注意数据类型匹配。
4.6 总结与知识图谱
节点与属性是设备树的核心语法单元,掌握其命名规则、数据类型和常见属性语义,是进行设备树开发的第一步。本章通过理论讲解、表格对比和实战案例,系统梳理了从硬件手册到 DTS 代码的映射方法。在实际项目中,建议读者先绘制硬件拓扑图,再逐节点编写属性,最后通过 dtc 编译和内核日志验证正确性。
| 学习维度 | 核心要点 | 验证方法 |
|---|---|---|
| 命名规则 | node-name@unit-address,unit-address 与 reg 匹配 | dtc 编译无警告 |
| 属性类型 | string/u32/u64/array/phandle/boolean | 驱动中读取类型一致 |
| compatible | 厂商,型号,按优先级匹配 | dmesg 中驱动 probe 成功 |
| reg | 地址与大小,受 #address-cells 影响 | ioremap 后读写正常 |
| interrupts | 中断类型、编号、触发方式 | 中断触发时回调执行 |
| status | "okay"/"disabled" 控制设备启用 | 设备出现在 /sys/devices/ 下 |
下一阶段,我们将深入探讨设备树中的中断映射机制、时钟与复位框架,以及如何通过设备树描述复杂的电源域和总线拓扑。掌握本章内容后,读者应能独立阅读和编写基础的设备树节点,为后续的驱动开发和系统移植打下坚实基础。