3. 设备树(DTS)基础:设备树语法、节点、属性、reg、interrupts

设备树(Device Tree)是一种描述硬件资源的数据结构,它把嵌入式系统中不可枚举的硬件设备信息(如内存基址、中断号、GPIO引脚、时钟频率等)从内核源码中剥离出来,以文本形式独立描述。内核在启动时解析设备树二进制文件(DTB),动态创建平台设备和资源映射,从而实现“一套内核镜像支持多款硬件”的目标。

核心思想:设备树是“硬件描述语言”,不是“驱动代码”。驱动通过匹配 compatible 属性找到对应设备节点,再读取节点中的 reg、interrupts、GPIO 等属性来获取硬件资源。

3.1 设备树源文件(DTS)基本语法

设备树源文件采用树形层次结构,以分号结尾,花括号表示层级。一个最小的 DTS 文件包含根节点、CPU 节点、内存节点和若干外设节点。

/dts-v1/;

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

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a7";
            reg = <0x0>;
        };
    };

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

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

        uart0: serial@10000000 {
            compatible = "vendor,uart";
            reg = <0x10000000 0x1000>;
            interrupts = <0 33 4>;
            status = "okay";
        };
    };
};
语法要点:
  • /dts-v1/; — 版本标识,必须放在文件开头。
  • / { }; — 根节点,所有设备节点都是它的子节点。
  • node-name@unit-address — 节点名 + 单元地址(可选,用于区分同类型设备)。
  • label: node-name — 标签,方便其他节点引用(如 &uart0)。
  • property = value; — 属性赋值,值可以是字符串、整数、字节数组等。

3.2 节点与属性

每个设备树节点代表一个硬件设备或总线,节点内部通过属性描述设备的配置信息。常用属性分类如下:

属性类别 属性名 说明 示例值
标识 compatible 驱动匹配字符串,格式 "厂商,型号" "ti,am335x-gpio"
地址 reg 寄存器地址范围,由 #address-cells 和 #size-cells 决定长度 <0x4804c000 0x1000>
中断 interrupts 中断号、触发类型,格式由中断控制器定义 <0 29 4>
GPIO gpios GPIO 引脚描述,包含控制器句柄、引脚号、标志 <&gpio1 15 0>
状态 status 设备启用状态,"okay" 或 "disabled" "okay"
时钟 clocks 时钟源句柄 <&clk_uart0>
引脚控制 pinctrl-0 引脚复用配置句柄 <&pinctrl_uart0>
常见错误:#address-cells 和 #size-cells 必须在父节点中定义,子节点的 reg 属性才能正确解析。如果忘记设置,内核解析 reg 时会默认使用 2 个 cell 表示地址、1 个 cell 表示长度,导致地址空间错乱。

3.3 reg 属性详解

reg 属性描述设备在总线上的地址空间,格式为 reg = <address1 length1 address2 length2 ...>。每个地址/长度对称为一个“reg 条目”。address 和 length 的 cell 数量由父节点的 #address-cells 和 #size-cells 决定。

/* 32位地址空间,1个cell表示地址,1个cell表示长度 */
#address-cells = <1>;
#size-cells = <1>;

gpio@4804c000 {
    reg = <0x4804c000 0x1000>;  /* 基址0x4804c000,长度0x1000 */
};

/* 64位地址空间,2个cell表示地址,1个cell表示长度 */
#address-cells = <2>;
#size-cells = <1>;

memory@80000000 {
    reg = <0x0 0x80000000 0x20000000>;  /* 地址0x80000000,长度0x20000000 */
};
驱动读取 reg 的 API:
  • platform_get_resource(pdev, IORESOURCE_MEM, index) — 获取第 index 个内存资源。
  • of_iomap(node, index) — 直接映射第 index 个 reg 条目到虚拟地址。
  • of_address_to_resource(node, index, &res) — 将 reg 条目转换为 resource 结构。

3.4 interrupts 属性详解

interrupts 属性描述设备的中断连接信息,其格式由中断控制器(interrupt-controller)的 #interrupt-cells 决定。最常见的是 ARM GIC(通用中断控制器),使用 3 个 cell:

Cell 位置 含义 取值说明
cell 0 中断类型 0 = SPI(共享外设中断),1 = PPI(私有外设中断)
cell 1 中断号 SPI 中断号范围 32~1019,PPI 中断号范围 16~31
cell 2 触发类型 1 = 上升沿触发,2 = 下降沿触发,4 = 高电平触发,8 = 低电平触发
/* 中断控制器节点 */
gic: interrupt-controller@1e00000 {
    compatible = "arm,cortex-a7-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x1e00000 0x1000>;
};

