ARM嵌入式Linux设备树驱动DTS编写实战工程 - 第2节

2. 设备树语法入门

设备树(Device Tree)是一种描述硬件资源的树形数据结构,它使用文本文件(.dts)来描述系统中所有可发现的硬件设备及其连接关系。DTS 文件是设备树的源文件,经过 DTC(Device Tree Compiler)编译后生成 DTB(Device Tree Blob)二进制文件,供内核在启动时解析。

核心概念:设备树将硬件描述从内核代码中分离出来,使得同一份内核镜像可以支持多种硬件平台,只需更换不同的 DTB 文件即可。

2.1.1 DTS 文件的基本结构

一个典型的 DTS 文件由以下部分组成:

  • 根节点(/):设备树的起点,所有其他节点都是根节点的子节点或后代节点。
  • 节点(node):表示一个硬件设备或总线,每个节点可以有子节点和属性。
  • 属性(property):描述节点的特性,如寄存器地址、中断号、时钟频率等。
  • 标签(label):为节点或属性定义的别名,方便在其他地方引用。
  • 引用(&):通过标签引用节点,用于在多个文件中扩展或修改节点内容。
// 一个简单的 DTS 文件示例
/dts-v1/;

/ {
    model = "ARM Cortex-A9 开发板";
    compatible = "arm,cortex-a9";
    #address-cells = <1>;
    #size-cells = <1>;

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

    chosen {
        bootargs = "console=ttyAMA0,115200 root=/dev/mmcblk0p2";
    };

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

        uart0: serial@101f1000 {
            compatible = "arm,pl011";
            reg = <0x101f1000 0x1000>;
            interrupts = <0 5 4>;
            clock-frequency = <24000000>;
        };

        gpio0: gpio@101f2000 {
            compatible = "arm,pl061";
            reg = <0x101f2000 0x1000>;
            interrupts = <0 6 4>;
            gpio-controller;
            #gpio-cells = <2>;
        };
    };
};
提示:DTS 文件使用 C 风格的注释(// 和 /* */),并且支持 #include 预处理指令,可以包含 .dtsi 头文件实现代码复用。

2.1.2 设备树的层级结构

设备树的层级结构反映了硬件系统的拓扑关系。根节点代表整个系统,其子节点代表 SoC 内部的总线或功能模块,再下一级节点代表具体的设备实例。

设备树层级结构示意图 根节点 "/" soc 节点 memory@80000000 chosen 节点 uart0: serial@101f1000 gpio0: gpio@101f2000 说明: 每个节点代表一个硬件设备或总线,节点名称通常包含设备类型和基地址(如 serial@101f1000)。

2.2 节点(Node)与属性(Property)

2.2.1 节点的定义与命名规则

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或功能模块。节点的定义格式如下:

[label:] node-name[@unit-address] {
    [property-list]
    [child-node-list]
};
组成部分 说明 示例
label 可选的标签名,用于在其他地方引用该节点 uart0:
node-name 节点名称,描述设备类型,必须唯一 serial
@unit-address 可选的单元地址,通常为设备基地址 @101f1000
property-list 属性列表,描述节点的特性 reg = <...>;
child-node-list 子节点列表,表示总线上的从设备 i2c-bus { ... };
注意:节点名称应使用通用设备类型名称(如 serial、gpio、i2c),而不是具体芯片型号。具体型号通过 compatible 属性指定。

2.2.2 标准属性详解

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

属性名 值类型 说明 示例
compatible 字符串列表 设备兼容性标识,用于驱动匹配 "arm,pl011"
reg 32位整数对列表 寄存器地址和长度 <0x101f1000 0x1000>
interrupts 32位整数列表 中断号、触发类型等 <0 5 4>
#address-cells 32位整数 子节点 reg 属性中地址字段的 cell 数 <1>
#size-cells 32位整数 子节点 reg 属性中长度字段的 cell 数 <1>
ranges 地址映射列表 子地址到父地址的映射关系 <0 0x10000000 0x1000>
status 字符串 设备状态(okay/disabled) "okay"

2.2.3 属性值类型

设备树属性支持多种数据类型,每种类型有其特定的编码方式:

  • 字符串(string):用双引号括起来,如 compatible = "arm,pl011";
  • 32位整数(u32):用尖括号括起来,如 reg = <0x101f1000 0x1000>;
  • 64位整数(u64):用两个 u32 表示,如 reg = <0x00000000 0x80000000 0x00000000 0x10000000>;
  • 字节数组(bytes):用方括号括起来,如 local-mac-address = [00 11 22 33 44 55];
  • 布尔值(boolean):属性存在即为 true,如 gpio-controller;
  • 混合类型:可以组合多种类型,如 interrupts = <0 5 4>;
// 属性值类型示例
/ {
    // 字符串
    compatible = "arm,cortex-a9";

    // 32位整数
    #address-cells = <1>;
    #size-cells = <1>;

    // 字节数组
    mac-address = [00 11 22 33 44 55];

    // 布尔值
    gpio-controller;

    // 混合类型
    interrupts = <0 5 4>;
};

2.3 标签(Label)与引用(&)

2.3.1 标签的定义与作用

标签(label)是为节点或属性定义的别名,用于在其他地方方便地引用该节点。标签定义在节点名称之前,使用冒号分隔:

uart0: serial@101f1000 {
    compatible = "arm,pl011";
    reg = <0x101f1000 0x1000>;
    interrupts = <0 5 4>;
};

标签的主要作用包括:

  • 简化引用:通过标签名引用节点,避免使用冗长的路径
  • 节点扩展:在多个 .dtsi 文件中扩展同一个节点
  • 跨文件引用:在包含多个文件的工程中引用其他文件定义的节点
  • 覆盖属性:在 board 级别的 .dts 文件中覆盖 SoC 级别的默认配置

2.3.2 引用操作符 &

引用操作符 & 用于通过标签引用节点,实现对节点的扩展或修改。引用语法如下:

&label {
    property = <new-value>;
    child-node {
        ...
    };
};

引用操作符的典型应用场景:

场景 说明 示例
添加属性 为已有节点添加新的属性 &uart0 { clock-frequency = <24000000>; };
覆盖属性 修改已有节点的属性值 &uart0 { status = "disabled"; };
添加子节点 为总线节点添加新的从设备 &i2c0 { eeprom@50 { ... }; };
引用 phandle 在属性中引用其他节点 clocks = <&clk_uart0>;
// 引用操作符的完整示例
// soc.dtsi - SoC 级设备树
/ {
    soc {
        uart0: serial@101f1000 {
            compatible = "arm,pl011";
            reg = <0x101f1000 0x1000>;
            interrupts = <0 5 4>;
            status = "disabled";  // 默认禁用
        };
    };
};

// board.dts - 板级设备树
#include "soc.dtsi"

// 引用 uart0 节点,启用并配置时钟
&uart0 {
    status = "okay";
    clock-frequency = <24000000>;
};
最佳实践:SoC 级 .dtsi 文件中的设备默认设置为 status = "disabled",板级 .dts 文件通过 &label 引用并设置为 status = "okay",这样可以避免未使用的设备占用资源。

2.3.3 标签与引用的工作流程

标签和引用在设备树编译过程中的工作流程如下:

标签与引用工作流程 步骤 1: 定义标签 uart0: serial@101f1000 步骤 2: 引用节点 &uart0 { status = "okay"; } 步骤 3: 编译合并 DTC 编译器 编译结果:合并后的节点 serial@101f1000 { status = "okay"; clock-frequency = <24000000>; } 关键点: 1. 标签在 .dtsi 文件中定义,引用在 .dts 文件中使用。 2. DTC 编译器会将引用展开,合并所有对同一节点的修改。

2.4 实战案例:构建一个完整的设备树

下面通过一个完整的实战案例,综合运用节点、属性、标签和引用的知识:

// my-board.dts - 完整设备树示例
/dts-v1/;

#include "skeleton.dtsi"

/ {
    model = "My ARM Development Board";
    compatible = "my,board-v1", "arm,vexpress";

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

    chosen {
        bootargs = "console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw";
    };

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

        uart0: serial@101f1000 {
            compatible = "arm,pl011";
            reg = <0x101f1000 0x1000>;
            interrupts = <0 5 4>;
            clocks = <&clk_uart0>;
            status = "disabled";
        };

        uart1: serial@101f2000 {
            compatible = "arm,pl011";
            reg = <0x101f2000 0x1000>;
            interrupts = <0 6 4>;
            clocks = <&clk_uart1>;
            status = "disabled";
        };

        clk_uart0: clock@10000000 {
            compatible = "fixed-clock";
            #clock-cells = <0>;
            clock-frequency = <24000000>;
        };

        clk_uart1: clock@10000010 {
            compatible = "fixed-clock";
            #clock-cells = <0>;
            clock-frequency = <48000000>;
        };
    };
};

