2. 设备树基础:设备树语法、节点与属性、reg属性、interrupts属性、compatible属性

设备树(Device Tree,DT)是嵌入式 Linux 系统中用于描述硬件拓扑结构和资源分配的一种数据结构。它取代了传统 ARM 平台中充斥在 arch/arm/mach-* 目录下的大量板级代码(board file),实现了“内核镜像与硬件描述分离”的设计目标。对于 Platform 驱动开发者而言,设备树是获取硬件资源(如寄存器地址、中断号、DMA 通道)的核心来源。

核心认知:设备树不是“代码”,而是“数据”。它告诉内核“硬件长什么样”,驱动则根据这些数据来初始化硬件。驱动代码与硬件描述解耦,是嵌入式 Linux 驱动现代化的基石。

2.1 设备树语法基础

设备树源文件(.dts)采用一种树形结构的文本格式,其基本语法单元包括:节点(node)、属性(property)和值(value)。一个典型的设备树片段如下:

/dts-v1/;

/ {
    model = "Embedded Development Board";
    compatible = "vendor,board-v1", "vendor,board";

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

    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        uart0: serial@101f1000 {
            compatible = "vendor,uart";
            reg = <0x101f1000 0x1000>;
            interrupts = <0 5 4>;
            clock-frequency = <24000000>;
            status = "okay";
        };
    };
};

语法要点:

  • 根节点:用 / 表示,是整个设备树的起点。
  • 节点名称:格式为 node-name@unit-address,其中 @unit-address 通常为设备寄存器基地址,用于区分同名节点。
  • 属性:键值对形式,值可以是字符串、32 位整数数组(< >)、字节数组([ ])、字符串列表等。
  • 标签(Label):如 uart0:,用于在 .dtsi 文件中引用或覆盖节点。
  • 包含文件:使用 #include 引入 .dtsi 头文件,实现 SoC 级描述的复用。
编译流程:.dts 文件通过 Device Tree Compiler(dtc)编译为 .dtb(Device Tree Blob)二进制文件。Bootloader(如 U-Boot)将 .dtb 加载到内存,并将地址传递给内核。

2.2 节点与属性

设备树中的每个硬件设备或总线都对应一个节点。节点可以嵌套,形成层次结构。属性则描述了节点的具体特征。

概念 说明 示例
节点(Node) 代表一个硬件设备、总线或控制器 spi@10010000 { ... };
属性(Property) 描述节点的特性或资源 reg = <0x10010000 0x1000>;
子节点 挂载在父节点下的设备 spi@10010000 { flash@0 { ... }; };
状态属性 控制设备是否启用 status = "disabled"; 或 "okay"

常见的标准属性包括:

  • compatible:驱动匹配的关键字。
  • reg:描述设备地址空间。
  • interrupts:描述中断资源。
  • clocks:关联时钟控制器。
  • dmas:关联 DMA 控制器(后续章节重点)。
  • status:设备状态。
避坑提示:节点名称中的 @unit-address 仅用于区分同名节点,内核并不直接解析该地址来获取寄存器基址。寄存器基址必须通过 reg 属性明确指定。

2.3 reg 属性:地址资源描述

reg 属性是 Platform 驱动获取寄存器物理地址和内存范围的核心依据。其格式由父节点的 #address-cells 和 #size-cells 决定。

  • #address-cells:表示地址字段占用的 32 位整数个数(通常为 1 或 2)。
  • #size-cells:表示长度字段占用的 32 位整数个数(通常为 1 或 2)。

每个 reg 条目由 (address, length) 对组成。例如:

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

dma@101e3000 {
    reg = <0x101e3000 0x1000>;
    /* 基址: 0x101e3000, 范围: 0x1000 (4KB) */
};

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

pcie@40000000 {
    reg = <0x0 0x40000000 0x0 0x10000000>;
    /* 基址: 0x40000000 (高32位0, 低32位0x40000000), 范围: 0x10000000 (256MB) */
};

驱动侧通过 platform_get_resource(pdev, IORESOURCE_MEM, index) 获取 reg 属性中第 index 个条目的物理地址和长度。

