2. 设备树语法入门:节点、属性、标签、路径

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、总线、外设等硬件信息组织起来。对于I2C设备驱动开发者而言,掌握设备树的基本语法是编写正确设备节点的前提。本节将系统讲解设备树的四个核心语法要素:节点(node)、属性(property)、标签(label)和路径(path)。

学习提示:设备树语法与C语言结构体初始化有相似之处,但更强调层次关系和硬件地址描述。建议在学习过程中对照实际硬件手册进行练习。

2.1 节点(node)—— 设备树的骨架

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或控制器。节点通过花括号 { } 定义,内部可以包含属性和子节点。节点名称通常采用 名称@地址 的格式,其中地址部分为寄存器基地址或设备地址。

节点定义格式:
[label:] node-name[@unit-address] {
    [property-list]
    [child-node-list]
};

以下是一个典型的I2C控制器节点示例:

i2c@1c2b000 {
    compatible = "allwinner,sun8i-h3-i2c";
    reg = <0x01c2b000 0x400>;
    interrupts = <0 7 4>;
    clocks = <&ccu CLK_BUS_I2C0>;
    #address-cells = <1>;
    #size-cells = <0>;

    /* 子节点:挂载在I2C总线上的设备 */
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
        pagesize = <8>;
    };
};
设备树节点层次结构 / (根节点) soc@1c00000 i2c@1c2b000 (控制器) eeprom@50 (子设备) 说明: 每个节点代表一个硬件实体,子节点通过父节点的总线协议进行通信。I2C子节点使用 reg 属性指定从设备地址。

2.2 属性(property)—— 描述设备特征

属性是附加在节点上的键值对,用于描述设备的各项特征。属性值可以是字符串、整数、整数数组、字节数组或字符串列表。常用的属性类型如下表所示:

属性类型 语法格式 示例 说明
字符串 property = "string"; compatible = "atmel,24c02"; 用于标识设备兼容性
32位整数 property = <value>; reg = <0x50>; I2C从设备地址
整数数组 property = <val1 val2 ...>; reg = <0x1c2b000 0x400>; 地址+长度组合
布尔值 property; (无值) status = "disabled"; 存在即表示真
字节数组 property = [hex-bytes]; local-mac-address = [00 11 22 33 44 55]; MAC地址等二进制数据
字符串列表 property = "str1", "str2"; clock-names = "core", "bus"; 多个字符串组合
常见错误:I2C设备节点中 reg 属性必须与 #address-cells 和 #size-cells 的设定一致。对于7位I2C地址,通常使用 #address-cells = <1>; #size-cells = <0>;,此时 reg = <0x50>; 表示从设备地址为0x50。

2.3 标签(label)—— 节点的别名

标签是节点的引用标识符,用于在设备树的其他位置或驱动代码中引用该节点。标签定义在节点名称之前,使用冒号分隔。通过标签可以避免硬编码路径,提高代码的可维护性。

// 定义标签
&i2c0: i2c@1c2b000 {
    status = "okay";
};

// 在其他位置通过 & 引用标签
&i2c0 {
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };
};

标签在驱动代码中的使用方式:

/* 内核驱动中通过标签引用节点 */
struct device_node *np = of_find_node_by_path("/soc/i2c@1c2b000");
/* 或使用标签对应的别名 */
struct device_node *np = of_find_node_by_name(NULL, "i2c0");
标签引用关系 定义标签: &i2c0: i2c@1c2b000 { status = "okay"; 引用标签: &i2c0 { eeprom@50 { ... }; 标签使用规则: 1. 标签名必须以字母或下划线开头,后跟字母、数字或下划线 2. 通过 &标签名 的方式引用节点,可以添加或覆盖属性 3. 标签在编译后会被转换为路径引用,不影响运行时性能 4. 常用标签命名:i2c0, i2c1, uart0, mmc0 等

2.4 路径(path)—— 节点的定位符

路径是从根节点到目标节点的完整定位字符串,使用斜杠分隔各级节点名称。路径是设备树中唯一标识一个节点的方式,常用于驱动代码中查找特定设备节点。

路径格式:
/soc/i2c@1c2b000/eeprom@50

上述路径表示:根节点 → soc节点 → i2c@1c2b000节点 → eeprom@50节点

路径与标签的对比:

特性 路径(Path) 标签(Label)
表示方式 /soc/i2c@1c2b000 &i2c0
唯一性 全局唯一 全局唯一(需手动定义)
可读性 完整但冗长 简洁易记
修改影响 节点移动后路径失效 标签不变,引用自动更新
驱动中使用 of_find_node_by_path() of_find_node_by_name()

内核驱动中通过路径查找节点的典型代码:

#include <linux/of.h>
#include <linux/i2c.h>

static int my_i2c_probe(struct i2c_client *client)
{
    struct device_node *np;
    const char *compatible;
    u32 reg_val;

    /* 方法1:通过路径查找节点 */
    np = of_find_node_by_path("/soc/i2c@1c2b000/eeprom@50");
    if (!np) {
        dev_err(&client->dev, "无法找到设备节点\n");
        return -ENODEV;
    }

    /* 方法2:通过别名查找(需要设备树中定义别名) */
    np = of_find_node_by_name(NULL, "eeprom");

    /* 读取属性 */
    if (of_property_read_u32(np, "reg", &reg_val) == 0) {
        dev_info(&client->dev, "I2C地址: 0x%02x\n", reg_val);
    }

    of_node_put(np);
    return 0;
}

2.5 综合示例:完整的I2C设备树节点

以下是一个包含温度传感器和EEPROM的完整I2C设备树节点示例,综合运用了节点、属性、标签和路径四种语法要素:

/dts-v1/;
#include "sun8i-h3.dtsi"

/ {
    model = "My I2C Test Board";
    compatible = "mycompany,myboard", "allwinner,sun8i-h3";

    soc {
        /* I2C控制器节点,使用标签方便引用 */
        &i2c0: i2c@1c2b000 {
            compatible = "allwinner,sun8i-h3-i2c";
            reg = <0x01c2b000 0x400>;
            interrupts = <0 7 4>;
            clocks = <&ccu CLK_BUS_I2C0>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";

            /* 温度传感器子节点 */
            temperature@48 {
                compatible = "ti,tmp102";
                reg = <0x48>;
                interrupt-parent = <&pio>;
                interrupts = <6 10 2>;  /* PG10, falling edge */
                #thermal-sensor-cells = <1>;
            };

            /* EEPROM子节点 */
            eeprom@50 {
                compatible = "atmel,24c02";
                reg = <0x50>;
                pagesize = <8>;
                read-only;
            };
        };
    };
};
完整I2C设备树结构(含标签与路径) / (根节点) soc &i2c0: i2c@1c2b000 标签: i2c0 | 路径: /soc/i2c@1c2b000 temperature@48 路径: /soc/i2c@1c2b000/temperature@48 eeprom@50 路径: /soc/i2c@1c2b000/eeprom@50 关键语法总结: • 节点:使用 名称@地址 格式,花括号内包含属性和子节点 • 属性:键值对形式,支持字符串、整数、数组等多种类型 • 标签:使用 &标签名 定义和引用,便于模块化开发 • 路径:从根节点开始的完整定位,用于驱动中查找节点

2.6 常见语法错误与调试技巧

在编写设备树节点时,以下错误最为常见,需要特别注意:

  • 缺少分号:每个属性定义和节点结束都必须以分号结尾
  • 地址格式错误:I2C地址应为7位或10位,且与 #address-cells 匹配
  • 标签重复:同一设备树文件中标签名必须唯一
  • 路径大小写:设备树节点名称区分大小写
调试建议:使用 dtc -I dts -O dtb -o output.dtb input.dts 编译设备树,通过编译错误信息定位语法问题。编译后的DTB文件可以使用 fdtdump 工具查看实际结构。