DIY编程器网

 找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 136|回复: 0
打印 上一主题 下一主题

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

[复制链接]
跳转到指定楼层
楼主
发表于 2012-1-16 18:42:31 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

                      作者:刘洪涛,华清远见嵌入式学院金牌讲师,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_ (parent PC) and spsr_
  @ (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_, already fixed up for correct return/restart
  @ r3 - spsr_
  @ 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 //各个具体处理器在此文件中定义asmlinkage
  #ifdef __cplusplus
  #define CPP_ASMLINKAGE extern "C"
  #else
  #define CPP_ASMLINKAGE
  #endif
  #ifndef asmlinkage//如果以前没有定义asmlinkage
  #define asmlinkage CPP_ASMLINKAGE
  #endif
  对于ARM处理器的,没有定义asmlinkage,所以没有意义(不要以为参数是从堆栈传递的,对于ARM平台来说还是符合ATPCS过程调用标准,通过寄存器传递的)。
  但对于X86处理器的中是这样定义的:
  #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)中断函数的过程。后面的章节还会继续讲解后面的内容。

  “本文由华清远见http://www.embedu.org/index.htm提供”
            
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 分享分享 支持支持 反对反对
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|文字版|手机版|DIY编程器网 ( 桂ICP备14005565号-1 )

GMT+8, 2025-7-22 14:49 , 耗时 0.110497 秒, 18 个查询请求 , Gzip 开启.

各位嘉宾言论仅代表个人观点,非属DIY编程器网立场。

桂公网安备 45031202000115号

DIY编程器群(超员):41210778 DIY编程器

DIY编程器群1(满员):3044634 DIY编程器1

diy编程器群2:551025008 diy编程器群2

QQ:28000622;Email:libyoufer@sina.com

本站由桂林市临桂区技兴电子商务经营部独家赞助。旨在技术交流,请自觉遵守国家法律法规,一旦发现将做封号删号处理。

快速回复 返回顶部 返回列表