OK,接下来,终于可以来研究中断处理了,也就是,我们辛辛苦苦添加进系统的中断处理例程被调用的整个过程。
不过,在分析源代码之前,还是让我们先了解一些原理性的东西, 我们都知道在处理中断时要保存现场,然后才能处理中断,处理完之后还要把现场状态恢复过来才能返回到被中断的地方继续执行,这里要说明的是在指令跳转到中断向量的地方开始执行之前,由CPU自动帮我们做了哪些事情:
R14_irq = 要执行的下条指令地址 + 4 //这里的下条指令是相对于被中断指令的下条。即返回地址。
SPSR_irq = CPSR //保存的现场状态,r0到r12要由我们软件来保存(如果需要的话)。
CPSR[4:0] = 0b10010 //进入中断模式
CPSR[5] = 0 //在ARM模式下执行(不是Thumb下)
CPSR[7] = 1 //关掉IRQ中断, FIQ还是开着
PC = 0Xffff0018 //根据异常向量表的位置,跳转到特定的中断向量处去执行。
然后,我们还是要回到异常向量表去看一下。每当中断控制器发出产生一个中断请求,则CPU总是跑到异常向量表的中断向量处取指令来执行。将中断向量中的宏解开,则就像下面这个样子:
/* * Interrupt dispatcher */ .macro vector_stub, name, mode, correction=0 .align 5 vector_irq: sub lr, lr, #4 //修正返回地址,也就是中断处理完之后要执行的指令的地址 @ Save r0, lr_<exception> (parent PC) and spsr_<exception> @ (parent CPSR) stmia sp, {r0, lr} @ save r0, lr // 保存返回地址,因为很快要使用r0寄存器,所以也要保存r0。 mrs lr, spsr str lr, [sp, #8] @ save spsr // 向上增长的栈。 // 此时的这个栈是中断模式下的栈,ARM下中断模式下和系统模式下的 // 栈是不同的。虽然ARM提供了七个模式,但Linux只使用了两个,一 // 个是用户模式,另一个为系统模式,所以这个栈只是一个临时性的栈。 @ Prepare for SVC32 mode. IRQs remain disabled. mrs r0, cpsr eor r0, r0, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE) msr spsr_cxsf, r0 //把spsr设置为管理模式 @ @ the branch table must immediately follow this code @ and lr, lr, #0x0f // 这条指令之后lr中位spsr的低4位 mov r0, sp // 将栈指针保存在r0中,传递为后面的中断处理过程 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_<exception>, already fixed up for correct return/restart @ r3 - spsr_<exception> @ 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_infotsk只是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<<irq); 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<<irq); 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,到现在,则系统模式下中断的整个处理过程则大致分析完了。