2. 设备树语法入门:节点(node)、属性(property)、标签(label)、路径(path)详解

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的处理器、总线、外设、中断控制器、GPIO 控制器等硬件组件组织起来。对于嵌入式 Linux 开发者而言,掌握设备树的语法是编写设备驱动和移植内核的基础。本章将深入解析设备树的四个核心语法要素:节点、属性、标签和路径。

学习提示:设备树源文件(.dts)经过 Device Tree Compiler(dtc)编译生成二进制设备树 Blob(.dtb),内核在启动时解析 .dtb 来枚举硬件。理解语法是读懂和编写 .dts 文件的前提。

2.1 节点(Node):设备树的骨架

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点通过父子关系形成一棵倒置的树,根节点是 /。

2.1.1 节点定义语法

node-name {
    property1 = <value>;
    property2 = "string";
    child-node {
        ...
    };
};
  • 节点名称:由字母、数字、连字符(-)、下划线(_)组成,长度不超过 31 个字符。
  • 节点单元地址:当节点代表内存映射设备时,名称后可以跟 @address,如 uart@ff010000,其中 ff010000 是设备基地址。
  • 节点体:用花括号 { } 包裹,内部包含属性和子节点。

2.1.2 节点层次结构示例

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

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

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

    ocp {
        compatible = "simple-bus";
        uart0: serial@44e09000 {
            compatible = "ti,am3352-uart", "ti,omap3-uart";
            reg = <0x44e09000 0x2000>;
            interrupts = <72>;
        };
    };
};
关键点:根节点 / 下包含 cpus、memory、ocp 等子节点。每个节点可以嵌套子节点,形成硬件拓扑的精确映射。
设备树节点层次结构图 / (根节点) cpus memory@80000000 ocp cpu@0 serial@44e09000 节点层次说明: • 根节点 / 是树的起点,包含所有硬件节点。 • cpus 节点是容器节点,包含 cpu@0 子节点。 • memory@80000000 节点描述系统内存,reg 属性定义地址范围。 • ocp 节点是总线节点,serial@44e09000 是挂载在总线上的 UART 设备。

2.2 属性(Property):描述节点的特征

属性是附着在节点上的键值对,用于描述设备的特性、配置和资源需求。属性值可以是空、整数、字符串、字节数组或这些类型的组合。

2.2.1 属性值类型

属性值类型 语法格式 示例 说明
空属性 property-name; interrupt-controller; 仅表示存在该属性,无值
32 位整数 <value> <0x1000> 单个或多个整数,空格分隔
64 位整数 <high low> <0x0 0x80000000> 两个 32 位整数拼接
字符串 "string" "ti,am3352-uart" 双引号包裹
字节数组 [hex bytes] [0x00 0x01 0x02] 十六进制字节序列
混合类型 <value> "string" <0x1> "default" 用空格分隔不同类型

2.2.2 标准属性

设备树规范定义了一系列标准属性,用于描述设备的核心特征:

  • compatible:指定设备兼容性字符串,驱动通过匹配该属性绑定设备。格式为 "厂商,型号",如 "ti,am3352-uart"。
  • reg:描述设备地址空间,格式为 <address size>,由 #address-cells 和 #size-cells 决定单元长度。
  • interrupts:描述设备使用的中断号,格式由 #interrupt-cells 定义。
  • status:设备状态,可选 "okay"(启用)、"disabled"(禁用)、"fail"(故障)。
  • model:设备型号字符串,如 "TI AM335x BeagleBone Black"。
  • device_type:设备类型,主要用于 memory 和 cpu 节点。
常见错误:属性值类型不匹配是编译错误的主要原因。例如,将字符串值赋给期望整数的属性,或忘记在整数列表外添加尖括号 <>。
属性与节点关系图 serial@44e09000 节点 compatible = "..." reg = <0x44e09000 0x2000> interrupts = <72> 属性作用: • compatible:驱动匹配的关键,内核通过该属性找到对应的驱动程序。 • reg:定义设备的寄存器基地址和映射长度,驱动通过 ioremap 访问硬件。

2.3 标签(Label):节点的别名

标签是节点的引用别名,用于在设备树的其他位置或外部文件中引用节点。标签以冒号结尾,放在节点名称之前。

2.3.1 标签定义语法

label: node-name {
    /* 节点内容 */
};

例如,在之前的示例中:

uart0: serial@44e09000 {
    compatible = "ti,am3352-uart", "ti,omap3-uart";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
};

这里 uart0 就是标签,可以在其他位置通过 &uart0 引用该节点。

2.3.2 标签的典型用途

