linux-2.6.26内核中ARM中断实现详解(2)

时间:2009-06-29

  作者:刘洪涛,华清远见嵌入式学院讲师,ARM公司ATC授权培训讲师。

  作者:刘洪涛,华清远见嵌入式学院讲师,ARM公司ATC授权培训讲师。

  三、中断处理过程

  这一节将以S3C2410为例,描述linux-2.6.26内核中,从中断开始,中断是如何一步一步执行到我们注册函数的。

  3.1 中断向量表 arch\arm\kernel\entry-armv.S

  __vectors_start:

  swi SYS_ERROR0

  b    vector_und + stubs_offset

  ldr pc, .LCvswi + stubs_offset

  b    vector_pa^ + stubs_offset

  b    vector_da^ + stubs_offset

  b    vector_addrexcptn + stubs_offset

  b    vector_IRq + stubs_offset

  b    vector_fiq + stubs_offset

  .globl   __vectors_end

  __vectors_end:

  中断发生后,跳转到b vector_irq + stubs_offset的位置执行。注意现在的向量表的初始位置是0xffff0000。

  3.2 中断跳转的入口位置 arch\arm\kernel\entry-armv.S

  .globl   __stubs_start

  __stubs_start:

  /*

  * Interrupt dispatcher

  */

  vector_stub    irq, IRQ_MODE, 4 @IRQ_MODE在include\asm\ptrace.h中定义:0x12

  .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

  上面代码中vector_stub宏的定义为:

  .macro vector_stub, nAME, mode, correction=0

  .align 5

  vector_\name:

  .if \correction

  sub lr, lr, #\correction

  .endif

  @

  @ Save r0, lr_<exception> (parent PC) and spsr_<exception>

  @ (parent CPSR)

  @

  stmia sp, {r0, lr} @ save r0, lr

  mrs lr, spsr

  str lr, [sp, #8] @ save spsr

  @

  @ Prepare for SVC32 mode. IRQs remain disabled.

  @

  mrs r0, cpsr

  eor r0, r0, #(\mode ^ SVC_MODE)

  msr spsr_cxsf, r0 @为后面进入svc模式做准备

  @

  @ the branch table must immediately follow this code

  @

  and lr, lr, #0x0f @进入中断前的mode的后4位

  @#define USR_MODE 0x00000010

  @#define FIQ_MODE 0x00000011

  @#define IRQ_MODE 0x00000012

  @#define SVC_MODE 0x00000013

  @#define ABT_MODE 0x00000017

  @#define UND_MODE 0x0000001b

  @#define SYSTEM_MODE 0x0000001f

  mov r0, sp

  ldr lr, [pc, lr, lsl #2] @如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr @如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc

  movs pc, lr @ 当指令的目标寄存器是PC,且指令以S结束,则它会把@ spsr的值恢复给cpsr branch to handler in SVC mode

  .endm

  .globl __stubs_start

  __stubs_start:

  /*

  * Interrupt dispatcher

  */

  vector_stub irq, IRQ_MODE, 4

  .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)

  用“irq, IRQ_MODE, 4”代替宏vector_stub中的“name, mode, correction”,找到了我们中断处理的入口位置为vector_irq(宏里面的vector_\name)。

  从上面代码中的注释可以看出,根据进入中断前的工作模式不同,程序下一步将跳转到_irq_usr 、或__irq_svc等位置。我们先选择__irq_usr作为下一步跟踪的目标。

  3.3 __irq_usr的实现 arch\arm\kernel\entry-armv.S

  __irq_usr:

  usr_entry @后面有解释

  kuser_cmpxchg_check

  #ifdef CONFIG_TRACE_IRQFLAGS

  bl trace_hardirqs_off

  #endif

  get_thread_info tsk @获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk等于r9(在entry-header.S中定义)

  #ifdef CONFIG_PREEMPT//如果定义了抢占,增加抢占数值

  ldr r8, [tsk, #TI_PREEMPT] @ get preempt count

  add r7, r8, #1 @ increment it

  str r7, [tsk, #TI_PREEMPT]

  #endif

  irq_handler @中断处理,我们关心的地方,3.4节有实现过程。

  #ifdef CONFIG_PREEMPT

  ldr r0, [tsk, #TI_PREEMPT]

  str r8, [tsk, #TI_PREEMPT]

  teq r0, r7

  strne r0, [r0, -r0]

  #endif

  #ifdef CONFIG_TRACE_IRQFLAGS

  bl trace_hardirqs_on

  #endif

  mov why, #0

  b ret_to_user @中断处理完成,返回中断产生的位置,3.7节有实现过程

  上面代码中的usr_entry是一个宏,主要实现了将usr模式下的寄存器、中断返回地址保存到堆栈中。

  .macro usr_entry

  sub sp, sp, #S_FRAME_SIZE @ S_FRAME_SIZE的值在arch\arm\kernel\asm-offsets.c

  @ 中定义 DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));实际上等于72

  stmib sp, {r1 - r12}

  ldmia r0, {r1 - r3}

  add r0, sp, #S_PC @ here for interlock avoidance

  mov r4, #-1 @ "" "" "" ""

  str r1, [sp] @ save the "real" r0 copied

  @ from the exception stack

  @

  @ We are now ready to fill in the remaining blanks on the stack:

  @

  @ r2 - lr_<exception>, already fixed up for correct return/restart

  @ r3 - spsr_<exception>

  @ r4 - orig_r0 (see pt_regs definition in ptrace.h)

  @

  @ Also, separately save sp_usr and lr_usr

  @

  stmia r0, {r2 - r4}

  stmdb r0, {sp, lr}^

  @

  @ Enable the alignment trap while in kernel mode

  @

  alignment_trap r0

  @

  @ Clear FP to mark the first stack frame

  @

  zero_fp

  .endm

  上面的这段代码主要在填充结构体pt_regs ,这里提到的struct pt_regs,在include/asm/ptrace.h中定义。此时sp指向struct pt_regs。

  struct pt_regs {

  long uregs[18];

  };

  #define ARM_cpsr uregs[16]

  #define ARM_pc uregs[15]

  #define ARM_lr uregs[14]

  #define ARM_sp uregs[13]

  #define ARM_ip uregs[12]

  #define ARM_fp uregs[11]

  #define ARM_r10 uregs[10]

  #define ARM_r9 uregs[9]

  #define ARM_r8 uregs[8]

  #define ARM_r7 uregs[7]

  #define ARM_r6 uregs[6]

  #define ARM_r5 uregs[5]

  #define ARM_r4 uregs[4]

  #define ARM_r3 uregs[3]

  #define ARM_r2 uregs[2]

  #define ARM_r1 uregs[1]

  #define ARM_r0 uregs[0]

  #define ARM_ORIG_r0 uregs[17]

  3.4 irq_handler的实现过程,arch\arm\kernel\entry-armv.S

  .macro irq_handler

  get_irqnr_preamble r5, lr

  @在include/asm/arch-s3c2410/entry-macro.s中定义了宏get_irqnr_preamble为空操作,什么都不做

  1: get_irqnr_and_base r0, r6, r5, lr @判断中断号,通过R0返回,3.5节有实现过程

  movne r1, sp

  @

  @ routine called with r0 = irq number, r1 = struct pt_regs *

  @

  adrne lr, 1b

  bne asm_do_IRQ @进入中断处理。

  ……

  .endm

  3.5 get_irqnr_and_base中断号判断过程,include/asm/arch-s3c2410/entry-macro.s

  .macro get_irqnr_and_base, irqnr, irqstat, base, tmp

  mov \base, #S3C24XX_VA_IRQ

  @@ try the interrupt offset register, since it is there

  ldr \irqstat, [ \base, #INTPND ]

  teq \irqstat, #0

  beq 1002f

  ldr \irqnr, [ \base, #INTOFFSET ] @通过判断INTOFFSET寄存器得到中断位置

  mov \tmp, #1

  tst \irqstat, \tmp, lsl \irqnr

  bne 1001f

  @@ the number specified is not a valid irq, so try

  @@ and work it out for ourselves

  mov \irqnr, #0 @@ start here

  @@ work out which irq (if any) we got

  movs \tmp, \irqstat, lsl#16

  addeq \irqnr, \irqnr, #16

  moveq \irqstat, \irqstat, lsr#16

  tst \irqstat, #0xff

  addeq \irqnr, \irqnr, #8

  moveq \irqstat, \irqstat, lsr#8

  tst \irqstat, #0xf

  addeq \irqnr, \irqnr, #4

  moveq \irqstat, \irqstat, lsr#4

  tst \irqstat, #0x3

  addeq \irqnr, \irqnr, #2

  moveq \irqstat, \irqstat, lsr#2

  tst \irqstat, #0x1

  addeq \irqnr, \irqnr, #1

  @@ we have the value

  1001:

  adds \irqnr, \irqnr, #IRQ_EINT0 @加上中断号的基准数值,得到终的中断号,注意:此时没有考虑子中断的具体情况,(子中断的问题后面会有讲解)。IRQ_EINT0在include/asm/arch-s3c2410/irqs.h中定义.从这里可以看出,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起寄存器中的中断号是不等的。

  1002:

  @@ exit here, Z flag unset if IRQ

  .endm

  3.6 asm_do_IRQ实现过程,arch/arm/kernel/irq.c

  asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

  {

  struct pt_regs *old_regs = set_irq_regs(regs);

  struct irq_desc *desc = irq_desc + irq;//根据中断号找到对应的irq_desc

  /*

  * Some hardware gives randomly wrong interrupts. Rather

  * than crashing, do something sensible.

  */

  if (irq >= NR_IRQS)

  desc = &bad_irq_desc;

  irq_enter();//没做什么特别的工作,可以跳过不看

  desc_handle_irq(irq, desc);// 根据中断号和desc进入中断处理

  /* AT91 specific workaround */

  irq_finish(irq);

  irq_exit();

  set_irq_regs(old_regs);

  }

  static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)

  {

  desc->handle_irq(irq, desc);//中断处理

  }

  上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage标识。那么这个标识的含义如何理解呢?

  该符号定义在kernel/include/linux/linkage.h中,如下所示:

  #include <asm/linkage.h>//各个具体处理器在此文件中定义asmlinkage

  #ifdef __cplusplus

  #define CPP_ASMLINKAGE extern "C"

  #else

  #define CPP_ASMLINKAGE

  #endif

  #ifndef asmlinkage//如果以前没有定义asmlinkage

  #define asmlinkage CPP_ASMLINKAGE

  #endif

  对于ARM处理器的<asm/linkage.h>,没有定义asmlinkage,所以没有意义(不要以为参数是从堆栈传递的,对于ARM平台来说还是符合ATPCS过程调用标准,通过寄存器传递的)。

  但对于X86处理器的<asm/linkage.h>中是这样定义的:

  #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

  表示函数的参数传递是通过堆栈完成的。

  3.7 描述3.3节中的ret_to_user 中断返回过程,/arch/arm/kernel/entry-common.S

  ENTRY(ret_to_user)

  ret_slow_syscall:

  disable_irq @ disable interrupts

  ldr r1, [tsk, #TI_FLAGS]

  tst r1, #_TIF_WORK_MASK

  bne work_pending

  no_work_pending:

  /* perform. architecture specific actions before user return */

  arch_ret_to_user r1, lr

  @ slow_restore_user_regs

  ldr r1, [sp, #S_PSR] @ get calling cpsr

  ldr lr, [sp, #S_PC]! @ get pc

  msr spsr_cxsf, r1 @ save in spsr_svc

  ldmdb sp, {r0 - lr}^ @ get calling r0 - lr

  mov r0, r0

  add sp, sp, #S_FRAME_SIZE - S_PC

  movs pc, lr @ return & move spsr_svc into cpsr

  第三章主要跟踪了从中断发生到调用到对应中断号的desc->handle_irq(irq, desc)中断函数的过程。后面的章节还会继续讲解后面的内容。

  “本文由华清远见
https://www.embedu.org/index.htm提供”



  
上一篇:开关电源技术参数
下一篇:Qt坐标系统与图形绘制

免责声明: 凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处。非本网作品均来自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

相关技术资料