4. 设备树(DTS)基础:设备树语法、节点、属性、compatible与reg

设备树(Device Tree,简称 DT)是一种描述硬件资源的数据结构,它使用树形节点来定义系统中的 CPU、内存、总线、外设及其连接关系。在 Linux 内核中,设备树是驱动与硬件解耦的关键桥梁。本章将系统讲解设备树的语法结构、节点与属性定义,以及 compatible 和 reg 这两个核心属性的作用与用法。

4.1 设备树文件体系与编译

设备树源文件(.dts)经过设备树编译器(DTC)编译为二进制设备树 Blob(.dtb),Bootloader 在启动时将 .dtb 传递给内核。常见的文件类型如下:

文件后缀 说明 作用
.dts 设备树源文件 描述单板硬件拓扑,可包含 .dtsi
.dtsi 设备树包含文件 存放 SoC 公共定义,被多个 .dts 引用
.dtb 编译后的二进制 Blob 内核解析的最终二进制数据
.dtbo 设备树覆盖文件 运行时动态修改设备树(overlay)
编译命令示例:
dtc -I dts -O dtb -o myboard.dtb myboard.dts
使用 dtc -I dtb -O dts -o myboard.dts myboard.dtb 可反向反编译。

4.2 设备树语法基础

设备树采用树形结构,根节点为 /,每个节点代表一个硬件设备或总线,节点内可包含子节点和属性。基本语法规则如下:

  • 节点定义:node-name@unit-address,其中 unit-address 通常为设备基地址。
  • 属性赋值:property-name = <value>; 或 property-name = "string";。
  • 标签引用:使用 &label 引用已定义的节点,用于追加或修改属性。
  • 包含文件:#include "soc.dtsi" 引入公共定义。
/dts-v1/;

#include "soc.dtsi"

/ {
    model = "MyBoard v1.0";
    compatible = "vendor,myboard", "vendor,baseboard";

    chosen {
        stdout-path = &uart0;
    };

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

    soc {
        uart0: serial@10000000 {
            compatible = "vendor,uart-ip";
            reg = <0x10000000 0x1000>;
            interrupts = <0 33 4>;
            clock-frequency = <24000000>;
            status = "okay";
        };
    };
};
语法要点:
  • 每个语句以分号 ; 结尾,节点体用花括号 { } 包裹。
  • 属性值支持 <u32>、<u64>、字符串、字节数组、phandle 引用。
  • status 属性控制设备启用状态:"okay" 启用,"disabled" 禁用。

4.3 节点与属性详解

4.3.1 节点结构

每个设备树节点由节点名、单元地址、属性和子节点组成。节点名应尽量反映设备类型,单元地址用于区分同类型设备。根节点下通常包含以下标准节点:

  • chosen:传递内核启动参数,如 stdout-path、bootargs。
  • memory:描述系统物理内存布局。
  • cpus:描述 CPU 核心信息。
  • aliases:为节点定义别名,方便引用。

4.3.2 常用属性

属性名 值类型 说明
compatible 字符串列表 设备驱动匹配标识,格式 "厂商,型号"
reg <address length> 数组 设备地址空间描述,与 #address-cells、#size-cells 配合
interrupts <type irq flags> 中断号与触发类型
clocks phandle 列表 引用时钟控制器节点
status 字符串 设备状态:"okay"、"disabled"、"reserved"
label 字符串 节点标签,用于调试或别名引用

4.4 compatible 属性:驱动匹配的核心

compatible 属性是设备树与驱动绑定的关键。内核通过 of_match_table 中的 compatible 字符串与设备树节点的 compatible 属性进行匹配。匹配规则如下:

  • 驱动中的 compatible 列表与设备树节点的 compatible 列表按顺序逐一比较。
  • 设备树节点的 compatible 通常包含多个字符串,从最具体到最通用排列。
  • 匹配成功后,内核调用驱动的 probe 函数。
/* 设备树节点 */
uart@10000000 {
    compatible = "vendor,uart-v2", "vendor,uart";
    reg = <0x10000000 0x1000>;
};

/* 驱动匹配表 */
static const struct of_device_id uart_of_match[] = {
    { .compatible = "vendor,uart-v2", },
    { .compatible = "vendor,uart", },
    { /* end */ }
};
MODULE_DEVICE_TABLE(of, uart_of_match);

