当ARM CPU接收到中断线上过来的信号以后,会执行一系列操作,如下面所示:
1、把cpsr保存到相应模式下的spsr(spsr_mode)
2、把pc保存到相应模式下的lr (lr_mode)
3、设置cpsr为相应异常模式,并屏蔽中断
4、设置pc为相应异常处理程序的入口地址
这些都是硬件上帮你做好的,接下来的工作就需要软件来处理了,进入了异常处理程序的入口,也就是linux内核中所设置的异常向量表的vector_irq。
.section .vectors, "ax", %progbits
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq ------------IRQ Vector
W(b) vector_fiq
这些都是汇编代码,而后会进入vector_irq中来执行。我们来看下vector_irq的实现:
在kernel/arch/arm/kernel/entry-armv.S中的定义如下,其中vector_stub是一个宏,展开以后就会成为vector_irq的函数实现,在此函数中会判断中断发生时是cpu是处于用户态还是内核态,而它下面紧接着就是不同模式下发生中断后要跳转的函数地址。
kernel/arch/arm/kernel/entry-armv.S:
1009 /*
1010 * Interrupt dispatcher
1011 */
1012 vector_stub irq, IRQ_MODE, 4
1013
1014 .long __irq_usr @ 0 (USR_26 / USR_32)
1015 .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
1016 .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
1017 .long __irq_svc @ 3 (SVC_26 / SVC_32)
1018 .long __irq_invalid @ 4
1019 .long __irq_invalid @ 5
1020 .long __irq_invalid @ 6
1021 .long __irq_invalid @ 7
1022 .long __irq_invalid @ 8
1023 .long __irq_invalid @ 9
1024 .long __irq_invalid @ a
1025 .long __irq_invalid @ b
1026 .long __irq_invalid @ c
1027 .long __irq_invalid @ d
1028 .long __irq_invalid @ e
1029 .long __irq_invalid @ f
可以看出内核只有两种模式下的中断处理,一个是用户模式下发生了中断,另一个是内核态时发生了中断,两种中断分开来处理的。注意这两种模式指的是在中断发生前的现场,而代码执行到此时,cpu已经处于了中断irq模式了,前面硬件处理部分已经做了说明。
在__irq_usr和__irq_svc中,他们虽有不同,但是有些操作也是必须要做的,和裸机中断处理函数类似,都是需要先保存现场,切换CPU进入到SVC模式,在SVC模式下进入真正的C中断服务代码中,并且在最终处理完之后恢复现场,打开中断。所以说在linux内核中,中断是不会被打断和嵌套的。
那么接下来我们来看一下,CPU的执行时如何从汇编代码跑到C代码中的。在__irq_usr和__irq_svc函数中,前后都是汇编,分别是用来保存现场和恢复现场用的,中间会调用中断核心处理irq_handler,在irq_handler里面会进入C代码中来执行上层的中断服务程序。
kernel/arch/arm/kernel/entry-armv.S:
35 /*
36 * Interrupt handling.
37 */
38 .macro irq_handler
39 #ifdef CONFIG_MULTI_IRQ_HANDLER
40 ldr r1, =handle_arch_irq
41 mov r0, sp
42 adr lr, BSYM(9997f)
43 ldr pc, [r1]
44 #else
45 arch_irq_handler_default
46 #endif
47 9997:
48 .endm
irq_handler的处理有两种配置。一种是配置了CONFIG_MULTI_IRQ_HANDLER。这种情况下,linux kernel允许run time设定irq handler。如果我们需要一个linux kernel image支持多个平台,这是就需要配置这个选项。另外一种是传统的linux的做法,irq_handler实际上就是arch_irq_handler_default.
第一种方式
对于第一种方式,直接跳转到handle_arch_irq的地址处开始执行,而这个地址就是个C函数的地址,并且是可以动态设置的,由此就进入的C代码阶段进行处理了,可以搜索一下这个函数地址在哪里赋值就能跟踪到C代码处理部分了。
kernel/arch/arm/kernel/setup.c:
void __init setup_arch(char **cmdline_p)
{
……
#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif
……
}
kernel/arch/arm/kernel/irq.c:
124 #ifdef CONFIG_MULTI_IRQ_HANDLER
125 void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
126 {
127 if (handle_arch_irq)
128 return;
129
130 handle_arch_irq = handle_irq;
131 }
132 #endif
由上可知,在setup_arch的时候就会设置这个函数地址为machine中的handle_irq.除此之外也可以利用下面的set_handle_irq接口来动态设置此函数。这种方式支持irq_domain和级联,各个中断控制器负责各自的irq_domain,并负责各自硬件中断线到irq num的转换。
第二种方式
第二种就是比较老的做法了,我们接下来进行介绍arch_irq_handler_default,在函数中会调用asm_do_IRQ进入C代码来处理。
3 /*
4 * Interrupt handling. Preserves r7, r8, r9
5 */
6 .macro arch_irq_handler_default
7 get_irqnr_preamble r6, lr
8 1: get_irqnr_and_base r0, r2, r6, lr
9 movne r1, sp
10 @
11 @ routine called with r0 = irq number, r1 = struct pt_regs *
12 @
13 adrne lr, BSYM(1b)
14 bne asm_do_IRQ
15
16 #ifdef CONFIG_SMP
17 /*
18 * XXX
19 *
20 * this macro assumes that irqstat (r2) and base (r6) are
21 * preserved from get_irqnr_and_base above
22 */
23 ALT_SMP(test_for_ipi r0, r2, r6, lr)
24 ALT_UP_B(9997f)
25 movne r1, sp
26 adrne lr, BSYM(1b)
27 bne do_IPI
28 #endif
29 9997:
30 .endm
这种方法通过asm_do_IRQ函数来进入到C代码中的,我们来看此函数的实现过程:
kernel/arch/arm/kernel/irq.c:
59 /*
60 * handle_IRQ handles all hardware IRQ's. Decoded IRQs should
61 * not come via this function. Instead, they should provide their
62 * own 'handler'. Used by platform code implementing C-based 1st
63 * level decoding.
64 */
65 void handle_IRQ(unsigned int irq, struct pt_regs *regs)
66 {
67 struct pt_regs *old_regs = set_irq_regs(regs);
68
69 irq_enter();
70
71 /*
72 * Some hardware gives randomly wrong interrupts. Rather
73 * than crashing, do something sensible.
74 */
75 if (unlikely(irq >= nr_irqs)) {
76 if (printk_ratelimit())
77 printk(KERN_WARNING "Bad IRQ%u\n", irq);
78 ack_bad_irq(irq);
79 } else {
80 generic_handle_irq(irq);
81 }
82
83 irq_exit();
84 set_irq_regs(old_regs);
85 }
86
87 /*
88 * asm_do_IRQ is the interface to be used from assembly code.
89 */
90 asmlinkage void __exception_irq_entry
91 asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
92 {
93 handle_IRQ(irq, regs);
94 }
95
由此进入generic_handle_irq,它的实现是在kernel/kernel/irq/irqdesc.c中的。它会做相关的irq num的映射并且调用注册到相应irq num的irq handle。
对于ARM平台而言,我们推荐使用第一种方法,因为从逻辑上讲,中断处理就是需要根据当前的硬件中断系统的状态,转换成一个IRQ number,然后调用该IRQ number的处理函数即可。generic_handle_irq是旧的ARM SOC系统使用的方法,它处理的硬件中断号和IRQ number之间的关系非常简单。但是实际上,ARM平台上的硬件中断系统已经是越来越复杂了,需要引入interrupt controller级联,irq domain等等概念,因此,第一种方法更加适应未来的发展。