3. 设备树节点与属性:标准属性详解与使用规范

设备树(Device Tree)是嵌入式 Linux 中描述硬件拓扑与资源配置的核心机制。节点(Node)与属性(Property)构成了设备树的骨架与血肉。本章聚焦于四个最基础、最通用的标准属性——compatible、reg、interrupts 和 status,深入解析其语义、编码规范与常见陷阱。

学习目标:掌握标准属性的语法规则、内核匹配逻辑、地址编码方式、中断描述模型以及设备启用/禁用的正确用法。能够独立编写或审查基本的设备树节点。

3.1 标准属性全景概览

下表列出了四个标准属性的核心作用与使用场景,帮助建立全局认知:

属性名 核心作用 所属节点类型 内核匹配机制
compatible 指定设备兼容的驱动型号 所有设备节点 of_match_table 字符串匹配
reg 描述设备地址空间(寄存器/内存) 有 MMIO 或总线地址的设备 of_iomap / of_get_address
interrupts 描述设备的中断信号连接 产生或接收中断的设备 of_irq_get / irq_of_parse_and_map
status 控制设备的启用或禁用 所有节点(可选) of_device_is_available
关键原则:设备树是硬件描述,不是驱动配置。标准属性必须严格遵循 ePAPR(Power.org Standard for Embedded Power Architecture Platform Requirements)规范,避免使用厂商自定义扩展替代标准属性。

3.2 compatible 属性:驱动匹配的钥匙

3.2.1 语法与格式

compatible 属性是一个字符串列表(),格式为 "厂商,型号"。内核通过该属性将设备节点与对应的驱动程序绑定。

// 标准格式:厂商前缀 + 具体型号
compatible = "ti,am335x-uart", "ns16550";

// 内核匹配顺序:从左到右,越具体越靠前
// 先尝试匹配 "ti,am335x-uart",失败后回退到 "ns16550"

3.2.2 命名规范与最佳实践

规范项 正确示例 错误示例 说明
厂商前缀 ti, fsl, samsung, texas, nxp, 使用内核文档 vendor-prefixes.yaml 中定义的官方缩写
型号名称 am335x-uart uart-am335x 使用数据手册中的芯片型号,小写字母,连字符分隔
回退兼容 "ti,am335x-uart", "ns16550" "ns16550", "ti,am335x-uart" 最具体的型号放在最前面,通用型号作为回退
长度限制 不超过 31 字符 超长字符串 内核解析缓冲区有限,过长可能导致截断

3.2.3 内核匹配流程

下图展示了从设备树节点到驱动 probe 的完整匹配链路:

compatible 属性驱动匹配流程 设备树节点 compatible = "A","B" of_match_table 遍历驱动兼容列表 字符串匹配 strcmp 逐项比较 匹配失败 返回 -ENODEV 匹配成功 调用 probe 回调 ⚠ 匹配优先级: 内核按 compatible 列表从左到右依次匹配驱动 of_match_table,一旦匹配立即停止搜索。 因此,最具体的兼容项必须放在第一位,通用回退项放在末尾。
常见错误:将通用兼容字符串放在最前面,导致内核永远匹配不到专用驱动。例如 "ns16550", "ti,am335x-uart" 会导致所有 ns16550 兼容的 UART 都使用通用驱动,丢失 TI 平台的特殊功能。

3.3 reg 属性:地址空间描述

3.3.1 地址编码模型

reg 属性描述设备在总线上的地址范围,格式为

的元组列表。地址和长度的位数由父节点的 #address-cells 和 #size-cells 决定。

// 32位地址空间示例
#address-cells = <1>;
#size-cells = <1>;
uart@44e09000 {
    reg = <0x44e09000 0x2000>;  // 起始地址 0x44e09000,长度 0x2000
};

// 64位地址空间示例
#address-cells = <2>;
#size-cells = <2>;
memory@80000000 {
    reg = <0x0 0x80000000 0x0 0x40000000>;  // 起始 0x80000000,长度 0x40000000
};

3.3.2 多地址区域描述

当设备拥有多个不连续的地址区域时,reg 可以包含多个元组:

// 网卡设备:控制寄存器 + DMA 描述符区 + 数据缓冲区
ethernet@10000000 {
    compatible = "vendor,eth-controller";
    reg = <0x10000000 0x1000>,   // 控制寄存器区
          <0x10001000 0x2000>,   // DMA 描述符区
          <0x10003000 0x4000>;   // 数据缓冲区
};

3.3.3 地址映射规则

场景 #address-cells #size-cells reg 元组格式 典型值
32位内存映射 1 1 <0x44e09000 0x2000>
64位内存映射 2 2 <0x0 0x80000000 0x0 0x40000000>
SPI 设备 1 0 <0x0>(片选号)
I2C 设备 1 0 <0x50>(从机地址)
内核 API 对应:of_iomap(node, index) 映射第 index 个 reg 区域;of_get_address(node, index, &size, &flags) 获取原始地址值。务必检查返回值,避免空指针解引用。

3.4 interrupts 属性:中断连接描述

3.4.1 中断描述结构

interrupts 属性描述设备的中断输出信号连接到中断控制器(Interrupt Controller)的哪个输入。其格式由 #interrupt-cells 决定,通常为 1、2 或 3 个 cell。

// 常见 ARM GIC 中断控制器:3 cells
// cell 0: 中断类型 (0=SPI, 1=PPI)
// cell 1: 中断号
// cell 2: 触发类型 (1=上升沿, 2=下降沿, 4=高电平, 8=低电平)

