4. 设备树常用属性详解:compatible、reg、interrupts、status、model 等核心属性

设备树中的属性(Property)是描述硬件节点特征的关键载体。每个节点通过一组属性向内核传递硬件配置信息,驱动则通过解析这些属性来初始化设备。本章将深入剖析最常用的五个核心属性:compatible、reg、interrupts、status 和 model,并结合实战案例说明其编写规范与驱动适配要点。

学习提示: 设备树属性是驱动与硬件之间的“契约”。掌握这些属性的含义和用法,是编写正确设备树和驱动的基础。建议在阅读时对照实际硬件手册进行理解。

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

compatible 属性是设备树中最关键的属性之一,它指定了设备与驱动之间的匹配关系。内核通过该属性查找对应的驱动程序。

4.1.1 属性格式与规范

compatible 属性是一个字符串列表,通常遵循 "厂商,设备型号" 的命名规范。列表中的第一个字符串是精确匹配,后续字符串是兼容的设备型号。

// 标准格式
compatible = "vendor,device", "vendor,device-family", "generic-driver";

// 实际示例:I2C控制器
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";

// 实际示例:串口设备
compatible = "ns16550a", "ns16550";
字段 说明 示例
厂商前缀 设备制造商的缩写,通常与内核中的 vendor 前缀一致 fsl (飞思卡尔)、ti (德州仪器)
设备型号 具体的芯片或模块型号 imx6q-i2c、omap4-uart
兼容列表 按优先级从高到低排列,驱动按顺序匹配 "fsl,imx6q-i2c", "fsl,imx21-i2c"

4.1.2 驱动匹配机制

Linux 内核的设备驱动模型通过 of_match_table 实现设备树与驱动的匹配。驱动编写者需要在驱动中声明一个 of_device_id 结构体数组,内核在设备树解析时会遍历该数组进行匹配。

// 驱动中的匹配表
static const struct of_device_id imx_i2c_of_match[] = {
    { .compatible = "fsl,imx6q-i2c", .data = &imx6q_i2c_hw },
    { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hw },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_i2c_of_match);

// 平台驱动结构体
static struct platform_driver imx_i2c_driver = {
    .driver = {
        .name = "imx-i2c",
        .of_match_table = imx_i2c_of_match,
    },
    .probe = imx_i2c_probe,
    .remove = imx_i2c_remove,
};
实战要点: 编写 compatible 属性时,务必遵循“先精确后通用”的原则。例如,如果设备兼容标准 16550 UART,应写为 compatible = "myvendor,myuart", "ns16550a",这样即使没有专用驱动,也能使用通用 16550 驱动。

4.1.3 compatible 匹配流程图

compatible 属性驱动匹配流程 设备树解析 compatible 属性 遍历驱动 of_match_table 比较 compatible 字符串 匹配成功? 是 调用驱动 probe 函数 否 继续遍历下一项 匹配规则:优先匹配列表第一个字符串,失败则依次尝试后续字符串

4.2 reg 属性:地址空间与资源映射

reg 属性用于描述设备在总线上的地址空间,包括寄存器基地址、内存区域、I/O 端口等资源。它是驱动获取硬件资源的核心依据。

4.2.1 属性格式

reg 属性由一组 <地址, 长度> 对组成,每个对称为一个 reg 条目。地址和长度的位数由父节点的 #address-cells 和 #size-cells 属性决定。

// 基本格式
reg = <address1 length1> [, <address2 length2> ...];

// 示例1:32位地址空间,单个区域
#address-cells = <1>;
#size-cells = <1>;
reg = <0x0209c000 0x4000>;  // 基地址0x0209c000,长度0x4000

// 示例2:64位地址空间,两个区域
#address-cells = <2>;
#size-cells = <2>;
reg = <0x0 0x4a000000 0x0 0x1000>,  // 第一个区域
      <0x0 0x4a001000 0x0 0x100>;   // 第二个区域

// 示例3:带片选的 SPI 设备
spi@0 {
    compatible = "m25p80";
    reg = <0>;  // 片选号0
    spi-max-frequency = <20000000>;
};

4.2.2 address-cells 与 size-cells

属性 含义 常见值 说明
#address-cells 地址字段占用的 32 位单元数 1 或 2 1 表示 32 位地址,2 表示 64 位地址
#size-cells 长度字段占用的 32 位单元数 0 或 1 0 表示无长度(如片选),1 表示 32 位长度
常见错误: 忘记设置 #address-cells 和 #size-cells 会导致 reg 属性解析错误。例如,在 64 位 ARM 平台上,如果父节点未正确设置这两个属性,子节点的 reg 地址可能被截断。

4.2.3 驱动中读取 reg 资源

在驱动中,通常使用 platform_get_resource() 或 of_iomap() 函数来获取 reg 属性定义的资源。

// 方法1:使用 platform_get_resource
static int my_driver_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;

    // 获取第0个reg条目(索引从0开始)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "failed to get memory resource\n");
        return -ENXIO;
    }

    // 映射寄存器地址
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);

    // ... 使用 base 操作硬件寄存器
    return 0;
}

