在Linux的中断系统中,对于每一个硬件中断,都有一个数据结构来描述,这些数据结构最后封装成数组。这个数组就是irq_desc数组,Linux内核对它起的名字很形象,方便记忆。irq:中断的意思。desc:描述的意思。
1、irq_desc数组
irq_desc结构体在include/linux/irqdesc.h中定义,主要内容如下图:
理解上图各个项的含义,需要我们先理解中断的处理流程是什么?
分析下图,当发生中断时,硬件上中断流程方向为从左到右。软件分析处理中断时则是从右到左。
硬件中断处理流程:
外部设备 1、外部设备 n 共享一个 GPIO 中断 B,多个 GPIO 中断汇聚到 GIC(通用中断控制器)的 A 号中断,GIC 再去中断 CPU。
软件中断处理流程:
1、CPU先去读取GIC分析得出发生的是A号中断,随后去调用irq_desc[A].handler_irq函数,该函数读取 GPIO_Reg,确定GPIO中断(B号)
2、GPIO B号中断结构体中的handle_irq被调用,该函数从结构体中的irqaction链表中依次取出每个链表节点中的handler函数,即外部设备1、2的中断处理函数。(对于A号中断,我们可以认为它的irqaction链表是空的)这些函数会判断是否自己发生了中断,如果发生中断会唤醒对应的内核线程。如果没有发生中断则立刻返回。irqacion这个结构体的具体内容是什么?
2、irqaction结构体
irqaction结构体如下图所示:
1、该结构体中handler指的是中断上半部。
2、thread_fn指的是不紧急的事。当提供次函数后,当request_irq()函数被执行后,内核会创建内核线程,当执行完handler后,内核线程有机会就会去调用对应的函数thread_fn。
3、当执行request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
时,根据irq软件中断号会为某个中断提供对应的中断处理函数。irq软件中断号是如何获得的?
3、irq_data结构体
irq_data结构体如下图所示:
该结构体为中转站,里边有irq_chip结构体指针、 irq_domain结构体指针。都是指向别的结构体。其中的irq(软件中断号)和hwirq(硬件中断号)之间的联系是通过irq_domain结构体来建立联系。irq_domain会把本地的hwirq映射为全局的irq。
4、irq_domain结构体
我们先解决上边遗留的问题: 软件中断号如何获取? 软件中断号可以通过irq.h中的宏获取。也可以通过设备树获取。
如何通过设备树获取irq,首先需要在设备树中声明。如何声明?
比如gpio1 有5号中断。gpio2 也有5号中断。通过以下的声明方式就可以进行区分。
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;
这样便可以指定表明要是用的是gpio1里边的5号中断,hwirq就是5。然而我们在驱动中使用
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
来注册中断,使用的是软件中断号。irq_domain帮我们解决这个问题。
在irq_domain中irq_domain_ops结构体中有各种操作函数:
1、xlate :用来解析设备树属性,提取出hwirq 、 type等信息。
2、map : 把hwirq转换为irq。hwirq和irq之间映射关系会存在linear_revmap[]中,这样以后通过hwirq就可以直接在数组中找到对应的irq。
5、irq_chip结构体
该结构体内容如下图:
我们在 request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的 irq_chip 里的函数帮我们使能了中断。
我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用 irq_chip中的相关函数。
对于外部设备的相关清中断操作,还是需要我们自己去做,因为外部设备千变万化,内核中没有对应的清中断操作。