3. 设备树常用属性:compatible、reg、interrupts、status、gpios 等属性详解

设备树(Device Tree)是描述硬件拓扑结构的配置文件,它通过一系列标准属性将非可枚举的硬件信息传递给内核。对于 Linux Platform 驱动开发者而言,掌握设备树常用属性的含义、语法和匹配规则,是编写可移植、可维护驱动的核心基础。本章将逐一剖析 compatible、reg、interrupts、status、gpios 等关键属性,并结合实际案例与 SVG 结构图进行讲解。

学习提示: 设备树属性是驱动与硬件之间的“契约”。理解每个属性的数据类型、单元格式和内核解析逻辑,能帮助你在调试设备树时快速定位问题,避免因属性配置错误导致驱动加载失败。

3.1 compatible 属性:驱动匹配的核心标识

compatible 属性是设备树中最关键的属性,它定义了设备与驱动之间的匹配关系。内核通过该属性的字符串值,在设备树节点与驱动列表之间建立连接。

3.1.1 属性格式与匹配规则

compatible 属性是一个字符串列表,通常遵循 "厂商,设备型号" 的命名规范。内核在匹配时,会按照列表中的顺序依次尝试,直到找到匹配的驱动。

// 设备树节点示例
i2c@1c00000 {
    compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
    reg = <0x01c00000 0x4000>;
    interrupts = <0 38 4>;
    status = "okay";
};

在上述示例中,内核会先尝试匹配 "fsl,imx6q-i2c",如果失败,则尝试 "fsl,imx21-i2c"。这种“最具体优先”的匹配策略,使得同一驱动可以兼容多个硬件版本。

3.1.2 驱动侧匹配方式

在 Platform 驱动中,通过 of_match_table 指定支持的设备列表:

static const struct of_device_id my_i2c_of_match[] = {
    { .compatible = "fsl,imx6q-i2c", .data = &imx6q_i2c_data },
    { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_data },
    { /* sentinel */ }
};

MODULE_DEVICE_TABLE(of, my_i2c_of_match);

static struct platform_driver my_i2c_driver = {
    .probe  = my_i2c_probe,
    .remove = my_i2c_remove,
    .driver = {
        .name = "my_i2c",
        .of_match_table = my_i2c_of_match,
    },
};
最佳实践: 编写 compatible 字符串时,应遵循“厂商前缀 + 逗号 + 设备型号”的格式。厂商前缀需在 Documentation/devicetree/bindings/vendor-prefixes.yaml 中注册,避免与其他厂商冲突。
compatible 属性匹配流程 设备树节点 compatible = "A", "B" 内核匹配层 遍历 of_match_table 驱动列表 匹配成功 → probe 匹配顺序详解 1. 内核读取设备树节点 compatible 属性,获取字符串列表 ["A", "B"] 2. 从列表第一个字符串 "A" 开始,遍历驱动的 of_match_table 3. 如果找到匹配项,调用 probe 函数,停止后续匹配 4. 如果 "A" 未匹配,继续尝试 "B",直到列表末尾或匹配成功

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

reg 属性用于描述设备在总线上的地址范围,包括起始地址和长度。它是内核为设备分配 I/O 内存、寄存器映射的基础。

3.2.1 属性格式与地址单元

reg 属性的格式由父节点的 #address-cells 和 #size-cells 决定:

// 32位地址系统
#address-cells = <1>;
#size-cells = <1>;
reg = <0x01c00000 0x4000>;  // 起始地址 0x01c00000,长度 0x4000

// 64位地址系统
#address-cells = <2>;
#size-cells = <2>;
reg = <0x00000000 0x01c00000 0x00000000 0x00004000>;  // 高32位+低32位地址,高32位+低32位长度

3.2.2 多段地址空间

一个设备可能拥有多个不连续的地址段,reg 属性可以包含多组地址-长度对:

// 两个地址段:控制寄存器区和数据缓冲区
reg = <0x01c00000 0x1000>,  // 控制寄存器:0x01c00000 ~ 0x01c00fff
      <0x01c01000 0x2000>;  // 数据缓冲区:0x01c01000 ~ 0x01c02fff
参数 说明 示例值
#address-cells 地址字段占用的 32 位单元数 1(32位地址)或 2(64位地址)
#size-cells 长度字段占用的 32 位单元数 1(32位长度)或 2(64位长度)
reg 地址-长度对列表,每对包含 (address-cells + size-cells) 个单元 <0x1000 0x200>
常见错误: 如果父节点的 #address-cells 和 #size-cells 设置错误,内核将无法正确解析 reg 属性,导致设备资源申请失败。务必检查总线控制器节点的地址单元配置。

3.3 interrupts 属性:中断资源描述

interrupts 属性用于描述设备使用的中断资源,包括中断号、触发类型和优先级等信息。它是设备与中断控制器之间的桥梁。

3.3.1 属性格式与中断单元

interrupts 属性的格式由父节点(中断控制器)的 #interrupt-cells 决定:

// 中断控制器定义
intc: interrupt-controller@00a01000 {
    compatible = "arm,cortex-a9-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x00a01000 0x1000>;
};

// 设备节点使用中断
device@1c00000 {
    compatible = "my,device";
    reg = <0x01c00000 0x4000>;
    interrupts = <0 38 4>;  // GIC SPI 中断号 38,高电平触发
};

3.3.2 中断类型与触发方式

对于 ARM GIC(Generic Interrupt Controller),#interrupt-cells = <3> 的格式为:

