2. 设备树语法入门:节点、属性、标签、包含文件与宏定义

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点(node)和键值对属性(property)来描述系统中的CPU、内存、外设、中断控制器等硬件信息。本章将系统讲解设备树源文件(.dts)的核心语法要素,为后续使用ftrace追踪解析函数调用打下坚实的语法基础。

学习提示:设备树语法是嵌入式Linux开发的“硬件语言”,掌握节点、属性、标签、包含与宏定义这五大要素,就能读懂并编写绝大多数设备树文件。

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

节点是设备树的基本组成单元,每个节点代表一个硬件设备或总线。节点通过路径(path)唯一标识,格式为 /node-name@unit-address。

2.1.1 节点定义语法

/dts-v1/;

/ {
    #address-cells = <1>;
    #size-cells = <1>;

    cpu@0 {
        compatible = "arm,cortex-a9";
        reg = <0x0 0x100>;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;
    };

    serial@101f1000 {
        compatible = "ns16550";
        reg = <0x101f1000 0x1000>;
        interrupts = <0 12 4>;
    };
};

2.1.2 节点命名规则

组成部分 说明 示例
节点名称 描述设备类型,使用小写字母、数字和连字符 cpu, serial, i2c-bus
单元地址 可选,通常为设备基地址或编号,用十六进制表示 @0, @101f1000
路径分隔 使用斜杠 / 表示层级关系 /soc/serial@101f1000
关键点:节点名称应尽量使用标准设备类型名称(如 serial、ethernet、gpio),避免使用厂商特定名称,以保证设备树的可移植性。
设备树节点层级结构 根节点 "/" cpu@0 memory@80000000 serial@101f1000 interrupt-controller cache-controller clocks pinctrl 节点属性说明 • 每个节点可包含子节点和属性 • 节点路径唯一标识:/soc/serial@101f1000 • 节点名称应使用标准设备类型,避免厂商特定名称

2.2 属性(Property):描述设备特征

属性是附加在节点上的键值对,用于描述设备的配置参数、兼容性、地址范围、中断信息等。属性值可以是空、整数、整数数组、字符串、字符串数组或二进制数据。

2.2.1 常用属性类型

属性类型 语法格式 示例 说明
空属性 property-name; status = "disabled"; 仅表示存在,无值
32位整数 property-name = <value>; #address-cells = <1>; 单个32位值
整数数组 property-name = <val1 val2 ...>; reg = <0x101f1000 0x1000>; 多个32位值,空格分隔
字符串 property-name = "string"; compatible = "ns16550"; 双引号包裹
字符串数组 property-name = "str1", "str2"; compatible = "ti,am335x-uart", "ns16550"; 逗号分隔多个字符串
二进制数据 property-name = [hex bytes]; local-mac-address = [00 11 22 33 44 55]; 十六进制字节序列

2.2.2 标准属性详解

  • compatible:设备驱动匹配的关键属性,格式为 "厂商,设备型号",如 "ti,am335x-uart"。内核通过该属性查找对应的驱动。
  • reg:描述设备地址空间,格式为 <基地址 长度>,由 #address-cells 和 #size-cells 决定单元数。
  • interrupts:描述中断信息,格式依赖 interrupt-parent 和 #interrupt-cells。
  • status:设备状态,常用值 "okay"(启用)、"disabled"(禁用)。
  • #address-cells / #size-cells:定义 reg 属性中地址和长度的单元数(32位字长)。
常见错误:compatible 属性值顺序很重要!内核优先匹配列表中的第一个字符串。如果厂商驱动未加载,会尝试匹配通用驱动。务必把最具体的兼容字符串放在最前面。

2.3 标签(Label):节点引用与复用

标签(label)是节点的别名,用于在设备树的其他位置引用该节点。标签以冒号结尾,放在节点名称之前。

2.3.1 标签定义与引用

/dts-v1/;

/ {
    soc {
        uart0: serial@101f1000 {
            compatible = "ns16550";
            reg = <0x101f1000 0x1000>;
            interrupts = <0 12 4>;
        };
    };
};

// 在其他位置通过 &uart0 引用该节点
&uart0 {
    status = "okay";
    clock-frequency = <24000000>;
};

2.3.2 标签的典型用途

用途 说明 示例
覆盖属性 在板级.dts中修改SoC级.dtsi中的节点属性 &uart0 { baud = <115200>; };
引用节点 在 interrupt-parent 或 clocks 中引用其他节点 interrupt-parent = &gic;
添加子节点 向已存在的节点添加新的子节点 &i2c0 { eeprom@50 { ... }; };
条件编译 配合 #ifdef 实现不同配置 &usb0 { status = "okay"; };
调试技巧:使用ftrace追踪设备树解析时,可以关注 of_find_node_by_path 和 of_find_node_by_name 函数,它们通过节点路径或名称查找节点。标签在编译后会被展开为完整路径,因此使用标签引用的节点在运行时会被正确解析。
标签引用与属性覆盖流程 uart0: serial@101f1000 compatible = "ns16550" &uart0 引用 通过标签找到节点 路径:/soc/serial@101f1000 属性覆盖 status = "okay" clock-frequency = <24MHz> 设备树编译(DTC)处理流程 1. 解析 .dts 文件,建立节点树结构 2. 遇到 &uart0 引用,查找标签对应的节点路径 3. 将引用块中的属性合并到目标节点(覆盖或新增) 4. 输出 .dtb 二进制文件,标签被展开为完整路径

