4. 设备树基础语法:节点、属性与关键绑定详解

设备树(Device Tree)是嵌入式 Linux 中描述硬件拓扑的标准方式。对于 I2C 驱动开发而言,设备树承担着“硬件配置清单”的角色:它告诉内核哪个 I2C 控制器挂在哪个总线上、哪个从机设备挂在哪个控制器下、以及该从机需要哪些资源(地址、中断、时钟等)。本章将系统讲解设备树的核心语法,并聚焦于 I2C 场景下最常用的属性。

4.1 设备树节点与属性基础

设备树由一系列节点(node)和属性(property)组成。每个节点代表一个硬件设备或总线,属性则描述该设备的配置参数。

关键概念:节点路径(如 /soc/i2c@12340000)是设备在树中的唯一位置;属性名(如 compatible、reg)是内核匹配驱动的关键。

4.1.1 节点定义格式

// 基本节点语法
node-name@unit-address {
    property-name = <value>;
    property-name = "string";
    property-name = [byte-sequence];
    child-node {
        // 子节点
    };
};
  • node-name:节点名称,通常为设备类型(如 i2c、gpio、uart)。
  • unit-address:设备地址(如寄存器基址或 I2C 从机地址),用于区分同名节点。
  • 属性值:可以是 32 位整数数组、字符串、字节序列或混合类型。

4.1.2 节点与属性的层次关系

下图展示了一个典型的 I2C 控制器及其从机设备的设备树层次结构:

设备树节点层次结构(I2C 场景) / (根节点) soc@0 i2c@12340000 compatible = "vendor,i2c-controller" temp-sensor@48 reg = <0x48>; compatible = "vendor,tmp117" eeprom@50 reg = <0x50>; compatible = "vendor,24c02" 说明:每个 I2C 从机节点必须包含 reg 属性(从机地址)和 compatible 属性(驱动匹配字符串) 子节点继承父节点的总线上下文,无需重复声明总线类型

4.2 关键属性详解

在 I2C 驱动开发中,以下五个属性出现的频率最高,是设备树与驱动代码之间的核心契约。

属性名 作用 I2C 场景典型值 是否必需
compatible 驱动匹配字符串,内核据此查找对应驱动 "vendor,device-name" 是
reg 设备地址(I2C 从机地址)或寄存器基址 <0x48>、<0x50> 是
interrupts 中断号、触发类型 <0x1e 0x2> 按需
clocks 时钟源引用 <&clkc 15> 按需
status 设备启用状态("okay" / "disabled") "okay" 推荐

4.3 compatible 属性:驱动匹配的核心

compatible 是设备树中最重要的属性。内核通过它找到对应的 platform_driver 或 i2c_driver。其值通常采用 "厂商,设备型号" 的格式,保证全局唯一性。

4.3.1 匹配规则

  1. 内核遍历设备节点的 compatible 字符串列表。
  2. 驱动注册时通过 of_match_table 声明支持的 compatible 列表。
  3. 当设备树中的字符串与驱动表中的任意一个匹配时,驱动 probe 函数被调用。
// 设备树中的 compatible(支持多个备选)
compatible = "vendor,tmp117", "ti,tmp117";

