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

设备树(Device Tree)中的属性(Property)是描述硬件配置与连接关系的关键载体。每个节点通过一组标准属性向内核驱动程序传递硬件资源信息。本章将深入解析最常用的六个标准属性,包括其语法规则、内核解析机制及典型应用场景。

3.1 属性概览与分类

设备树属性按功能可分为三大类:设备标识类、资源描述类和状态控制类。下表列出了本章重点讲解的属性及其所属类别:

属性名 类别 功能简述 必需性
compatible 设备标识 指定设备兼容的驱动名称列表 必需
reg 资源描述 定义设备地址空间与寄存器范围 视总线类型而定
interrupts 资源描述 描述设备使用的中断号与触发类型 中断设备必需
status 状态控制 控制设备节点的启用或禁用 可选
clocks 资源描述 关联设备使用的时钟源与时钟索引 有时钟依赖时必需
gpios 资源描述 声明设备连接的 GPIO 引脚及用途 使用 GPIO 时必需
提示:在编写设备树时,compatible 是唯一一个所有节点都必须包含的属性。缺少该属性将导致内核无法为设备匹配对应的驱动程序。

3.2 compatible 属性:驱动匹配的核心

compatible 属性是一个字符串列表,用于将设备节点与内核驱动程序绑定。其格式为 "厂商,设备型号",内核通过设备树匹配表(OF Match Table)进行查找。

// 设备树节点示例
uart0: serial@10000000 {
    compatible = "fsl,imx6q-uart", "ns16550";
    reg = <0x10000000 0x1000>;
    interrupts = <0 31 4>;
};

内核驱动中的匹配表定义:

static const struct of_device_id imx_uart_dt_ids[] = {
    { .compatible = "fsl,imx6q-uart", .data = &imx6q_uart_data },
    { .compatible = "ns16550",        .data = &ns16550_data },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_uart_dt_ids);

匹配优先级规则:

  • 内核按列表顺序依次尝试匹配,第一个匹配成功的驱动被使用。
  • 通常将最具体的兼容字符串放在最前面,通用兼容字符串放在后面。
  • 驱动可以通过 of_match_device() 获取匹配到的 of_device_id 中的私有数据。
常见错误:compatible 字符串中的逗号前后不能有空格,例如 "fsl, imx6q-uart" 是错误的写法,会导致匹配失败。

3.3 reg 属性:地址空间描述

reg 属性用于描述设备在总线上的地址范围,由一组或多组 (地址,长度) 对组成。其格式由父节点的 #address-cells 和 #size-cells 决定。

父节点属性 含义 常见值
#address-cells 地址字段占用的 32 位单元数 1(32位地址)或 2(64位地址)
#size-cells 长度字段占用的 32 位单元数 1(32位长度)或 0(无长度)
// 32位地址空间示例
soc {
    #address-cells = <1>;
    #size-cells = <1>;

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

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

    reg = <0x00 0x40000000 0x00 0x10000000>;  // 高32位地址,低32位地址,高32位长度,低32位长度
};

内核通过 of_iomap() 或 platform_get_resource() 获取 reg 属性中的地址信息:

struct resource *res;
void __iomem *base;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);

3.4 interrupts 属性:中断资源绑定

interrupts 属性描述设备使用的中断资源,其格式依赖于 中断控制器 的 #interrupt-cells 属性。常见的中断类型包括 SPI(共享外设中断)和 PPI(私有外设中断)。

// GIC(通用中断控制器)中断描述示例
gic: interrupt-controller@00a00000 {
    compatible = "arm,cortex-a9-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
};

device@30000000 {
    interrupts = <0 45 4>;  // 0=SPI, 45=中断号, 4=高电平触发
};

中断触发类型编码:

编码值 触发类型 说明
0 IRQ_TYPE_NONE 未指定
1 IRQ_TYPE_EDGE_RISING 上升沿触发
2 IRQ_TYPE_EDGE_FALLING 下降沿触发
3 IRQ_TYPE_EDGE_BOTH 双边沿触发
4 IRQ_TYPE_LEVEL_HIGH 高电平触发
8 IRQ_TYPE_LEVEL_LOW 低电平触发
调试要点:使用 cat /proc/interrupts 可以查看系统中所有中断的分配情况。如果设备树中中断号配置错误,驱动将无法注册中断处理函数。

3.5 status 属性:设备状态控制

status 属性用于控制设备节点的启用或禁用状态,在板级设备树中经常用于覆盖 SoC 级设备树的默认配置。

状态值 含义 使用场景
"okay" 设备启用 明确启用某个外设
"disabled" 设备禁用 默认关闭,板级文件按需开启
"reserved" 保留状态 由固件或安全世界使用
"fail" 设备故障 检测到硬件错误时标记
// SoC 级设备树:默认禁用
i2c1: i2c@02180000 {
    compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
    reg = <0x02180000 0x4000>;
    status = "disabled";
};

// 板级设备树:启用并配置
&i2c1 {
    status = "okay";
    clock-frequency = <100000>;
    pinctrl-0 = <&pinctrl_i2c1>;
};

