来自:linux内核修炼之道
内核使用一个通用 IRQ 层(Generic IRQ Layer,即 GenIRQ)进行中断的处理。
GenIRQ 的目的是为设备驱动程序提供有关中断处理过程的完整抽象,从而在设备驱动执行注册、激活、禁止以及释放中断等操作的同时,不必了解关于硬件的任何细节,因此不需要进行代码修改即可适用于不同的平台。
6.6.1 GenIRQ 的起源及发展
在 GenIRQ 出现之前,对于中断的处理,内核的实现是使用一个超级的服务程序__do_IRQ 来完成所有的操作。
而在 GenIRQ 最初的实现中,中断控制器的硬件操作细节被封装在 hw_interrupt_type 结构中,__do_IRQ函数则致力于完成对 IRQ 流程(IRQ Flow)的处理,只是在需要对硬件进行操作的时候,调用相应hw_interrupt_type 结构对象的成员函数,比如 ack()、end()等。
于是,原有的超级处理函数__do_IRQ 就被划分为 IRQ Flow 处理与中断控制器硬件操作两个层次。
但是,并不是所有类型的中断都具有同样的 IRQ Flow,比如电平触发中断和边沿触发中断的 IRQ Flow就截然不同,如果希望在同一个函数中都进行支持,必然会导致混乱的代码逻辑。
同时,同一个中断控制器也可能支持不同的中断类型,比如 IOAPIC 既支持电平触发又可支持边沿触发,因此它具有两个 hw_interrupt_type 结构对象:ioapic_level_type 以及 ioapic_edge_type,它们的底层硬件操作方式大致相同,区别主要体现在电平触发中断和边沿触发中断的 IRQ Flow,这样就导致了很多不必要的重复代码。
为了解决这些问题,Thomas Gleixner 和 Ingo Molnar 在 2.6.17 版内核提交了 GenIRQ 补丁,对 GenIRQ的抽象层次进行更为清晰的划分。
do_IRQ可以参考http://blog.csdn.net/xzongyuan/article/details/19495785
6.6.2 GenIRQ 的抽象层次
如图 6.13 所示,在最新的内核代码里,GenIRQ 共抽象为 3 层:中断控制器硬件操作、IRQ 流程处理以及驱动 API。
1.中断控制器硬件操作
新的 irq_chip 结构取代了旧的 hw_interrupt_type 结构,并增加了一些新的底层硬件操作成员函数,包括 mask、mask_ack、unmask、set_type 以及 set_wake。基于 irq_chip 结构的众多成员函数,内核实现了对中断控制器更为精细的管理。
2.IRQ 流程处理
在 irq_desc 结构中,irq_chip 指针取代了旧的 hw_interrupt_type 指针,同时添加了一个新的函数指针handle_irq,指向处理该 IRQ 线的实际服务程序,这样就摒弃了之前对所有中断都采用一个服务程序__do_IRQ 的做法。
中断之间最大的不同在于如何产生和被处理,GenIRQ 定义了 4 种 IRQ Flow 类型。
(1)Level-triggered(电平触发)中断,只要设备驱动了(asserts)中断线就会被激活。它们在处理时必须被屏蔽,并仅仅在设备放弃中断线之后被 unmask。
(2)Edge-triggered(边沿触发)中断,在中断线发生改变时产生,如从低电压到高电压,或者从高电压到低电压。它们在处理时不需要被屏蔽,但如果没有被屏蔽,在前一个中断处理结束之前,更多的中断将到达。
因此,内核必须跟踪正在 pending 的中断,中断服务程序必须循环处理所有的中断。
(3)Simple 中断不需要任何特别的控制,能够直接被处理。
(4)Per CPU 中断被绑定于单个 CPU 上。它们类似于 Simple 中断,甚至更加简单,因为中断服务程序仅仅运行在同一 CPU 上,不需要使用任何锁机制。
之前的内核尝试在一个函数中处理上面所有的 4 种情况,而新的代码创建了针对不同 IRQ Flow 类型的服务程序,然后将它们指定给各个 irq_desc 的 handle_irq 指针,因此代码能够根据特定的需要进行优化。
3.驱动 API
在驱动 API 层,GenIRQ 的变化相当少,因此原有的驱动一般不需要做出改变即可使用。但仍然有一些新的特点需要被了解。
(1)有一些新增标志用于 request_irq 函数。
SA_TRIGGER_LOW 和 SA_TRIGGER_HIGH:用于电平触发方式的中断,指示中断是发生在低电平还是高电平。
SA_TRIGGER_FALLING 和 SA_TRIGGER_RISING:用于边沿触发方式的中断。
(2)新增如下函数用于直接更改一个 IRQ 的 Flow 类型。
int set_irq_type(unsigned int irq, unsigned int type);
type 可以取 IRQ_TYPE_EDGE_RISING、IRQ_TYPE_EDGE_FALLING、IRQ_TYPE_EDGE_BOTH、IRQ_TYPE_LEVEL_HIGH、IRQ_TYPE_LEVEL_LOW、IRQ_TYPE_SIMPLE 和 IRQ_TYPE_PERCPU 之中的一个。
(3)某些设备可以产生将系统从 suspend 状态唤醒的中断,比如通过 keypad 中断唤醒系统。GenIRQ增加了下面的函数来激活或者禁止中断控制器的这个特点:
int set_irq_wake(unsigned int irq, unsigned int on);