2. 设备树语法入门:节点、属性、标签、别名的定义与规则

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设、总线等硬件组件组织起来。在Linux内核驱动移植中,设备树是连接驱动代码与具体硬件配置的桥梁。本章将系统讲解设备树源文件(.dts)的核心语法要素:节点、属性、标签与别名。

学习目标:掌握设备树节点的定义与嵌套规则;理解属性(reg、interrupts、compatible等)的编码方式;学会使用标签(label)进行节点引用;掌握别名(aliases)节点的作用与配置方法。

2.1 设备树节点(Node)

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点通过树形层次结构组织,根节点(/)是整棵树的起点。

2.1.1 节点定义语法

/dts-v1/;

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a8";
            device_type = "cpu";
            reg = <0x00>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512 MB */
    };

    leds {
        compatible = "gpio-leds";
        led0 {
            label = "beaglebone:green:usr0";
            gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
        };
    };
};

节点命名规则:

  • 节点名称:由字母、数字、连字符(-)、下划线(_)和点(.)组成,长度不超过31个字符。
  • 单元地址(unit-address):可选,使用@符号与节点名连接,通常对应设备的基地址或编号,如 cpu@0、serial@44e09000。
  • 节点路径:从根节点到目标节点的完整路径,如 /cpus/cpu@0、/leds/led0。
关键规则:同一层级下,节点名称必须唯一。若节点名相同,则必须通过不同的单元地址区分,例如 cpu@0 和 cpu@1。

2.1.2 节点嵌套与层次结构

设备树的层次结构通过花括号 { } 实现嵌套。子节点继承父节点的总线上下文,并可以访问父节点定义的地址空间和中断域。

设备树节点层次结构示例 / (根节点) cpus memory@80000000 soc leds cpu@0 cpu@1 serial@44e09000 i2c@44e0b000 led0 led1 节点层次规则: • 根节点 "/" 是唯一顶层节点,所有设备节点均为其子节点或后代节点。 • 子节点通过花括号嵌套在父节点内部,形成树形结构。 • 节点路径从根开始,如 /soc/serial@44e09000 唯一标识一个节点。 • 同一父节点下的子节点名称(含单元地址)必须唯一。

2.2 属性(Property)

属性是节点的描述信息,以键值对(key-value)的形式存在。每个属性定义了硬件设备的一个特征,如地址范围、中断号、兼容性、时钟频率等。

2.2.1 属性值类型

属性值类型 语法格式 示例 说明
空值 属性名; interrupt-controller; 表示布尔型真值,存在即有效
字符串 "字符串" model = "TI AM335x BeagleBone Black"; 双引号包裹的文本
字符串列表 "str1", "str2", ... compatible = "ti,am335x-bone", "ti,am33xx"; 逗号分隔的多个字符串
32位整数 <数值> reg = <0x80000000 0x20000000>; 尖括号包裹,可包含多个值
64位整数 <高32位 低32位> reg = <0x00000000 0x80000000>; 使用两个32位单元表示64位地址
字节数组 [十六进制字节] local-mac-address = [00 11 22 33 44 55]; 方括号包裹的十六进制字节序列
混合值 <数值> "字符串" [字节] foo = <0x1234> "bar" [0xab]; 多种类型组合,空格分隔
引用(phandle) &标签 clocks = <&clk_mcasp0>; 引用其他节点的标签

2.2.2 标准属性详解

设备树规范定义了一系列标准属性,驱动开发者必须熟悉这些属性的含义和用法。

属性名 用途 值类型 示例
compatible 设备驱动匹配标识,格式为"厂商,设备型号" 字符串列表 "ti,am335x-uart", "ns16550"
reg 设备地址空间描述(基地址+长度) 整数列表 <0x44e09000 0x2000>
#address-cells 子节点reg地址字段的单元数(32位/单元) 整数 <1>
#size-cells 子节点reg长度字段的单元数 整数 <1>
interrupts 设备使用的中断号及触发类型 整数列表 <0 56 4>
interrupt-parent 指定中断控制器节点引用 phandle <&intc>
clocks 设备使用的时钟源引用 phandle列表 <&clk_uart0>
status 设备状态:"okay"、"disabled"、"reserved" 字符串 "okay"
常见错误:compatible属性必须按照从具体到通用的顺序排列。内核驱动匹配时,会依次尝试列表中的每个字符串,匹配到第一个即停止。顺序错误可能导致驱动加载失败。

2.3 标签(Label)

标签是设备树中用于节点引用的标识符。通过为节点定义标签,可以在设备树的其他位置方便地引用该节点,而无需使用完整的节点路径。

2.3.1 标签定义语法

// 标签定义:在节点名称前添加 "标签名:"
uart0: serial@44e09000 {
    compatible = "ti,am335x-uart", "ns16550";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
    status = "okay";
};

// 标签引用:使用 &标签名 进行引用
&uart0 {
    pinctrl-0 = <&uart0_pins>;
    pinctrl-names = "default";
};

标签命名规则:

  • 由字母、数字、下划线(_)组成,首字符不能为数字。
  • 在同一设备树文件及其包含的文件中,标签必须唯一。
  • 标签通常使用有意义的名称,如 uart0、i2c1、mmc0、gpio2。

2.3.2 标签的典型应用场景

