2. 设备树语法入门:节点、属性、标签与包含机制

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设、中断控制器、GPIO控制器等硬件信息传递给操作系统内核。对于嵌入式Linux驱动开发者而言,掌握设备树语法是编写可移植驱动的第一步。

本章将围绕设备树的四个核心语法要素展开:节点(node)、属性(property)、标签(label)和包含(include)机制。理解这些基础概念后,你就能读懂并编写简单的设备树源文件(.dts)。

设备树节点层次结构示例 / (根节点) soc chosen uart0 gpio0 gpio-leds 图:设备树节点层次结构 — 根节点包含 soc 和 chosen,soc 包含 uart0 和 gpio0,gpio0 包含 gpio-leds

2.1 节点(Node)

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点以层次结构组织,从根节点 / 开始向下展开。

节点语法格式:
[label:] node-name[@unit-address] {
    [property-list];
    [child-node1 {
        ...
    }];
    [child-node2 {
        ...
    }];
};
  • 根节点:用 / 表示,是整个设备树的起点,每个 .dts 文件有且只有一个根节点。
  • 节点名称:描述设备类型,如 cpu、memory、uart、gpio。名称应尽量通用。
  • 单元地址(unit-address):可选,通常为设备在总线上的地址或寄存器基址,用于区分同类型设备。例如 uart@ff010000。
  • 子节点:节点可以嵌套,形成树形结构。子节点继承父节点的总线环境。
常见节点命名示例
节点路径含义单元地址
/soc/uart@ff010000SoC 内部的 UART 控制器,基址 0xff010000ff010000
/soc/gpio@ff020000GPIO 控制器,基址 0xff020000ff020000
/soc/gpio@ff020000/gpio-ledsGPIO 控制器下的 LED 子节点无(功能节点)
/chosen系统启动参数节点,无硬件对应无

2.2 属性(Property)

属性是附着在节点上的键值对,用于描述设备的配置参数、寄存器地址、中断信息、时钟频率等。属性值可以是空、整数、字符串、字节数组或这些类型的组合。

属性类型速查: 设备树规范定义了多种标准属性类型,驱动开发者最常用的是 compatible、reg、interrupts、gpios 和 status。
常用属性类型与格式
属性类型格式示例说明
compatible"vendor,device"驱动匹配字符串,格式为"厂商,设备型号"
reg<0xff010000 0x1000>寄存器地址和长度,可包含多组
interrupts<0 31 4>中断号、触发类型等,格式依赖中断控制器
gpios<&gpio0 12 0>GPIO 引用:控制器 phandle、引脚号、标志
status"okay" 或 "disabled"设备启用状态
label"led0"设备别名,便于用户空间识别
属性定义示例(gpio-leds 节点):
gpio-leds {
    compatible = "gpio-leds";
    led0 {
        label = "power";
        gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
        linux,default-trigger = "heartbeat";
    };
    led1 {
        label = "status";
        gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
        default-state = "off";
    };
};

2.3 标签(Label)

标签是节点的别名,用于在设备树的其他位置引用该节点。标签以 : 结尾,放在节点名称之前。标签的主要用途是:

  • 简化引用:通过 &label 语法引用节点,避免写完整路径。
  • 节点覆盖:在 .dtsi 文件中定义带标签的节点,在 .dts 文件中通过 &label 追加或修改属性。
  • phandle 生成:编译器自动为带标签的节点生成唯一的 phandle(整数句柄),供其他节点引用。