驱动开发要点:获取 reg 资源后,通常需要调用 devm_ioremap_resource() 将物理地址映射到内核虚拟地址空间,才能对寄存器进行读写操作。

2.4 interrupts 属性:中断资源描述

interrupts 属性描述了设备的中断请求线。其格式依赖于中断控制器(Interrupt Controller)的 #interrupt-cells 属性。

以常见的 GIC(Generic Interrupt Controller)为例,#interrupt-cells = <3>,三个字段的含义为:

字段 含义 常见值
字段 1 中断类型 0 = SPI (共享外设中断), 1 = PPI (私有外设中断)
字段 2 中断号 SPI 中断号范围: 32 ~ 1019
字段 3 触发类型与极性 4 = 高电平触发, 8 = 低电平触发, 2 = 上升沿触发, 1 = 下降沿触发

设备节点示例:

interrupt-parent = <&gic>;  /* 指定中断控制器 */
interrupts = <0 45 4>;      /* SPI 中断, 中断号 45, 高电平触发 */

驱动侧通过 platform_get_resource(pdev, IORESOURCE_IRQ, index) 或 platform_get_irq(pdev, index) 获取中断号,然后调用 devm_request_irq() 注册中断处理函数。

多中断场景:一个设备可能拥有多个中断源,例如 TX 中断和 RX 中断。此时 interrupts 属性可以包含多个条目,驱动通过 index 参数区分。

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

compatible 属性是设备树与驱动代码之间的“桥梁”。它包含一个或多个字符串,用于标识设备的具体型号和兼容性。

  • 格式:"厂商,设备型号",例如 "vendor,uart"。
  • 匹配优先级:内核在注册 Platform 设备时,会按照 compatible 属性中的字符串顺序,依次与驱动中 of_device_id 表进行匹配。
  • 兼容性列表:可以包含多个字符串,表示该设备兼容多种驱动。例如 compatible = "vendor,uart-v2", "vendor,uart-v1",表示优先匹配 v2 驱动,若不存在则回退到 v1 驱动。

驱动侧匹配代码示例:

static const struct of_device_id my_uart_of_match[] = {
    { .compatible = "vendor,uart-v2", .data = &uart_v2_data },
    { .compatible = "vendor,uart-v1", .data = &uart_v1_data },
    { /* end of list */ }
};
MODULE_DEVICE_TABLE(of, my_uart_of_match);

static struct platform_driver my_uart_driver = {
    .probe  = my_uart_probe,
    .remove = my_uart_remove,
    .driver = {
        .name = "my_uart",
        .of_match_table = my_uart_of_match,
    },
};
避坑提示:compatible 字符串必须与驱动中 of_device_id 表完全一致(包括大小写和标点符号)。一个常见的错误是设备树中写 "vendor,uart",而驱动中写 "vendor,uart" 但多了一个空格,导致匹配失败。

2.6 设备树资源获取流程总览

下面的 SVG 图展示了从设备树节点到 Platform 驱动获取资源的完整流程:

设备树资源获取流程 设备树 → Platform 驱动资源获取流程 .dts 源文件 dtc 编译 .dtb 二进制 Bootloader 加载到内存 内核解析并创建 Platform 设备 Platform 驱动 probe 回调 驱动通过 platform_get_resource() 获取 reg / interrupts 等资源

2.7 关键属性对比总结

属性 用途 驱动获取 API 典型值示例
compatible 驱动匹配 of_match_device() "vendor,uart"
reg 寄存器/内存地址 platform_get_resource(pdev, IORESOURCE_MEM, 0) <0x101f1000 0x1000>
interrupts 中断号与触发方式 platform_get_irq(pdev, 0) <0 45 4>
status 设备启用/禁用 of_device_is_available() "okay" / "disabled"
学习路径建议:掌握设备树语法是编写 Platform 驱动的前提。建议读者在开发板上实际查看 /sys/firmware/devicetree/base 目录下的设备树结构,加深对节点和属性的理解。