场景 说明 示例
节点追加 在.dtsi文件中定义基础节点,在.dts文件中通过标签追加属性 &uart0 { status = "okay"; };
属性引用 在属性值中引用其他节点,如时钟、中断、GPIO clocks = <&clk_mcasp0>;
覆盖配置 在设备树覆盖层(.dtso)中修改已有节点的属性 &mmc1 { max-frequency = <50000000>; };
pinctrl绑定 将引脚复用配置绑定到具体设备 pinctrl-0 = <&uart0_pins &uart0_ctsrts_pins>;
标签引用与节点追加流程 uart0: serial@44e09000 (基础定义 in .dtsi) &uart0 { ... } (引用追加 in .dts) 编译后节点 (合并结果) 标签引用追加规则: 1. 标签引用必须使用 & 前缀,后跟已定义的标签名。 2. 引用块中的属性会追加到原节点,同名属性则覆盖原值。 3. 引用块不能定义子节点,只能修改或追加属性。 4. 标签在 #include 包含的文件中也可引用,实现跨文件节点操作。 5. 编译时,所有引用会被解析为对应的节点路径。

2.4 别名(Aliases)

别名节点(aliases)是根节点下的一个特殊子节点,用于为系统中的关键设备定义简短、易记的名称。别名在引导加载程序(如U-Boot)和内核初始化阶段有重要作用。

2.4.1 别名定义语法

/ {
    aliases {
        serial0 = &uart0;
        serial1 = &uart1;
        i2c0 = &i2c0;
        i2c1 = &i2c1;
        mmc0 = &mmc0;
        mmc1 = &mmc1;
        ethernet0 = &cpsw_emac0;
        gpio0 = &gpio0;
        gpio1 = &gpio1;
    };

    uart0: serial@44e09000 { /* ... */ };
    uart1: serial@48022000 { /* ... */ };
    i2c0: i2c@44e0b000 { /* ... */ };
};

别名命名规范:

  • 别名通常使用 设备类型+序号 的格式,如 serial0、i2c1、mmc0。
  • 别名值必须是对设备节点标签的引用(&标签),不能直接写节点路径。
  • 别名节点本身没有标准属性,所有子项均为属性名到节点引用的映射。

2.4.2 别名的核心作用

作用领域 具体用途 说明
U-Boot引导 通过别名查找控制台串口、网络设备 U-Boot使用 serial0 别名定位早期控制台输出设备
内核启动 解析 chosen 节点中的 stdout-path stdout-path = &serial0; 指定内核启动日志输出设备
设备编号 为设备分配逻辑序号(如ttyS0、mmcblk0) 内核根据别名中的序号为设备注册对应的设备号
设备树覆盖 在覆盖层中通过别名快速定位目标节点 避免使用长路径,提高可读性和可维护性
实践要点:别名中的序号(如serial0、serial1)直接影响内核注册的设备名称。例如,serial0 对应 /dev/ttyS0,mmc0 对应 /dev/mmcblk0。在移植过程中,必须确保别名序号与硬件设计一致,否则会导致用户空间程序无法找到正确的设备节点。

2.5 节点、标签、别名的关系与对比

节点、标签、别名关系图 节点定义 uart0: serial@44e09000 { } 标签 (Label) "uart0" 作为节点引用标识 别名 (Alias) serial0 = &uart0; 三者关系总结: 1. 节点是设备树的实体,描述硬件设备及其属性。 2. 标签是节点的别名标识,用于在设备树内部引用节点。 3. 别名是根节点下的特殊映射,为关键设备提供 固定名称,供引导程序和内核使用。 4. 标签是别名的前提:别名必须引用已定义的标签。 5. 一个节点可以有多个标签,但只能有一个节点名。 6. 别名中的序号决定设备在用户空间的名称。 7. 标签用于跨文件引用,别名用于跨阶段引用。 8. 编译后,标签和别名都会被解析为节点路径。

2.6 实战案例:完整设备树片段解析

以下是一个基于TI AM335x平台的完整设备树片段,综合展示了节点、属性、标签和别名的实际应用:

/dts-v1/;

#include "am33xx.dtsi"

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    aliases {
        serial0 = &uart0;
        serial1 = &uart1;
        serial2 = &uart2;
        i2c0 = &i2c0;
        i2c1 = &i2c1;
        i2c2 = &i2c2;
        mmc0 = &mmc0;
        mmc1 = &mmc1;
        ethernet0 = &cpsw_emac0;
        gpio0 = &gpio0;
        gpio1 = &gpio1;
        gpio2 = &gpio2;
        gpio3 = &gpio3;
    };

    chosen {
        stdout-path = &serial0;
        bootargs = "console=ttyO0,115200 root=/dev/mmcblk0p2 rw";
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512 MB */
    };

    leds {
        compatible = "gpio-leds";
        pinctrl-names = "default";
        pinctrl-0 = <&user_led_pins>;

        led0 {
            label = "beaglebone:green:usr0";
            gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
            default-state = "off";
        };

        led1 {
            label = "beaglebone:green:usr1";
            gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "mmc0";
            default-state = "off";
        };
    };
};

/* 通过标签引用追加属性 */
&uart0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
};

&i2c0 {
    status = "okay";
    clock-frequency = <400000>;
    pinctrl-names = "default";
    pinctrl-0 = <&i2c0_pins>;

    tps65217: tps@24 {
        compatible = "ti,tps65217";
        reg = <0x24>;
        interrupt-parent = <&intc>;
        interrupts = <7>;  /* NMI */
    };
};

&mmc0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&mmc0_pins>;
    bus-width = <4>;
    cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
};
代码解析:上述设备树片段展示了以下关键点:
• aliases 节点为所有关键外设定义了固定名称,确保U-Boot和内核能正确识别设备。
• <