static struct platform_driver uart_driver = {
    .probe  = uart_probe,
    .remove = uart_remove,
    .driver = {
        .name = "vendor-uart",
        .of_match_table = uart_of_match,
    },
};
匹配优先级:设备树节点的 compatible 列表顺序决定匹配优先级。驱动应优先匹配最具体的版本号,再匹配通用版本。例如 "vendor,uart-v2" 优先于 "vendor,uart"。

4.5 reg 属性:地址空间描述

reg 属性用于描述设备在总线上的地址空间,通常包含基地址和长度。其解析依赖于父节点的 #address-cells 和 #size-cells 属性:

  • #address-cells:表示地址字段占用的 u32 个数。
  • #size-cells:表示长度字段占用的 u32 个数。
  • reg 属性由 (address, length) 对组成,每个对按 #address-cells + #size-cells 个 u32 排列。
/* 父节点定义地址空间格式 */
soc {
    #address-cells = <1>;   /* 地址用 1 个 u32 */
    #size-cells    = <1>;   /* 长度用 1 个 u32 */

    uart@10000000 {
        reg = <0x10000000 0x1000>;  /* 基址 0x10000000,长度 0x1000 */
    };

    ethernet@20000000 {
        reg = <0x20000000 0x10000>; /* 基址 0x20000000,长度 0x10000 */
    };
};

/* 64 位地址示例 */
bus {
    #address-cells = <2>;   /* 地址用 2 个 u32(64 位) */
    #size-cells    = <2>;   /* 长度用 2 个 u32(64 位) */

    dma@0,0 {
        reg = <0x0 0x80000000 0x0 0x100000>;  /* 地址 0x80000000,长度 0x100000 */
    };
};

4.6 设备树与驱动绑定流程 SVG 图

下图展示了从设备树节点到驱动 probe 的完整绑定流程:

设备树与驱动绑定流程 设备树节点 compatible = "vendor,uart-v2" reg = <0x10000000 0x1000> interrupts = <0 33 4> 内核 OF 核心 of_match_table 匹配 Platform 驱动 .probe = uart_probe .of_match_table = uart_of_match 匹配成功 → 调用 probe() platform_get_resource() of_iomap() / ioremap() platform_get_irq() 绑定流程说明 内核 OF 核心遍历设备树节点,与驱动的 of_match_table 进行 compatible 字符串匹配。 匹配成功后调用 probe,驱动通过 API 获取 reg、interrupts 等资源。

4.7 设备树节点结构 SVG 图

下图展示了一个典型 SoC 设备树的节点层级关系:

设备树节点层级结构 / (根节点) chosen memory@80000000 soc uart@10000000 i2c@20000000 spi@30000000 rtc@50 节点属性示例 uart@10000000 { compatible = "vendor,uart"; reg = <0x10000000 0x1000>; interrupts = <0 33 4>; } i2c@20000000 { compatible = "vendor,i2c"; reg = <0x20000000 0x1000>; #address-cells = <1>; #size-cells = <0>; } rtc@50 { compatible = "vendor,rtc"; reg = <0x50>; }

4.8 常见问题与避坑指南

常见错误:
  • compatible 字符串不匹配:驱动中的 compatible 与设备树中的字符串必须完全一致,包括大小写和标点。
  • reg 地址空间错误:#address-cells 和 #size-cells 定义错误会导致 reg 解析偏移,驱动获取到错误的基地址。
  • 中断号配置错误:interrupts 属性中的中断号必须与硬件中断控制器一致,否则驱动无法正确响应中断。
  • status 未设置:节点默认 status 为 "disabled",需显式设置为 "okay" 才能被驱动识别。
调试技巧:
  • 使用 dtc -I dtb -O dts 反编译 .dtb 文件,检查实际传递给内核的设备树内容。
  • 内核启动时添加 earlycon 和 console=ttyS0 参数,查看设备树解析日志。
  • 在驱动 probe 函数中打印 pdev->dev.of_node->name 和 of_get_property() 返回值,验证资源获取是否正确。

4.9 本章小结

设备树是 Linux 内核硬件描述的标准方式,掌握其语法和核心属性是进行 Platform 驱动开发的基础。本章重点内容包括:

  • 设备树文件体系:.dts、.dtsi、.dtb 的作用与编译方法。
  • 节点定义与属性赋值的基本语法规则。
  • compatible 属性作为驱动匹配的核心标识,其字符串顺序决定匹配优先级。
  • reg 属性配合 #address-cells 和 #size-cells 描述设备地址空间。
  • 设备树节点到驱动 probe 的完整绑定流程,以及资源获取的常用 API。