interrupt-parent = <&gic>;  // 指定中断控制器
interrupts = <0 29 4>;      // SPI 中断号 29,高电平触发

// 简单中断控制器:1 cell
interrupts = <14>;          // 中断号 14,触发类型由控制器默认

3.4.2 中断映射关系

下图展示了设备、中断控制器与 CPU 之间的中断传递路径:

中断信号传递路径 外设设备 interrupts = <0 29 4> interrupt-parent = <&gic> 中断控制器 (GIC) #interrupt-cells = <3> 中断号映射与优先级 CPU 核心 IRQ 异常处理 调用中断服务例程 🔌 中断属性关键点: 1. interrupt-parent:指向中断控制器的 phandle,若未指定则继承父节点。 2. interrupts:中断描述符,格式由中断控制器的 #interrupt-cells 决定。 3. interrupt-names:可选,为每个中断指定名称,便于驱动通过名称获取中断号。 4. 触发类型必须与硬件实际连接一致,错误配置会导致中断丢失或误触发。

3.4.3 中断类型与触发方式

中断类型 GIC cell 0 值 说明 典型中断号范围
SPI (Shared Peripheral Interrupt) 0 共享外设中断,可路由到任意 CPU 32 ~ 1019
PPI (Private Peripheral Interrupt) 1 私有外设中断,仅发送到指定 CPU 16 ~ 31
SGI (Software Generated Interrupt) 2 软件触发中断,用于核间通信 0 ~ 15
中断号偏移陷阱:设备树中的中断号通常是硬件中断号,但 Linux 内核内部使用 IRQ 编号(virq)。GIC 驱动在初始化时会加上 16(SGI 占用的编号),因此设备树中 SPI 中断号 29 对应内核 IRQ 号 45。驱动开发时务必使用 platform_get_irq() 获取已转换的 IRQ 号。

3.5 status 属性:设备启用控制

3.5.1 标准取值与语义

status 属性控制设备节点是否被内核识别和驱动。它是一个可选的字符串属性,默认值为 "okay"(若未显式指定)。

取值 语义 内核行为 使用场景
"okay" 设备已启用 正常扫描、匹配驱动、调用 probe 默认状态,硬件正常时使用
"disabled" 设备已禁用 跳过该节点,不创建 platform device 硬件不存在、未焊接、或需要用户空间手动启用
"reserved" 设备保留 类似 disabled,但暗示未来可能启用 Bootloader 或固件保留的设备
"fail" 设备故障 跳过,并记录错误日志 硬件检测到故障时由 Bootloader 设置

3.5.2 覆盖机制与使用规范

设备树支持通过 &{label} 语法在多个 .dtsi 文件中覆盖 status 属性,这是板级配置中常用的模式:

// 芯片级 .dtsi 文件:默认禁用所有外设
&uart1 {
    status = "disabled";
};

&uart2 {
    status = "disabled";
};

// 板级 .dts 文件:按需启用
&uart1 {
    status = "okay";
    pinctrl-0 = <&uart1_pins>;
    pinctrl-names = "default";
};
最佳实践:芯片级 .dtsi 文件应将所有外设的 status 设为 "disabled",板级 .dts 文件再按实际硬件连接逐个启用。这样可以避免未使用的外设占用中断号、时钟和电源资源,降低功耗并减少驱动加载冲突。

3.6 综合示例:完整设备节点

以下是一个集成了所有标准属性的完整 I2C 控制器节点示例:

// i2c0 控制器节点
i2c0: i2c@44e0b000 {
    compatible = "ti,am335x-i2c", "ti,omap4-i2c";
    reg = <0x44e0b000 0x1000>;
    interrupts = <0 70 4>;          // SPI 中断号 70,高电平触发
    interrupt-parent = <&gic>;
    status = "okay";
    #address-cells = <1>;
    #size-cells = <0>;

    // 挂载在 I2C 总线上的从设备
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;               // I2C 从机地址 0x50
        status = "okay";
    };

    rtc@68 {
        compatible = "dallas,ds1307";
        reg = <0x68>;
        status = "disabled";        // 该 RTC 未焊接,禁用
    };
};

3.7 常见错误与调试方法

错误类型 现象 根因 解决方案
compatible 不匹配 驱动 probe 未调用 字符串拼写错误或厂商前缀不规范 检查 /sys/firmware/devicetree/base/.../compatible 文件内容
reg 地址越界 of_iomap 返回 NULL 地址超出父节点 ranges 映射范围 检查父节点 ranges 属性是否包含该地址段
中断号冲突 中断无法触发或触发异常 多个设备使用相同中断号且类型不兼容 使用 cat /proc/interrupts 查看中断分配情况
status 未正确覆盖 设备意外禁用 覆盖顺序错误或语法错误 使用 dtc -I dtb -O dts 反编译确认最终状态
调试利器:内核启动参数添加 of_dev_dbg=1 可以打印设备树节点扫描和匹配的详细信息。使用 dtc -I fs -O dts /sys/firmware/devicetree/base 可以查看内核实际使用的设备树内容,与源文件对比定位问题。

掌握这四个标准属性是设备树开发的基础。在实际项目中,compatible 决定了驱动能否绑定,reg 决定了驱动能否访问硬件寄存器,interrupts 决定了中断能否正常工作,status 决定了设备是否被启用。每一个属性的错误配置都可能导致设备无法工作,因此必须严格遵循规范,并在开发过程中反复验证。