2. 设备树基础回顾:语法、节点与标准属性

设备树(Device Tree,DT)是嵌入式 Linux 中用于描述硬件拓扑结构的数据结构,它替代了传统板级文件(board-*.c)中硬编码的硬件信息。内核通过解析设备树二进制(DTB)来发现设备、匹配驱动并配置资源。本节将系统回顾设备树的语法规则、节点与属性结构,以及三个最核心的标准属性:compatible、reg 和 interrupts。

学习提示:设备树是驱动开发者的“硬件说明书”。掌握节点与属性的关系,是理解后续时钟配置与驱动匹配流程的基础。

2.1 设备树语法基础

设备树源文件(.dts)采用树形结构,以 /dts-v1/; 开头,根节点用 / 表示,内部包含若干子节点,每个节点描述一个硬件设备或总线。基本语法规则如下:

  • 节点定义:node-name@unit-address,其中 unit-address 通常为设备基地址。
  • 属性赋值:property-name = <value>; 或 property-name = "string";。
  • 标签引用:使用 &label 引用已定义的节点,用于追加或覆盖属性。
  • 包含文件:#include "soc.dtsi" 引入公共 SoC 定义。
// 设备树源文件示例:描述一个 UART 控制器
/dts-v1/;

/ {
    model = "Embedded Dev Board v2.0";
    compatible = "vendor,dev-board";

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

        uart0: serial@10000000 {
            compatible = "vendor,uart-ip";
            reg = <0x10000000 0x1000>;
            interrupts = <0 33 4>;
            clock-frequency = <24000000>;
            status = "okay";
        };
    };
};
语法要点:
  • #address-cells 和 #size-cells 定义子节点中 reg 属性的地址和长度字段宽度。
  • status 属性控制设备启用("okay")或禁用("disabled")。
  • 节点名 serial@10000000 中的 @10000000 仅用于区分同名节点,不强制要求与 reg 一致。

2.2 节点与属性结构

设备树的每个节点可以包含子节点和属性。属性是键值对,值可以是以下类型之一:

属性值类型 语法示例 说明
空值 property; 仅表示存在该属性,无值
32 位整数数组 reg = <0x1000 0x2000>; 用尖括号包裹,空格分隔
64 位整数数组 reg = <0x00000000 0x10000000>; 两个 32 位单元表示一个 64 位值
字符串 compatible = "vendor,device"; 双引号包裹
字符串列表 compatible = "vendor,device", "generic,device"; 逗号分隔多个字符串
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号包裹十六进制字节
混合结构 gpios = <&gpio1 6 0>; 可包含 phandle 和整数

节点之间的层级关系通过缩进体现,根节点 / 下通常包含 cpus、memory、chosen、soc 等顶层节点。

设备树节点层级结构 / (根节点) cpus memory chosen soc i2c@... uart@... spi@... gpio@... cpu@0 cpu@1 节点层级说明 根节点 / 包含 cpus、memory、chosen、soc 等顶层节点;soc 下挂载具体外设控制器节点

2.3 标准属性详解

设备树规范定义了一系列标准属性,用于统一描述硬件资源。其中 compatible、reg 和 interrupts 是驱动匹配与资源获取的三大支柱。

2.3.1 compatible 属性——驱动匹配的钥匙

compatible 属性是一个字符串列表,用于将设备节点与内核驱动绑定。内核通过 of_match_table 中的 compatible 字符串与设备树节点进行匹配。匹配顺序遵循“最具体优先”原则:

// 设备树节点
compatible = "vendor,uart-ip-v2", "vendor,uart-ip";

// 内核驱动匹配表
static const struct of_device_id uart_of_match[] = {
    { .compatible = "vendor,uart-ip-v2", .data = &v2_data },
    { .compatible = "vendor,uart-ip",    .data = &v1_data },
    { /* sentinel */ }
};
常见误区:驱动匹配时优先使用列表中的第一个字符串。如果第一个字符串匹配失败,内核会尝试第二个。务必确保 compatible 字符串在驱动表中存在对应项,否则设备将无法绑定驱动。

