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

设备树(Device Tree,DT)是嵌入式 Linux 驱动开发中描述硬件拓扑结构的核心机制。它将硬件配置信息从内核代码中剥离,以树形结构的数据格式传递给内核,实现“硬件描述”与“驱动代码”的解耦。本章将系统讲解设备树的语法、节点与属性,并深入剖析三个最核心的标准属性:compatible、reg 和 interrupts。

学习提示: 设备树是驱动工程师的“硬件地图”。掌握 DTS 语法和标准属性,是编写可移植、可维护驱动的第一步。建议在学习过程中对照实际开发板上的 DTS 文件进行练习。

2.1 设备树语法入门

设备树源文件(.dts)采用一种层次化的、人类可读的文本格式。其基本语法规则如下:

  • 树形结构: 以根节点 / 开始,内部包含若干子节点,子节点下可继续嵌套子节点。
  • 节点定义: 节点名 { 属性; 子节点; };
  • 属性赋值: 属性名 = 值; 或 属性名;(空属性)。
  • 标签(Label): 用于引用节点,格式为 标签名: 节点名 { ... };。
  • 包含文件: 使用 #include 引入 .dtsi 头文件,实现复用。
最小 DTS 示例:
/dts-v1/;

/ {
    model = "Example Board";
    compatible = "vendor,example-board";

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

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

    uart0: serial@10000000 {
        compatible = "vendor,uart";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
    };
};

上述代码定义了一个包含 CPU、内存和 UART 外设的简单系统。每个节点代表一个硬件设备或总线,属性则描述其配置信息。

设备树结构图 / (根节点) cpu@0 memory@80000000 uart0: serial@10000000 节点属性示例 • cpu@0: device_type = "cpu"; compatible = "arm,cortex-a9"; reg = <0x0>; • memory@80000000: device_type = "memory"; reg = <0x80000000 0x40000000>; • uart0: compatible = "vendor,uart"; reg = <0x10000000 0x1000>; interrupts = <0 33 4>; • 标签 (Label): uart0 可在其他位置通过 &uart0 引用。

2.2 节点与属性详解

设备树的每个节点都代表一个设备、总线或控制器。节点通过属性来描述其硬件特征、资源需求和配置参数。

2.2.1 节点命名规范

  • 通用格式: 节点名@单元地址,例如 serial@10000000。
  • 单元地址: 通常为设备在总线上的起始地址,用于区分同一类型的不同实例。
  • 特殊节点: aliases、chosen、cpus、memory 等具有特定语义。

2.2.2 属性类型与值格式

属性类型 值格式 示例 说明
字符串 "string" compatible = "vendor,device"; 用于标识设备兼容性
32位整数 <value> reg = <0x10000000 0x1000>; 地址、长度、中断号等
64位整数 <0x00000000 0x80000000> reg = <0x00000000 0x80000000 0x00000000 0x40000000>; 高32位在前,低32位在后
字节数组 [byte1 byte2 ...] local-mac-address = [00 11 22 33 44 55]; MAC地址、UUID等
布尔值 仅属性名,无值 status = "disabled"; 通过存在与否表示 true/false
字符串列表 "str1", "str2" clock-names = "uart_clk", "baud_clk"; 用于命名资源
常见错误: 忘记在整数列表中使用尖括号 <>,或字符串忘记加双引号。例如 reg = 0x10000000; 是错误的,必须写成 reg = <0x10000000>;。

2.3 标准属性详解

设备树规范定义了一系列标准属性,用于统一描述硬件资源。其中 compatible、reg 和 interrupts 是最基础、最常用的三个属性。

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

compatible 属性是设备树与驱动代码之间的桥梁。内核通过该属性查找并绑定对应的驱动程序。

  • 格式: compatible = "厂商,设备型号"; 或 compatible = "通用型号", "厂商,设备型号";(降级兼容)。
  • 匹配优先级: 内核优先匹配列表中的第一个字符串,失败后依次尝试后续字符串。
  • 命名规范: 厂商名称为小写字母,设备型号为小写字母和数字,用逗号分隔。
驱动匹配示例:
// DTS 节点
uart0: serial@10000000 {
    compatible = "vendor,uart-v2", "vendor,uart";
    reg = <0x10000000 0x1000>;
    interrupts = <0 33 4>;
};

// 驱动代码 (of_match_table)
static const struct of_device_id uart_of_match[] = {
    { .compatible = "vendor,uart-v2", .data = &uart_v2_data },
    { .compatible = "vendor,uart",    .data = &uart_v1_data },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uart_of_match);