字段 说明 取值
中断类型 0 = SPI(共享外设中断),1 = PPI(私有外设中断) 0 或 1
中断号 SPI 中断号(32~1019),PPI 中断号(16~31) 数值
触发方式 1 = 上升沿触发,2 = 下降沿触发,4 = 高电平触发,8 = 低电平触发 1, 2, 4, 8
// 常见中断配置示例
interrupts = <0 38 4>;   // SPI 中断号38,高电平触发
interrupts = <0 42 1>;   // SPI 中断号42,上升沿触发
interrupts = <1 14 8>;   // PPI 中断号14,低电平触发
中断资源映射关系 设备节点 interrupts = <0 38 4> 中断控制器 (GIC) #interrupt-cells = <3> CPU 内核 中断处理函数 中断属性解析流程 1. 内核读取设备节点 interrupts 属性,获取中断描述符 2. 根据中断控制器的 #interrupt-cells 解析每个中断单元 3. 通过 irq_domain 映射,将设备中断号转换为 Linux IRQ 号

3.4 status 属性:设备状态控制

status 属性用于控制设备节点的启用状态,允许在不修改硬件描述的情况下,通过设备树动态启用或禁用设备。

3.4.1 属性取值与含义

取值 含义 内核行为
"okay" 设备启用 内核正常创建设备并调用 probe
"disabled" 设备禁用 内核跳过该设备,不创建 platform_device
"reserved" 设备保留 内核不创建设备,但保留资源供其他用途
"fail" 设备故障 内核标记设备为故障状态,不加载驱动

3.4.2 实际应用场景

// 在 SoC 级设备树中默认禁用某些外设
&uart1 {
    status = "disabled";
};

&uart2 {
    status = "disabled";
};

// 在板级设备树中按需启用
&uart1 {
    status = "okay";
    pinctrl-0 = <&pinctrl_uart1>;
    pinctrl-names = "default";
};
使用技巧: 在调试阶段,可以通过修改 status 属性快速启用或禁用设备,避免重新编译内核。对于未使用的硬件模块,建议在板级设备树中设置为 "disabled",以节省系统资源。

3.5 gpios 属性:GPIO 资源描述

gpios 属性用于描述设备使用的 GPIO 引脚,包括 GPIO 控制器引用、引脚号和标志位。它是设备与 GPIO 子系统之间的接口。

3.5.1 属性格式与 GPIO 说明符

gpios 属性是一个 phandle 加 GPIO 说明符的列表,格式由 GPIO 控制器的 #gpio-cells 决定:

// GPIO 控制器定义
gpio0: gpio@02000000 {
    compatible = "fsl,imx6q-gpio";
    reg = <0x02000000 0x4000>;
    gpio-controller;
    #gpio-cells = <2>;  // 引脚号 + 标志位
};

// 设备使用 GPIO
leds {
    compatible = "gpio-leds";
    led-red {
        gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;  // GPIO0_3,低电平有效
        label = "red-led";
    };
    led-green {
        gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; // GPIO0_4,高电平有效
        label = "green-led";
    };
};

3.5.2 GPIO 标志位定义

标志位 宏定义 说明
0 GPIO_ACTIVE_HIGH 高电平有效(默认)
1 GPIO_ACTIVE_LOW 低电平有效
2 GPIO_OPEN_DRAIN 开漏输出
4 GPIO_OPEN_SOURCE 开源输出

3.5.3 驱动侧获取 GPIO

// 在驱动 probe 函数中获取 GPIO
static int my_led_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct gpio_desc *led_gpio;
    
    // 通过设备树获取 GPIO
    led_gpio = devm_gpiod_get(dev, "led", GPIOD_OUT_LOW);
    if (IS_ERR(led_gpio)) {
        dev_err(dev, "Failed to get LED GPIO\n");
        return PTR_ERR(led_gpio);
    }
    
    // 使用 GPIO
    gpiod_set_value(led_gpio, 1);
    
    return 0;
}
GPIO 资源映射与使用流程 设备树节点 gpios = <&gpio0 3 0> GPIO 控制器 #gpio-cells = <2> GPIO 子系统 gpiod_get / gpiod_set GPIO 属性解析步骤 1. 内核解析 gpios 属性,获取 phandle 指向的 GPIO 控制器 2. 根据 #gpio-cells 解析引脚号和标志位(如 3 和 0) 3. 驱动通过 devm_gpiod_get() 获取 gpio_desc,操作 GPIO 4. 标志位决定 GPIO 的有效电平(ACTIVE_HIGH / ACTIVE_LOW)

3.6 其他常用属性

除了上述核心属性外,设备树还定义了多个常用属性,用于描述设备的其他硬件特性:

属性名 用途 示例
clocks 指定设备使用的时钟源 clocks = <&clks IMX6QDL_CLK_UART1>;
resets 指定设备使用的复位信号 resets = <&rst 10>;
dmas 指定设备使用的 DMA 通道 dmas = <&sdma 2>;
pinctrl-0 指定设备的引脚复用配置 pinctrl-0 = <&pinctrl_uart1>;
power-domains 指定设备所属的电源域 power-domains = <&pd_core>;
关键总结: 设备树属性是驱动与硬件之间的“数据契约”。compatible 决定驱动匹配,reg 定义地址空间,interrupts 描述中断资源,status 控制设备状态,gpios 提供 GPIO 接口。掌握这些属性的语法和内核解析逻辑,是编写高质量 Platform 驱动的基础。