/* 使用中断的设备节点 */
uart0: serial@10000000 {
    compatible = "vendor,uart";
    reg = <0x10000000 0x1000>;
    interrupts = <0 33 4>;  /* SPI 中断号33,高电平触发 */
    interrupt-parent = <&gic>;  /* 指定中断控制器 */
};
interrupt-parent 的作用:当系统中有多个中断控制器时,设备节点必须通过 interrupt-parent 属性显式指定连接到哪个中断控制器。如果省略,内核会向上查找父节点的 interrupt-parent 或根节点的 interrupt-parent。

3.5 设备树层次结构与引用

设备树采用树形结构,根节点下包含 cpus、memory、soc 等子节点。通过标签和 & 引用,可以在 dtsi 文件中定义基础节点,在 dts 文件中追加或覆盖属性。

/* am335x.dtsi — SoC 级定义 */
/ {
    soc {
        uart0: serial@44e09000 {
            compatible = "ti,am3352-uart";
            reg = <0x44e09000 0x2000>;
            interrupts = <72>;
            status = "disabled";
        };
    };
};

/* myboard.dts — 板级定义 */
#include "am335x.dtsi"

&uart0 {
    status = "okay";
    pinctrl-0 = <&pinctrl_uart0>;
    pinctrl-names = "default";
};
引用机制优势:
  • SoC 厂商提供 dtsi 文件,板卡厂商只需 include 并修改差异部分。
  • 避免重复定义,减少错误。
  • 通过 &label 可以追加属性、修改 status、添加子节点。

3.6 设备树结构总览

下面这张 SVG 图展示了设备树从 DTS 源文件到内核驱动匹配的完整流程,以及节点、属性、reg、interrupts 在其中的位置。

设备树结构总览:从 DTS 到驱动匹配 设备树结构总览:从 DTS 到驱动匹配 DTS 源文件 DTSI 包含文件 DTC 编译器 DTB 二进制 内核启动:解析 DTB → 创建 device_node 树 → 注册平台设备 of_platform_populate() / of_scan_flat_dt() 设备节点示例 gpio-keys { compatible = "gpio-keys"; key-power { gpios = <&gpio1 15 0>; interrupts = <15 2>; reg 属性 #address-cells = <1>; #size-cells = <1>; uart@10000000 { reg = <0x10000000 0x1000>; // 基址 0x10000000,长度 0x1000 interrupts 属性 #interrupt-cells = <3>; interrupts = <0 33 4>; // 类型: SPI // 中断号: 33 // 触发: 高电平 驱动匹配:of_match_table → compatible 匹配 → probe() 执行 platform_driver_register() → of_device_is_compatible() 驱动获取资源:platform_get_resource() / of_iomap() / platform_get_irq()

3.7 常用工具与调试方法

工具/命令 用途 示例
dtc DTS 编译/反编译 dtc -I dts -O dtb -o output.dtb input.dts
dtc -I dtb -O dts 将 DTB 反编译为 DTS dtc -I dtb -O dts -o output.dts input.dtb
ls /proc/device-tree 查看内核解析后的设备树 ls /proc/device-tree/soc/uart0
of_* 调试打印 驱动中打印节点信息 of_node_name(node)、of_get_property()
devicetree.org 设备树规范与绑定文档 查看 compatible 字符串定义
调试建议:在驱动 probe() 函数中加入 dev_info(dev, "node name: %s\n", of_node_name(np)); 可以快速确认设备树节点是否被正确匹配。如果 probe 没有被调用,优先检查 compatible 字符串是否一致、status 是否为 "okay"。

3.8 本章小结

  • 设备树是硬件描述文件,使用树形结构组织节点和属性。
  • compatible 属性是驱动匹配的关键,必须与驱动中 of_match_table 的字符串完全一致。
  • reg 属性描述设备地址空间,由 #address-cells 和 #size-cells 决定格式。
  • interrupts 属性描述中断连接,格式由中断控制器的 #interrupt-cells 定义。
  • 通过标签引用和 dtsi 包含,可以实现 SoC 级与板级设备树的分离管理。
  • dtc 工具和 /proc/device-tree 是调试设备树的利器。