拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Linux generic IRQ 框架详解:中断描述符、流控处理器与 irq_chip 三层抽象

Linux generic IRQ 框架详解中断描述符、流控处理器与 irq_chip 三层抽象【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读本文以内核文档 Documentation/core-api/genericirq.rst 为骨架结合本仓库kernel/irq/目录下的真实源码实现系统讲解 Linux 通用中断处理层generic IRQ handling的设计与工作原理。内容覆盖中断子系统从“设备驱动高层 API”到“高层 IRQ 流控处理器flow handler”再到“芯片级硬件封装irq_chip”的三层抽象、各预置流控处理器的控制流细节、irq_chip 原始接口语义以及 Generic Interrupt Chip 的复用方法。读完本文你将能够理解中断在 Linux 内核中的完整分发路径并具备为自有架构实现中断子系统或为控制器编写 irq_chip 的实战能力。引言为什么需要通用中断处理层Linux 通用中断处理层generic IRQ handling layer的设计目标是为设备驱动提供一套完整且与硬件无关的中断处理抽象。它能够适配不同类型的中断控制器硬件而设备驱动只需调用通用 API 来请求request、使能enable、禁用disable和释放free中断即可完全不需要了解底层中断控制器的细节因此驱动代码可以不加修改地在不同平台上运行。从源码结构上看这一层主要由 kernel/irq/ 目录下的这些核心文件承载kernel/irq/manage.c高层驱动 APIrequest_irq、free_irq、enable_irq、disable_irq等的实现kernel/irq/chip.c预置的 IRQ 流控处理器handle_level_irq、handle_edge_irq等与芯片回调调度kernel/irq/irqdesc.c中断描述符irq_desc的管理kernel/irq/handle.chandle_bad_irq等杂项处理kernel/irq/generic-chip.c可配置的通用中断芯片实现。原文档面向的对象是那些希望借助通用 IRQ 层为自己的架构实现中断子系统的开发者。本文同样以此为主线展开。设计动机从 __do_IRQ() 超级处理器到分类流控在通用 IRQ 层出现之前Linux 的中断处理采用__do_IRQ()超级处理器super-handler模型它试图用一套逻辑应对所有类型的中断。早期 ARM 中断子系统的实现者 Russell King 在 Linux 2.5/2.6 时代识别出三类典型处理器并构建了一套相当通用的处理集合Level type电平触发型Edge type边沿触发型Simple type简单型在后续实现过程中又识别出另一种类型Fast EOI type快速 EOI 型进入 SMP 世界后__do_IRQ()超级处理器中还识别出一种新类型Per CPU type每 CPU 型将高层 IRQ 处理器按类型拆分使得每个特定中断类型的处理流程都能被独立优化特定代码路径的复杂度降低处理路径也变得更短、更高效。原__do_IRQ()通用实现还存在一个结构性缺陷它依赖hw_interrupt_type结构及其-ack、-end等回调来区分流控导致流控逻辑与底层硬件逻辑混在一起并带来大量不必要的代码重复。原文档举了 i386 的例子ioapic_level_irq和ioapic_edge_irq两个 IRQ 类型共享了大量底层细节却因流控不同而不得不各自实现一份。更自然的抽象方式是把IRQ 流控irq flow与芯片细节chip details彻底分离。对多个架构的 IRQ 子系统实现分析后可以发现绝大多数架构都能复用一套通用的 irq flow 方法只需自行补充芯片级代码对于需要在 IRQ 流控本身做特殊处理quirks而芯片细节无需改动子架构这种分离也让 IRQ 子系统设计更加透明。每个中断描述符都被分配自己的高层流控处理器通常就是预置的通用实现之一。这种设计让解复用处理器demultiplexing handlers的实现变得简单——这在各架构的嵌入式平台上很常见一个物理中断线解复用出多个虚拟中断。另外为了平滑过渡、避免破坏已有实现__do_IRQ()超级处理器在很长一段时间内仍然可用与新模式并存duality。不过文档明确指出它已被弃用deprecated多年处于即将被移除的状态——事实上在当前版本源码中该入口已不再存在详见下文__do_IRQ 入口点一节。三层抽象架构中断代码中存在三个主要抽象层级高层驱动 APIHigh-level driver API高层 IRQ 流控处理器High-level IRQ flow handlers芯片级硬件封装Chip-level hardware encapsulation中断控制流irq_desc 描述符与 handle_irq 入口每个中断都由一个中断描述符结构irq_desc描述中断通过一个unsigned int数值引用该数值用于在描述符结构数组中选中对应的描述符。描述符中包含状态信息以及分配给该中断的流控方法指针handle_irq和中断芯片结构指针irq_chip。每当一个中断触发时底层架构代码通过调用desc-handle_irq()进入通用中断代码。这个高层 IRQ 处理函数只使用desc-irq_data.chip指向的芯片描述符所提供的原语。struct irq_data与struct irq_chip的定义位于 include/linux/irq.h其中irq_desc的定义同样在该头文件中。在 kernel/irq/irqdesc.c 中可以看到新分配的描述符默认把handle_irq设置为handle_bad_irq第 133 行附近即没有正确初始化流控处理器之前任何中断都按坏中断处理这是非常稳妥的安全默认值。高层驱动 API文档列出的高层驱动 API 如下request_irq()request_threaded_irq()free_irq()disable_irq()enable_irq()disable_irq_nosync()仅 SMPsynchronize_irq()仅 SMPirq_set_irq_type()irq_set_irq_wake()irq_set_handler_data()irq_set_chip()irq_set_chip_data()这些函数主要实现在 kernel/irq/manage.c 与 kernel/irq/chip.c 中。以最常用的request_irq()为例它定义在 include/linux/interrupt.h第 173 行附近是一个内联包装函数最终调用request_threaded_irq(irq, handler, NULL, flags, name, dev)——即只有主处理器、没有线程处理器的线程化请求特例。request_threaded_irq()的核心实现kernel/irq/manage.c 第 2124 行起揭示了几个重要约定传入IRQ_NOTCONNECTED会返回-ENOTCONN共享中断IRQF_SHARED必须传入非 NULL 的dev_id否则返回-EINVAL——因为释放中断时必须靠dev_id区分是哪个设备在占用该线共享中断不允许与IRQF_NO_AUTOEN组合IRQF_COND_SUSPEND只能用于共享中断且不能与IRQF_NO_SUSPEND同时设置如果handler为 NULL 而thread_fn非 NULL内核会安装默认主处理器irq_default_primary_handler分配struct irqaction记录 handler、thread_fn、flags、name 与 dev_id再经__setup_irq()完成与描述符的绑定。request_threaded_irq()的参数语义摘自函数注释可在 kernel/irq/manage.c 第 2085 行起查看irq要分配的中断线handler中断发生时被调用的主处理器对线程化中断而言是主处理器。若为 NULL 且thread_fn非空则安装默认主处理器thread_fn从中断处理线程中调用的函数若为 NULL 则不创建中断线程irqflags中断类型标志包括IRQF_SHARED共享中断、IRQF_TRIGGER_*指定有效沿或电平、IRQF_ONESHOT线程执行期间保持中断线被掩蔽等devname声明设备的 ASCII 名称dev_id回传给处理函数的 cookie通常使用设备数据结构地址。线程化中断采用拆分处理器设计handler仍在硬中断上下文中执行负责检查中断是否来自本设备若是则在设备侧禁用中断并返回IRQ_WAKE_THREAD唤醒处理线程去执行thread_fn。这种拆分对于支持共享中断是必要的。如果中断是共享的释放时必须传非 NULL 的dev_id。高层 IRQ 流控处理器flow handlers通用层提供了一组预定义的 irq-flow 方法handle_level_irq()handle_edge_irq()handle_fasteoi_irq()handle_simple_irq()handle_percpu_irq()handle_edge_eoi_irq()handle_bad_irq()流控处理器无论预置的还是架构自定义的由架构在启动期间或设备初始化期间分配给具体中断。这些函数的完整实现集中在 kernel/irq/chip.chandle_bad_irq则在 kernel/irq/handle.c。辅助函数Helper functions辅助函数负责调用芯片原语供默认流控实现使用。原文档给出了如下简化摘录default_enable(struct irq_data *data) { desc-irq_data.chip-irq_unmask(data); } default_disable(struct irq_data *data) { if (!delay_disable(data)) desc-irq_data.chip-irq_mask(data); } default_ack(struct irq_data *data) { chip-irq_ack(data); } default_mask_ack(struct irq_data *data) { if (chip-irq_mask_ack) { chip-irq_mask_ack(data); } else { chip-irq_mask(data); chip-irq_ack(data); } } noop(struct irq_data *data) { }从源码看这些辅助函数在 kernel/irq/chip.c 中体现为mask_ack_irq()、unmask_irq()等内部封装它们统一走desc-irq_data.chip的回调。注意default_mask_ack体现了irq_mask_ack组合原语的优化意义若芯片提供该回调则一次调用完成掩蔽 ACK否则退化为两次独立调用。默认电平触发流控处理器handle_level_irq()handle_level_irq为电平触发中断提供通用实现。电平触发中断在硬件线保持有效电平期间一直处于激活状态因此通常需要先掩蔽中断等设备被确认ack后、中断线恢复到非激活电平再解除掩蔽。原文档给出的简化控制流desc-irq_data.chip-irq_mask_ack(); handle_irq_event(desc-action); desc-irq_data.chip-irq_unmask();当前源码实现kernel/irq/chip.c 第 689 行起与之呼应并在细节上做了增强void handle_level_irq(struct irq_desc *desc) { guard(raw_spinlock)(desc-lock); mask_ack_irq(desc); if (!irq_can_handle(desc)) return; kstat_incr_irqs_this_cpu(desc); handle_irq_event(desc); cond_unmask_irq(desc); }要点整个处理在desc-lock保护下进行先mask_ack_irq即文档中的irq_mask_ack随后更新每 CPU 中断统计并执行事件处理最后通过cond_unmask_irq()有条件解除掩蔽——该函数同文件第 666 行起只在中断未被禁用且处于掩蔽态、且没有挂起的 oneshot 线程时执行unmask_irq()。也就是说执行完处理函数就立即解除掩蔽只是默认情形在IRQF_ONESHOT或中断被禁用等场景下会推迟。默认快速 EOI 流控处理器handle_fasteoi_irq()handle_fasteoi_irq面向只需在处理器结束时执行一次 EOI 的中断——这类现代中断控制器把流控细节都在硬件里透明处理了。原文档简化控制流handle_irq_event(desc-action); desc-irq_data.chip-irq_eoi();当前实现kernel/irq/chip.c 第 740 行起更完整地展示了实际调度逻辑void handle_fasteoi_irq(struct irq_desc *desc) { struct irq_chip *chip desc-irq_data.chip; guard(raw_spinlock)(desc-lock); /* 亲和性变更与中断处理竞争时中断可能在新 CPU 上先于旧 CPU 完成上一次处理到达此时可能需要重发 */ if (!irq_can_handle_pm(desc)) { if (irqd_needs_resend_when_in_progress(desc-irq_data)) desc-istate | IRQS_PENDING; cond_eoi_irq(chip, desc-irq_data); return; } if (!irq_can_handle_actions(desc)) { mask_irq(desc); cond_eoi_irq(chip, desc-irq_data); return; } kstat_incr_irqs_this_cpu(desc); if (desc-istate IRQS_ONESHOT) mask_irq(desc); handle_irq_event(desc); cond_unmask_eoi_irq(desc, chip); /* 上述竞争发生时这里会重发中断 */ if (unlikely(desc-istate IRQS_PENDING)) check_irq_resend(desc, false); }可见现代实现还对 fasteoi ONESHOT、亲和性迁移期间中断重发check_irq_resend等场景做了专门处理并通过IRQCHIP_EOI_IF_HANDLED芯片标志支持仅当中断确实被处理时才调用irq_eoi()的优化cond_eoi_irq第 725 行起。同文件还提供了面向 NMI 中断线的handle_fasteoi_nmi()第 792 行起。默认边沿触发流控处理器handle_edge_irq()handle_edge_irq为边沿触发中断提供通用实现。边沿中断在硬件信号上升沿/下降沿触发事件被锁存进控制器硬件必须 ACK 才能重新使能。同一个中断源在第一次事件被处理完之前可能再次触发此时视控制器硬件情况可能需要掩蔽中断并在处理循环内部重新使能所有挂起事件处理完才退出循环。原文档简化控制流if (desc-status running) { desc-irq_data.chip-irq_mask_ack(); desc-status | pending | masked; return; } desc-irq_data.chip-irq_ack(); desc-status | running; do { if (desc-status masked) desc-irq_data.chip-irq_unmask(); desc-status ~pending; handle_irq_event(desc-action); } while (desc-status pending); desc-status ~running;当前实现kernel/irq/chip.c 第 827 行起使用了istate位IRQS_PENDING等现代标志逻辑与文档一致void handle_edge_irq(struct irq_desc *desc) { guard(raw_spinlock)(desc-lock); if (!irq_can_handle(desc)) { desc-istate | IRQS_PENDING; mask_ack_irq(desc); return; } kstat_incr_irqs_this_cpu(desc); /* 开始处理该中断 */ desc-irq_data.chip-irq_ack(desc-irq_data); do { if (unlikely(!desc-action)) { mask_irq(desc); return; } /* 处理期间又有新中断到达时可能已掩蔽中断 若期间未被禁用则重新使能 */ if (unlikely(desc-istate IRQS_PENDING)) { if (!irqd_irq_disabled(desc-irq_data) irqd_irq_masked(desc-irq_data)) unmask_irq(desc); } handle_irq_event(desc); } while ((desc-istate IRQS_PENDING) !irqd_irq_disabled(desc-irq_data)); }do/while循环即文档所述只要仍有IRQS_PENDING且中断未被显式禁用就继续处理新到达的边沿事件——这正是边沿事件可能在处理途中再次到达这一硬件特性的核心应对逻辑。默认简单流控处理器handle_simple_irq()handle_simple_irq为简单中断提供通用实现。原文档特别强调简单流控处理器不调用任何 handler/chip 原语——ACK、清除、掩蔽、解除掩蔽等都由调用方如解复用处理器自行负责。handle_irq_event(desc-action);当前实现kernel/irq/chip.c 第 614 行起在desc-lock保护下先做电源管理/动作可用性检查必要时置IRQS_PENDING等待重发然后更新统计并调用handle_irq_event(desc)。注释明确写道简单中断要么来自解复用中断处理器要么来自无需任何中断硬件控制的硬件。同文件还提供了其变体handle_untracked_irq()第 645 行起——用于解复用器无法确定具体设备的情形不做统计、随机熵与伪中断检测。默认每 CPU 流控处理器handle_percpu_irq()handle_percpu_irq为每 CPU 中断提供通用实现。每 CPU 中断只在 SMP 上可用其处理器提供不涉及锁的简化版本因为每个 CPU 各自处理自己的中断线无需跨 CPU 串行化。原文档简化控制流if (desc-irq_data.chip-irq_ack) desc-irq_data.chip-irq_ack(); handle_irq_event(desc-action); if (desc-irq_data.chip-irq_eoi) desc-irq_data.chip-irq_eoi();当前实现kernel/irq/chip.c 第 871 行起与此一致并特别注释PER CPU 中断不被串行化不要触碰desc-tot_count改用__kstat_incr_irqs_this_cpu()做本地统计再依次调用可选的irq_ack、handle_irq_event_percpu()与可选的irq_eoi。同文件还提供handle_percpu_devid_irq()第 904 行起使用每 CPU 的dev_id可用于 NMI 中断线。EOI 边沿流控处理器与坏中断处理器handle_edge_eoi_irq文档直言这是边沿处理器的一个异种abnomination仅用于驯服 powerpc/cell 上被严重损坏的中断控制器属于特例而非通用方案。handle_bad_irq用于没有真实处理器分配的伪中断spurious interrupts。其实现位于 kernel/irq/handle.c 第 33 行起打印描述符调试信息、累加每 CPU 统计并调用ack_bad_irq()。如前述所有新分配的描述符默认挂接的就是它直到架构或驱动为其设置真正的流控处理器。Quirks 与优化延迟中断禁用Delayed interrupt disable预置的通用函数是为干净的架构与芯片准备的即那些没有平台特有 IRQ 处理怪癖的实现。若某架构需要在 flow 层面实现 quirks可以通过覆盖高层 irq-flow 处理器来实现。延迟中断禁用是文档详细介绍的一个特性由 Russell King 在 ARM 中断实现中引入按中断可单独选择但整体不可配置。其核心思想调用disable_irq()时不在硬件层面立即掩蔽中断中断保持使能状态而是在流控处理器中、当实际中断事件发生时再掩蔽这避免了硬件在中断被禁用期间不保存边沿事件而导致的边沿中断丢失问题当IRQ_DISABLED标志置位期间有中断到达则在硬件层掩蔽该中断并置IRQ_PENDING位当enable_irq()重新使能中断时检查 pending 位若置位则通过硬件重触发或软件重发机制重新投递该中断若硬件不具备重触发能力使用该特性必须开启CONFIG_HARDIRQS_SW_RESEND配置项。这一机制在当前内核中演化为irqd_needs_resend_when_in_progress()与check_irq_resend()等逻辑可分别在 kernel/irq/chip.c 与 kernel/irq/resend.c 中查看前述handle_fasteoi_irq实现中的IRQS_PENDING重发路径即是其现代体现。芯片级硬件封装struct irq_chip芯片级硬件描述符结构struct irq_chip包含所有与芯片直接相关的回调供 irq flow 实现调用。结构定义在 include/linux/irq.h 第 499 行起文档列出的核心原语为irq_ack新中断的开始ACKirq_mask_ack可选推荐用于性能一次调用同时 ACK 与掩蔽irq_mask掩蔽一个中断源irq_unmask解除掩蔽一个中断源irq_eoi可选EOI 流控处理器必需irq_retrigger可选向 CPU 重发 IRQirq_set_type可选设置中断流控类型IRQ_TYPE_LEVEL 等irq_set_wake可选使能/禁用电源管理的唤醒功能原文档强调这些原语必须严格按字面意思实现——ack 就是 ACK掩蔽就是掩蔽中断线等等。由哪个流控处理器、如何组合这些底层功能单元是流控层自己的职责。也就是说irq_chip只回答硬件怎么做handle_*_irq只回答什么时候做、按什么顺序做。完整的struct irq_chip远不止这些还包括irq_startup/irq_shutdown分别默认回退到enable/disable、irq_enable默认回退到unmask、irq_set_affinity、irq_bus_lock/irq_bus_sync_unlock用于 i2c 等慢速总线芯片、MSI 相关回调irq_compose_msi_msg/irq_write_msi_msg、irq_get_irqchip_state/irq_set_irqchip_state、虚拟化相关irq_set_vcpu_affinity、IPI 相关ipi_send_single/ipi_send_mask、NMI 相关irq_nmi_setup/irq_nmi_teardown以及flags如IRQCHIP_SET_TYPE_MASKED、IRQCHIP_EOI_IF_HANDLED、IRQCHIP_SKIP_SET_WAKE等芯片标志可查看 include/linux/irq.h 第 448-540 行附近的完整注释。__do_IRQ 入口点的消亡原实现__do_IRQ()曾作为所有类型中断的备选入口点但它被证明并不适用于所有中断硬件因此被按 edge/level/simple/percpu 拆分重写。这不仅是功能层面的优化也缩短了中断处理路径。原文档写作时该函数已被弃用在当前仓库中这个入口已经不再存在——kernel/irq/下已没有__do_IRQ的实现验证了文档no longer exists的预言。SMP 下的锁机制芯片寄存器的加锁由定义芯片原语的架构负责而每个 IRQ 描述符则由通用层通过desc-lock保护。这一点在 kernel/irq/chip.c 的所有流控处理器实现中体现得非常明显——每个handle_*_irq都使用guard(raw_spinlock)(desc-lock)包裹每 CPU 处理器除外因其设计上无需锁。synchronize_irq()与disable_irq_nosync()等 SMP 专属 API 的存在也正是为了配合这种逐描述符串行化模型。通用中断芯片Generic Interrupt Chip为避免各个平台重复实现几乎相同的 IRQ 芯片逻辑核心层提供了可配置的通用中断芯片实现。原文档建议开发者动手实现相似功能前先仔细评估通用芯片是否满足需求。该实现位于 kernel/irq/generic-chip.c其文档注释可通过kernel-doc生成对外提供的主要接口包括irq_alloc_generic_chip()分配通用芯片irq_setup_generic_chip()将通用芯片与描述符绑定并安装irq_gc_mask_clr_bit()/irq_gc_mask_set_bit()通过清除/置位掩蔽寄存器中的位实现掩蔽irq_gc_ack_set_bit()通过置位寄存器位实现 ACKirq_gc_unmask_enable_reg()、irq_gc_eoi()等系列标准回调。从 kernel/irq/generic-chip.c 第 69 行起可以看到这类回调的典型模式从irq_data中取得struct irq_chip_generic *gcirq_data_get_irq_chip_data()再对gc-reg_base映射的寄存器做位操作。这使得一个寄存器位对应一条中断线的常见控制器可以在不写任何芯片代码的情况下接入通用 IRQ 层只需在平台代码中配置好寄存器基址、位掩码与流控类型。结构体与函数的自动生成文档原文档最后三章引用了内核文档系统自动生成的 API 文档对应源码位置如下需要查阅完整签名与注释时可深入对应文件Structuresinclude/linux/irq.hirq_desc、irq_data、irq_chip等与 include/linux/interrupt.hirqaction、irq_handler_t等Public Functionskernel/irq/manage.c导出 API与 kernel/irq/chip.c 的:export:部分Internal Functionskernel/irq/irqdesc.c、kernel/irq/handle.c 与 kernel/irq/chip.c 的内部函数。参考源码索引Documentation/core-api/genericirq.rst本文骨架原文档作者为 Thomas Gleixner 与 Ingo Molnar2005-2010kernel/irq/chip.c全部预置流控处理器与芯片回调调度kernel/irq/handle.chandle_bad_irq与线程唤醒逻辑kernel/irq/manage.c高层驱动 APIkernel/irq/irqdesc.c描述符分配与默认流控处理器安装kernel/irq/generic-chip.c通用中断芯片kernel/irq/resend.c软件重发机制CONFIG_HARDIRQS_SW_RESEND相关include/linux/irq.h 与 include/linux/interrupt.h核心结构体与 API 声明。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门