您的位置:首页 > 运维架构 > Linux

ARM Linux对中断的处理--中断处理

2014-07-01 15:49 253 查看
中断处理

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
f9b1
\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<<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,到现在,则系统模式下中断的整个处理过程则大致分析完了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  arm kernel linux 中断