3. 设备树基础语法

设备树(Device Tree)是嵌入式Linux系统中用于描述硬件拓扑结构和资源配置的核心机制。它通过一种树形数据结构,将CPU、内存、外设、总线等硬件信息从内核源码中剥离出来,以数据驱动的方式实现硬件描述与驱动代码的解耦。本章将深入剖析设备树的文件格式、节点与属性的语法规则,以及关键标准属性的使用方法。

3.1 设备树文件格式:DTS / DTSI / DTB

设备树的开发流程涉及三种文件格式,它们分别对应源码、包含文件和编译后的二进制文件。理解这三者的关系是进行设备树开发的第一步。

文件格式 全称 作用 可读性 编译工具
.dts Device Tree Source 设备树源文件,描述单板级硬件 文本,可读 dtc
.dtsi Device Tree Source Include 设备树包含文件,描述SoC级公共硬件 文本,可读 dtc
.dtb Device Tree Blob 设备树二进制文件,供Bootloader加载 二进制,不可读 dtc
开发流程: 编写 .dts / .dtsi → 使用 dtc 工具编译 → 生成 .dtb → Bootloader 将 .dtb 传递给内核。
最佳实践: SoC 厂商提供 .dtsi 文件描述芯片内部资源(如中断控制器、时钟、GPIO控制器),板级开发者通过 #include 引入 .dtsi,并在 .dts 中覆盖或添加板级特有的外设节点。

下图展示了从源码到二进制文件的完整编译流程:

设备树文件编译流程 .dts 源文件 板级硬件描述 .dtsi 包含文件 SoC 公共硬件描述 dtc 编译器 Device Tree Compiler .dtb 二进制文件 内核解析使用 Bootloader(如U-Boot)从存储介质读取 .dtb 文件,加载到内存后传递给内核 内核在启动早期解析 .dtb,创建 device_node 结构,驱动通过 of_ 系列 API 获取硬件信息

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

设备树的核心结构由节点和属性组成。节点表示硬件设备或总线,属性描述节点的特征和配置参数。

3.2.1 节点语法

每个节点以 节点名@地址 的形式定义,节点名应体现设备类型,地址通常为寄存器基地址或设备编号。节点可以嵌套,形成树形层次结构。

/dts-v1/;

/ {
    model = "Embedded Linux Board";
    compatible = "vendor,board-name";

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

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

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

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

        uart@10000000 {
            compatible = "vendor,uart";
            reg = <0x10000000 0x1000>;
            interrupts = <0 33 4>;
            status = "okay";
        };

        ethernet@20000000 {
            compatible = "vendor,gmac";
            reg = <0x20000000 0x2000>;
            interrupts = <0 45 4>;
            phy-mode = "rgmii";
            status = "disabled";
        };
    };
};
常见错误: 节点名不能包含空格和特殊字符;同一层级下节点名必须唯一;@地址 部分在节点名重复时用于区分,但地址本身不参与匹配。

3.2.2 属性语法

属性是键值对,用于描述节点的具体参数。属性值支持多种数据类型:

数据类型 语法示例 说明
字符串 compatible = "vendor,device"; 双引号包裹,用于设备匹配
32位整数 reg = <0x10000000 0x1000>; 尖括号包裹,多个值用空格分隔
64位整数 reg = <0x00000000 0x80000000 0x0 0x40000000>; 使用两个32位单元表示
布尔值 status = "disabled"; 通过字符串表示,如 "okay" / "disabled"
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号包裹,十六进制字节
字符串列表 clock-names = "eth", "mac"; 多个字符串用逗号分隔

3.3 标准属性详解

设备树规范定义了一组标准属性,它们是驱动与设备树绑定的核心接口。以下逐一解析最常用的四个标准属性。

3.3.1 compatible 属性

compatible 是设备树中最重要的属性,用于将设备节点与驱动程序进行匹配。其值为字符串列表,按优先级从高到低排列。

// 驱动匹配时,内核按顺序尝试匹配
compatible = "vendor,device-rev2", "vendor,device-rev1", "vendor,device";

