Linux handle_edge_irq边沿触发与IRQ_WAITING检测handle_edge_irq是Linux内核中断子系统为边沿触发中断提供的标准处理函数定义在kernel/irq/chip.c。边沿触发中断的特点是在信号电平跳变沿上升沿或下降沿产生中断请求锁存在中断控制器中要求处理函数在清除pending位前必须完成所有必要的操作。handle_edge_irq的设计围绕这个特性展开。函数源码实现cvoid handle_edge_irq(struct irq_desc *desc){raw_spin_lock(desc-lock);desc-istate ~(IRQS_REPLAY | IRQS_WAITING);if (irqd_irq_disabled(desc-irq_data)) {desc-istate | IRQS_PENDING;mask_ack_irq(desc);goto out_unlock;}if ((desc-istate IRQS_PENDING) || !desc-action) {if (!desc-action)mask_ack_irq(desc);elseack_irq(desc);goto out_unlock;}kstat_incr_irqs_this_cpu(desc);ack_irq(desc);do {if (unlikely(!desc-action)) {mask_irq(desc);goto out_unlock;}if (unlikely(irqd_irq_disabled(desc-irq_data))) {desc-istate | IRQS_PENDING;mask_ack_irq(desc);goto out_unlock;}irq_compat_set_prog_affinity(desc);raw_spin_unlock(desc-lock);handle_irq_event(desc);raw_spin_lock(desc-lock);} while ((desc-istate IRQS_PENDING) !irqd_irq_disabled(desc-irq_data));out_unlock:raw_spin_unlock(desc-lock);}IRQ_WAITING标志是handle_edge_irq中重要的检测机制。在中断注册过程中__setup_irq会调用irq_startup而在startup过程中内核会为边沿触发中断设置IRQS_WAITING标志。当该标志存在时第一次中断到达后handle_edge_irq在顶部清除它以确认中断源确实可工作并正确连接cdesc-istate ~(IRQS_REPLAY | IRQS_WAITING);IRQS_WAITING的赋值点在__setup_irq的启动路径上cstatic void irq_state_clr_started(struct irq_desc *desc){desc-istate ~IRQS_STARTED;if (irq_settings_is_edge_triggered(desc))desc-istate | IRQS_WAITING;}这个标志用于检测虚假中断spurious interrupt。如果ISR中该中断的IRQ_WAITING已被清除但在后续中断中又被检测到pending状态且action为空则说明设备可能无法正确释放中断请求线。IRQS_PENDING标志管理中断的二次触发。边沿触发中断在handler执行期间如果再次产生边沿信号硬件锁存该请求并在EOI后再次上报。内核通过IRQS_PENDING标记记录这个二次触发cif (irqd_irq_disabled(desc-irq_data)) {desc-istate | IRQS_PENDING;mask_ack_irq(desc);goto out_unlock;}当检测到IRQS_PENDING时可能有两种情况一是当前handler正在处理时新的边沿触发了二是handler还没执行完就发生了中断嵌套。由于边沿中断一旦丢失就无法恢复内核使用do-while循环反复调用handle_irq_event直到IRQS_PENDING被消耗完毕cdo {...handle_irq_event(desc);} while ((desc-istate IRQS_PENDING) !irqd_irq_disabled(desc-irq_data));ack_irq在handle_edge_irq的每个分支中都有调用。ack操作清除中断控制器中的pending位使后续边沿触发信号能够再次被锁存和上报。对于x86 APICack_irq展开为cstatic inline void ack_irq(struct irq_desc *desc){struct irq_chip *chip desc-irq_data.chip;chip-irq_ack(desc-irq_data);}掩码与ack的关系在边沿触发中较为特殊。当中断被disable时先mask_ack_irq同时mask和ack然后等待条件恢复。mask操作抑制了后续上报但ack必须发出以清除当前pending状态。若只mask不ack可能造成中断控制器持续上报已屏蔽的中断。kstat_incr_irqs_this_cpu在每次有效中断到达时递增Per-CPU统计计数器该数据通过/proc/interrupts暴露。用户可以通过观察该计数器来验证边沿中断是否频繁丢失—如果期望的中断数远大于统计计数可能存在硬件锁存器溢出。IRQS_REPLAY标志用于回放中断。当处理器在mask状态下丢失了一个边沿中断时irq_poll或resend_irq机制设置IRQS_REPLAY并重新触发中断cif (chip-irq_retrigger) {chip-irq_retrigger(desc-irq_data);desc-istate | IRQS_REPLAY;}retrigger回调通过APIC向自身发送IPI模拟中断源重新产生中断信号。handle_edge_irq在顶部清除IRQS_REPLAY避免重复回放。对于线程化边沿中断handle_edge_irq的行为略有差异。当设置了IRQF_ONESHOT时中断处理线程结束后才unmask因此在边沿触发模式下存在丢失潜在边沿信号的风险。解决办法是利用IRQS_PENDING标记在unmask前记录所有到达的边沿信号在do-while循环中一次性处理。
Linux handle_edge_irq边沿触发与IRQ_WAITING检测
Linux handle_edge_irq边沿触发与IRQ_WAITING检测handle_edge_irq是Linux内核中断子系统为边沿触发中断提供的标准处理函数定义在kernel/irq/chip.c。边沿触发中断的特点是在信号电平跳变沿上升沿或下降沿产生中断请求锁存在中断控制器中要求处理函数在清除pending位前必须完成所有必要的操作。handle_edge_irq的设计围绕这个特性展开。函数源码实现cvoid handle_edge_irq(struct irq_desc *desc){raw_spin_lock(desc-lock);desc-istate ~(IRQS_REPLAY | IRQS_WAITING);if (irqd_irq_disabled(desc-irq_data)) {desc-istate | IRQS_PENDING;mask_ack_irq(desc);goto out_unlock;}if ((desc-istate IRQS_PENDING) || !desc-action) {if (!desc-action)mask_ack_irq(desc);elseack_irq(desc);goto out_unlock;}kstat_incr_irqs_this_cpu(desc);ack_irq(desc);do {if (unlikely(!desc-action)) {mask_irq(desc);goto out_unlock;}if (unlikely(irqd_irq_disabled(desc-irq_data))) {desc-istate | IRQS_PENDING;mask_ack_irq(desc);goto out_unlock;}irq_compat_set_prog_affinity(desc);raw_spin_unlock(desc-lock);handle_irq_event(desc);raw_spin_lock(desc-lock);} while ((desc-istate IRQS_PENDING) !irqd_irq_disabled(desc-irq_data));out_unlock:raw_spin_unlock(desc-lock);}IRQ_WAITING标志是handle_edge_irq中重要的检测机制。在中断注册过程中__setup_irq会调用irq_startup而在startup过程中内核会为边沿触发中断设置IRQS_WAITING标志。当该标志存在时第一次中断到达后handle_edge_irq在顶部清除它以确认中断源确实可工作并正确连接cdesc-istate ~(IRQS_REPLAY | IRQS_WAITING);IRQS_WAITING的赋值点在__setup_irq的启动路径上cstatic void irq_state_clr_started(struct irq_desc *desc){desc-istate ~IRQS_STARTED;if (irq_settings_is_edge_triggered(desc))desc-istate | IRQS_WAITING;}这个标志用于检测虚假中断spurious interrupt。如果ISR中该中断的IRQ_WAITING已被清除但在后续中断中又被检测到pending状态且action为空则说明设备可能无法正确释放中断请求线。IRQS_PENDING标志管理中断的二次触发。边沿触发中断在handler执行期间如果再次产生边沿信号硬件锁存该请求并在EOI后再次上报。内核通过IRQS_PENDING标记记录这个二次触发cif (irqd_irq_disabled(desc-irq_data)) {desc-istate | IRQS_PENDING;mask_ack_irq(desc);goto out_unlock;}当检测到IRQS_PENDING时可能有两种情况一是当前handler正在处理时新的边沿触发了二是handler还没执行完就发生了中断嵌套。由于边沿中断一旦丢失就无法恢复内核使用do-while循环反复调用handle_irq_event直到IRQS_PENDING被消耗完毕cdo {...handle_irq_event(desc);} while ((desc-istate IRQS_PENDING) !irqd_irq_disabled(desc-irq_data));ack_irq在handle_edge_irq的每个分支中都有调用。ack操作清除中断控制器中的pending位使后续边沿触发信号能够再次被锁存和上报。对于x86 APICack_irq展开为cstatic inline void ack_irq(struct irq_desc *desc){struct irq_chip *chip desc-irq_data.chip;chip-irq_ack(desc-irq_data);}掩码与ack的关系在边沿触发中较为特殊。当中断被disable时先mask_ack_irq同时mask和ack然后等待条件恢复。mask操作抑制了后续上报但ack必须发出以清除当前pending状态。若只mask不ack可能造成中断控制器持续上报已屏蔽的中断。kstat_incr_irqs_this_cpu在每次有效中断到达时递增Per-CPU统计计数器该数据通过/proc/interrupts暴露。用户可以通过观察该计数器来验证边沿中断是否频繁丢失—如果期望的中断数远大于统计计数可能存在硬件锁存器溢出。IRQS_REPLAY标志用于回放中断。当处理器在mask状态下丢失了一个边沿中断时irq_poll或resend_irq机制设置IRQS_REPLAY并重新触发中断cif (chip-irq_retrigger) {chip-irq_retrigger(desc-irq_data);desc-istate | IRQS_REPLAY;}retrigger回调通过APIC向自身发送IPI模拟中断源重新产生中断信号。handle_edge_irq在顶部清除IRQS_REPLAY避免重复回放。对于线程化边沿中断handle_edge_irq的行为略有差异。当设置了IRQF_ONESHOT时中断处理线程结束后才unmask因此在边沿触发模式下存在丢失潜在边沿信号的风险。解决办法是利用IRQS_PENDING标记在unmask前记录所有到达的边沿信号在do-while循环中一次性处理。