// 方法2:使用 of_iomap(更简洁)
static int my_driver_probe(struct platform_device *pdev)
{
    void __iomem *base;

    // 直接映射第0个reg条目
    base = of_iomap(pdev->dev.of_node, 0);
    if (!base) {
        dev_err(&pdev->dev, "failed to iomap\n");
        return -ENOMEM;
    }

    // ... 使用 base
    return 0;
}

4.2.4 reg 属性地址映射示意图

reg 属性地址映射结构 设备树节点 #address-cells = <1> #size-cells = <1> reg = <0x4a000000 0x1000> 内核解析 reg 提取地址和长度 生成 resource 结构 物理内存 0x4a000000 长度 0x1000 驱动调用 platform_get_resource() → 获取 resource → devm_ioremap_resource() → 虚拟地址访问 多区域 reg 示例: reg = <0x4a000000 0x1000>, <0x4a001000 0x100>; 索引0:控制寄存器区 | 索引1:数据缓冲区

4.3 interrupts 属性:中断资源描述

interrupts 属性用于描述设备的中断请求线,包括中断号、触发类型等信息。它是设备与中断控制器之间的桥梁。

4.3.1 属性格式

interrupts 属性的格式依赖于父节点 interrupt-parent 指定的中断控制器。通常包含中断号和触发类型两个字段。

// 基本格式(依赖 interrupt-parent)
interrupt-parent = <&gic>;  // 指定中断控制器
interrupts = <0 89 4>;       // 中断号 89,触发类型 4

// 多中断描述
interrupts = <0 89 4>, <0 90 4>;  // 两个中断

// 无中断控制器引用(直接使用父节点)
interrupts = <14 2>;  // 中断号14,触发类型2

4.3.2 中断触发类型

值 宏定义 触发方式 适用场景
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 低电平触发 低电平有效设备

4.3.3 中断控制器与 interrupts-extended

当设备连接到多个中断控制器时,可以使用 interrupts-extended 属性为每个中断指定不同的中断控制器。

// 使用 interrupts-extended 指定多个中断控制器
interrupts-extended = <&gic 0 89 4>, <&gpio1 15 2>;
// 第一个中断来自 GIC,第二个中断来自 GPIO1 控制器

4.3.4 驱动中获取中断号

static int my_driver_probe(struct platform_device *pdev)
{
    int irq;
    int ret;

    // 获取第0个中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "failed to get irq\n");
        return irq;
    }

    // 注册中断处理函数
    ret = devm_request_irq(&pdev->dev, irq, my_irq_handler,
                           IRQF_TRIGGER_RISING, "my_device", dev);
    if (ret) {
        dev_err(&pdev->dev, "failed to request irq\n");
        return ret;
    }

    return 0;
}

4.3.5 中断路由结构图

设备中断路由结构 外设设备 interrupts = <0 89 4> interrupt-parent = <&gic> 中断控制器 (GIC) 中断号 89 触发类型 4 (高电平) CPU 内核 中断处理函数 my_irq_handler() 多中断场景:interrupts = <0 89 4>, <0 90 4> → 驱动注册两个中断处理函数 跨控制器中断:interrupts-extended = <&gic 0 89 4>, <&gpio1 15 2> 中断号映射规则:设备树中的中断号是相对于中断控制器的本地编号 GIC 使用 <中断类型 中断号 触发类型> 格式,类型 0 表示 SPI,1 表示 PPI

4.4 status 属性:设备状态控制

status 属性用于控制设备节点的启用或禁用状态。它允许在不修改设备树文件的情况下,通过 overlay 或 bootloader 动态调整设备状态。

4.4.1 可选值

值 含义 说明
"okay" 设备启用 设备正常初始化,驱动可以 probe
"disabled" 设备禁用 设备不会被初始化,驱动不会 probe
"reserved" 保留状态 设备被保留,不用于操作系统
"fail" 设备故障 设备检测到故障,不启用
"fail-sss" 故障原因 如 "fail-gpio" 表示 GPIO 检测失败

4.4.2 典型用法

// 在 dtsi 中默认禁用
&uart1 {
    status = "disabled";
};

// 在板级 dts 中启用
&uart1 {
    status = "okay";
    pinctrl-0 = <&pinctrl_uart1>;
    pinctrl-names = "default";
};

// 条件启用(通过 bootloader 修改)
// bootloader 根据硬件版本设置 status
最佳实践: SoC 级 dtsi 文件中的设备默认设置为 disabled,板级 dts 文件中按需设置为 okay。这样可以避免未使用的设备占用资源,同时保持设备树的可维护性。

4.4.3 内核处理 status 的流程

内核处理 status 属性流程 解析设备树节点 检查 status 属性 status = "okay"? 是 创建设备 否:跳过该节点,不创建设备 注意:status 属性不存在时,内核默认视为 "okay"(兼容旧设备树) 建议显式设置 status,避免依赖默认行为

4.5 model 属性:设备型号标识

model 属性用于描述设备或板卡的型号名称。它通常作为人类可读的标识,用于区分不同硬件版本或配置。

4.5.1 属性格式

// 根节点中的 model 描述整板型号
/ {
    model = "Freescale i.M