3. 设备树常用属性详解:compatible、reg、interrupts、clocks、gpios、status 等核心属性的含义与用法

设备树(Device Tree)中的属性是描述硬件配置与连接关系的基本单元。每个节点通过一组标准属性向内核驱动传递硬件特征信息。本章将逐一剖析最常用的六大核心属性,包括其语法规范、绑定约定及驱动接口中的读取方式。

3.1 compatible 属性:设备与驱动的匹配钥匙

compatible 是设备树中最关键的属性,它建立了硬件设备与内核驱动之间的绑定关系。该属性是一个字符串列表,用于标识设备的具体型号和兼容的驱动。

3.1.1 语法与规范

compatible = "manufacturer,model", "generic,driver";
  • 第一优先:精确的厂商与型号,如 "ti,am335x-uart"。
  • 后续备选:更通用的兼容驱动,如 "ns16550a"。
  • 内核匹配时按列表顺序查找驱动,找到即停止。

3.1.2 驱动匹配示例

/* 设备树节点 */
uart0: serial@44e09000 {
    compatible = "ti,am335x-uart", "ns16550a";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
};

/* 驱动端匹配表 */
static const struct of_device_id my_uart_of_match[] = {
    { .compatible = "ti,am335x-uart", .data = &am335x_data },
    { .compatible = "ns16550a",       .data = &ns16550_data },
    { /* end */ }
};
MODULE_DEVICE_TABLE(of, my_uart_of_match);
调试技巧:使用 cat /sys/firmware/devicetree/base/.../compatible 可查看实际生效的兼容字符串列表。驱动 probe 失败时,优先检查 compatible 字符串是否完全匹配(包括大小写和标点)。

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

reg 属性用于描述设备在总线上的地址范围,通常包含起始地址和长度。其格式由父节点的 #address-cells 和 #size-cells 决定。

3.2.1 地址单元与大小单元

属性 含义 典型值
#address-cells 地址字段占用的 32 位单元数 1(32位地址)或 2(64位地址)
#size-cells 长度字段占用的 32 位单元数 1(32位长度)或 0(无长度)

3.2.2 典型用法

/* 32位地址空间 */
memory@80000000 {
    device_type = "memory";
    reg = <0x80000000 0x20000000>;  /* 起始0x80000000,长度512MB */
};

/* 带片选的外设 */
spi@e0006000 {
    compatible = "xlnx,zynq-spi";
    reg = <0xe0006000 0x1000>;      /* 寄存器基址+范围 */
    interrupts = <0 26 4>;
};

3.2.3 驱动读取接口

struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
    return -ENODEV;

base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
    return PTR_ERR(base);

3.3 interrupts 属性:中断连接描述

interrupts 属性描述设备产生的中断信号,需要配合 interrupt-parent 和中断控制器节点使用。

3.3.1 中断属性结构

interrupts = <中断类型 中断号 触发方式>;
interrupt-parent = <&gic>;  /* 引用中断控制器 */
字段 说明 常见值
中断类型 SPI(共享外设中断)或 PPI(私有外设中断) 0 (SPI), 1 (PPI)
中断号 中断控制器内部的编号 0~987
触发方式 电平或边沿触发 1 (上升沿), 4 (高电平)

3.3.2 完整示例

gic: interrupt-controller@f8f01000 {
    compatible = "arm,cortex-a9-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0xf8f01000 0x1000>;
};

uart1: serial@e0001000 {
    compatible = "xlnx,xuartps";
    reg = <0xe0001000 0x1000>;
    interrupts = <0 50 4>;   /* SPI, 中断号50, 高电平触发 */
    interrupt-parent = <&gic>;
};
常见错误:中断号在设备树中通常从 0 开始编号,但某些架构(如 ARM GIC)的 SPI 中断号在驱动中会加上 32 的偏移。调试时使用 cat /proc/interrupts 对比实际注册的中断号。

3.4 clocks 属性:时钟源绑定

clocks 属性将设备连接到时钟框架中的时钟源,配合 clock-names 实现多时钟管理。

3.4.1 标准格式

clocks = <&clkc 15>, <&clkc 16>;
clock-names = "uart_clk", "pclk";
  • clocks:引用时钟控制器节点及输出索引。
  • clock-names:为每个时钟指定名称,驱动通过名称获取句柄。

3.4.2 驱动获取时钟

struct clk *clk_uart, *clk_p;

clk_uart = devm_clk_get(&pdev->dev, "uart_clk");
if (IS_ERR(clk_uart))
    return PTR_ERR(clk_uart);

clk_prepare_enable(clk_uart);

3.5 gpios 属性:GPIO 引脚描述

gpios 属性用于描述设备使用的 GPIO 引脚,包括 GPIO 控制器引用、引脚号和标志位。

3.5.1 属性格式

gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
/* 或使用命名方式 */
reset-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
enable-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>;
字段 说明
&gpio0 GPIO 控制器 phandle
12 控制器内部的引脚编号
GPIO_ACTIVE_LOW 标志位:低电平有效(值为 1)

3.5.2 驱动使用示例

struct gpio_desc *reset_gpio;

reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(reset_gpio))
    return PTR_ERR(reset_gpio);

gpiod_set_value(reset_gpio, 0);  /* 拉低复位 */
msleep(10);
gpiod_set_value(reset_gpio, 1);  /* 释放复位 */

3.6 status 属性:设备启用控制

status 属性控制设备节点的启用状态,是板级配置中常用的开关属性。

3.6.1 可选值

值 含义 说明
"okay" 设备启用 驱动正常 probe
"disabled" 设备禁用 驱动不会 probe
"reserved" 保留状态 特殊用途,通常不用于普通外设
"fail" 设备故障 硬件检测失败时使用

3.6.2 典型应用场景

/* SoC dtsi 中默认禁用 */
uart2: serial@e0002000 {
    compatible = "xlnx,xuartps";
    reg = <0xe0002000 0x1000>;
    interrupts = <0 51 4>;
    status = "disabled";
};

/* 板级 dts 中启用 */
&uart2 {
    status = "okay";
    clocks = <&clkc 17>, <&clkc 18>;
    clock-names = "uart_clk", "pclk";
};
调试要点:status = "disabled" 的设备不会出现在 /sys/devices/platform/ 中。如果驱动未加载,首先检查设备树中 status 是否为 "okay",以及是否存在覆盖(override)冲突。

3.7 核心属性关系总览

设备树核心属性与驱动接口关系图 设备树节点 compatible reg interrupts clocks gpios status 解析 内核 OF API of_match_device / of_property_read_* 调用 设备驱动 probe / 操作函数 of_match_device platform_get_resource platform_get_irq devm_clk_get devm_gpiod_get of_device_is_available 调试要点 • 使用 dtc -I dtb -O dts 反编译 DTB 验证属性值 • 检查 /sys/firmware/devicetree/base/ 下的属性文件内容

3.8 属性调试速查表

属性 驱动读取接口 调试文件路径 常见问题
compatible of_match_device() /sys/firmware/devicetree/base/.../compatible 字符串拼写错误、大小写不匹配
reg platform_get_resource() /proc/iomem 地址重叠、#address-cells 配置错误
interrupts platform_get_irq() /proc/interrupts 中断号偏移、interrupt-parent 缺失
clocks devm_clk_get() /sys/kernel/debug/clk/clk_summary 时钟名不匹配、时钟未使能
gpios devm_gpiod_get() /sys/kernel/debug/gpio 引脚号错误、标志位配置反相
status of_device_is_available() /sys/devices/platform/ 目录存在性 覆盖冲突、拼写非 "okay"

3.9 综合示例:完整设备节点与驱动匹配

/* 设备树节点 */
ethernet@e000b000 {
    compatible = "xlnx,zynq-gem", "cdns,gem";
    reg = <0xe000b000 0x1000>;
    interrupts = <0 56 4>;
    interrupt-parent = <&gic>;
    clocks = <&clkc 30>, <&clkc 31>, <&clkc 29>;
    clock-names = "pclk", "hclk", "tx_clk";
    phy-mode = "rgmii-id";
    status = "okay";

    phy-handle = <&phy0>;
    mdio {
        #address-cells = <1>;
        #size-cells = <0>;
        phy0: ethernet-phy@7 {
            reg = <7>;
            reset-gpios = <&gpio0 89 GPIO_ACTIVE_LOW>;
        };
    };
};

/* 驱动 probe 函数片段 */
static int gem_probe(struct platform_device *pdev)
{
    struct resource *res;
    struct clk *pclk, *hclk;
    int irq;

    /* 匹配 compatible */
    if (!of_match_node(gem_of_match, pdev->dev.of_node))
        return -ENODEV;

    /* 获取 reg 地址 */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENXIO;

    /* 获取中断 */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    /* 获取时钟 */
    pclk = devm_clk_get(&pdev->dev, "pclk");
    hclk = devm_clk_get(&pdev->dev, "hclk");
    if (IS_ERR(pclk) || IS_ERR(hclk))
        return -EPROBE_DEFER;

    /* 检查 status */
    if (!of_device_is_available(pdev->dev.of_node))
        return -ENODEV;

    /* 获取 PHY 复位 GPIO */
    phy_reset = devm_gpiod_get_optional(&pdev->dev, "reset", GPIOD_OUT_HIGH);

    /* 继续初始化... */
    return 0;
}
最佳实践:编写驱动时,始终使用 devm_* 系列接口获取资源,它们会在驱动卸载时自动释放。对于可选属性(如某些 GPIO),使用 _optional 变体避免强制要求。