Chinaunix首页 | 论坛 | 博客
  • 博客访问: 98983
  • 博文数量: 35
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 147
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-05 22:10
个人简介

不经历风雨,怎能见彩虹!

文章分类

全部博文(35)

文章存档

2013年(35)

我的朋友

分类: 嵌入式

2013-04-28 14:55:26

中断处理

OK,接下来,终于可以来研究中断处理了,也就是,我们辛辛苦苦添加进系统的中断处理例程被调用的整个过程。

 

不过,在分析源代码之前,还是让我们先了解一些原理性的基础知识吧。我们都知道在处理中断时要保存现场,也就是要保存产生中断时,各个寄存器的内容。然后才能开始处理中断,处理完之后还要把现场状态恢复过来,才能返回到被中断的地方继续执行。这里要说明的是在指令跳转到中断向量的地方开始执行之前,由CPU自动帮我们完成的事情:

R14_irq = 要执行的下一条指令 + 4 ,这里的下条指令是相对于被中断指令的下条,即返回地址,R14是中断模式下的R14

SPSR_irq = CPSR,保存的当前状态寄存器,r0r12要由我们软件来保存(如果需要的话)

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_SIZEarch/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} //保存r1r12的值

// 在前面我们看到r0中存储的是进入中断时的临时栈的栈指针,在这个地址

// 处存储有r0lrspsr,将这三个值分别加载进r1-r3寄存器中

   ldmia r0, {r1 - r3}

 

// S_SPsp寄存器在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_, already fixed up for correct return/restart

   @  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就是通过把sp13位清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、清除中断描述符状态字段statusIRQ_REPLAYIRQ_WAITING标志。

3、检查desc->statusdesc->action,若desc->status设置了IRQ_INPROGRESSIRQ_DISABLED标志,即中断处理中或中断禁用,或者desc->action为空,则设置desc->statusIRQ_PENDINGIRQ_MASKED标志,屏蔽并确认中断,释放自旋锁并退出。

4、增加中断产生计数值。

5、若desc->chip->ack非空,则调用desc->chip->ack(irq)开始处理中断。

6、标记IRQ处理当前正在进行中。

7、通过一个循环来处理中断。主要完成的工作即是调用中断描述符的irqaction链。

在这里我们可以看一下Linux内核中对于中断嵌套的处理。Linux使用desc->statusIRQ_INPROGRESS来标记中断处理正在进行中,当第一次进入中断处理时,设置相应的中断描述符状态字段的该标志。则在重新使能中断后,即使前面的中断处理过程还没有结束,依然有可能会产生中断会进入中断处理流程。则在后面的中断处理流程里,进入handle_edge_irq()后,检测到前一个中断处理流程没有结束,则仅仅是设置desc->statusIRQ_PENDINGIRQ_MASKED标志便迅速退出。而在前一个中断处理流程的handle_edge_irq()的一个do{}while循环结束后,会检查desc->statusIRQ_PENDINGIRQ_MASKED标志,若设置了这两个标志,则会进行另外的一个中断处理do{}while循环。

8、清除desc->statusIRQ_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,到现在,则系统模式下中断的整个处理过程则大致分析完了。

阅读(1087) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~