2. 设备树基础:语法、节点、属性与核心概念详解

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它把原本硬编码在 Linux 内核中的板级细节剥离出来,变成一份独立于内核的文本描述文件(.dts)。内核在启动时解析设备树,动态创建 platform_device、注册中断、映射内存、配置 GPIO,从而实现“一套内核二进制,适配多种硬件变体”。

本章从设备树的语法结构出发,逐一拆解节点、属性、reg、compatible、interrupts 等核心概念,并为每个概念提供内联 SVG 结构图,帮助你在后续 GPIO 控制器节点配置中建立扎实的认知基础。

2.1 设备树语法与节点层次

设备树采用树形结构,根节点是“/”,所有硬件设备都挂载在根节点下。每个节点代表一个设备或总线,节点可以嵌套,形成父子关系。节点名称通常为 device-name@unit-address,其中 unit-address 对应设备在总线上的起始地址。

语法要点: 节点以花括号 { } 界定,属性以 key = value; 形式赋值,每行以分号结尾。注释支持 C 风格 /* */ 和 C++ 风格 //。
// 设备树最小示例
/dts-v1/;

/ {
    model = "Embedded GPIO Dev Board";
    compatible = "vendor,gpio-board";

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

        gpio0: gpio@ff710000 {
            compatible = "vendor,gpio-controller";
            reg = <0xff710000 0x1000>;
            gpio-controller;
            #gpio-cells = <2>;
            interrupts = <0 32 4>;
        };
    };
};
节点标签(label): 上例中的 gpio0: 就是标签,可以在其他节点中通过 &gpio0 引用该节点,实现跨节点属性覆盖或 phandle 引用。
设备树节点层次结构 / (根节点) soc gpio0: gpio@ff710000 节点内部属性 compatible = "vendor,gpio-controller"; reg = <0xff710000 0x1000>; interrupts = <0 32 4>; gpio-controller; #gpio-cells = <2>;

2.2 核心属性详解

设备树属性是描述设备特征的关键字段。以下表格列出 GPIO 控制器节点最常用的属性及其含义:

属性名 类型 说明 GPIO 控制器场景
compatible 字符串列表 设备驱动匹配的关键标识,格式为 "厂商,设备型号" 驱动通过 of_match_table 匹配该值
reg u32 数组 描述设备地址空间,格式为 <address size> 指定 GPIO 控制器的寄存器基地址和映射长度
interrupts u32 数组 描述设备的中断资源,格式由 interrupt-parent 决定 GPIO 控制器作为中断控制器时,描述其输出中断
gpio-controller 空属性 标记该节点是一个 GPIO 控制器 必须存在,否则内核不识别其为 GPIO 控制器
#gpio-cells u32 指定 GPIO 描述符需要的 cell 数量 通常为 2:<gpio_phandle gpio_num flags>
interrupt-parent phandle 指定该设备的中断控制器父节点 GPIO 控制器通常连接到 GIC
常见错误: 忘记添加 gpio-controller; 属性,或者 #gpio-cells 的值与驱动期望不符,会导致 GPIO 申请失败或系统崩溃。务必检查设备树绑定文档。

2.3 compatible 属性与驱动匹配机制

compatible 是设备树中最关键的属性之一。内核在启动时遍历设备树节点,将节点的 compatible 值与驱动注册的 of_match_table 进行比对。匹配成功后,内核调用驱动的 probe 函数。

匹配优先级:设备树中的 compatible 字符串列表按顺序排列,内核优先匹配列表中的第一个字符串。驱动可以同时支持多个兼容型号。

// 设备树节点
gpio0: gpio@ff710000 {
    compatible = "vendor,gpio-v2", "vendor,gpio-v1";
    // 内核优先尝试匹配 "vendor,gpio-v2"
};

