从零开始触发中断到cpu执行
1. 按下中断,cpu找到异常向量入口
在汇编文件找到异常向量入口: arch\arm\kernel\entry-armv.S
.section .vectors, "ax", %progbits
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
比如我们的按键中断就是—> vector_irq
2. 根据异常向量找到__irq_usr或__irq_svc
根据SPSR寄存器的值,判断被中断时CPU是处于USR状态还是SVC状态,然后调用下面的__irq_usr或__irq_svc
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4 // 相当于 vector_irq: ...,
// 它会根据SPSR寄存器的值,
// 判断被中断时CPU是处于USR状态还是SVC状态,
// 然后调用下面的__irq_usr或__irq_svc
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
__irq_usr/__irq_svc: 这2个函数的处理过程类似
保存现场
调用 irq_handler
恢复现场
3.用irq_handler调用handle_arch_irq
在handle_arch_irq里根据不同的芯片找到它的中断调用函数
对于S3C2440, 找到s3c24xx_handle_irq 是用于处理中断的第一个C语言入口函数
4.执行s3c24xx_handle_irq函数
s3c24xx_handle_irq 找到我们的中断寄存器(interrupt control)
得到hwirq(比如0/1/2/3)硬件中断号,根据硬件中断号的到唯一的virtirq(虚拟中断号)图中有
根据虚拟中断号进入irq_dcsc数组找到触发中断的一项
5.进入irq_dcsc处理中断
调用虚拟中断项里------->handle_irq
handle_irq---->*action(链表)
*action(链表)----->handler(具体处理函数)
调用虚拟中断项里---->irq_data
irq_data---->irq_data.chip(清理中断)
所以使用这种强大的框架我们就只用编写handler(具体处理函数)
6.当一个中段号有多个设备时候的处理
假设我们的0号中断连接了 net芯片 和cam芯片
当有一个芯片触发中断的时候 也会调用 s3c24xx_handle_irq 用于处理中断的第一个C语言入口函数
判断出是0号中断产生了触发
根据硬件中断号找到虚拟中断号,进入irq_dcsc数组
调用数组里的handle_irq
handle_irq 分别调用 cam的handle net的handle
看是哪一个触发的中断并进行处理
7.当一个硬件中段号有多个中断引脚触发时
比如图中的在硬件中断号为4的地方,同时包括了引脚4-7
当在调用调用 s3c24xx_handle_irq时候 会去寄存器里查看是哪个引脚被触发,从而算出虚拟中断号
查看寄存器
对于上面的硬件中断如何找到虚拟中断
以前中断号(virq)跟硬件密切相关,现在的趋势是中断号跟硬件无关, 仅仅是一个标号而已
以前
对于每一个硬件中断(hwirq)都预先确定它的中断号(virq)
这些中断号一般都写在一个头文件里, 比如arch\arm\mach-s3c24xx\include\mach\irqs.h
使用时
a. 执行 request_irq(virq, my_handler) :
内核根据virq可以知道对应的硬件中断, 然后去设置、使能中断等
b. 发生硬件中断时,
内核读取硬件信息, 确定hwirq, 反算出virq,
然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler
怎么根据hwirq计算出virq?
硬件上有多个intc(中断控制器),
对于同一个hwirq数值, 会对应不同的virq
所以在讲hwirq时,应该强调"是哪一个intc的hwirq",
在描述hwirq转换为virq时, 引入一个概念: irq_domain, 域, 在这个域里hwirq转换为某一个virq
现在
当中断控制器越来越多、当中断越来越多,上述方法(virq和hwirq固定绑定)有缺陷:
a. 增加工作量, 你需要给每一个中断确定它的中断号, 写出对应的宏, 可能有成百上千个
b. 你要确保每一个硬件中断对应的中断号互不重复
有什么方法改进?
a. hwirq跟virq之间不再绑定
b. 要使用某个hwirq时,
先在irq_desc数组中找到一个空闲项, 它的位置就是virq
再在irq_desc[virq]中放置处理函数
新中断体系中, 怎么使用中断:
a.以前是request_irq发起,
现在是先在设备树文件中声明想使用哪一个中断(哪一个中断控制器下的哪一个中断)
b. 内核解析设备树时,
会根据"中断控制器"确定irq_domain,
根据"哪一个中断"确定hwirq,
然后在irq_desc数组中找出一个空闲项, 它的位置就是virq
并且把virq和hwirq的关系保存在irq_domain中: irq_domain.linear_revmap[hwirq] = virq;
c. 驱动程序 request_irq(virq, my_handler)
d. 发生硬件中断时,
内核读取硬件信息, 确定hwirq, 确定 virq = irq_domain.linear_revmap[hwirq];
然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler
举例
假设要使用子中断控制器(subintc)的n号中断, 它发生时会导致父中断控制器(intc)的m号中断:
a. 设备树表明要使用
subintc表示要使用
b. 解析设备树时,
会为找到空闲项 irq_desc[virq’], sub irq_domain.linear_revmap[n] = virq’;
会为 找到空闲项 irq_desc[virq], irq_domain.linear_revmap[m] = virq;
并且设置它的handle_irq为某个分析函数demux_func
c. 驱动程序 request_irq(virq’, my_handler)
d. 发生硬件中断时,
内核读取intc硬件信息, 确定hwirq = m, 确定 virq = irq_domain.linear_revmap[m];
然后调用 irq_desc[m].handle_irq, 即demux_func
e. demux_func:
读取sub intc硬件信息, 确定hwirq = n, 确定 virq’ = sub irq_domain.linear_revmap[n];
然后调用 irq_desc[n].handle_irq, 即my_handler
查看中断框架
在中断框架中系统帮我们做了,分为6点
1,CPU 自动进入“异常模式”。调用“异常处理函数”。
2,在“异常处理函数”中如跳到“b vector_irq + stubs_offset”。
3,调用到列表中的“__irq_usr”后,可以具体分析这个“__irq_usr”中处理的情况
4,“asm_do_IRQ”调用“irq_desc[IRQ 中断下标]以中断为下标取出里面的一项“handle_irq”
5,“handle_edge_irq”做的事:
①,desc->chip->ack() 清中断
②,handle_IRQ_event() 处理中断
③,取出“action”链表中的成员,执行“action->handler”。
6,核心在“irq_desc” 结构数组:
分析“irq_desc[]”结构数组:里面有“action 链表”,有“chip”等。
我们自己的程序在 irqrequest 我们用request_irq函数告诉内核处理函数是什么
图为linux内核中断体系架构 内核里面有数组irq_desc[0] (中断号为下标)
handle_irq 执行 a清中断 b调用action链表里面的各种处理函数
chip 指向底成硬件的访问函数 用来屏蔽中断,使能中断,响应中断
action 指向我们注册的中断处理函数
所以我们在链表里把自己的函数填入irqaction结构
使用request_irq 先看看源码的定义
传入参数:
irq:中断号。
handler:处理函数。
irqflags:上升沿触发,下降沿触发,边沿触发等。指定了快速中断或中断共享等中断处理属性.
*devname:中断名字。通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟)
文件上,或内核发生中断错误时使用。
第 5 个参数 dev_id 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需
要参考的数据地址。
内部实现
1 setup_irq (irq,action)
a 分配了irqaction结构
b 找到desc[0] 中断数组项,看是否为空的,或共享中断(一个引脚有多个中断),加入新的中断功能
2 desc->chip->settpye
把引脚设置为中断引脚
3 desc->chip->startup/enable
引脚使能
最后卸载irq
free_irq(irq,dev_id)
作用 : 出链 禁止中断
如何写int request_irq
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags const char *devname,void *dev_id)
先看 电路图我的是jz2440 v3
则四个按键对应着 0,2,11,19
中断号 在irq.h 里已经写好了
第一个参数unsigned int irq
第二个参数处理函数irq_handler_t handler
第三个参数
在irq.h里面选择什么时候触发
这里选择用双边缘触发
第四个参数
取一个名字。如“原理图”有按键 key4,所以这个名字用“S4
第五个参数
是 dev_id,它是“free_irq”函数用来从“action”链表中删除“irqaction”结构。使
用参 1 中断号“irq”定位“action”链表,再使用“dev_id”在“action”链表中找到卸载的表
项。同一个中断的不同中断处理函数必须使用不同的“dev_id”来区分,这要求注册共享中
断时参数“dev_id”必须惟一。