// 驱动端 of_match_table 示例
static const struct of_device_id my_driver_of_match[] = {
    { .compatible = "vendor,device-rev2", },
    { .compatible = "vendor,device-rev1", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
命名规范: 采用 厂商名,设备名 格式,如 "fsl,imx6q-uart"、"ti,am335x-gpio"。厂商名使用小写,设备名使用小写字母和连字符。

3.3.2 reg 属性

reg 属性描述设备的地址空间信息,包括寄存器基地址和地址范围长度。其格式由父节点的 #address-cells 和 #size-cells 决定。

父节点属性 含义 reg 示例 解析
#address-cells = <1>
#size-cells = <1>
32位地址,32位长度 reg = <0x10000000 0x1000>; 基址 0x10000000,长度 0x1000
#address-cells = <2>
#size-cells = <1>
64位地址,32位长度 reg = <0x00000000 0x80000000 0x1000>; 基址 0x0000000080000000,长度 0x1000
#address-cells = <1>
#size-cells = <0>
32位地址,无长度 reg = <0x3>; 仅地址 0x3(如I2C设备地址)

3.3.3 interrupts 属性

interrupts 属性描述设备使用的中断资源。其格式依赖于中断控制器(interrupt-parent)的规范。

// 标准 ARM GIC 中断格式
// interrupts = <中断类型 中断号 触发方式>;
// 中断类型: 0=SPI(共享外设中断), 1=PPI(私有外设中断)
// 触发方式: 1=上升沿触发, 2=下降沿触发, 4=高电平触发, 8=低电平触发

uart@10000000 {
    compatible = "vendor,uart";
    reg = <0x10000000 0x1000>;
    interrupt-parent = <&gic>;  // 引用中断控制器
    interrupts = <0 33 4>;       // SPI 中断号33,高电平触发
};
中断级联: 当设备连接到 GPIO 控制器作为中断源时,需要 interrupts-extended 属性或使用 interrupt-parent 指定父中断控制器。GPIO 中断的格式通常为 <&gpio1 15 2>,表示 GPIO1 的第15号引脚,下降沿触发。

3.3.4 status 属性

status 属性控制设备的启用状态,是设备树中实现硬件配置灵活性的关键属性。

状态值 含义 使用场景
"okay" 设备启用,驱动正常加载 板级上实际存在的设备
"disabled" 设备禁用,驱动不加载 SoC 中存在但板级未使用的设备
"reserved" 设备保留,不分配给操作系统 由固件或安全世界使用的设备
"fail" 设备检测失败 硬件自检未通过
// 在 .dtsi 中默认禁用,在 .dts 中启用
// soc.dtsi
uart1: uart@10001000 {
    compatible = "vendor,uart";
    reg = <0x10001000 0x1000>;
    status = "disabled";
};

// board.dts
#include "soc.dtsi"
&uart1 {
    status = "okay";
    pinctrl-0 = <&uart1_pins>;
};

3.4 设备树节点与属性的关系图

下图展示了设备树中根节点、子节点、属性以及标准属性之间的层次关系:

设备树节点与属性结构 根节点 "/" model, compatible cpu@0 device_type, reg memory@80000000 device_type, reg soc #address-cells, ranges uart@10000000 compatible, reg, interrupts ethernet@20000000 compatible, reg, status 标准属性作用总结 compatible → 驱动匹配 | reg → 地址空间 | interrupts → 中断资源 | status → 设备启用状态 每个节点可包含多个属性,属性值类型包括字符串、整数、布尔值、字节数组等

3.5 设备树编译与反编译实践

掌握 dtc 工具的使用是设备树开发的基本功。以下列出常用操作命令:

# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o board.dtb board.dts

# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o board_decompiled.dts board.dtb

# 检查 .dtb 文件是否有语法错误
dtc -I dtb -O dtb -o /dev/null board.dtb

# 生成设备树符号表(用于查看节点引用)
dtc -I dts -O dtb -o board.dtb board.dts -@
注意事项: 反编译得到的 .dts 文件会丢失注释和部分格式信息,仅用于调试分析,不应作为修改的源文件。始终维护原始的 .dts / .dtsi 文件。

3.6 本章小结

设备树基础语法是嵌入式Linux驱动开发的必修课。通过本章学习,你应该掌握:

  • 文件格式: .dts 源文件、.dtsi 包含文件、.dtb 二进制文件的角色与编译流程。
  • 节点语法: 节点命名规则、层次结构、#address-cells 和 #size-cells 的地址空间定义。
  • 属性语法: 字符串、整数、布尔值、字节数组等数据类型的表示方法。
  • 标准属性: compatible 驱动匹配、reg 地址映射、interrupts 中断配置、status 设备启用的具体用法。

在下一阶段的网络驱动移植实践中,你将直接操作设备树节点,为网络控制器添加或修改 compatible、reg、interrupts 等属性,实现驱动与硬件信息的正确绑定。