内核在遍历设备树时,会自动跳过 status 为 "disabled" 的节点,不会为其创建 platform 设备。

3.6 clocks 属性:时钟依赖管理

clocks 属性通过 phandle + 时钟索引 的方式,将设备与其所需的时钟源关联起来。配合 clock-names 属性,驱动可以通过名称而非索引来获取时钟。

// 时钟控制器定义
clks: clock-controller@020c4000 {
    compatible = "fsl,imx6q-ccm";
    #clock-cells = <1>;
};

// 使用时钟的设备节点
uart1: serial@02020000 {
    compatible = "fsl,imx6q-uart";
    reg = <0x02020000 0x4000>;
    interrupts = <0 26 4>;
    clocks = <&clks IMX6Q_CLK_UART_IPG>,
             <&clks IMX6Q_CLK_UART_SERIAL>;
    clock-names = "ipg", "per";
};

驱动中获取时钟的典型代码:

struct clk *ipg_clk, *per_clk;

ipg_clk = devm_clk_get(&pdev->dev, "ipg");
per_clk = devm_clk_get(&pdev->dev, "per");

clk_prepare_enable(ipg_clk);
clk_prepare_enable(per_clk);
最佳实践:始终使用 clock-names 属性为每个时钟指定有意义的名称,这可以避免因时钟索引顺序变化导致的驱动兼容性问题。

3.7 gpios 属性:GPIO 引脚声明

gpios 属性用于声明设备使用的 GPIO 引脚,格式为 phandle + GPIO 编号 + 标志位。与时钟类似,gpio-names 属性可以为每个 GPIO 提供语义化名称。

// GPIO 控制器定义
gpio1: gpio@0209c000 {
    compatible = "fsl,imx6q-gpio";
    gpio-controller;
    #gpio-cells = <2>;
};

// 使用 GPIO 的设备节点
leds {
    compatible = "gpio-leds";
    led-red {
        label = "red";
        gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
        linux,default-trigger = "heartbeat";
    };
};

GPIO 标志位说明:

标志位宏 值 含义
GPIO_ACTIVE_HIGH 0 高电平有效
GPIO_ACTIVE_LOW 1 低电平有效
GPIO_OPEN_DRAIN 2 开漏输出
GPIO_OPEN_SOURCE 4 开源输出

驱动中获取 GPIO 的接口:

int gpio_pin;
enum of_gpio_flags flags;

gpio_pin = of_get_named_gpio_flags(np, "gpios", 0, &flags);
if (gpio_pin < 0) {
    dev_err(dev, "Failed to get GPIO\n");
    return gpio_pin;
}

gpio_direction_output(gpio_pin, !(flags & OF_GPIO_ACTIVE_LOW));

3.8 属性关联关系与数据流

下图展示了设备树属性与内核驱动之间的数据流动关系:

设备树属性 → 内核驱动数据流 设备树节点 compatible = "..." reg = <...> interrupts = <...> clocks = <...> OF 核心层 of_match_device() of_iomap() of_irq_parse() of_clk_get() 设备驱动 probe() 回调 platform_get_resource() devm_clk_get() devm_gpiod_get() 属性解析流程说明 1. 内核启动时,OF 核心层解析 DTB 并建立设备节点树。 2. 总线注册时,内核遍历设备节点,通过 compatible 匹配驱动。 3. 驱动 probe() 函数调用 OF 接口获取 reg/interrupts/clocks/gpios 等资源。 4. 资源获取成功后,驱动完成硬件初始化并注册到内核框架。 5. 若 status 为 "disabled",内核跳过该节点,不执行 probe()。

3.9 综合调试案例:UART 设备树属性检查

以下是一个完整的 UART 设备树节点及其对应的驱动调试检查清单:

// 完整 UART 设备树节点
uart2: serial@021e8000 {
    compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x021e8000 0x4000>;
    interrupts = <0 27 4>;
    clocks = <&clks IMX6Q_CLK_UART_IPG>,
             <&clks IMX6Q_CLK_UART_SERIAL>;
    clock-names = "ipg", "per";
    dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;
    dma-names = "rx", "tx";
    status = "okay";
};

调试检查清单:

  1. compatible 匹配检查:使用 cat /sys/devices/platform/serial@021e8000/of_node/compatible 确认字符串与驱动匹配表一致。
  2. 地址范围检查:通过 cat /proc/iomem | grep 021e8000 确认寄存器地址已正确映射。
  3. 中断分配检查:使用 cat /proc/interrupts | grep uart 查看中断号是否与设备树一致。
  4. 时钟状态检查:通过 clk_summary 调试文件(需开启 CONFIG_DEBUG_FS)查看 UART 时钟是否已启用。
  5. 状态确认:检查 /sys/devices/platform/ 下是否存在对应的设备目录,若不存在则可能是 status 为 "disabled"。
工程经验:在调试新板级设备树时,建议先使用 dt-validate 工具对 DTS 文件进行语法校验,再通过 fdtdump 查看编译后的 DTB 内容,确保属性值正确无误。