// 板级配置:启用 UART0,禁用 UART1
&uart0 {
    status = "okay";
};

&uart1 {
    status = "disabled";
};
关键要点:
  • 根节点包含 model 和 compatible 属性,用于标识硬件平台
  • memory 节点描述系统内存的起始地址和大小
  • chosen 节点传递内核启动参数
  • soc 节点作为总线容器,包含所有外设节点
  • 标签(uart0、clk_uart0)用于在板级配置中引用和修改节点
  • 引用操作符 & 用于启用或禁用设备

2.5 本章小结

知识点 核心内容 掌握程度
DTS 文件结构 根节点、子节点、属性、标签、引用的组织方式 必须掌握
节点定义 节点名称、单元地址、子节点嵌套规则 必须掌握
标准属性 compatible、reg、interrupts、status 等属性的含义和用法 必须掌握
属性值类型 字符串、整数、字节数组、布尔值的编码方式 必须掌握
标签与引用 标签定义、& 引用操作符、节点扩展与覆盖 必须掌握
实战应用 构建完整的设备树,实现 SoC 级与板级配置分离 重点练习
下节预告:第 3 章将深入讲解设备树中最重要的属性之一——compatible 属性,以及驱动与设备树的匹配机制,包括 OF 匹配表、设备 ID 表等核心内容。