分类: 嵌入式
2013-04-28 14:55:26
原文地址:ARM Linux对中断的处理--内核模式下的中断处理 作者:tq08g2z
中断处理
OK,接下来,终于可以来研究中断处理了,也就是,我们辛辛苦苦添加进系统的中断处理例程被调用的整个过程。
不过,在分析源代码之前,还是让我们先了解一些原理性的基础知识吧。我们都知道在处理中断时要保存现场,也就是要保存产生中断时,各个寄存器的内容。然后才能开始处理中断,处理完之后还要把现场状态恢复过来,才能返回到被中断的地方继续执行。这里要说明的是在指令跳转到中断向量的地方开始执行之前,由CPU自动帮我们完成的事情:
R14_irq = 要执行的下一条指令 + 4 ,这里的下条指令是相对于被中断指令的下条,即返回地址,R14是中断模式下的R14。
SPSR_irq = CPSR,保存的当前状态寄存器,r0到r12要由我们软件来保存(如果需要的话)。
CPSR[4:0] = 0b10010,进入中断模式
CPSR[5] = 0,在ARM模式下执行(不是Thumb下)
CPSR[7] = 1,关掉IRQ中断, FIQ还是开着
PC = 0Xffff0018,根据异常向量表的位置,跳转到特定的中断向量处去执行。
我们还是要回到异常向量表去看一下。每当中断控制器产生一个中断请求,则CPU总是会从异常向量表的中断向量处取指令来执行,在前面对异常向量表建立过程的研究中,我们看到,中断向量是一条跳转指令,跳转到vector_irq符号处,也就是中断处理入口,中断处理的入口由一个宏来建立,将该宏解开,则得到下面这样的一段代码(arch/arm/kernel/entry-armv.S中):
.align 5
vector_irq:
// 修正返回地址,也就是中断处理完之后要执行的指令的地址
sub lr, lr, #4
// 保存返回地址,因为很快要使用r0寄存器,所以也要保存r0。
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
// 这个地方用的是向上增长的栈,而且没有改变栈顶指针。
// 此时的这个栈是中断模式下的栈,ARM下中断模式下和系统模式下的
// 栈是不同的。虽然ARM提供了七个模式,但Linux只使用了两个,一
// 个是用户模式,另一个为系统模式,所以这个栈只是一个临时性的栈。
//把spsr设置为管理模式
mrs r0, cpsr
eor r0, r0, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
// 跳转表必须紧跟在代码后面
and lr, lr, #0x0f // 这条指令之后lr中为spsr的低4位
// 将临时栈的栈顶指针保存在r0寄存器中,传递给后面的中断处理过程
mov r0, sp
ldr lr, [pc, lr, lsl #2]
// PC寄存器中保存的是当前正在取指的地址,也就是当前正在执行的指令
// 之后的第二条指令的地址,而不是紧接着当前指令的第一条指令的地址。
// 以spsr的低4位为索引,以PC值为基地址来获得相应的中断处理
// 程序的地址
movs pc, lr @ branch to handler in SVC mode
// movs 的目的对象如果是pc的话,则还会把spsr赋值给cpsr,上面我
// 们看到spsr被设成管理模式,因此这条语句过后的代码也就跑在了管
// 理模式下。
// 可以看到该汇编代码主要是把被中断的代码在执行过程中的状态(cpsr), // 返回地址(lr)等保存在中断模式下的栈里,然后进 入到管理模式下去执// 行中断,同时令r0 = sp,这样可以在管理模式下找到该地址,进而获取// spsr等信息。该汇编代码最终根据被中断的代码所处的模式跳转到相应// 的处理程序中去。
// 注意,管理模式下的栈和中断模式下的栈不是同一个。同时由于在上面
// 的代码中栈指针(sp)没有改变,因此即使后面的代码没对这个栈进行
// 出栈操作,也不会因为不断的产生中断而导致栈溢出。
.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
前面有提到过,这是一段很巧妙的位置无关的代码,它将中断产生时,CPSR的模式位的值作为相对于PC值的索引来调用相应的中断处理程序。如果在进入终中断时是用户模式,则调用__irq_usr例程,如果为系统模式,则调用__irq_svc,如果是其他模式,则说明出错了,则调用__irq_invalid。接下来我们分别瞧一下这些个不同模式的中断处理程序。
内核模式下的中断处理
内核模式下的中断处理,也就是调用__irq_svc例程,__irq_svc例程在文件arch/arm/kernel/entry-armv.S中定义,首先我们来看这个例程的定义:
__irq_svc:
svc_entry
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler
#ifdef CONFIG_PREEMPT
str r8, [tsk, #TI_PREEMPT] @ restore preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
ldr r4, [sp, #S_PSR] @ irqs are already disabled
#ifdef CONFIG_TRACE_IRQFLAGS
tst r4, #PSR_I_BIT
bleq trace_hardirqs_on
#endif
svc_exit r4 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
首先来看上面的svc_entry,这是一个宏,也在arch/arm/kernel/entry-armv.S中定义:
.macro svc_entry, stack_hole=0
UNWIND(.fnstart )
UNWIND(.save {r0 - pc} )
// 在栈中分配一个栈帧的空间用来存储各个寄存器的值。
// S_FRAME_SIZE在arch/arm/kernel/asm-offsets.c中定义,值为:
// DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));实际上
// 等于72。最后之所以又加了个4,是因为下面保存寄存器是从r1开始的
// 满递减的栈
sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
// 检查栈指针的对齐,内核要求此时的栈指针是8字节对齐的
#ifdef CONFIG_THUMB2_KERNEL
SPFIX( str r0, [sp] ) @ temporarily saved
SPFIX( mov r0, sp )
SPFIX( tst r0, #4 ) @ test original stack alignment
SPFIX( ldr r0, [sp] ) @ restored
#else
SPFIX( tst sp, #4 )
#endif
SPFIX( subeq sp, sp, #4 )
// sp指向struct pt_regs结构底部,简单的多寄存器存储指令
stmia sp, {r1 - r12} //保存r1到r12的值
// 在前面我们看到r0中存储的是进入中断时的临时栈的栈指针,在这个地址
// 处存储有r0,lr和spsr,将这三个值分别加载进r1-r3寄存器中
ldmia r0, {r1 - r3}
// S_SP为sp寄存器在pt_regs中的偏移,在文
// 件arch/arm/kernel/asm-offsets.c中定义,值为:
// DEFINE(S_SP, offsetof(struct pt_regs, ARM_sp));
// struct pt_regs {
// long uregs[18];
// };
// 则寄存器r5中存放的是pt_regs结构中存储SP的位置
add r5, sp, #S_SP - 4 @ here for interlock avoidance
mov r4, #-1 @ "" "" "" ""
// r0为中断发生以前的堆栈指针,将成为pt_regs中的sp的值
add r0, sp, #(S_FRAME_SIZE + \stack_hole - 4)
SPFIX( addeq r0, r0, #4 )
// 保存实际的r0,并使得sp指向栈帧的开始地址。
str r1, [sp, #-4]! @ save the "real" r0 copied
@ from the exception stack
mov r1, lr
@
@ We are now ready to fill in the remaining blanks on the stack:
@
@ r0 - sp_svc
@ r1 - lr_svc
@ r2 - lr_
@ r3 - spsr_
@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
stmia r5, {r0 - r4}
// 这一段代码保存所有的寄存器
asm_trace_hardirqs_off
.endm
这个宏主要就是保存各个寄存器值到栈上相应的位置,这个宏执 行完后的栈如下所示:
接着来看get_thread_info,它也是个宏,用来获取当前线程的地址。如果配置了内核抢占,则会执行宏展开的代码。线程的定义在include/linux/sched.h中:
union thread_union {
struct thread_info thread_info; // 线程属性
unsigned long stack[THREAD_SIZE/sizeof(long)]; // 栈
};
由它定义的线程是8K字节边界对齐的,并且在这8K的最低地址处存放的就是thread_info对象,即该栈拥有者线程的对象,而get_thread_info就是通过把sp低13位清0(8K边 界)来获取当前thread_info对象的地址。get_thread_info宏在arch/arm/kernel/entry-header.S中定义:
.macro get_thread_info, rd
mov \rd, sp, lsr #13
mov \rd, \rd, lsl #13
.endm
调用该宏后寄存器tsk里存放的就是当前线程对象的地址了, tsk是哪个寄存器呢,在arch/arm/kernel/entry-header.S文件中我们看到:
tsk .req r9 @ current thread_info
tsk只是r9的别名而已, 因此这时r9里保存的就是当前线程的地址。上面的那一段代码主要完成的工作即是获得线程对象基地址,进而增加线程对象的抢占计数
接着看irq_handler,它在文件arch/arm/kernel/entry-armv.S中定义:
.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr // 平台相关,获取中断号
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
// 中断处理完成后返回的地方:获得中断号的地方,根据中断控制器中相
// 应寄存器的内容作为退出条件。退出时下面的两行代码就会被略过去。
adrne lr, BSYM(1b)
// 通过上面的宏get_irqnr_and_base为调用asm_do_IRQ准备了参数中断号
// struct pt_regs *参数也早已获得,于是乎调用asm_do_IRQ来处理中断
bne asm_do_IRQ
#ifdef CONFIG_SMP
/*
* XXX
*
* this macro assumes that irqstat (r6) and base (r5) are
* preserved from get_irqnr_and_base above
*/
test_for_ipi r0, r6, r5, lr
movne r0, sp
adrne lr, BSYM(1b)
bne do_IPI
#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movne r0, sp
adrne lr, BSYM(1b)
bne do_local_timer
#endif
#endif
.endm
对于我们的平台来说get_irqnr_preamble是空的宏。irq_handler首先通过宏 get_irqnr_and_base获得中断号,存入r0。然后把上面建立的pt_regs结构的指针,也就是sp值赋给r1,把调用宏 get_irqnr_and_base的位置作为返回地址(为了循环地处理挂起的所有中断)。最后调用 asm_do_IRQ进一步处理中断。以上这些操作都建立在获得中断号的前提下,也就是有中断发生,某个外部设备触发中断的时候,kernel最终会调用到asm_do_IRQ()函数。
get_irqnr_and_base是平台相关的,这个宏查询ISPR(IRQ挂起中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中。该宏结束后,r0 = 中断号。这个宏在不同的ARM芯片上是不一样的,它需要读写中断控制器中的寄存器。对于s3c2440,代码在arch/arm/mach-s3c2410/include/entry-macro.S里,用上面的调用参数将宏展开,如下:
1:
mov r5, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr r6, [ r5, #INTPND ]
teq r6, #0
beq 1002f
ldr r0, [ r5, #INTOFFSET ]
mov lr, #1
tst r6, lr, lsl r0
bne 1001f
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov r0, #0 @@ start here
@@ work out which irq (if any) we got
movs lr, r6, lsl#16
addeq r0, r0, #16
moveq r6, r6, lsr#16
tst r6, #0xff
addeq r0, r0, #8
moveq r6, r6, lsr#8
tst r6, #0xf
addeq r0, r0, #4
moveq r6, r6, lsr#4
tst r6, #0x3
addeq r0, r0, #2
moveq r6, r6, lsr#2
tst r6, #0x1
addeq r0, r0, #1
@@ we have the value
1001:
adds r0, r0, #IRQ_EINT0
1002:
@@ exit here, Z flag unset if IRQ
我们把__irq_svc的汇编部分分析完后再来分析asm_do_IRQ()等c函数。宏irq_handler执行完毕,如果配置了抢占,则还会恢复线程对象的抢占计数,获得线程对象的标记字段值,以检查是否需要重新调度。
__irq_svc例程调用svc_exit宏来退出中断处理过程。前面的一条语句,我们看到,中断发生时的CPSR被保存在了r4寄存器中了,这个宏在arch/arm/kernel/entry-armv.S中定义:
.macro svc_exit, rpsr
msr spsr_cxsf, \rpsr
#if defined(CONFIG_CPU_32v6K)
clrex @ clear the exclusive monitor
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
#elif defined (CONFIG_CPU_V6)
ldr r0, [sp]
strex r1, r2, [sp] @ clear the exclusive monitor
ldmib sp, {r1 - pc}^ @ load r1 - pc, cpsr
#else
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
#endif
.endm
这个宏恢复中断时运行环境,也就是各个寄存器中的值,从而推出中断的处理过程。
OK,中断的流程大体就是这样的,下面我们就开始分析c部分的中断处理流程。在上面的汇编语言代码里,我们看到,系统在保存好中断时环境,获得中断号之后,调用了函数asm_do_IRQ(),从而进入中断处理的C程序部分。asm_do_IRQ()函数定义如下:
---------------------------------------------------------------------
arch/arm/kernel/irq.c
105 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
106 {
107 struct pt_regs *old_regs = set_irq_regs(regs);
108
109 irq_enter();
110
111 /*
112 * Some hardware gives randomly wrong interrupts. Rather
113 * than crashing, do something sensible.
114 */
115 if (unlikely(irq >= NR_IRQS)) {
116 if (printk_ratelimit())
117 printk(KERN_WARNING "Bad IRQ%u\n", irq);
118 ack_bad_irq(irq);
119 } else {
120 generic_handle_irq(irq);
121 }
122
123 /* AT91 specific workaround */
124 irq_finish(irq);
125
126 irq_exit();
127 set_irq_regs(old_regs);
128 }
---------------------------------------------------------------------
这个函数完成如下操作:
1、调用set_irq_regs(regs)函数更新处理器的当前帧指针,并在局部变量old_regs中保存老的帧指针。
---------------------------------------------------------------------
include/asm-generic/irq_regs.h
21 DECLARE_PER_CPU(struct pt_regs *, __irq_regs);
28 static inline struct pt_regs *set_irq_regs(struct pt_regs *new_regs)
29 {
30 struct pt_regs *old_regs, **pp_regs = &__get_cpu_var(__irq_regs);
31
32 old_regs = *pp_regs;
33 *pp_regs = new_regs;
34 return old_regs;
35 }
---------------------------------------------------------------------
2、调用irq_enter()进入一个中断处理上下文。
3、检查中断号的有效性,有些硬件会随机的给一些错误的中断,做一些检查以防止系统崩溃。如果不正确,就调用ack_bad_irq(irq),该函数会增加用来表征发生的错误中断数量的变量irq_err_count,这个变量貌似仅供了解系统状况之用。
4、若传递的中断号有效,则会掉用generic_handle_irq(irq)来处理中断。
5、调用irq_exit()来推出中断处理上下文。
6、调用set_irq_regs(old_regs)来恢复处理器的当前帧指针。
接下来我们来看看函数generic_handle_irq()对于中断的处理,这个函数仅仅是对generic_handle_irq_desc()函数的封装而已:
---------------------------------------------------------------------
include/linux/irq.h
320 static inline void generic_handle_irq(unsigned int irq)
321 {
322 generic_handle_irq_desc(irq, irq_to_desc(irq));
323 }
---------------------------------------------------------------------
generic_handle_irq_desc()函数才是最值得我们关注的:
---------------------------------------------------------------------
include/linux/irq.h
308 static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
309 {
310 #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
311 desc->handle_irq(irq, desc);
312 #else
313 if (likely(desc->handle_irq))
314 desc->handle_irq(irq, desc);
315 else
316 __do_IRQ(irq);
317 #endif
318 }
---------------------------------------------------------------------
这个函数接收两个参数,中断号及对应的中断描述符指针。体系架构相关的中断处理函数调用这个函数来进行通用IRQ层的中断处理。如果中断的irq_desc结构的handle_irq成员非空则调用它。否则,会调用__do_IRQ()来让通用的IRQ层来处理一个中断。中断描述符irq_desc结构的handle_irq成员因中断类型的不同而不同,在我们前面分析的芯片级中断初始化函数s3c24xx_init_irq()中,我们看到这个字段基本上被设置为了这么几个函数:
用来处理具有多个子中断源的中断线的情况(SoC中断控制器的特性,而不是中断共享)的一组函数s3c_irq_demux_extint4t7()、s3c_irq_demux_extint8()、s3c_irq_demux_uart0()、s3c_irq_demux_uart1)、s3c_irq_demux_uart2()、s3c_irq_demux_adc()
其他情况的handle_edge_irq()函数
还有handle_level_irq()函数,只是很快就被第一种情况的几个函数取代。
OMG,这个地方似乎好复杂,如此之多的函数。不过,结构还是蛮清晰的。首先我们来看这几个特定于SoC的函数:
---------------------------------------------------------------------
arch/arm/plat-s3c24xx/irq.c
370 static void s3c_irq_demux_adc(unsigned int irq,
371 struct irq_desc *desc)
372 {
373 unsigned int subsrc, submsk;
374 unsigned int offset = 9;
375
376 /* read the current pending interrupts, and the mask
377 * for what it is available */
378
379 subsrc = __raw_readl(S3C2410_SUBSRCPND);
380 submsk = __raw_readl(S3C2410_INTSUBMSK);
381
382 subsrc &= ~submsk;
383 subsrc >>= offset;
384 subsrc &= 3;
385
386 if (subsrc != 0) {
387 if (subsrc & 1) {
388 generic_handle_irq(IRQ_TC);
389 }
390 if (subsrc & 2) {
391 generic_handle_irq(IRQ_ADC);
392 }
393 }
394 }
396 static void s3c_irq_demux_uart(unsigned int start)
397 {
398 unsigned int subsrc, submsk;
399 unsigned int offset = start - IRQ_S3CUART_RX0;
400
401 /* read the current pending interrupts, and the mask
402 * for what it is available */
403
404 subsrc = __raw_readl(S3C2410_SUBSRCPND);
405 submsk = __raw_readl(S3C2410_INTSUBMSK);
406
407 irqdbf2("s3c_irq_demux_uart: start=%d (%d), subsrc=0x%08x,0x%08x\n",
408 start, offset, subsrc, submsk);
409
410 subsrc &= ~submsk;
411 subsrc >>= offset;
412 subsrc &= 7;
413
414 if (subsrc != 0) {
415 if (subsrc & 1)
416 generic_handle_irq(start);
417
418 if (subsrc & 2)
419 generic_handle_irq(start+1);
420
421 if (subsrc & 4)
422 generic_handle_irq(start+2);
423 }
424 }
428 static void
429 s3c_irq_demux_uart0(unsigned int irq,
430 struct irq_desc *desc)
431 {
432 irq = irq;
433 s3c_irq_demux_uart(IRQ_S3CUART_RX0);
434 }
435
436 static void
437 s3c_irq_demux_uart1(unsigned int irq,
438 struct irq_desc *desc)
439 {
440 irq = irq;
441 s3c_irq_demux_uart(IRQ_S3CUART_RX1);
442 }
443
444 static void
445 s3c_irq_demux_uart2(unsigned int irq,
446 struct irq_desc *desc)
447 {
448 irq = irq;
449 s3c_irq_demux_uart(IRQ_S3CUART_RX2);
450 }
452 static void
453 s3c_irq_demux_extint8(unsigned int irq,
454 struct irq_desc *desc)
455 {
456 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
457 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
458
459 eintpnd &= ~eintmsk;
460 eintpnd &= ~0xff; /* ignore lower irqs */
461
462 /* we may as well handle all the pending IRQs here */
463
464 while (eintpnd) {
465 irq = __ffs(eintpnd);
466 eintpnd &= ~(1<
467
468 irq += (IRQ_EINT4 - 4);
469 generic_handle_irq(irq);
470 }
471
472 }
474 static void
475 s3c_irq_demux_extint4t7(unsigned int irq,
476 struct irq_desc *desc)
477 {
478 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
479 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
480
481 eintpnd &= ~eintmsk;
482 eintpnd &= 0xff; /* only lower irqs */
483
484 /* we may as well handle all the pending IRQs here */
485
486 while (eintpnd) {
487 irq = __ffs(eintpnd);
488 eintpnd &= ~(1<
489
490 irq += (IRQ_EINT4 - 4);
491
492 generic_handle_irq(irq);
493 }
494 }
---------------------------------------------------------------------
话说这几个函数执行的操作都还是非常相似的:
SoC中断控制器中有一个中断挂起寄存器SRCPND,当相应的中断发生时,这个寄存器中相应的位就被置位,寄存器总共有32位。而实际上SoC支持多得多的中断源,于是中断控制器被扩展,中断挂起寄存器SRCPND中有些位可以表征多个中断源的发生,然后另外有子中断源挂起寄存器SUBSRCPND等来告诉系统到底发生的中断是哪一个。上面的这组函数就是找到产生中断的子中断源的中断号,然后用找到的这个中断号做为参数来调用generic_handle_irq(irq)。更多详细情况可以参考S3C24XX系列SoC的数据手册中断控制器的相关部分内容。
在前面的中断系统初始化函数中我们看到,如果中断线没有子中断源的话,则其中断描述符的handle_irq字段会被设置为handle_edge_irq()函数,接下来我们来看handle_edge_irq()函数:
---------------------------------------------------------------------
krnel/irq/chip.c
578 void
579 handle_edge_irq(unsigned int irq, struct irq_desc *desc)
580 {
581 raw_spin_lock(&desc->lock);
582
583 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
584
585 /*
586 * If we're currently running this IRQ, or its disabled,
587 * we shouldn't process the IRQ. Mark it pending, handle
588 * the necessary masking and go out
589 */
590 if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
591 !desc->action)) {
592 desc->status |= (IRQ_PENDING | IRQ_MASKED);
593 mask_ack_irq(desc, irq);
594 goto out_unlock;
595 }
596 kstat_incr_irqs_this_cpu(irq, desc);
597
598 /* Start handling the irq */
599 if (desc->chip->ack)
600 desc->chip->ack(irq);
601
602 /* Mark the IRQ currently in progress.*/
603 desc->status |= IRQ_INPROGRESS;
604
605 do {
606 struct irqaction *action = desc->action;
607 irqreturn_t action_ret;
608
609 if (unlikely(!action)) {
610 mask_irq(desc, irq);
611 goto out_unlock;
612 }
613
614 /*
615 * When another irq arrived while we were handling
616 * one, we could have masked the irq.
617 * Renable it, if it was not disabled in meantime.
618 */
619 if (unlikely((desc->status &
620 (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
621 (IRQ_PENDING | IRQ_MASKED))) {
622 unmask_irq(desc, irq);
623 }
624
625 desc->status &= ~IRQ_PENDING;
626 raw_spin_unlock(&desc->lock);
627 action_ret = handle_IRQ_event(irq, action);
628 if (!noirqdebug)
629 note_interrupt(irq, desc, action_ret);
630 raw_spin_lock(&desc->lock);
631
632 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
633
634 desc->status &= ~IRQ_INPROGRESS;
635 out_unlock:
636 raw_spin_unlock(&desc->lock);
637 }
---------------------------------------------------------------------
这个函数接收两个参数,irq为中断号, desc为相应的中断描述符。中断发生在硬件信号的上升沿或下降沿。中断被锁进中断控制器,中断必须被确认,以重新使能。在中断被确认之后,则另一个在相同的中断源上的中断就可能会发生,即使前面的一个正在被相关的中断处理程序处理。如果这种情况发生了,则通过硬件控制器,禁用中断是必要的。这需要在处理中断处理程序执行时产生的中断的中断处理循环中重新使能中断。如果所有挂起的中断都已经被处理,则循环退出。
这个函数完成如下操作:
1、获得中断描述符的自旋锁。
2、清除中断描述符状态字段status的IRQ_REPLAY和IRQ_WAITING标志。
3、检查desc->status及desc->action,若desc->status设置了IRQ_INPROGRESS或IRQ_DISABLED标志,即中断处理中或中断禁用,或者desc->action为空,则设置desc->status的IRQ_PENDING和IRQ_MASKED标志,屏蔽并确认中断,释放自旋锁并退出。
4、增加中断产生计数值。
5、若desc->chip->ack非空,则调用desc->chip->ack(irq)开始处理中断。
6、标记IRQ处理当前正在进行中。
7、通过一个循环来处理中断。主要完成的工作即是调用中断描述符的irqaction链。
在这里我们可以看一下Linux内核中对于中断嵌套的处理。Linux使用desc->status的IRQ_INPROGRESS来标记中断处理正在进行中,当第一次进入中断处理时,设置相应的中断描述符状态字段的该标志。则在重新使能中断后,即使前面的中断处理过程还没有结束,依然有可能会产生中断会进入中断处理流程。则在后面的中断处理流程里,进入handle_edge_irq()后,检测到前一个中断处理流程没有结束,则仅仅是设置desc->status的IRQ_PENDING和IRQ_MASKED标志便迅速退出。而在前一个中断处理流程的handle_edge_irq()的一个do{}while循环结束后,会检查desc->status的IRQ_PENDING和IRQ_MASKED标志,若设置了这两个标志,则会进行另外的一个中断处理do{}while循环。
8、清除desc->status的IRQ_INPROGRESS标志,释放自旋锁。
我们接着来看handle_IRQ_event()函数,这个函数定义为:
---------------------------------------------------------------------
kernel/irq/handle.c
368 irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
369 {
370 irqreturn_t ret, retval = IRQ_NONE;
371 unsigned int status = 0;
372
373 if (!(action->flags & IRQF_DISABLED))
374 local_irq_enable_in_hardirq();
375
376 do {
377 trace_irq_handler_entry(irq, action);
378 ret = action->handler(irq, action->dev_id);
379 trace_irq_handler_exit(irq, action, ret);
380
381 switch (ret) {
382 case IRQ_WAKE_THREAD:
383 /*
384 * Set result to handled so the spurious check
385 * does not trigger.
386 */
387 ret = IRQ_HANDLED;
388
389 /*
390 * Catch drivers which return WAKE_THREAD but
391 * did not set up a thread function
392 */
393 if (unlikely(!action->thread_fn)) {
394 warn_no_thread(irq, action);
395 break;
396 }
397
398 /*
399 * Wake up the handler thread for this
400 * action. In case the thread crashed and was
401 * killed we just pretend that we handled the
402 * interrupt. The hardirq handler above has
403 * disabled the device interrupt, so no irq
404 * storm is lurking.
405 */
406 if (likely(!test_bit(IRQTF_DIED,
407 &action->thread_flags))) {
408 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
409 wake_up_process(action->thread);
410 }
411
412 /* Fall through to add to randomness */
413 case IRQ_HANDLED:
414 status |= action->flags;
415 break;
416
417 default:
418 break;
419 }
420
421 retval |= ret;
422 action = action->next;
423 } while (action);
424
425 if (status & IRQF_SAMPLE_RANDOM)
426 add_interrupt_randomness(irq);
427 local_irq_disable();
428
429 return retval;
430 }
---------------------------------------------------------------------
该函数主要的工作即是逐个地调用特定中断号的action链表的handler函数,也就是我们在驱动程序中用request_irq注册的中断例程。这里需要注意的是:如果我们注册中断的时候指明可以共享的话,则必须在我们的中断例程里判断当前产生的中断是否就是我们自己的中断,这可以通过传进来的参数来判断(该参数就是我们注册时提供的action->dev_id)。
接下来来看handle_level_irq()函数,其定义为:
---------------------------------------------------------------------
kernel/irq/handle.c
472 void
473 handle_level_irq(unsigned int irq, struct irq_desc *desc)
474 {
475 struct irqaction *action;
476 irqreturn_t action_ret;
477
478 raw_spin_lock(&desc->lock);
479 mask_ack_irq(desc, irq);
480
481 if (unlikely(desc->status & IRQ_INPROGRESS))
482 goto out_unlock;
483 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
484 kstat_incr_irqs_this_cpu(irq, desc);
485
486 /*
487 * If its disabled or no action available
488 * keep it masked and get out of here
489 */
490 action = desc->action;
491 if (unlikely(!action || (desc->status & IRQ_DISABLED)))
492 goto out_unlock;
493
494 desc->status |= IRQ_INPROGRESS;
495 raw_spin_unlock(&desc->lock);
496
497 action_ret = handle_IRQ_event(irq, action);
498 if (!noirqdebug)
499 note_interrupt(irq, desc, action_ret);
500
501 raw_spin_lock(&desc->lock);
502 desc->status &= ~IRQ_INPROGRESS;
503
504 if (!(desc->status & (IRQ_DISABLED | IRQ_ONESHOT)))
505 unmask_irq(desc, irq);
506 out_unlock:
507 raw_spin_unlock(&desc->lock);
508 }
509 EXPORT_SYMBOL_GPL(handle_level_irq);
---------------------------------------------------------------------
这个函数的功能基本上和handle_edge_irq()相同,只不过这个函数用来处理电平触发的中断,而handle_edge_irq()则用来处理边缘触发的中断。
OK,到现在,则系统模式下中断的整个处理过程则大致分析完了。