1. 设备树基础:DTS 的起源、作用与基本语法结构

1.1 设备树的起源与设计动机

在早期的嵌入式 Linux 系统中,板级硬件信息被硬编码在 arch/arm/ 或 arch/powerpc/ 目录下的 C 源文件中(如 board-xxx.c)。这种“硬编码”方式导致每次硬件变更都需要重新编译内核,且不同架构的板级代码难以复用。设备树(Device Tree)正是为了解决这一问题而引入的。

对比维度 传统板级文件(Board File) 设备树(Device Tree)
硬件描述方式 C 语言结构体 + 宏定义 文本化 .dts 文件
内核耦合度 高,硬件变更需重新编译内核 低,仅需更新 .dtb 文件
跨架构支持 差,各架构独立维护 好,统一描述模型
启动参数传递 ATAG 列表(ARM 特有) 设备树二进制块(DTB)
维护成本 高,每块板卡需独立文件 低,通过 .dtsi 复用

设备树最早源于 Open Firmware(IEEE 1275),后被 PowerPC 架构采用,最终在 ARM Linux 社区推动下成为嵌入式 Linux 的标准硬件描述语言。其核心思想是:将硬件拓扑、寄存器地址、中断号、时钟频率等静态信息从内核源码中剥离,以结构化文本形式独立描述。

关键理解: 设备树不是“驱动”,而是“硬件配置清单”。内核在启动时解析 DTB,根据其中的节点信息自动匹配并初始化对应的驱动。

1.2 设备树的作用与数据流

设备树在嵌入式 Linux 启动流程中扮演“硬件翻译官”的角色。从源码到内核使用的完整数据流如下:

  1. DTS(Device Tree Source):开发者编写的文本源文件,后缀为 .dts 或 .dtsi。
  2. DTC(Device Tree Compiler):将 DTS 编译为二进制 DTB 的工具,位于内核源码 scripts/dtc/ 目录。
  3. DTB(Device Tree Blob):二进制格式的硬件描述,由 Bootloader(如 U-Boot)加载到内存并传递给内核。
  4. 内核解析:内核启动早期调用 unflatten_device_tree() 将 DTB 展开为树状结构,驱动通过 of_* 系列 API 读取节点信息。
数据流简图:
.dts / .dtsi → DTC 编译器 → .dtb → Bootloader → 内核解析 → 驱动匹配
设备树数据流与编译流程 设备树数据流与编译流程 .dts / .dtsi 文本源文件 DTC 编译器 scripts/dtc/ .dtb 二进制硬件描述 Bootloader U-Boot 加载 内核解析 DTB unflatten_device_tree() 驱动匹配 & 初始化 💡 关键路径 DTS → DTC → DTB → Bootloader → 内核

1.3 基本语法结构:节点、属性与值

设备树源文件采用树形层级结构,每个硬件设备或总线被抽象为一个节点(Node),节点内包含若干属性(Property),属性拥有具体的值(Value)。

1.3.1 节点(Node)

节点是设备树的基本组成单元,使用 节点名@地址 的格式定义。根节点用 / 表示,所有设备节点都是根节点的子节点或后代节点。

/dts-v1/;

/ {
    compatible = "vendor,board-name";
    #address-cells = <1>;
    #size-cells = <1>;

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

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;  /* 1GB RAM */
    };

    serial@10000000 {
        compatible = "ns16550a";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
        clock-frequency = <24000000>;
    };
};
节点语法要素 说明 示例
节点名 描述设备类型,通常为通用名称 cpu, memory, serial
单元地址 可选,用于区分同名节点,通常为寄存器基址 @0, @80000000
路径 从根节点到目标节点的全路径 /soc/serial@10000000
标签 为节点起别名,方便引用 uart0: serial@10000000

1.3.2 属性(Property)与值(Value)

属性描述节点的特征,每个属性有一个名称和一个或多个值。值的类型可以是 u32 整数、64 位整数、字符串、字节数组 或 phandle(指针引用)。

