3. Subdev驱动模型简介:platform设备、I2C设备、SPI设备与中断的关联

在Linux内核中,Subdev(子设备)驱动模型是设备驱动开发的核心抽象。不同的总线类型(platform、I2C、SPI)决定了设备与内核的绑定方式、资源获取路径以及中断处理流程。理解这些模型与中断的关联,是设计可靠中断处理程序的前提。

3.1 三种总线模型概述

下表对比了三种常见Subdev总线模型的核心特征及其与中断的关联方式:

总线类型 设备发现方式 资源获取 中断号来源 典型应用场景
Platform 设备树匹配 / ACPI platform_get_resource() 设备树 interrupts 属性 SoC内部外设(GPIO、UART、DMA)
I2C 设备树 + 地址匹配 i2c_client 结构体 通过 client->irq 获取 传感器、ADC、音频编解码器
SPI 设备树 + chip select spi_device 结构体 通过 spi->irq 获取 显示驱动、ADC、无线收发器
关键点:无论哪种总线,中断号最终都会映射到内核的irq_desc结构,并通过request_threaded_irq()或devm_request_irq()注册处理函数。总线模型只影响中断号的获取方式,不影响中断处理流程本身。

3.2 Platform设备与中断

Platform设备是Linux内核中最基础的虚拟总线设备,主要用于SoC内部集成的外设。其中断资源通常通过设备树(Device Tree)描述,驱动在probe阶段通过platform_get_irq()获取中断号。

典型流程:
  1. 设备树中定义 interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>
  2. 驱动调用 irq = platform_get_irq(pdev, 0) 获取中断号
  3. 调用 devm_request_irq(dev, irq, handler, flags, name, data) 注册
  4. 中断触发时,内核调用注册的handler,handler中操作硬件寄存器
Platform设备中断注册流程 设备树解析 platform_get_irq() devm_request_irq() 中断处理函数 硬件寄存器操作 interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>

3.3 I2C设备与中断

I2C设备通过i2c_client结构体与驱动绑定。中断号通常由设备树中的interrupts属性指定,内核在I2C核心层自动解析并赋值给client->irq。驱动在probe阶段直接使用该字段注册中断。

// I2C设备驱动probe函数中的中断注册示例
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct device *dev = &client->dev;
    int irq = client->irq;  // 中断号已由I2C核心自动填充
    int ret;

    if (!irq) {
        dev_err(dev, "no IRQ specified\n");
        return -EINVAL;
    }

    ret = devm_request_threaded_irq(dev, irq, sensor_isr,
                                    sensor_threaded_isr,
                                    IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                                    "sensor_irq", sensor_data);
    if (ret) {
        dev_err(dev, "failed to request IRQ %d\n", irq);
        return ret;
    }

    return 0;
}
注意:I2C总线通信本身是慢速协议,中断处理函数中不应直接发起I2C传输。推荐使用线程化中断(threaded IRQ),在threaded handler中执行I2C读写操作,避免在原子上下文中阻塞。
I2C设备中断数据流 I2C控制器 i2c_client client->irq 中断控制器 Threaded Handler I2C传输操作 硬件中断触发 (INT引脚拉低)

3.4 SPI设备与中断

SPI设备的中断机制与I2C类似,中断号通过spi_device结构体的irq字段获取。SPI设备通常有独立的INT引脚连接到SoC的GPIO或中断控制器,设备树中通过interrupt-parent和interrupts属性描述。

SPI设备中断注册要点:
  • 中断号获取:直接使用 spi->irq,无需额外解析
  • 中断触发方式:SPI设备多为电平触发(IRQF_TRIGGER_LOW/HIGH),需与硬件手册一致
  • 共享中断:SPI总线上的多个设备可能共享同一中断线,需设置 IRQF_SHARED 标志
  • 线程化处理:SPI传输是同步阻塞操作,必须在threaded handler中执行
// SPI设备中断注册示例(含共享中断处理)
static int spi_device_probe(struct spi_device *spi)
{
    struct device *dev = &spi->dev;
    int irq = spi->irq;
    unsigned long irq_flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT;

    // 如果多个设备共享中断线,添加IRQF_SHARED标志
    if (spi->irq && spi->irq == some_shared_irq)
        irq_flags |= IRQF_SHARED;

    return devm_request_threaded_irq(dev, irq, NULL,
                                     spi_device_threaded_isr,
                                     irq_flags, "spi_dev", spi);
}

3.5 中断关联的核心差异总结

维度 Platform I2C SPI
中断号获取API platform_get_irq() client->irq spi->irq
设备树属性 interrupts interrupts (在子节点) interrupts (在子节点)
中断上下文限制 可快速访问MMIO寄存器 禁止在top half中发起I2C传输 禁止在top half中发起SPI传输
推荐中断模型 快速中断 + tasklet / threaded threaded IRQ(必须) threaded IRQ(必须)
共享中断支持 常见(多个外设共享GIC线) 较少见 常见(多个SPI设备共享GPIO中断线)
设计建议:在Subdev驱动开发中,建议统一使用devm_request_threaded_irq()配合IRQF_ONESHOT标志。这种模式能自动管理中断生命周期,避免资源泄漏,同时保证中断处理函数在安全上下文中执行,适用于所有三种总线模型。
三种Subdev模型中断关联对比 Platform设备 platform_get_irq() MMIO寄存器直接访问 I2C设备 client->irq Threaded Handler + I2C传输 SPI设备 spi->irq Threaded Handler + SPI传输 统一接口:devm_request_threaded_irq() + IRQF_ONESHOT

3.6 中断号映射与设备树

设备树是描述中断关联的核心手段。无论哪种总线,中断控制器(如GIC、GPIO控制器)通过interrupt-parent和interrupts属性建立从设备到中断控制器的映射关系。内核在设备枚举阶段自动完成中断号的解析和分配。

// 设备树中三种设备的中断描述示例
/ {
    soc {
        // Platform设备:直接挂载在soc节点下
        uart0: serial@10000000 {
            compatible = "vendor,uart";
            reg = <0x10000000 0x1000>;
            interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>;
        };

        i2c0: i2c@20000000 {
            compatible = "vendor,i2c";
            reg = <0x20000000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;

            // I2C子设备
            sensor@48 {
                compatible = "vendor,sensor";
                reg = <0x48>;
                interrupt-parent = <&gpio1>;
                interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
            };
        };

        spi0: spi@30000000 {
            compatible = "vendor,spi";
            reg = <0x30000000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;

            // SPI子设备
            adc@0 {
                compatible = "vendor,adc";
                reg = <0>;
                spi-max-frequency = <10000000>;
                interrupt-parent = <&gpio2>;
                interrupts = <10 IRQ_TYPE_LEVEL_LOW>;
            };
        };
    };
};
常见错误:在I2C/SPI子设备节点中忘记指定interrupt-parent,导致client->irq或spi->irq为0。如果中断控制器不是父节点(如GPIO控制器),必须显式声明interrupt-parent属性。