3. 网络设备中断注册
中断注册是网络驱动与内核中断子系统交互的入口。驱动必须为设备分配的中断线(IRQ)注册一个处理函数,内核才能在硬件产生中断时正确调用该函数。Linux 内核提供了两套核心注册 API:request_irq() 和 devm_request_irq(),以及一组用于描述中断行为的中断标志(IRQF_*)。
3.1 核心 API:request_irq()
request_irq() 是中断注册的基础函数,定义在 include/linux/interrupt.h 中。驱动在探测(probe)阶段调用该函数,将 IRQ 号、处理函数、中断标志、设备名称和设备上下文绑定在一起。
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
| 参数 | 类型 | 说明 |
|---|---|---|
irq |
unsigned int | 要注册的中断号,通常由 pci_alloc_irq_vectors() 或平台设备资源获取 |
handler |
irq_handler_t | 中断处理函数指针,原型为 irqreturn_t (*handler)(int irq, void *dev_id) |
flags |
unsigned long | 中断标志位掩码,控制中断触发方式、共享属性、线程化等行为 |
name |
const char * | 设备名称,显示在 /proc/interrupts 中,用于调试和统计 |
dev |
void * | 传递给处理函数的设备标识,共享中断时用于区分设备;通常传入网络设备的 struct net_device * 或 struct adapter * |
关键约束: 共享中断(IRQF_SHARED)场景下,
dev 参数必须唯一且非 NULL,内核在调用处理函数前会遍历所有注册该 IRQ 的处理程序,只有 dev 匹配的设备才会被调用。驱动必须检查中断状态寄存器,确认中断是否来自本设备。
3.2 托管版本:devm_request_irq()
devm_request_irq() 是设备资源管理(devres)框架下的中断注册函数。它自动将中断的生命周期绑定到设备生命周期,驱动卸载或 probe 失败时,内核自动释放中断,无需手动调用 free_irq()。
int devm_request_irq(struct device *dev,
unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id);
| 对比维度 | request_irq() | devm_request_irq() |
|---|---|---|
| 资源释放方式 | 手动调用 free_irq() |
自动释放,设备脱离时内核回收 |
| 适用场景 | 需要精细控制中断生命周期,或中断与设备生命周期不同步 | 标准网络驱动 probe/remove 流程,推荐使用 |
| 错误路径处理 | probe 失败时需手动清理已注册的中断 | probe 失败时自动回滚,减少内存泄漏风险 |
| 代码简洁性 | 需要维护中断释放逻辑 | 更简洁,减少样板代码 |
最佳实践: 现代网络驱动优先使用
devm_request_irq()。它消除了 probe 失败路径中遗漏 free_irq() 的风险,且与 devres 框架的其他资源(如 DMA 映射、寄存器映射)统一管理。
3.3 中断标志(IRQF_*)详解
中断标志通过 flags 参数传递给注册函数,多个标志使用按位或(|)组合。以下是网络驱动中最常用的标志:
| 标志 | 值 | 含义 | 网络驱动典型用途 |
|---|---|---|---|
IRQF_SHARED |
0x00000080 | 允许共享中断线,多个设备共用同一 IRQ | PCI 传统中断(INTx)场景,多个设备可能共享同一物理中断线 |
IRQF_TRIGGER_NONE |
0x00000000 | 不指定触发方式,由固件或 BIOS 配置 | 平台设备或旧式 PCI 设备 |
IRQF_TRIGGER_RISING |
0x00000001 | 上升沿触发 | 部分 SoC 内部网络控制器 |
IRQF_TRIGGER_FALLING |
0x00000002 | 下降沿触发 | 部分 SoC 内部网络控制器 |
IRQF_TRIGGER_HIGH |
0x00000004 | 高电平触发 | PCIe MSI/MSI-X 中断通常使用电平触发 |
IRQF_TRIGGER_LOW |
0x00000008 | 低电平触发 | 部分嵌入式网络设备 |
IRQF_ONESHOT |
0x00002000 | 中断处理期间屏蔽该中断线,处理完成后自动重新使能 | 线程化中断处理(request_threaded_irq)必须使用 |
IRQF_NO_SUSPEND |
0x00004000 | 系统休眠时不关闭该中断 | 网络唤醒(WoL)功能需要保持中断活跃 |
共享中断陷阱: 使用
IRQF_SHARED 时,处理函数必须首先读取设备中断状态寄存器,确认中断是否属于本设备。如果中断不属于本设备,必须立即返回 IRQ_NONE,否则会导致内核误判为"伪中断"(spurious interrupt),严重时内核会禁用该中断线。
3.4 中断注册流程与标志组合
网络驱动在 probe 函数中注册中断的典型流程如下:
- 获取 IRQ 资源: 通过
pci_alloc_irq_vectors()分配 MSI/MSI-X 向量,或从platform_get_irq()获取传统中断号。 - 确定中断标志: MSI/MSI-X 场景使用
IRQF_TRIGGER_HIGH;传统 INTx 场景使用IRQF_SHARED | IRQF_TRIGGER_HIGH。 - 注册中断: 调用
devm_request_irq()或request_irq(),传入 IRQ 号、处理函数、标志、设备名称和设备上下文。 - 启用中断: 在设备寄存器中使能中断位,开始接收硬件中断。
// 典型网络驱动中断注册示例(使用 devm_request_irq)
static int netdev_probe(struct platform_device *pdev)
{
struct net_device *ndev;
struct adapter *adapter;
int irq, ret;
// ... 分配 net_device、初始化硬件 ...
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 使用 IRQF_TRIGGER_HIGH | IRQF_SHARED 注册中断
ret = devm_request_irq(&pdev->dev, irq,
netdev_interrupt,
IRQF_TRIGGER_HIGH | IRQF_SHARED,
ndev->name, ndev);
if (ret) {
dev_err(&pdev->dev, "failed to request irq %d\n", irq);
return ret;
}
// 使能设备中断寄存器
writel(INT_ENABLE, adapter->ioaddr + INT_MASK_REG);
return 0;
}
3.5 中断注册架构图
下图展示了从驱动 probe 到中断子系统注册的完整数据流与标志决策路径:
3.6 中断处理函数签名与返回值
中断处理函数必须遵循内核定义的签名:
irqreturn_t (*handler)(int irq, void *dev_id);
| 返回值 | 含义 | 使用场景 |
|---|---|---|
IRQ_NONE (0) |
中断不是本设备产生的 | 共享中断中,读取状态寄存器后发现中断不属于本设备 |
IRQ_HANDLED (1) |
中断已被本设备处理 | 正常处理完成,或 NAPI 调度后返回 |
IRQ_WAKE_THREAD (2) |
唤醒线程化中断处理函数 | 使用 request_threaded_irq() 时,主处理函数返回此值 |
性能提示: 网络驱动的中断处理函数应尽可能轻量。典型做法是:在中断处理函数中读取状态寄存器、调度 NAPI(
napi_schedule())、清除中断状态位,然后返回 IRQ_HANDLED。实际的数据包接收和协议栈处理在 NAPI 轮询上下文中完成。
3.7 中断注册的常见错误与调试
- IRQ 号无效: 注册前未正确获取 IRQ 资源,导致传入 0 或负数。使用
platform_get_irq()时需检查返回值。 - 共享中断未检查设备状态: 处理函数未读取中断状态寄存器就直接返回
IRQ_HANDLED,导致内核统计异常。 - 中断标志不匹配: 设备实际使用电平触发但注册时指定边沿触发,导致中断丢失或重复触发。
- dev 参数重复: 同一 IRQ 上注册多个处理函数时
dev参数相同,内核无法区分设备。
调试中断注册问题时,/proc/interrupts 是最直接的诊断工具:
# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
16: 123 456 789 012 IR-IO-APIC 16-fasteoi eth0
17: 34 56 78 90 IR-IO-APIC 17-fasteoi eth1
每一行显示 IRQ 号、各 CPU 上的中断计数、中断控制器信息、触发方式以及注册时传入的设备名称。如果某个设备的中断计数不增长,说明中断注册或硬件配置存在问题。