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()获取中断号。
典型流程:
- 设备树中定义
interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING> - 驱动调用
irq = platform_get_irq(pdev, 0)获取中断号 - 调用
devm_request_irq(dev, irq, handler, flags, name, data)注册 - 中断触发时,内核调用注册的handler,handler中操作硬件寄存器
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读写操作,避免在原子上下文中阻塞。
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标志。这种模式能自动管理中断生命周期,避免资源泄漏,同时保证中断处理函数在安全上下文中执行,适用于所有三种总线模型。
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属性。