// 驱动中的匹配表
static const struct of_device_id tmp117_of_match[] = {
    { .compatible = "vendor,tmp117" },
    { .compatible = "ti,tmp117" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, tmp117_of_match);
工程建议:compatible 字符串应遵循“厂商前缀+设备型号”的命名规范。Linux 内核维护了官方前缀列表(Documentation/devicetree/bindings/vendor-prefixes.yaml),新设备应优先使用已注册前缀。

4.4 reg 属性:地址空间与从机地址

reg 属性描述设备的地址空间信息。对于 I2C 从机,它表示从机地址;对于内存映射设备,它表示寄存器基址和长度。

4.4.1 I2C 从机地址编码

// 7 位从机地址(常用)
reg = <0x48>;

// 10 位从机地址(需在父节点声明 address-cells = <2>)
reg = <0x0000 0x0050>;

reg 属性的格式由父节点的 #address-cells 和 #size-cells 决定:

父节点属性 含义 reg 子项格式
#address-cells = <1>; #size-cells = <0> I2C 从机(只有地址,无长度) reg = <0x48>;
#address-cells = <1>; #size-cells = <1> 内存映射设备(基址 + 长度) reg = <0x1000 0x100>;
#address-cells = <2>; #size-cells = <1> 64 位地址空间 reg = <0x0 0xfe000000 0x1000>;

4.5 interrupts 属性:中断资源描述

当 I2C 从机设备需要中断通知时,通过 interrupts 属性描述中断号和触发类型。该属性依赖父节点(通常是中断控制器)的 #interrupt-cells 定义。

4.5.1 中断属性格式

// 设备树节点
sensor@48 {
    compatible = "vendor,tmp117";
    reg = <0x48>;
    interrupt-parent = <&gpio1>;   // 指定中断控制器
    interrupts = <0x1e 0x2>;        // GPIO 30 号引脚,下降沿触发
};

// 中断控制器定义(gpio1)
gpio1: gpio@12350000 {
    #interrupt-cells = <2>;         // 两个 cell:中断号 + 触发类型
    interrupt-controller;           // 声明本节点是中断控制器
};

4.5.2 常见触发类型编码

宏定义 值 触发方式
IRQ_TYPE_EDGE_RISING 0x01 上升沿触发
IRQ_TYPE_EDGE_FALLING 0x02 下降沿触发
IRQ_TYPE_EDGE_BOTH 0x03 双边沿触发
IRQ_TYPE_LEVEL_HIGH 0x04 高电平触发
IRQ_TYPE_LEVEL_LOW 0x08 低电平触发
常见错误:忘记设置 interrupt-parent 属性,或者 interrupts 的 cell 数量与中断控制器的 #interrupt-cells 不匹配,会导致中断无法正确映射,驱动 probe 失败。

4.6 完整 I2C 设备树实例

以下是一个包含 I2C 控制器和两个从机设备的完整设备树片段,综合展示了上述关键属性的使用:

/dts-v1/;
#include <dt-bindings/interrupt-controller/irq.h>

/ {
    model = "Embedded I2C Demo Board";
    compatible = "vendor,emb-board";

    soc@0 {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        i2c@12340000 {
            compatible = "vendor,i2c-controller";
            reg = <0x12340000 0x1000>;
            interrupts = <0x3d 0x4>;          // 中断号 61,高电平触发
            interrupt-parent = <&gic>;
            #address-cells = <1>;
            #size-cells = <0>;
            clock-frequency = <400000>;        // 400kHz 快速模式
            status = "okay";

            temp-sensor@48 {
                compatible = "vendor,tmp117";
                reg = <0x48>;
                interrupt-parent = <&gpio1>;
                interrupts = <0x1e 0x2>;       // 下降沿触发
                status = "okay";
            };

            eeprom@50 {
                compatible = "vendor,24c02";
                reg = <0x50>;
                pagesize = <8>;                // 自定义属性:页大小
                status = "okay";
            };
        };
    };
};

4.7 设备树到驱动数据流的映射

下图展示了设备树属性如何被内核解析并传递给 I2C 驱动:

设备树到 I2C 驱动的数据流 设备树节点 compatible = "vendor,tmp117" reg = <0x48>; interrupts = ... 内核 OF 核心 of_match_table 匹配 of_i2c_register_devices() I2C 核心 i2c_new_client_device() 填充 i2c_client i2c_client 结构体 addr = 0x48; irq = gpio1_30; name = "vendor,tmp117" 驱动 probe 函数 i2c_client->addr / irq 可用 关键:驱动 probe 时无需手动解析设备树 I2C 核心已将从机地址、中断号等填入 i2c_client

4.8 属性继承与覆盖规则

设备树支持通过 &node 引用已有节点并追加或覆盖属性,这在板级配置中非常实用:

// 原始 dtsi(芯片级)
&i2c1 {
    status = "disabled";
};

// 板级 dts(板级覆盖)
&i2c1 {
    status = "okay";
    clock-frequency = <100000>;
    temp-sensor@48 {
        compatible = "vendor,tmp117";
        reg = <0x48>;
    };
};
工程要点:属性覆盖遵循“最后生效”原则。dtsi 中定义的属性可以被 dts 中的同名属性覆盖。但 compatible 和 reg 等关键属性一旦在 dts 中重新定义,会完全替换 dtsi 中的值,而非追加。

4.9 本章小结

  • 节点是设备树的基本单元,通过路径唯一标识硬件设备。
  • compatible 是驱动匹配的核心,必须与驱动中的 of_match_table 一致。
  • reg 在 I2C 场景下表示从机地址,格式由父节点的 address-cells / size-cells 决定。
  • interrupts 描述中断资源,需配合 interrupt-parent 和中断控制器的 #interrupt-cells 使用。
  • 设备树属性通过内核 OF 核心和 I2C 核心自动解析,驱动 probe 时可直接使用 i2c_client 中的字段。