2.4 包含文件(#include):模块化设计

设备树支持C语言风格的 #include 预处理指令,用于引入头文件(.h)或其他设备树包含文件(.dtsi)。这是实现设备树模块化、复用SoC级定义的关键机制。

2.4.1 包含文件的使用

// am335x.dtsi - SoC级定义
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/gpio/gpio.h>

/ {
    compatible = "ti,am335x";
    #address-cells = <1>;
    #size-cells = <1>;

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

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

// am335x-boneblack.dts - 板级定义
#include "am335x.dtsi"

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

    chosen {
        stdout-path = &uart0;
    };
};

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

2.4.2 .dtsi 与 .dts 的分工

文件类型 扩展名 内容范围 复用层级
SoC包含文件 .dtsi CPU、总线、外设IP核、中断控制器、时钟等 同一SoC系列所有板卡共享
板级文件 .dts 板卡型号、内存大小、外设使能、引脚复用、时钟频率 单板卡独有
头文件 .h 宏定义、中断号、GPIO编号、时钟索引等常量 跨SoC/板卡共享
最佳实践:将SoC级通用定义放在 .dtsi 中,板级差异化配置放在 .dts 中。头文件中的宏定义可以大幅提高代码可读性,例如 IRQ_TYPE_EDGE_RISING 比直接写数字 4 更清晰。

2.5 宏定义:提升可读性与可维护性

设备树源文件支持C预处理器宏定义,通过 #define 定义常量,在头文件中集中管理硬件参数。

2.5.1 宏定义示例

// dt-bindings/interrupt-controller/irq.h
#define IRQ_TYPE_NONE           0
#define IRQ_TYPE_EDGE_RISING    1
#define IRQ_TYPE_EDGE_FALLING   2
#define IRQ_TYPE_LEVEL_HIGH     3
#define IRQ_TYPE_LEVEL_LOW      4

// dt-bindings/gpio/gpio.h
#define GPIO_ACTIVE_HIGH        0
#define GPIO_ACTIVE_LOW         1

// 在设备树中使用宏
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/gpio/gpio.h>

gpio_keys {
    compatible = "gpio-keys";
    #address-cells = <1>;
    #size-cells = <0>;

    button@0 {
        label = "user-button";
        gpios = <&gpio0 7 GPIO_ACTIVE_LOW>;
        interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
    };
};

2.5.2 宏定义的优势

  • 可读性:IRQ_TYPE_EDGE_FALLING 比 2 更直观,一眼就能看出中断触发类型。
  • 可维护性:修改宏定义一处,所有使用该宏的地方自动更新。
  • 跨平台一致性:不同SoC厂商可以使用相同的宏定义规范,降低学习成本。
  • 编译时检查:预处理器在编译阶段展开宏,错误会在编译时暴露。
注意事项:宏定义只在预处理阶段生效,编译后的 .dtb 文件中所有宏已被展开为数值。因此,使用ftrace追踪设备树解析时,你看到的是展开后的数值,而不是宏名称。调试时需要对照头文件中的宏定义来理解数值含义。

2.6 综合示例:完整设备树文件

以下是一个综合了节点、属性、标签、包含文件和宏定义的完整设备树示例,展示了各语法要素如何协同工作。

// imx6ull.dtsi - SoC级定义
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/imx6ull-clock.h>

/ {
    #address-cells = <1>;
    #size-cells = <1>;

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

        gic: interrupt-controller@a01000 {
            compatible = "arm,cortex-a7-gic";
            reg = <0xa01000 0x1000>,
                  <0xa02000 0x100>;
            interrupt-controller;
            #interrupt-cells = <3>;
        };

        uart1: serial@2020000 {
            compatible = "fsl,imx6ul-uart", "fsl,imx6q-uart";
            reg = <0x2020000 0x4000>;
            interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&clks IMX6UL_CLK_UART1>;
            status = "disabled";
        };

        i2c1: i2c@21a0000 {
            compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
            reg = <0x21a0000 0x4000>;
            interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&clks IMX6UL_CLK_I2C1>;
            status = "disabled";
            #address-cells = <1>;
            #size-cells = <0>;
        };
    };
};

// myboard.dts - 板级定义
#include "imx6ull.dtsi"

/ {
    model = "My i.MX6ULL Board";
    compatible = "my,imx6ull-board", "fsl,imx6ull";

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

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

        led0: led-0 {
            label = "heartbeat";
            gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
        };
    };
};

&uart1 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart1_pins>;
};

&i2c1 {
    status = "okay";
    clock-frequency = <100000>;

    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
        pagesize = <16>;
    };
};
实战要点:在后续章节使用ftrace追踪设备树解析时,你将看到内核如何递归遍历节点树、解析 compatible 属性匹配驱动、处理 reg 地址资源、解析 interrupts 中断信息。理解本章的语法要素,是读懂ftrace输出日志的前提。
设备树语法要素全景图 设备树源文件 .dts 节点 (Node) 硬件设备骨架 属性 (Property) 设备特征描述 标签 (Label) 节点别名引用 包含文件 #include 模块化 宏定义 #define 常量 编译输出 .dtb 内核解析 (ftrace追踪) ← 上一章 📖 返回目录 下一章 →