// 驱动匹配表
static const struct of_device_id gpio_of_match[] = {
    { .compatible = "vendor,gpio-v2", .data = &gpio_v2_data },
    { .compatible = "vendor,gpio-v1", .data = &gpio_v1_data },
    { /* end */ }
};
MODULE_DEVICE_TABLE(of, gpio_of_match);
compatible 属性与驱动匹配流程 设备树节点 compatible = "vendor,gpio-v2" 内核匹配层 of_match_table 遍历 probe() 驱动加载 匹配失败 → 跳过该节点 💡 匹配顺序:设备树 compatible 列表第一个字符串优先匹配驱动表中的第一个条目。 如果驱动表中没有匹配项,内核继续尝试下一个 compatible 字符串。

2.4 reg 属性与地址编码

reg 属性描述设备在总线上的地址空间。其格式由父节点的 #address-cells 和 #size-cells 决定:

  • #address-cells:表示地址字段占用的 u32 数量,通常为 1(32 位地址)或 2(64 位地址)。
  • #size-cells:表示长度字段占用的 u32 数量,通常为 1(32 位长度)或 0(无长度,如纯寄存器地址)。
// 32 位地址空间示例
soc {
    #address-cells = <1>;
    #size-cells = <1>;

    gpio0: gpio@ff710000 {
        reg = <0xff710000 0x1000>;  // 基地址 0xff710000,长度 0x1000
    };
};

// 64 位地址空间示例
soc {
    #address-cells = <2>;
    #size-cells = <2>;

    gpio0: gpio@3000000000 {
        reg = <0x0 0x3000000000 0x0 0x1000>;  // 高32位 低32位 高32位 低32位
    };
};
驱动获取 reg 资源: 驱动中通过 platform_get_resource(pdev, IORESOURCE_MEM, 0) 获取 reg 属性对应的内存资源,然后使用 devm_ioremap_resource() 完成寄存器映射。

2.5 interrupts 属性与中断描述

interrupts 属性描述设备的中断资源。其格式由中断控制器(interrupt-parent)的 #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 = 低电平触发
// GPIO 控制器中断示例
gpio0: gpio@ff710000 {
    compatible = "vendor,gpio-controller";
    reg = <0xff710000 0x1000>;
    interrupt-parent = <&gic>;          // 连接到 GIC
    interrupts = <0 32 4>;              // SPI 类型,中断号 32,高电平触发
    gpio-controller;
    #gpio-cells = <2>;
};
中断号偏移: 设备树中的 SPI 中断号是硬件中断号减去 32 后的值。例如 GIC 硬件 SPI 中断号 64,在设备树中应写为 <0 32 4>(64 - 32 = 32)。不同架构可能有差异,务必参考 SoC 手册。
interrupts 属性与中断控制器关系 gpio@ff710000 interrupts = <0 32 4> interrupt-parent = <&gic> GIC (中断控制器) #interrupt-cells = <3> 中断路由与分发 CPU Core IRQ Handler 中断服务 interrupts 三个 cell 详解 Cell 0 (0): 中断类型 — 0=SPI(共享外设中断),1=PPI(私有外设中断) Cell 1 (32): 中断号 — SPI 中断号 = 硬件中断号 - 32 Cell 2 (4): 触发类型 — 1=上升沿,2=下降沿,4=高电平,8=低电平 💡 驱动通过 platform_get_irq(pdev, 0) 获取中断号,然后调用 request_irq() 注册中断处理函数。

2.6 节点引用与覆盖(&label 机制)

设备树支持通过标签引用节点,实现增量覆盖。这在多板级设备树中非常有用:基础 .dtsi 文件定义默认配置,具体板级 .dts 文件通过 &label 覆盖属性。

// 基础 .dtsi 文件
gpio0: gpio@ff710000 {
    compatible = "vendor,gpio-controller";
    reg = <0xff710000 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    status = "disabled";  // 默认禁用
};

// 板级 .dts 文件
&gpio0 {
    status = "okay";      // 覆盖为启用
    gpio-line-names = "LED_RED", "LED_GREEN", "BTN_USER";
};
工程实践: SoC 厂商提供 .dtsi 文件定义所有外设节点,板级厂商只需通过 #include 包含并覆盖需要的属性。GPIO 控制器的 gpio-line-names 属性通常只在板级文件中定义,用于给每个 GPIO 引脚命名,方便驱动和用户空间识别。

2.7 本章小结