注意: 标签名称在全局范围内必须唯一,不能与节点名称或其他标签冲突。标签通常使用小写字母和下划线,如 gpio0、uart1。
标签引用与节点覆盖流程 gpio0.dtsi (基础定义) gpio0: gpio@ff020000 { compatible = "vendor,gpio"; reg = <0xff020000 0x1000>; #gpio-cells = <2>; status = "disabled"; }; gpio-leds { compatible = "gpio-leds"; }; 板级 .dts #include "gpio0.dtsi" &gpio0 { status = "okay"; }; &gpio-leds { led0 { ... }; }; 编译后 .dtb gpio0: gpio@ff020000 { compatible = "vendor,gpio"; reg = <0xff020000 0x1000>; #gpio-cells = <2>; status = "okay"; // 已覆盖 }; gpio-leds { compatible = "gpio-leds"; led0 { ... }; // 已追加 }; 图:标签引用与覆盖 — .dtsi 定义基础节点,板级 .dts 通过 &label 覆盖 status 并追加子节点

2.4 包含(Include)机制

设备树源文件支持 #include 预处理指令,用于引入公共定义、头文件或其他 .dtsi 文件。包含机制是设备树模块化设计的核心,它允许:

  • 复用公共定义:将 SoC 通用外设定义放在 .dtsi 文件中,不同板级 .dts 文件通过 #include 引入。
  • 引入头文件:包含 .h 头文件,使用其中的宏定义(如 GPIO_ACTIVE_HIGH、IRQ_TYPE_EDGE_RISING)。
  • 分层覆盖:通过 #include 链实现多级覆盖(SoC级 → 板级 → 变体级)。
典型包含链示例:
// board.dts
#include "soc.dtsi"          // 引入 SoC 通用外设
#include "board-common.dtsi"  // 引入板级公共配置
#include   // 引入 GPIO 宏定义

/ {
    model = "MyBoard v2";
    compatible = "vendor,myboard-v2", "vendor,myboard";
};
包含机制常用场景
文件类型扩展名用途包含方式
SoC 定义文件.dtsi定义 CPU、中断控制器、GPIO、UART 等 SoC 级外设#include "soc.dtsi"
板级公共文件.dtsi定义板级 LED、按键、电源管理等公共配置#include "board.dtsi"
头文件.h定义 GPIO 标志、中断类型、时钟 ID 等宏#include <dt-bindings/gpio/gpio.h>
最终板级文件.dts包含所有 .dtsi 和 .h,并做最终覆盖编译入口文件
最佳实践: 使用 #include 时,优先包含 .dtsi 文件(双引号),再包含系统头文件(尖括号)。不要在 .dtsi 文件中放置板级特有配置,保持 SoC 定义的通用性。标签覆盖应在最终的 .dts 文件中完成。

2.5 节点、属性、标签与包含的协同工作

在实际的 LED 驱动配置中,这四个语法要素协同工作:

  1. SoC 级 .dtsi 定义 GPIO 控制器节点,并为其添加标签(如 gpio0: gpio@ff020000)。
  2. 板级 .dts 通过 #include 引入 SoC 定义,并通过 &gpio0 覆盖其 status 属性为 "okay"。
  3. 板级 .dts 在根节点或合适位置定义 gpio-leds 子节点,使用 gpios = <&gpio0 12 0> 引用 GPIO 控制器标签。
  4. 头文件宏 如 GPIO_ACTIVE_HIGH 通过 #include 引入,使属性值更具可读性。
完整示例:gpio-leds 节点配置
// soc.dtsi
gpio0: gpio@ff020000 {
    compatible = "vendor,gpio";
    reg = <0xff020000 0x1000>;
    #gpio-cells = <2>;
    status = "disabled";  // 默认关闭,板级覆盖
};

// board.dts
#include "soc.dtsi"
#include 

/ {
    model = "LED Test Board";
    compatible = "vendor,led-board";
};

&gpio0 {
    status = "okay";
};

/ {
    gpio-leds {
        compatible = "gpio-leds";
        led-power {
            label = "power";
            gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
        };
        led-alert {
            label = "alert";
            gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
            default-state = "off";
        };
    };
};
常见错误:
  • 忘记在板级 .dts 中通过 &gpio0 将 status 设为 "okay",导致 GPIO 控制器未启用,LED 无法工作。
  • gpios 属性中引用的标签(如 &gpio0)未在包含链中定义,编译时报 phandle 未找到错误。
  • 在 .dtsi 文件中直接定义板级 LED 节点,导致不同板级文件冲突。应将板级特有节点放在 .dts 中。