用途场景 示例 说明
覆盖节点属性 &uart0 { status = "disabled"; }; 在 board.dts 中禁用 SoC 默认启用的 UART
添加子节点 &i2c0 { eeprom@50 { ... }; }; 在 I2C 控制器节点下挂载 EEPROM 设备
引用中断/GPIO interrupt-parent = &intc; 指定设备的中断父控制器
设备树包含文件 #include "imx6ul.dtsi" .dtsi 文件中定义的标签可在 .dts 中引用
最佳实践:在 SoC 级 .dtsi 文件中为所有外设节点定义标签(如 uart1、i2c2、spi0),在板级 .dts 文件中通过 &label 进行定制化配置。这种分离设计提高了代码复用性。
标签引用机制图 uart0: serial@44e09000 定义标签 uart0 &uart0 { ... } 通过 & 引用标签 标签引用流程: 1. 在 .dtsi 文件中定义标签:uart0: serial@44e09000 2. 在 .dts 文件中通过 &uart0 引用,添加或覆盖属性。 3. 编译时 dtc 将引用展开为对原节点的修改,最终生成完整的设备树。

2.4 路径(Path):节点的唯一地址

路径是从根节点到目标节点的完整节点名称序列,使用斜杠分隔。路径用于在设备树中唯一标识一个节点,类似于文件系统中的绝对路径。

2.4.1 路径格式

/soc/uart@44e09000
/ocp/serial@44e09000
/cpus/cpu@0

路径以根节点 / 开头,逐级向下,每级使用节点名称(含单元地址)分隔。

2.4.2 路径的用途

  • 设备树覆盖(DT Overlay):通过路径指定要修改的目标节点。
  • 内核 API 查找节点:驱动中调用 of_find_node_by_path() 通过路径获取节点。
  • 设备树调试:在 /proc/device-tree 文件系统中,路径直接映射为目录结构。
  • Bootloader 修改:U-Boot 通过 fdt set 命令使用路径修改设备树。

2.4.3 路径与标签的对比

特性 路径(Path) 标签(Label)
唯一性 全局唯一,从根到叶的完整描述 在文件作用域内唯一
可读性 较长,但结构清晰 简短,便于记忆
使用场景 调试、Bootloader、内核 API 设备树覆盖、属性引用
依赖关系 依赖节点名称和层次结构 依赖标签定义,可跨文件引用
编译时检查 路径错误会导致编译失败 标签未定义会导致编译失败
实用技巧:在 /proc/device-tree 下查看设备树时,路径直接对应目录结构。例如,/ocp/serial@44e09000 对应 /proc/device-tree/ocp/serial@44e09000,该目录下的每个文件对应一个属性。
路径与标签对比图 路径(Path) /ocp/serial@44e09000 /cpus/cpu@0 /memory@80000000 特点:完整、唯一、结构清晰 标签(Label) uart0: serial@44e09000 i2c0: i2c@21a0000 spi0: spi@2000000 特点:简短、可跨文件引用 何时使用路径 vs 标签? • 在 .dts 文件中修改节点属性时,优先使用标签:&uart0 { ... } • 在 Bootloader 或内核 API 中查找节点时,使用路径:of_find_node_by_path("/ocp/serial@44e09000") • 在设备树覆盖(Overlay)中,路径用于指定目标节点,标签用于引用已定义的节点。

2.5 综合示例:解析一个完整的设备树片段

下面是一个综合示例,展示节点、属性、标签和路径如何协同工作:

/* soc.dtsi - SoC 级定义 */
/ {
    compatible = "vendor,soc-model";

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

        intc: interrupt-controller@48200000 {
            compatible = "ti,am33xx-intc";
            reg = <0x48200000 0x1000>;
            interrupt-controller;
            #interrupt-cells = <1>;
        };

        uart0: serial@44e09000 {
            compatible = "ti,am3352-uart", "ti,omap3-uart";
            reg = <0x44e09000 0x2000>;
            interrupts = <72>;
            interrupt-parent = <&intc>;
            status = "disabled";
        };
    };
};

/* board.dts - 板级定制 */
#include "soc.dtsi"

&uart0 {
    status = "okay";
    pinctrl-0 = <&uart0_pins>;
    pinctrl-names = "default";
};

&uart0_pins: pinmux_uart0_pins {
    pinctrl-single,pins = <
        0x70 0x30  /* UART0_RXD */
        0x74 0x00  /* UART0_TXD */
    >;
};
解析:
  • intc 和 uart0 是标签,在 soc.dtsi 中定义。
  • board.dts 通过 #include "soc.dtsi" 包含 SoC 定义。
  • &uart0 引用标签,将 status 从 "disabled" 改为 "okay",并添加引脚控制属性。
  • interrupt-parent = <&intc> 通过标签引用中断控制器节点。
  • 路径 /soc/serial@44e09000 可唯一标识 UART 节点。

2.6 本章小结

设备树的四个核心语法要素构成了硬件描述的基础:

  • 节点(Node):树形结构的骨架,每个节点代表一个硬件组件。
  • 属性(Property):节点的特征描述,包括兼容性、地址、中断等关键信息。
  • 标签(Label):节点的别名,用于跨文件引用和属性覆盖。
  • 路径(Path):节点的唯一地址,用于调试和内核 API 查找。