当内核解析到 uart0 节点时,会先尝试匹配 vendor,uart-v2,若驱动不支持则回退到 vendor,uart。这种机制保证了硬件升级时驱动向后兼容。

compatible 属性匹配流程 DTS 节点 compatible = "vendor,uart-v2" compatible = "vendor,uart" 匹配失败 (无驱动) 匹配成功 → 调用 probe() 函数,绑定驱动

2.3.2 reg 属性:描述设备地址空间

reg 属性用于描述设备在总线上的地址范围,包括起始地址和长度。它是驱动获取 MMIO 地址、DMA 区域等资源的关键。

  • 格式: reg = <地址1 长度1 地址2 长度2 ...>;
  • 地址单元: 由父节点的 #address-cells 指定,通常为 1(32位)或 2(64位)。
  • 长度单元: 由父节点的 #size-cells 指定,通常为 1(32位)或 2(64位)。
  • 多段地址: 一个设备可能拥有多个不连续的地址空间,如 FIFO 区域和配置寄存器区域。
reg 属性示例:
// 32位地址空间
soc {
    #address-cells = <1>;
    #size-cells = <1>;

    ethernet@20000000 {
        reg = <0x20000000 0x10000>;  // 起始 0x20000000,长度 0x10000
    };

    dma@30000000 {
        reg = <0x30000000 0x1000     // 配置寄存器
               0x40000000 0x2000>;   // DMA 描述符区域
    };
};

// 64位地址空间
bus@0 {
    #address-cells = <2>;
    #size-cells = <2>;

    memory@0 {
        reg = <0x00000000 0x80000000   // 高32位 低32位
               0x00000000 0x40000000>; // 长度 1GB
    };
};

驱动中通过 platform_get_resource(pdev, IORESOURCE_MEM, index) 获取 reg 属性定义的地址资源,index 对应多段地址的序号(从0开始)。

2.3.3 interrupts 属性:连接中断控制器

interrupts 属性描述设备的中断请求线,包括中断号、触发类型等信息。它必须与中断控制器节点配合使用。

  • 格式: interrupts = <中断控制器域 中断号 触发类型>;
  • 中断控制器域: 通常为 0(GIC 中断)、1(PPI 中断)等,由中断控制器定义。
  • 中断号: 在中断控制器域内的编号。
  • 触发类型: 如 0(上升沿)、1(下降沿)、4(高电平)、8(低电平)等。
  • 父中断控制器: 通过 interrupt-parent 属性指定,若未指定则继承父节点。
中断属性示例:
// 中断控制器定义
gic: interrupt-controller@f0001000 {
    compatible = "arm,cortex-a9-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0xf0001000 0x1000>,
          <0xf0002000 0x100>;
};

// 使用中断的设备
uart0: serial@10000000 {
    compatible = "vendor,uart";
    reg = <0x10000000 0x1000>;
    interrupt-parent = <&gic>;      // 指定父中断控制器
    interrupts = <0 33 4>;          // 域0, 中断号33, 高电平触发
};

// 多个中断
dma@30000000 {
    compatible = "vendor,dma";
    reg = <0x30000000 0x1000>;
    interrupt-parent = <&gic>;
    interrupts = <0 40 4>,          // 传输完成中断
                 <0 41 4>;          // 错误中断
    interrupt-names = "tx_done", "error";  // 命名中断
};

驱动中通过 platform_get_irq(pdev, index) 获取中断号,index 对应 interrupts 列表中的序号。使用 interrupt-names 属性可以为中断命名,提高代码可读性。

中断控制器与设备连接关系 GIC 中断控制器 #interrupt-cells = <3> UART0: irq 33 DMA: irq 40, 41 Timer: irq 29 中断属性关键点 • interrupt-parent: 指定设备连接的中断控制器(默认继承父节点) • interrupts: 中断域、中断号、触发类型(由中断控制器的 #interrupt-cells 决定)

2.4 标准属性对比总结

属性 作用 驱动获取方式 典型值示例
compatible 驱动匹配 of_match_table "vendor,device-v2", "vendor,device"
reg 地址空间描述 platform_get_resource(pdev, IORESOURCE_MEM, index) <0x10000000 0x1000>
interrupts 中断请求描述 platform_get_irq(pdev, index) <0 33 4>
实践建议: 在编写 DTS 文件时,始终遵循“一个节点一个设备”的原则。对于复杂设备,使用 reg 描述所有地址区域,使用 interrupts 描述所有中断线,并使用 interrupt-names 和 reg-names 提高可读性。这不仅能减少调试时间,还能让驱动代码更加健壮。