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

三、中断处理过程

这一节将以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_pabt + stubs_offset
                             b     vector_dabt + 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)中断函数的过程。后面的章节还会继续讲解后面的内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值