对于中断流程不了解的伙伴可以去看看我之前写过的文章,点击文章的链接出,我这里简单描述下,一般来说,中断是属于异常的一种,但是中断是可以屏蔽的,对于异常它是不可以屏蔽的,如下图所示,对于中断我们需要设置其中断源,图中的按键,定时器,网络数据…就是会触发中断的中断源,我可以在中断控制器中设置其是否屏蔽该中断,而对于异常部分一般是系统的出错,这种出错必须告知CPU,所以是不可以屏蔽的,当发送中断时,CPU跳到特定的地址执行指令,这个地址一般和处理器平台相关,这条指令一般是一条跳转指令跳到一个处理入口,然后保护现场,调用处理函数(如果是irq中断在处理函数还需要判断中断源然后执行相应的处理工作),恢复现场,
ARM对异常(中断)的使用过程
1.初始化: a.设置中断源,让它可以产生中断 b.设置中断控制器(屏蔽,设置优先级) c.设置CPU总开关(使能中断)
2.正常执行程序中
3.有中断产生,通知中断控制器,中断控制器通知CPU,跳到不同的地址执行程序,这些地址构成一张异常向量表,这些地址只是一条跳转指令,跳去执行某个函数
4.在该函数中,先保护现场,处理异常(分辨中断源,再调用不同的函数),恢复现场
在ARM9中可以看到在中断控制器中它有32位,每一位代表一种中断,也就是说这个中断控制器可以向CPU发出32种中断,每一种中断的处理函数都不一样,我们可以创建一个函数指针数组,每一项存放对应中断号的处理函数,比如在数组[0]处存放0号中断的处理函数指针,实际上在linux内核中它也是大概这么做的,只不过这些数组项它用一个irq_desc结构体来表示,
中断描述结构体
irq_desc结构体中的hanle_irq是这个中断总的处理函数,action是一个链表,指向一个或者多个struct irqaction结构体,在struct irqaction结构体里面的handler是具体的处理函数,所以在irq_desc结构体中的hanle_irq会调用struct irqaction结构体里面的具体的处理函数handler,接着清中断,这样做的好处就是我们不需要在我们的handler中清中断,只需要在handler中专注于我们要处理的事情就可以了
由上图可以看出,irq_data结构体里面有chip成员,在chip成员中含有一堆的函数,有屏蔽中断函数,清中断函数,使能中断函数…,我们现在来总结下linux处理中断的过程,当发生中断时,CPU跳动中断向量那里执行,在中断向量那里保存现场后调用第一个处理中断的C语言处理函数,在这个C语言函数里面它应该去读取这个硬件寄存器,根据hwirq硬件中断号在irq_desc数组里面找到一项,这一项的下标称为虚拟中断号,该项的hanle_irq会调用struct irqaction结构体里面的具体的处理函数handler,然后调用该项的的irq_data.chip函数来清中断,我们写驱动的人只需要提供handler这些具体的处理函数就可以了,该具体函数只需要关注我们要处理的事情即可
如上图所示,0号中断可以连接一个或门,该或门可以连接网卡,摄像头,当任意一个产生中断时都会产生0号中断,在数组项中找到irq_desc结构体依次调用action链表中的handler函数,然后在函数里面判断是否有数据接受,可以看出linux对于中断的处理的强大之处,支持共享中断,对于共享中断,action链表中的handler函数都会被执行一遍
如上图所示,也是共享中断的一种表现,当发生外部中断3,5,6,7任意一个时子中断控制器都会向上一级中断控制器发出通知,在上一级的中断控制器就会向CPU发出通知,CPU读取INTPND寄存器时就可以发现发生了4号中断,外部中断3,5,6,7都会构造相应的handler函数到数组[4]的action链表中,发生4号中断时,外部中断3,5,6,7的handler都会被执行一次,这样会比较浪费,我们可以读取子中断控制器的EINTPAND寄存器确定是具体几号中断发生,如第二张图所示,数组[4]的irq_desc中的handle_irq指向一个上s3c_irq_demux函数,s3c_irq_demux函数中读取EINTPAND寄存器进一步分辨中断,根据hwirq得到virq,调用irq_desc[virq].handle_irq
以前,对于每一个硬件中断(hwirq)都预先确定它的中断号(virq),这些中断号一般都写在一个头文件里,比如arch\arm\mach-s3c24xx\include\mach\irqs.h,如上图所示的宏定义就是虚拟中断号,当我们执行 request_irq(virq, my_handler)去注册一个中断时,内核根据virq可以知道对应的硬件中断,然后去设置,使能中断等,那怎么根据hwirq计算出virq?在描述hwirq转换为virq时,引入一个概念:irq_domain,域,在这个域里hwirq转换为某一个virq,引入irq_domain域后,要使用某个hwirq时,先在irq_desc数组中找到一个空闲项,它的位置就是virq,在irq_desc[virq]中放置处理函数,那从头到位查找这个irq_desc数组的空闲项显然是不高效的,LINUX中引入位图的概念,将已经填充的数组项置一,举个例子,对于2号中断,我们可以从数组的第二位开始查找,查找到为0的一项,假设这里的第二位为0,那么该2号中断的虚拟中断号就是2,这个虚拟中断号就保存在irq_domain域中,irq_domain有一个数组linear_revmap用来保存virq,发生硬件中断时,内核读取硬件信息,确定hwirq,反算出virq,然后调用 irq_desc[virq].handle_irq,最终会用到my_handle_irq,每个中断控制器都有自己的irq_domain域
所以在以前的驱动程序中request_irq(virq, my_handler)时,hwirq和virq的对应关系在头文件已经确定好了,当hwirq的中断发生时会自动找到 irq_desc[virq].handle_irq,然后调用我们注册到action链表的my_handler处理函数,现在我们使用设备树,需要我们自己声明属于哪个中断控制器的哪号中断(设备树中设备节点转换为 platform_device 的过程调用irq_domain->ops->xlate,把设备节点里的中断信息解析为hwirq,type),这些信息就会转化为虚拟中断号,然后找到空位的irq_desc项填充,这时候我们request_irq(virq,…)才能找到对应关系(设备树中设备节点转换为 platform_device 的过程调用irq_domain->ops->map建立hwirq到virq的关系)