2. 设备树基础:设备树(DTS)语法入门、节点与属性、标准属性详解

设备树(Device Tree,DT)是嵌入式 Linux 系统中用于描述硬件拓扑结构和资源分配的一种数据结构。它取代了传统架构中硬编码的板级文件(如 arch/arm/mach-xxx),实现了内核与硬件描述的分离。本章将系统讲解设备树的语法、节点与属性,以及三个最核心的标准属性:compatible、reg 和 interrupts。

2.1 设备树语法入门

设备树源文件(.dts)采用树形结构描述硬件,其基本语法规则如下:

  • 节点(Node):以 node-name@unit-address 形式定义,花括号 { } 包裹子节点和属性。
  • 属性(Property):键值对,值可以是字符串、整数、字节数组、字符串列表等。
  • 根节点:/ 表示设备树的根,所有设备节点都挂载在根下。
  • 标签(Label):label: node-name 用于引用节点,方便在 .dtsi 中覆盖。
  • 包含文件:#include 引入 .dtsi 头文件,实现 SoC 级描述复用。
💡 关键点:设备树是“扁平化”的硬件描述,不包含任何内核逻辑。它只回答“硬件有什么、在哪里、怎么访问”,不回答“驱动怎么用”。

2.1.1 一个完整的 DTS 示例

/dts-v1/;

#include "imx6ul.dtsi"

/ {
    model = "Embedded Device Board";
    compatible = "vendor,embedded-board", "vendor,imx6ul";

    chosen {
        stdout-path = &uart1;
    };

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

    leds {
        compatible = "gpio-leds";
        led0: led-red {
            gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
            label = "red-led";
        };
    };

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

2.2 节点与属性详解

设备树的每个节点代表一个硬件设备或总线,属性则描述该设备的配置参数。节点可以嵌套,形成层次化的总线拓扑。

概念 语法示例 说明
节点定义 spi@1c00000 { ... }; 节点名 spi,单元地址 1c00000(基址)
属性赋值 reg = <0x1c00000 0x1000>; 键 reg,值 <地址 长度>
字符串 compatible = "vendor,device"; 双引号包裹,用于驱动匹配
字符串列表 compatible = "a", "b", "c"; 逗号分隔,优先级从高到低
32位整数数组 reg = <0x1000 0x2000 0x3000>; 尖括号包裹,空格分隔
64位整数 reg = <0x00000000 0x80000000 0x0 0x20000000>; 两个 32 位单元拼接
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号包裹,十六进制字节
布尔属性 status = "disabled"; 存在即真,常用 okay/disabled
引用(phandle) &gpio1 & 引用标签,编译器替换为整数句柄

2.3 标准属性详解

设备树规范定义了一系列标准属性,用于统一描述设备资源。其中 compatible、reg 和 interrupts 是驱动开发中最核心的三个属性。

2.3.1 compatible 属性:驱动匹配的“身份证”

compatible 是设备树中最重要的属性,它建立了设备节点与内核驱动之间的匹配桥梁。其值是一个字符串列表,格式通常为 "厂商,设备型号"。

  • 匹配优先级:列表中的第一个字符串优先级最高,内核按顺序尝试匹配。
  • 通配匹配:如果设备与多个驱动兼容,可以列出多个字符串,如 "ti,am3352-uart", "ns16550"。
  • 驱动侧对应:驱动通过 of_match_table 声明支持的 compatible 值。
✅ 最佳实践:compatible 字符串应遵循“厂商前缀+设备名”的命名规范,避免使用过于通用的名称(如 "uart"),防止匹配冲突。

2.3.2 reg 属性:描述地址空间

reg 属性用于描述设备在总线上的地址范围,其格式由父节点的 #address-cells 和 #size-cells 决定。

  • #address-cells:表示地址字段占用的 32 位单元数(1 为 32 位,2 为 64 位)。
  • #size-cells:表示长度字段占用的 32 位单元数。
  • reg 格式:reg = <地址1 长度1 地址2 长度2 ...>;
/* 32位地址空间 */
spi@1c00000 {
    #address-cells = <1>;
    #size-cells = <1>;
    reg = <0x1c00000 0x1000>;  /* 基址0x1c00000,长度0x1000 */
};

/* 64位地址空间 */
memory@80000000 {
    #address-cells = <2>;
    #size-cells = <2>;
    reg = <0x0 0x80000000 0x0 0x20000000>;  /* 基址0x80000000,长度0x20000000 */
};

2.3.3 interrupts 属性:中断资源描述

interrupts 属性描述设备使用的中断线,其解析依赖于父节点 interrupt-controller 和 #interrupt-cells。

  • interrupt-parent:指定设备连接的中断控制器(phandle 引用)。
  • interrupts:中断描述符,格式由中断控制器的 #interrupt-cells 决定。
  • 常见格式:<中断号 触发类型>,触发类型如 IRQ_TYPE_EDGE_RISING (1)、IRQ_TYPE_LEVEL_HIGH (4)。
/* 中断控制器定义 */
intc: interrupt-controller@a01000 {
    compatible = "arm,gic-v3";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0xa01000 0x1000>;
};

/* 使用中断的设备 */
uart@21c0000 {
    compatible = "vendor,uart";
    reg = <0x21c0000 0x1000>;
    interrupt-parent = <&intc>;
    interrupts = <0 33 4>;  /* SPI 33, 高电平触发 */
};

2.4 设备树节点与标准属性关系图

下图展示了设备树中节点、标准属性与内核驱动之间的核心关系:

设备树节点与标准属性关系图 根节点 / uart@21c0000 spi@1c00000 gpio@20a0000 compatible 属性 • 驱动匹配的“身份证” • 格式: "厂商,设备型号" • 优先级: 列表顺序 • 对应 of_match_table reg 属性 • 描述地址空间 • 格式: <地址 长度> • 依赖 #address-cells • 依赖 #size-cells interrupts 属性 • 描述中断资源 • 格式: <中断号 触发类型> • 依赖 interrupt-parent • 依赖 #interrupt-cells 内核驱动匹配层 (of_match_table) 驱动 probe() 被调用,设备初始化

2.5 标准属性对比总结

属性 作用 驱动解析 API 典型值示例
compatible 驱动匹配标识 of_match_device() "vendor,uart"
reg 地址/内存资源 of_iomap() / platform_get_resource() <0x21c0000 0x1000>
interrupts 中断资源 platform_get_irq() / irq_of_parse_and_map() <0 33 4>

2.6 常见陷阱与避坑指南

⚠️ 常见错误:
  • compatible 拼写错误:驱动侧和设备树侧字符串必须完全一致,包括大小写和标点。
  • reg 的 address-cells 不匹配:父节点与子节点的地址单元数必须一致,否则解析偏移。
  • 中断号混淆:SPI(共享外设中断)和 PPI(私有外设中断)的编号范围不同,GIC 中 SPI 从 32 开始。
  • 缺少 interrupt-parent:如果设备不在中断控制器子节点下,必须显式指定 interrupt-parent。

2.7 设备树编译与反编译

设备树源文件(.dts)需要编译为二进制设备树 blob(.dtb),内核在启动时解析 .dtb 并构建设备树。常用工具如下:

  • 编译:dtc -I dts -O dtb -o output.dtb input.dts
  • 反编译:dtc -I dtb -O dts -o output.dts input.dtb
  • 查看设备树:dtc -I fs -O dts /proc/device-tree(运行时查看)
💡 调试技巧:在驱动 probe 函数中添加 dev_info(dev, "compatible: %s\n", of_get_property(dev->of_node, "compatible", NULL)); 可以验证设备树解析是否正确。