属性值格式 语法 示例 说明
32 位整数 <value> <0x10000000> 单个或多个 u32 值
64 位整数 <high low> <0x0 0x80000000> 高 32 位 + 低 32 位
字符串 "string" "ns16550a" 双引号包裹
字节数组 [hex bytes] [00 01 02 03] 十六进制字节序列
字符串列表 "str1", "str2" "ti,omap3-uart", "ns16550" 逗号分隔
布尔值 仅属性名,无值 status = "disabled"; 存在即真,不存在即假
phandle &label &uart0 引用其他节点
常见错误: 忘记在 .dts 文件第一行声明 /dts-v1/; 会导致 DTC 编译失败。此外,reg 属性的 #address-cells 和 #size-cells 必须与父节点定义一致,否则地址解析会出错。

1.4 标准属性详解

设备树规范定义了一组标准属性,用于描述设备的核心资源。以下是最常用的几个:

  • compatible:驱动匹配的关键,格式为 "厂商,设备型号",内核通过该属性查找对应的 of_match_table。
  • reg:描述设备地址空间,格式为 <基址 长度>,长度由父节点的 #address-cells 和 #size-cells 决定。
  • interrupts:描述中断号、触发类型,格式依赖 interrupt-parent 和 #interrupt-cells。
  • status:设备状态,常用值 "okay"(启用)、"disabled"(禁用)。
  • device_type:早期规范遗留属性,新设备树中已不推荐使用,仅 cpu 和 memory 节点保留。
设备树节点与属性结构 设备树节点与属性结构 / (根节点) compatible = "vendor,board" cpu@0 compatible, reg, device_type memory@80000000 device_type, reg serial@10000000 compatible, reg, interrupts 标准属性说明 • compatible:驱动匹配字符串,格式 "厂商,设备型号" • reg:地址资源,格式 <基址 长度>,由父节点 #address-cells / #size-cells 决定 • interrupts:中断描述,依赖 interrupt-parent 和 #interrupt-cells • status:设备状态,"okay" 启用,"disabled" 禁用

1.5 设备树包含与复用机制

实际项目中,一个 SoC 可能被多个板卡使用。设备树通过 #include 和 .dtsi 文件实现复用:

  • .dtsi:设备树包含文件(Device Tree Source Include),通常存放 SoC 公共定义,如 CPU 集群、中断控制器、时钟等。
  • .dts:板级文件,通过 #include 引入 .dtsi,然后添加或覆盖板级特有的节点和属性。
  • /include/:DTC 原生包含指令,功能与 C 语言的 #include 类似,但推荐使用 #include 以支持 C 预处理宏。
/* 文件:soc.dtsi — SoC 公共定义 */
/ {
    compatible = "vendor,soc";
    #address-cells = <2>;
    #size-cells = <2>;

    gic: interrupt-controller@10400000 {
        compatible = "arm,gic-v3";
        reg = <0x0 0x10400000 0x0 0x20000>;
        interrupt-controller;
        #interrupt-cells = <3>;
    };

    uart0: serial@10000000 {
        compatible = "ns16550a";
        reg = <0x0 0x10000000 0x0 0x1000>;
        interrupts = <0 33 4>;
        status = "disabled";
    };
};

/* 文件:board.dts — 板级文件 */
/dts-v1/;
#include "soc.dtsi"

/ {
    model = "Vendor Board Rev2";
    compatible = "vendor,board-rev2", "vendor,board";

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

    &uart0 {
        status = "okay";
        clock-frequency = <24000000>;
    };
};
复用技巧: 使用 &标签 语法可以在板级文件中引用并覆盖 SoC 级节点中的属性,无需重写整个节点。这是设备树实现“增量修改”的核心机制。

1.6 设备树编译与反编译

DTC 工具是设备树开发者的日常工具,支持正向编译和反向反编译:

操作 命令 说明
编译 DTS → DTB dtc -I dts -O dtb -o board.dtb board.dts 将文本源文件编译为二进制
反编译 DTB → DTS dtc -I dtb -O dts -o board.dts board.dtb 将二进制还原为可读文本
查看 DTB 内容 dtc -I dtb -O dts /sys/firmware/fdt 查看内核当前使用的设备树
检查 DTB 有效性 dtc -I dtb -O dtb board.dtb 重新编译以验证格式
调试建议: 当内核启动时设备无法识别,首先通过 dtc -I dtb -O dts /sys/firmware/fdt 反编译当前 DTB,确认节点和属性是否与预期一致。这是设备树调试的第一步。