2. 中断基础:Linux中断子系统架构、中断号、中断处理函数注册
在Linux网络驱动中,中断是硬件通知软件“有事发生”的核心机制。对于PHY链路检测而言,链路状态变化(Link Up/Down)正是通过中断信号从PHY芯片传递到CPU,进而触发驱动执行链路重协商、状态更新等操作。理解中断子系统是掌握网络驱动协同工作的前提。
2.1 Linux中断子系统整体架构
Linux中断子系统采用分层设计,从上到下依次为:中断处理函数层、中断分发层、硬件中断控制器层。这种分层使得驱动开发者只需关注上层API,而无需关心底层中断控制器的具体实现。
2.2 中断号:从硬件到软件的桥梁
中断号是中断子系统中最核心的标识符。在Linux中,中断号分为两个层次:硬件中断号(HW IRQ Number)和Linux IRQ号(Virtual IRQ Number)。驱动开发中,我们通常操作的是Linux IRQ号。
| 概念 | 说明 | 获取方式 |
|---|---|---|
| 硬件中断号 | 由中断控制器(如GIC)分配的物理中断线编号,与SoC引脚绑定 | 查阅芯片手册/设备树 |
| Linux IRQ号 | Linux内核抽象出的虚拟中断号,驱动通过此号注册处理函数 | platform_get_irq() / irq_of_parse_and_map() |
| irq_domain | 负责将硬件中断号映射到Linux IRQ号的中间层 | 内核自动管理 |
实战提示:在设备树中,PHY中断通常通过
interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_LOW> 描述。驱动中调用 platform_get_irq(pdev, 0) 即可获得Linux IRQ号,无需手动计算硬件中断号。
2.3 中断处理函数注册:request_irq
request_irq() 是Linux驱动中最经典的中断注册函数,用于注册一个顶半部(Top Half)中断处理函数。顶半部在中断上下文中执行,要求快速、不可睡眠。
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id);
| 参数 | 含义 | 典型值 |
|---|---|---|
irq |
Linux IRQ号 | 从 platform_get_irq() 获取 |
handler |
中断处理函数指针,返回 irqreturn_t |
phy_interrupt_handler |
flags |
中断触发类型与共享标志 | IRQF_TRIGGER_LOW | IRQF_SHARED |
name |
中断名称,用于 /proc/interrupts 显示 |
"eth0_phy" |
dev_id |
设备标识,共享中断时用于区分 | 指向 net_device 或 phy_device |
关键点:顶半部执行期间,当前CPU的中断被禁用(取决于
IRQF_DISABLED 标志)。因此顶半部必须极快完成,通常只做:读取中断状态寄存器、清除中断标志、唤醒底半部(Bottom Half)。
2.4 线程化中断:request_threaded_irq
对于PHY链路检测这类需要执行耗时操作(如MDIO读写、状态机切换)的中断场景,顶半部显然不合适。Linux提供了线程化中断(Threaded IRQ)机制,将中断处理分为:
- 顶半部(handler):在中断上下文中快速执行,返回
IRQ_WAKE_THREAD唤醒线程。 - 线程化处理函数(thread_fn):在进程上下文中执行,可以睡眠、可以持有锁、可以调用
phy_read()等耗时函数。
int request_threaded_irq(unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags,
const char *name,
void *dev_id);
| 参数 | 说明 |
|---|---|
handler |
顶半部函数。若为 NULL,内核使用默认的 irq_default_primary_handler(),直接唤醒线程 |
thread_fn |
线程化处理函数。在进程上下文中运行,可睡眠 |
2.5 request_irq vs request_threaded_irq 对比
| 对比维度 | request_irq | request_threaded_irq |
|---|---|---|
| 执行上下文 | 中断上下文(不可睡眠) | 顶半部:中断上下文;线程化函数:进程上下文 |
| 可调用函数 | 仅限原子操作(如 writel、tasklet_schedule) |
线程化函数可调用 phy_read、mutex_lock、msleep |
| 适用场景 | 简单中断确认、唤醒底半部 | PHY链路检测、I2C/SPI设备中断、需要等待硬件的操作 |
| 中断延迟 | 低(直接执行) | 略高(需调度内核线程) |
| 注册方式 | request_irq(irq, handler, flags, name, dev) |
request_threaded_irq(irq, handler, thread_fn, flags, name, dev) |
常见错误:在
request_irq 注册的顶半部中调用 phy_read() 或 printk() 等可能睡眠的函数,会导致内核崩溃(BUG: scheduling while atomic)。PHY链路检测必须使用线程化中断。
2.6 中断注册实战:PHY中断示例
以下代码展示了在PHY驱动中注册线程化中断的典型模式:
/* PHY中断顶半部:快速确认中断源 */
static irqreturn_t phy_interrupt_handler(int irq, void *dev_id)
{
struct phy_device *phydev = dev_id;
/* 读取中断状态寄存器,确认中断是否来自本PHY */
int irq_status = phy_read(phydev, MII_INT_STATUS);
if (irq_status & PHY_INT_LINK_CHANGE) {
/* 清除中断标志 */
phy_write(phydev, MII_INT_STATUS, irq_status);
return IRQ_WAKE_THREAD; /* 唤醒线程化处理 */
}
return IRQ_NONE; /* 非本设备中断 */
}
/* PHY中断线程化处理:执行链路状态检测 */
static irqreturn_t phy_link_change_thread(int irq, void *dev_id)
{
struct phy_device *phydev = dev_id;
/* 在进程上下文中读取PHY状态寄存器 */
int bmsr = phy_read(phydev, MII_BMSR);
int lpa = phy_read(phydev, MII_LPA);
/* 更新链路状态 */
if (bmsr & BMSR_LSTATUS) {
netif_carrier_on(phydev->attached_dev);
phydev->link = 1;
} else {
netif_carrier_off(phydev->attached_dev);
phydev->link = 0;
}
/* 触发上层状态机 */
phy_state_machine(phydev);
return IRQ_HANDLED;
}
/* 中断注册(在PHY驱动probe函数中调用) */
int phy_request_irq(struct phy_device *phydev)
{
int ret;
ret = request_threaded_irq(phydev->irq,
phy_interrupt_handler, /* 顶半部 */
phy_link_change_thread, /* 线程化函数 */
IRQF_TRIGGER_LOW | IRQF_SHARED,
"phy_link_irq",
phydev);
if (ret)
dev_err(&phydev->mdio.dev, "Failed to request PHY IRQ\n");
return ret;
}
最佳实践:在PHY驱动中,建议始终使用
request_threaded_irq 而非 request_irq + tasklet。线程化中断天然支持进程上下文操作,且与内核的 phy_state_machine() 配合更自然,避免了tasklet中无法持有mutex的问题。
2.7 中断释放与注意事项
在驱动卸载或PHY设备移除时,必须释放已注册的中断:
void phy_free_irq(struct phy_device *phydev)
{
if (phydev->irq >= 0)
free_irq(phydev->irq, phydev);
}
中断注册与释放的注意事项:
- 中断号有效性检查:注册前确认
phydev->irq不为0或负数。 - 共享中断的dev_id:使用
IRQF_SHARED标志时,dev_id必须唯一且非NULL,用于free_irq()时区分。 - 中断触发类型匹配:PHY芯片的中断触发类型(电平/边沿)必须与设备树或平台数据一致,否则可能导致中断丢失或风暴。
- 禁止在顶半部调用phy_read:MDIO总线操作可能睡眠,必须放在线程化函数中。
总结:中断子系统是网络驱动与PHY硬件交互的“第一响应人”。
request_threaded_irq 是PHY链路检测场景的标准选择,它将快速中断确认与耗时链路检测分离,既保证了中断响应速度,又避免了原子上下文违规。下一节将深入PHY中断的具体配置与链路状态机协同。