2.3.2 reg 属性——地址空间描述

reg 属性描述设备在总线上的地址范围,由一组 (address, length) 对组成。地址和长度的单元宽度由父节点的 #address-cells 和 #size-cells 决定:

#address-cells #size-cells reg 格式 示例
1 1 <addr len> reg = <0x10000000 0x1000>;
2 1 <hi_addr lo_addr len> reg = <0x0 0x10000000 0x1000>;
1 0 <addr> reg = <0x10000000>;

驱动通过 platform_get_resource(pdev, IORESOURCE_MEM, index) 获取 reg 中定义的地址资源。

2.3.3 interrupts 属性——中断资源描述

interrupts 属性描述设备使用的中断号、触发类型等信息。其格式依赖于中断控制器(interrupt-parent)的定义。常见格式如下:

// 设备树节点
uart0: serial@10000000 {
    compatible = "vendor,uart-ip";
    reg = <0x10000000 0x1000>;
    interrupt-parent = <&gic>;          // 引用中断控制器
    interrupts = <0 33 4>;              // (中断类型, 中断号, 触发标志)
};

中断属性字段含义(以 ARM GIC 为例):

  • 字段 0:中断类型——0 表示 SPI(共享外设中断),1 表示 PPI(私有外设中断)。
  • 字段 1:中断号——对于 SPI,范围为 32~1019;对于 PPI,范围为 16~31。
  • 字段 2:触发标志——1 表示上升沿触发,2 表示下降沿触发,4 表示高电平触发,8 表示低电平触发。
驱动获取中断资源:
// 在 probe 函数中获取中断号
int irq = platform_get_irq(pdev, 0);
if (irq < 0) {
    dev_err(&pdev->dev, "failed to get IRQ\n");
    return irq;
}
// 注册中断处理函数
ret = devm_request_irq(&pdev->dev, irq, uart_isr, 0, "uart", dev);
标准属性与驱动匹配关系 设备树节点 compatible = "vendor,uart" reg = <0x1000 0x100> interrupts = <0 33 4> clock-frequency = <24e6> status = "okay" 内核驱动 of_match_table .compatible = "vendor,uart" probe() 函数 platform_get_resource() platform_get_irq() compatible 匹配 reg → IORESOURCE_MEM interrupts → IRQ number 标准属性驱动匹配流程 1. 内核遍历设备树节点,读取 compatible 属性 2. 与驱动 of_match_table 中的字符串逐一比对 3. 匹配成功后调用 probe(),驱动通过 reg/interrupts 获取硬件资源

2.4 属性继承与覆盖机制

设备树支持通过 /include/ 或 #include 引入公共定义(.dtsi 文件),并在板级文件(.dts)中覆盖或追加属性。这种机制极大减少了重复代码:

// soc.dtsi —— SoC 公共定义
soc {
    uart0: serial@10000000 {
        compatible = "vendor,uart-ip";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
        status = "disabled";   // 默认禁用
    };
};

// board.dts —— 板级文件
#include "soc.dtsi"

&uart0 {
    status = "okay";           // 覆盖为启用
    clock-frequency = <24000000>;  // 追加属性
};
最佳实践:SoC 级 .dtsi 文件定义硬件 IP 的默认属性(如 compatible、reg、interrupts),板级 .dts 文件只负责启用设备(status = "okay")和配置板级特定参数(如时钟频率、引脚复用)。这种分层设计便于多板卡复用同一 SoC 定义。

2.5 本章小结

设备树是嵌入式 Linux 驱动开发的基石。通过本节回顾,你应该掌握:

  • 设备树的树形语法结构,节点与属性的定义方式。
  • compatible 属性作为驱动匹配的唯一标识,其字符串列表的匹配优先级。
  • reg 属性描述地址空间,受 #address-cells 和 #size-cells 控制。
  • interrupts 属性描述中断资源,依赖中断控制器的格式定义。
  • 属性继承与覆盖机制,实现 SoC 与板级代码的分离。