精华内容
下载资源
问答
  • ARM Linux 中断向量表建立流程

    千次阅读 2012-09-27 09:18:02
    一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,...

      一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,因为在学习arm的时候,根本没去看arm中的协处理器CP15中的c1控制寄存器中的v位来控制,我们一般都使用默认的值0,则必须将中断向量表放在0x00000000~0x0000001c中。
      在看Linux内核对arm中的中断的初始化的时候,就一直对0xffff0000的地址有点怀疑,果然在网上发现这个地址不是随便写的,当我看到arm的协处理器进行控制,中断向量表的地址的时候,真的是哭笑不得啊!!
      有人肯定会问?v位是什么时候设置的呢?其实仔细的朋友就知道在head.S中,在创建完页表的时候,如add pc,r10,#PROCINFO_INITFUNC
    别急,r10保存在前面设置的procinfo的地址,但是很多人就觉得PROCINFO_INITFUNC的宏定义就不知道在哪找了,在include/asm/asm-offset.h中有定义。
      这些搞懂了,首先必须将中断向量表拷贝到0xffff0000的地址上去,把中断处理函数也拷贝到0xffff0200的地址上去,那么在中断向量表进行跳转的时候,如b vector_irq+stubs_offset,但是stubs_offset的偏移怎么设置呢?如果用b vector_irq的话,它就会跳转到原先的中断处理函数中去,因为它也拷贝到了0xffff0200的地址上去,所以将__vector_start-_stubs_start+0x200的话就转移到拷贝后的地址上去执行了。
      很多人应该会有点疑问吧,vector_irq好像找不到,别急,细心点,就在宏定义.macro vector_stubs,name,mode,correction中对各种处理函数有定义,所以很快就将中断向量表创建好了。

     

    Linux Version : 2.6.29

    1. start_kernel-->setup_arch-->early_trap_init

       1:  
        memcpy((void
     *)vectors, __vectors_start, __vectors_end - __vectors_start);
       2:  
        memcpy((void
     *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
       3:  
        memcpy((void
     *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

    对于第一行:

             __vectors_start 和 __vectors_end 定义在 arch/arm/kernel/entry-armv.S  , 它们之间保存了中断向量表。      

       1:  
        .globl    __vectors_start
       2:  
    __vectors_start:
       3:  
        swi    SYS_ERROR0   
       4:  
        b    vector_und + stubs_offset
       5:  
        ldr    pc, .LCvswi + stubs_offset
       6:  
        b    vector_pabt + stubs_offset
       7:  
        b    vector_dabt + stubs_offset
       8:  
        b    vector_addrexcptn + stubs_offset
       9:  
        b    vector_irq + stubs_offset
      10:  
        b    vector_fiq + stubs_offset
      11:  
     
      12:  
        .globl    __vectors_end
      13:  
    __vectors_end:

             vectors 的地址为CONFIG_VECTORS_BASE , 在.config中定义为0xffff0000

             所以 第1行就是把中断向量表拷贝到0xffff0000

      对于第二行: 

                 vector_stub是一个带参数的宏,第一个是name,第二个是arm excepiton mode,第三个是为了得到返回地址,lr需要减去的偏移

       1:  
        .macro    vector_stub, name, mode, correction=0
       2:  
        .align    5
       3:  
     
       4:  
    vector_/name:
       5:  
        .if
     /correction
       6:  
        sub    lr, lr, #/correction          @得到正确的返回地址
       7:  
        .endif
       8:  
     
       9:  
        @
      10:  
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
      11:  
        @ (parent CPSR)
      12:  
        @
      13:  
        stmia    sp, {r0, lr}        @ save r0, lr
      14:  
        mrs    lr, spsr
      15:  
        str    lr, [sp, #8]        @ save spsr
      16:  
     
      17:  
        @
      18:  
        @ Prepare for
     SVC32 mode.  IRQs remain disabled.
      19:  
        @ 
      20:  
        mrs    r0, cpsr
      21:  
        eor    r0, r0, #(/mode ^ SVC_MODE) @把cpsr内容与(mode^SVC_mode)异或,即r0里为SVC_MODE      
      22:  
        msr    spsr_cxsf, r0  @把r0的值写入整个spsr寄存器(cxsf表示要往哪个字节写入)
      23:  
     
      24:  
        @
      25:  
        @ the branch table must immediately follow this
     code
      26:  
        @
      27:  
        and    lr, lr, #0x0f  @lr为spsr_<exception>的值,此语句取到进入异常前的mode
      28:  
        mov    r0, sp         @ 
      29:  
        ldr    lr, [pc, lr, lsl #2] @lr=pc+mode*4,其中pc为紧接着30的指令,即vector_stub后的第一条指令
      30:  
        movs    pc, lr            @ movs会把spsr的值赋给cpsr,所以branch to handler in
     SVC mode
      31:  
    ENDPROC(vector_/name)
      32:  
        .endm

                再来看下vector 跳转表

       1:  
        .long
        __irq_usr            @  0  (USR_26 / USR_32)
       2:  
        .long
        __irq_invalid            @  1  (FIQ_26 / FIQ_32)
       3:  
        .long
        __irq_invalid            @  2  (IRQ_26 / IRQ_32)
       4:  
        .long
        __irq_svc            @  3  (SVC_26 / SVC_32)
       5:  
        .long
        __irq_invalid            @  4
       6:  
        .long
        __irq_invalid            @  5
       7:  
        .long
        __irq_invalid            @  6
       8:  
        .long
        __irq_invalid            @  7
       9:  
        .long
        __irq_invalid            @  8
      10:  
        .long
        __irq_invalid            @  9
      11:  
        .long
        __irq_invalid            @  a
      12:  
        .long
        __irq_invalid            @  b
      13:  
        .long
        __irq_invalid            @  c
      14:  
        .long
        __irq_invalid            @  d
      15:  
        .long
        __irq_invalid            @  e
      16:  
        .long
        __irq_invalid            @  f

                 这里只有usr 和svc 有入口,而其他都是invalid ,是因为linux只会从usr(application) 和svc(kernel)两种mode跳转到exception来

              __stubs_start 和 __stubs_end 之间的代码简化后为:

       1:  
    __stubs_start:
       2:  
       vector_irq:    @vector_stub    irq, IRQ_MODE, 4
       3:  
       vector_dabt:   @vector_stub    dabt, ABT_MODE, 8
       4:  
       vector_pabt:   @vector_stub    pabt, ABT_MODE, 4
       5:  
       vector_und:    @vector_stub    und, UND_MODE
       6:  
       vector_fiq:
       7:  
       vector_addrexcptn:
       8:  
       .LCvswi:
       9:  
    __stubs_end:

              由此可以知道 __stubs_start 和 __stubs_end 之间定义了各种异常的入口

             我们再来看为什么异常入口是“b vector_und + stubs_offset”, 同时为什么stubs_offset 的定义如下

    .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start

              arm 的跳转指令b 是跳转到相对于PC的一个偏移地址( offset ),汇编器在编译时会对label 减去PC 得到offset,同时vector 拷贝后是如下排列的

     

    __vectors_start

     

     

     

    B vector_<exception>

     

     

     

    __vectors_end

     

     

    +0x200

    __stubs_start

     

     

     

    vector_<exception>

     

     

     

    __stubs_end

                因此,"b vector_<exception>"  的label –PC =  offset, 而offset 为 b 指令与vector的offset,即

                             vector_<exception>-__stubs_start + ( 0x200 – ( PC_old – __vectors_start ) )

                           = vector_<exception> + __vectors_start + 0x200 – __stubs_start – PC_old

                    所以异常入口为“b vector_und + stubs_offset”, 同时stubs_offset= __vectors_start + 0x200 – __stubs_start

                我们可以通过objdump反汇编来验证:

     

    00000060 <vector_irq>: 
        .globl    __stubs_start 
    __stubs_start: 
    /* 
    * Interrupt dispatcher 
    */ 
        vector_stub    irq, IRQ_MODE, 4 
      60 :    e24ee004     sub    lr, lr, #4    ; 0x4 
      64:    e88d4001     stm    sp, {r0, lr}

     

    1d4:    e1a00000     .word    0xe1a00000 
    1d8:    e1a00000     .word    0xe1a00000 
    1dc:    e1a00000     .word    0xe1a00000

    000001e0 <vector_und>:

    /* 
    * Undef instr entry dispatcher 
    * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC 
    */

     

    __vectors_start: 
        swi    SYS_ERROR0 
    284:    ef9f0000     svc    0x009f0000 
        b    vector_und + stubs_offset 
     288:    ea0000dd     b    604 <vector_swi+0x604> 
        ldr    pc, .LCvswi + stubs_offset 
    28c:    e59ff410     ldr    pc, [pc, #1040]    ; 6a4 <vector_swi+0x6a4> 
        b    vector_pabt + stubs_offset 
    290:    ea0000bb     b    584 <vector_swi+0x584> 
        b    vector_dabt + stubs_offset 
    294:    ea00009a     b    504 <vector_swi+0x504> 
        b    vector_addrexcptn + stubs_offset 
    298:    ea0000fa     b    688 <vector_swi+0x688> 
        b    vector_irq + stubs_offset 
    29c:    ea000078     b    484 <vector_swi+0x484> 
        b    vector_fiq + stubs_offset 
    2a0:    ea0000f7     b    684 <vector_swi+0x684>

                                  0x1e0 – 0x60 + 0x200 – ( 0x288 + 8 ) – 0x284 = 0xdd*4

     

     

    ARM Linux外部中断处理过程

    最近在学习arm linux的整套外部中断的处理过程,在网上汇总了一些资料,整个过程差不多都了解到了。如果没有这些资料我真是没信心从汇编开始读代码,感谢 奔腾年代的jimmy.lee和 linux论坛的bx_bird。 
    在下面的的注释中有一些我读代码时遇到的问题,要是大家知道是怎么回事,希望多多回复。 

    ============================================= 
    一.ARM linux的中断向量表初始化分析 

    ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表. 

    /* arch/arm/kernel/traps.c */ 
    void __init trap_init(void) 
    { 
    extern void __trap_init(unsigned long); 
    unsigned long base = vectors_base(); 
    __trap_init(base); 
    if (base != 0) 
    oopsprintk(KERN_DEBUG "Relocating machine vectors to 0x%08lx\n", base); 
    #ifdef CONFIG_CPU_32 
    modify_domain(DOMAIN_USER, DOMAIN_CLIENT); 
    #endif 
    } 
    vectors_base是一个宏,它的作用是获取ARM异常向量的地址,该宏在include/arch/asm-arm/proc-armv/system.h中定义: 

    extern unsigned long cr_no_alignment; /* defined in entry-armv.S */ 
    extern unsigned long cr_alignment; /* defined in entry-armv.S */ 
    #if __LINUX_ARM_ARCH__ >= 4 
    #define vectors_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0) 
    #else 
    #define vectors_base() (0) 
    #endif 
      对于ARMv4以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(control register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual) 
      下面分析一下cr_alginment的值是在哪确定的,我们在arch/arm/kernel/entry-armv.S找到cr_alignment的定义: 

    .globl SYMBOL_NAME(cr_alignment) 
    .globl SYMBOL_NAME(cr_no_alignment) 
    SYMBOL_NAME(cr_alignment): 
    .space 4 
    SYMBOL_NAME(cr_no_alignment): 
    .space 4 

      分析过head-armv.S文件的朋友都会知道,head-armv.S是非压缩内核的入口: 

    1 .section ".text.init",#alloc,#execinstr 
    2 .type stext, #function 
    3ENTRY(stext) 
    4 mov r12, r0 
     
    6 mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode 
    7 msr cpsr_c, r0 @ and all irqs disabled 
    8 bl __lookup_processor_type 
    9 teq r10, #0 @ invalid processor? 
    10 moveq r0, #'p' @ yes, error 'p' 
    11 beq __error 
    12 bl __lookup_architecture_type 
    13 teq r7, #0 @ invalid architecture? 
    14 moveq r0, #'a' @ yes, error 'a' 
    15 beq __error 
    16 bl __create_page_tables 
    17 adr lr, __ret @ return address 
    18 add pc, r10, #12 @ initialise processor 
    19 @ (return control reg) 
    20 
    21 .type __switch_data, %object 
    22__switch_data: .long __mmap_switched 
    23 .long SYMBOL_NAME(__bss_start) 
    24 .long SYMBOL_NAME(_end) 
    25 .long SYMBOL_NAME(processor_id) 
    26 .long SYMBOL_NAME(__machine_arch_type) 
    27 .long SYMBOL_NAME(cr_alignment) 
    28 .long SYMBOL_NAME(init_task_union)+8192 
    29 
    30 .type __ret, %function 
    31__ret: ldr lr, __switch_data 
    32 mcr p15, 0, r0, c1, c0 
    33 mrc p15, 0, r0, c1, c0, 0 @ read it back. 
    34 mov r0, r0 
    35 mov r0, r0 
    36 mov pc, lr 
    这里我们关心的是从17行开始,17行code处将lr放置为__ret标号处的相对地址,以便将来某处返回时跳转到31行继续运行18行,对于我所分析的pxa270平台,它将是跳转到arch/arm/mm/proc-xscale.S中执行__xscale_setup函数,(在s3c2410平台中,它跳转到arch/arm/mm/proc-arm920.S,在 
    type __arm920_proc_info,#object 
    __arm920_proc_info: 
    .long 0x41009200 
    .long 0xff00fff0 
    .long 0x00000c1e @ mmuflags 
    b __arm920_setup 
    .long cpu_arch_name 
    .long cpu_elf_name 
    .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB 
    .long cpu_arm920_info 
    .long arm920_processor_functions 
    可以知道add pc, r10, #12 的#12意思是跳过3个指令,执行b _arm920_setup 
    在arm920_setup设置完协处理器和返回寄存器r0之后,跳回到__ret:(31行)。 
    在__xscale_setup中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其中包括设置V位=1),但在__xscale_setup中,r1寄存器并不立即写回到Cp15的control register中,而是在返回后的某个地方,接下来会慢慢分析到。__xscale_setup调用move pc, lr指令返回跳转到31行。 
      31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。 
      32,33行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。 
       
      接下来再来看一下跳转到__mmap_switched处的代码: 
    40 _mmap_switched: 
    41 adr r3, __switch_data + 4 
    42 ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat 
    43 @ sp = stack pointer 
    44 
    45 mov fp, #0 @ Clear BSS (and zero fp) 
    46 1: cmp r4, r5 
    47 strcc fp, [r4],#4 
    48 bcc 1b 
    49 
    50 str r9, [r6] @ Save processor ID 
    51 str r1, [r7] @ Save machine type 
    52 bic r2, r0, #2 @ Clear 'A' bit 
    53 stmia r8, {r0, r2} @ Save control register values 
    54 b SYMBOL_NAME(start_kernel) 

    41~42行的结果是:r4=__bss_start,r5=__end,...,r8=cr_alignment,..,这里r8保存的是cr_alignment变量的地址. 
      到了53行,由于之前r0保存的是cp15的control register(c1)的值,这里把r0的值写入r8指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。 
       
      让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S: 
        .align 5 
    __stubs_start: 
    vector_IRQ: 
         ... 
    vector_data: 
        .... 
    vector_prefetch: 
         ... 
    vector_undefinstr: 
         ... 
    vector_FIQ: disable_fiq 
         subs pc, lr, #4 
    vector_addrexcptn: 
         b vector_addrexcptn 
        ... 
    __stubs_end: 
         .equ __real_stubs_start, .LCvectors + 0x200 
    .LCvectors: swi SYS_ERROR0 
         b __real_stubs_start + (vector_undefinstr - __stubs_start) 
         ldr pc, __real_stubs_start + (.LCvswi - __stubs_start) 
         b __real_stubs_start + (vector_prefetch - __stubs_start) 
         b __real_stubs_start + (vector_data - __stubs_start) 
         b __real_stubs_start + (vector_addrexcptn - __stubs_start) 
         b __real_stubs_start + (vector_IRQ - __stubs_start) 
         b __real_stubs_start + (vector_FIQ - __stubs_start) 
    ENTRY(__trap_init) 
        stmfd sp!, {r4 - r6, lr} /* 压栈,保存数据*/ 
        /* 复制异常向量表(.LCvectors起始的8个地址)到r0指向的地址(异常向量地址),r0就是__trap_init(base)函数调用时传递的参数,不明白的请参考ATPCS*/(传递参数顺次利用r0,r1,r2,r3) 
        adr r1, .LCvectors @ set up the vectors 
        ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr} 
         stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr} 

    /* 在异常向量地址后的0x200偏移处,放置散转代码,即__stubs_start~__stubs_end之间的各个异常处理代码*/ 
         add r2, r0, #0x200 
         adr r0, __stubs_start @ copy stubs to 0x200 
         adr r1, __stubs_end 
    1: ldr r3, [r0], #4 
         str r3, [r2], #4 
         cmp r0, r1 
    blt 1b 
    LOADREGS(fd, sp!, {r4 - r6, pc}) /*出栈,恢复数据,函数__trap_init返回*/ 
    __trap_init函数填充后的向量表如下: 
    虚拟地址 异常 处理代码 
    0xffff0000 reset swi SYS_ERROR0 
    0xffff0004 undefined b __real_stubs_start + (vector_undefinstr - __stubs_start) 
    0xffff0008 软件中断 ldr pc, __real_stubs_start + (.LCvswi - __stubs_start) 
    0xffff000c 取指令异常 b __real_stubs_start + (vector_prefetch - __stubs_start) 
    0xffff0010 数据异常 b __real_stubs_start + (vector_data - __stubs_start) 
    0xffff0014 reserved b __real_stubs_start + (vector_addrexcptn - __stubs_start) 
    0xffff0018 irq b __real_stubs_start + (vector_IRQ - __stubs_start) 
    0xffff001c fiq b __real_stubs_start + (vector_FIQ - __stubs_start) 

       当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。 
    二.ARM Linux中断处理过程分析(1) 

    在我的上一篇文章(ARM linux的中断向量表初始化分析)中已经分析了ARM Linux中断向量表是如何建立的,在这篇文章中,我将分析一下Linux内核的ARM体系下,中断处理是如何响应的一个过程。 
    在ARM体系架构下,定义了7种异常,每一种异常都有自己的入口地址,即异常向量表,当异常发生时,处理器会自动跳转到相应的入口处执行。对于ARMv4及其以上的版本,异常向量表的起始位置由协处理器15(cp15)的控制寄存器(c1)里的V位(bit13)有关,当V=0时,异常向量表的起始位置在0x00000000,而当V=1时,异常向量表就起始于0xffff0000位置。在上一篇文章中,我们已经分析知道异常向量表放置于0xffff0000起始位置,而IRQ中断处理入口地址为:0xffff0018,所以当发生一IRQ中断异常时,处理器会自动跳转到0xffff0018这个虚拟地址上。 
    0xffff0018这个虚拟地址上是一条跳转指令: 
    b __real_stubs_start + (vector_IRQ - __stubs_start) 

    所以对于IRQ的处理就是从vector_IRQ标号处开始的。在linux2.4.19内核中相应代码如下: 
    __stubs_start: 
    /* 
    * Interrupt dispatcher 
    * Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC 
    */说明其实linux只用到了arm的svc和usr模式,其他的几个模式都没怎么用。 
    1 vector_IRQ: @ 
    2 @ save mode specific registers 
    3 @ 
    4 ldr r13, .LCsirq 
    5 sub lr, lr, #4 
    6 str lr, [r13] @ save lr_IRQ 
    7 mrs lr, spsr 
    8 str lr, [r13, #4] @ save spsr_IRQ 
    9 @ 
    10 @ now branch to the relevent MODE handling routine 
    11 @ 
    12 mrs r13, cpsr 
    13 bic r13, r13, #MODE_MASK 
    14 orr r13, r13, #I_BIT | MODE_SVC 
    15 msr spsr_c, r13 @ switch to SVC_32 mode 
    16 
    17 and lr, lr, #15 
    18 ldr lr, [pc, lr, lsl #2] 
    19 movs pc, lr @ Changes mode and branches 
    20 
    21.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32) 
    22 .word __irq_invalid @ 1 (FIQ_26 / FIQ_32) 
    23 .word __irq_invalid @ 2 (IRQ_26 / IRQ_32) 
    24 .word __irq_svc @ 3 (SVC_26 / SVC_32) 
    25 .word __irq_invalid @ 4 
    26 .word __irq_invalid @ 5 
    27 .word __irq_invalid @ 6 
    28 .word __irq_invalid @ 7 
    29 .word __irq_invalid @ 8 
    30 .word __irq_invalid @ 9 
    31 .word __irq_invalid @ a 
    32 .word __irq_invalid @ b 
    33 .word __irq_invalid @ c 
    34 .word __irq_invalid @ d 
    35 .word __irq_invalid @ e 
    36 .word __irq_invalid @ f 
    首先,行4~8是保存进入IRQ模式之前的pc指针(在lr_IRQ)和CPSR(在SPSR_IRQ)到.LCsirq所指向的地址中。.LCsirq相关代码也是位于entry-armv.S中: 
    .LCsirq: .word __temp_irq 
     
    __temp_irq: .word 0 @ saved lr_irq 
    .word 0 @ saved spsr_irq 
    .word -1 @ old_r0 
    在这里补充一下ARM对于异常的处理过程,可以用下面的一段伪码来表示: 
    r14_<异常模式> = return link 
    SPSR_<异常模式> = CPSR 
    CPSR[4:0] = 异常模式编码 
    CPSR[5] = 0 ;运行于ARM状态 
    If<异常模式> == Reset or FIQ then{ 
    ;当复位或响应FIQ异常时,禁止新的fiq和irq异常 
    CPSR[6] = 1; 
    CPSR[7] = 1; 
    }else if<异常模式> == IRQ then{ 
    ;当响应IRQ异常时,禁止新的IRQ异常 
    CPSR[7] = 1; 
    } 
    PC = 异常向量地址 

    所以在运行到行4~8之前时,lr为进入IRQ之前的pc指针,spsr为进入IRQ之前的cpsr指针。 
    接着,行12~15更新spsr寄存器为SVR模式,并关闭IRQ,为从IRQ模式切换到SVR模式做准备。 
    行17,根据进入IRQ模式之前的psr(因为在行7,lr已经被置以spsr_irq),获取之前的处理器模式(psr &0b1111)。 
    行18,根据获取的进入IRQ之前的处理器模式,查找相应的跳转入口(__irq_usr 对应于之前是USR模式,__irq_svc对于之前是SVC模式,对于其它模式均跳转到__irq_invalid,在linux系统中处理器进入IRQ之前只有usr和svc两种模式,其它模式均不允许开启IRQ)。此行实际上是:lr = pc+lr<<2,pc指向当前指令地址值加8个字节的地址,即pc指向当前指令的下两条指令的地址,所以pc在此时指向的是.LCtab_irq地址。 
    (这里有点疑惑要进入__irq_usr,则18行lr应该为pc+4那么向回推算第7行的mrs lr, spsr中spsr[3:0]应该为0b0001;如果要进入__irq_svc,则18行lr应该为pc+16,那么spsr[3:0]应该为0b0100; 
    而cprs[4:0]= 
    10000 User 模式 
    10011 SVC 模式 
    请达人指点迷津。。。。) 
    行19,跳转到相应入口,并且ARM寄存器r13和r14则切换到了SVC模式下的寄存器

    三.ARM Linux中断处理过程分析(2) 
    续前文,让我们先分析进入IRQ之前的处理器模式为SVC时的情况,程序会跳转到__irq_svc继续运行,其相应代码如下: 
    20__irq_svc: sub sp, sp, #S_FRAME_SIZE 
    21 stmia sp, {r0 - r12} @ save r0 - r12 
    22 ldr r7, .LCirq 
    23 add r5, sp, #S_FRAME_SIZE 
    24 ldmia r7, {r7 - r9} 
    25 add r4, sp, #S_SP 
    26 mov r6, lr 
    27 stmia r4, {r5, r6, r7, r8, r9} @ save sp_SVC, lr_SVC, pc, cpsr, old_ro 
    28 1: get_irqnr_and_base r0, r6, r5, lr 
    29 movne r1, sp 
    30 @ 
    31 @ routine called with r0 = irq number, r1 = struct pt_regs * 
    32 @ 
    33 adrsvc ne, lr, 1b 
    34 bne asm_do_IRQ 
    35 ldr r0, [sp, #S_PSR] @ irqs are already disabled 
    36 msr spsr, r0 
    37 ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr 

    行20~27:保存进入中断之前的寄存器,把它们放在堆栈中。其中#S_FRAME_SIZE和#S_SP的定义在arch/arm/kernel/entry-header.S中: 
    #ifdef CONFIG_CPU_32 
    #define S_FRAME_SIZE 72 
    #define S_OLD_R0 68 
    #define S_PSR 64 
    #else 
    #define S_FRAME_SIZE 68 
    #define S_OLD_R0 64 
    #define S_PSR 60 
    #endif 

    #define S_PC 60 
    #define S_LR 56 
    #define S_SP 52 
    #define S_IP 48 
    #define S_FP 44 
    #define S_R10 40 
    #define S_R9 36 
    #define S_R8 32 
    #define S_R7 28 
    #define S_R6 24 
    #define S_R5 20 
    #define S_R4 16 
    #define S_R3 12 
    #define S_R2 8 
    #define S_R1 4 
    #define S_R0 0 
    #define S_OFF 8 

    .LCirq在entry-armv.S中是这样定义的: 
    .LCirq: .word __temp_irq 
    这与行4处的.LCsirq定义是一样的,可见整个过程利用__temp_irq作为中转,把进入中断之前的CPSR和PC(中断处理结束后要返回的地址)放入堆栈,以便中断返回时直接恢复。 
    行20~27执行的结果是: 
    r5-> old_r0 
    cpsr 
    pc 
    lr_svc 
    r4-> sp_svc 
    r12 
    r11 
     
    r1 
    sp-> r0 
    行28的get_irqnr_and_base,它是一个宏定义,作用是获取中断号(irq number),它将被保存在r0中。另外,get_irqnr_and_base还会改变cpsr寄存器中的Z位,如果确实找到了发生的中断号,则Z位被清除,否则Z位被置位。get_irqnr_and_base这个宏定义的实现是依赖具体的硬件的,对于pxa270 cpu,其实现如下: 
    .macro get_irqnr_and_base, irqnr, irqstat, base, tmp 
    mov \base, #io_p2v(0x40000000) @ IIR Ctl = 0x40d00000 
    add \base, \base, #0x00d00000 
    ldr \irqstat, [\base, #0] @ ICIP 
    ldr \irqnr, [\base, #4] @ ICMR 
    ands \irqstat, \irqstat, \irqnr 
    beq 1001f /* 没找到中断,跳转*/ 
    rsb \irqnr, \irqstat, #0 
    and \irqstat, \irqstat, \irqnr 
    clz \irqnr, \irqstat 
    rsb \irqnr, \irqnr, #(31 - PXA_IRQ_SKIP) 
    #ifdef CONFIG_CPU_BULVERDE 
    b 1002f 
    #endif 
    1001: 
    1002: 
    .endm 

    .macro irq_prio_table 
    .endm 
    bics \irqstat, \irqstat, \irqnr 对照intmsk将intpnd中禁止的中断清0。因为intpnd在某一时刻只可以有一位为1,所以有一位被bics清0了,就会影响标志位从而beq跳转,return r0=0;从1001:开始所作的事情是循环查intpnd哪一位置为了1。有点疑惑的是tst 指令: 
    tst 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 tst 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。 
    那么这里的tst \irqstat, #1,当zero置1了表示有中断位,为什么下面是bne 1002f而不是beq?请教请教。。。。。。。) 


    asm_do_IRQ是用C语言编码的函数,它在arch/arm/kernel/irq.c中被定义,其原型为: 
    asmlinkage void asm_do_IRQ(int irq, struct pt_regs *regs); 
    这里牵扯到一个问题就是,在汇编中如何调用C语言的函数,参数是如何传递的?为了让ARM的汇编代码可与C代码一起连接,在编写ARM汇编时,应遵循一套标准,这就是ATPCS(The ARM-Thumb Procedure Call Standard)。ATPCS定义{r0~r3}为参数传递和结果返回寄存器;若参数超过4个字型(32bit),则使用堆栈进行传递;头4个参数依次存于r0...r3,大于4个的后续字型参数通过栈传送。关于栈的使用,是使用满递减的堆栈标准,也就是栈是从高地址向低地址方向增长的(递减堆栈),栈指针寄存器指向的数据是最后压入堆栈内的有效数据(满堆栈)。 
    所以在跳转到asm_do_IRQ函数之前,r0就必须设置为中断号(行28get_irqnr_and_base把中断号放置于r0),r1就必须是指向pt_regs这样结构(定义于include/asm-arm/proc-armv/ptrace.h)的指针,而行29把sp指针赋予r1,就完成了这样的一个调用准备。 
    行35~37:恢复寄存器,返回到发生中断之前的代码中继续执行。 
    这就是整个ARM linux中断处理的过程。以后有时间,再继续展开asm_do_IRQ继续分析。对于进入中断前处理器模式是USR的中断处理过程(__irq_usr),这里就不再做分析,这与__irq_svc基本相同 
    asmlinkage void do_IRQ(int irq, struct pt_regs * regs) 
    { 
    struct irqdesc * desc; 
    struct irqaction * action; 
    int cpu; 

    irq = fixup_irq(irq);// 查找子中断号,如无子中断return 原irq 
    /* 
    * Some hardware gives randomly wrong interrupts. Rather 
    * than crashing, do something sensible. 
    */ 
    if (irq >= NR_IRQS) 
    goto bad_irq; 

    desc = irq_desc + irq; 

    spin_lock(&irq_controller_lock); 
    desc->mask_ack(irq); 
    /*---------------------------------- 
    void __init init_IRQ(void) 
    { 
    extern void init_dma(void); 
    int irq; 

    for (irq = 0; irq < NR_IRQS; irq++) { 
    irq_desc[irq].probe_ok = 0; 
    irq_desc[irq].valid = 0; 
    irq_desc[irq].noautoenable = 0; 
    irq_desc[irq].mask_ack = dummy_mask_unmask_irq; 
    irq_desc[irq].mask = dummy_mask_unmask_irq; 
    irq_desc[irq].unmask = dummy_mask_unmask_irq; 
    } 
    init_arch_irq(); 
    init_dma(); 
    } 
    init_arch_irq(); init_dma();最后被指向/mach-s3c2410中的s3c2410_init_irq(void)和s3c2410_init_dma(void), desc->mask_ack(irq);将在那里被填充。 

    --------------------------------*/ 

    spin_unlock(&irq_controller_lock); 

    cpu = smp_processor_id(); //#define smp_processor_id() 0 
    irq_enter(cpu, irq); 
    kstat.irqs[cpu][irq]++; 
    desc->triggered = 1; 

    /* Return with this interrupt masked if no action */ 
    action = desc->action; 
    /* 这个结构由driver通过request_irq()挂入,包括了具体的中断处理程序入口和flags.一个中断的irq_desc下面可能会挂几个action(一个action队列)来实现中断的复用。也就是说几个driver可以公用一个中断号。*/ 

    if (action) { 
    int status = 0; 

    if (desc->nomask) { 
    spin_lock(&irq_controller_lock); 
    desc->unmask(irq); 
    spin_unlock(&irq_controller_lock); 
    } 

    if (!(action->flags & SA_INTERRUPT)) 
    /* SA_INTERRUPT Disable local interrupts while processing 
    SA_SHIRQ is shared 
    这个flag可以一直追到request irq的action->flags = irq_flags(传递参数); 
    */ 
    __sti();//清除cpsr的I_bit,开中断。 
    /*如果在上面的nomask处判断后,没有执行unmask动作,那么这里的__sti只是允许不同中断通道(即icip上不同的位)上的嵌套*/ 
    do { 
    status |= action->flags; 
    action->handler(irq, action->dev_id, regs); 
    action = action->next; 
    } while (action); 
    /*值得注意的是:整个action队列都会被调用,所以在driver里要判定是否是属于自己的中断*/ 
    if (status & SA_SAMPLE_RANDOM) 
    add_interrupt_randomness(irq); 
    __cli(); 

    if (!desc->nomask && desc->enabled) { 
    spin_lock(&irq_controller_lock); 
    desc->unmask(irq); 
    spin_unlock(&irq_controller_lock); 
    } 
    } 

    unsigned int fixup_irq(int irq) { 
    unsigned int ret; 
    unsigned long sub_mask, ext_mask; 

    if (irq == OS_TIMER) 
    return irq; 

    switch (irq) { 
    case IRQ_UART0: 
    sub_mask = SUBSRCPND & ~INTSUBMSK; 
    ret = get_subIRQ(sub_mask, 0, 2, irq); 
    break; 
    case IRQ_UART1: 
    sub_mask = SUBSRCPND & ~INTSUBMSK; 
    ret = get_subIRQ(sub_mask, 3, 5, irq); 
    break; 
    case IRQ_UART2: 
    sub_mask = SUBSRCPND & ~INTSUBMSK; 
    ret = get_subIRQ(sub_mask, 6, 8, irq); 
    break; 
    case IRQ_ADCTC: 
    sub_mask = SUBSRCPND & ~INTSUBMSK; 
    ret = get_subIRQ(sub_mask, 9, 10, irq); 
    break; 
    case IRQ_EINT4_7: 
    ext_mask = EINTPEND & ~EINTMASK; 
    ret = get_extIRQ(ext_mask, 4, 7, irq); 
    break; 
    case IRQ_EINT8_23: 
    ext_mask = EINTPEND & ~EINTMASK; 
    ret = get_extIRQ(ext_mask, 8, 23, irq); 
    break; 
    default: 
    ret = irq; 
    } 
    这个函数一看就知道是找子中断号的, 
    inline unsigned int get_subIRQ(int irq, int begin, int end, int fail_irq) { 
    int i; 

    for(i=begin; i <= end; i++) { 
    if (irq & (1 << i)) 
    return (EXT_IRQ_OFFSET + i); 
    } 
    return fail_irq; 
    } 

    inline unsigned int get_extIRQ(int irq, int begin, int end, int fail_irq) { 
    int i; 

    for(i=begin; i <= end; i++) { 
    if (irq & (1 << i)) 
    return (NORMAL_IRQ_OFFSET - 4 + i); 
    } 
    return fail_irq; 
    } 
    #define NORMAL_IRQ_OFFSET 32 
    #define EXT_IRQ_OFFSET (20 +NORMAL_IRQ_OFFSET) 

    ========================================= 
    申请中断: 
    int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *), 
    unsigned long irq_flags, const char * devname, void *dev_id) 
    { 
    unsigned long retval; 
    struct irqaction *action; 

    if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler || 
    (irq_flags & SA_SHIRQ && !dev_id)) 
    return -EINVAL; 

    action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL); 
    if (!action) 
    return -ENOMEM; 

    action->handler = handler; 
    action->flags = irq_flags; 
    action->mask = 0; 
    action->name = devname; 
    action->next = NULL; 
    action->dev_id = dev_id; 

    retval = setup_arm_irq(irq, action); /* 把这个action挂到对应irq的action链表中*/ 

    if (retval) 
    kfree(action); 
    return retval; 
    } 

    int setup_arm_irq(int irq, struct irqaction * new) 
    { 
    int shared = 0; 
    struct irqaction *old, **p; /*这里的**p 用的太妙了*/ 
    unsigned long flags; 
    struct irqdesc *desc; 

    /* 
    * Some drivers like serial.c use request_irq() heavily, 
    * so we have to be careful not to interfere with a 
    * running system. 
    */ 
    if (new->flags & SA_SAMPLE_RANDOM) { 
    /* 
    * This function might sleep, we want to call it first, 
    * outside of the atomic block. 
    * Yes, this might clear the entropy pool if the wrong 
    * driver is attempted to be loaded, without actually 
    * installing a new handler, but is this really a problem, 
    * only the sysadmin is able to do this. 
    */ 
    rand_initialize_irq(irq); /*这个函数的作用是利用中断的随机性来产生随机数列*/ 
    } 

    /* 
    * The following block of code has to be executed atomically 
    */ 
    desc = irq_desc + irq; 
    spin_lock_irqsave(&irq_controller_lock, flags); 
    p = &desc->action; 
    if ((old = *p) != NULL) { 
    注意/* Can't share interrupts unless both agree to */ 
    if (!(old->flags & new->flags & SA_SHIRQ)) { 
    spin_unlock_irqrestore(&irq_controller_lock, flags); 
    return -EBUSY; 
    } 

    /* add new interrupt at end of irq queue */ 
    do { 
    p = &old->next; 
    old = *p; 
    } while (old);/*当没有下一个irqaction链表元素时,next就位null*/ 
    shared = 1; 
    } 

    *p = new; 

    if (!shared) { 
    desc->nomask = (new->flags & SA_IRQNOMASK) ? 1 : 0; 
    desc->probing = 0; 
    if (!desc->noautoenable) { 
    desc->enabled = 1; 
    desc->unmask(irq); 
    } 
    } 

    spin_unlock_irqrestore(&irq_controller_lock, flags); 
    return 0; 
    }
    四.ARM Linux中断处理过程分析(3) 

    在之前的文章中,我分析了进入IRQ之前处理器模式为SVC的情况,在本篇文章中,将要讨论的是进入IRQ之前处理器模式为USR的情形。 
    843 __irq_usr: sub sp, sp, #S_FRAME_SIZE 
    844 stmia sp, {r0 - r12} @ save r0 - r12 
    845 ldr r4, .LCirq 
    846 add r8, sp, #S_PC 
    847 ldmia r4, {r5 - r7} @ get saved PC, SPSR 
    848 stmia r8, {r5 - r7} @ save pc, psr, old_r0 
    849 stmdb r8, {sp, lr}^ 
    850 alignment_trap r4, r7, __temp_irq 
    851 zero_fp 
    852 1: get_irqnr_and_base r0, r6, r5, lr 
    853 movne r1, sp 
    854 adrsvc ne, lr, 1b 
    855 @ 
    856 @ routine called with r0 = irq number, r1 = struct pt_regs * 
    857 @ 
    858 bne asm_do_IRQ 
    859 mov why, #0 
    860 get_current_task tsk 
    861 b ret_to_user 
    __irq_usr关于中断处理的过程大体与__irq_svc是一样的,这里我们重点要分析中断处理返回时的不同。 
    研读过linux内核进程调度的朋友都知道,进程的调度可以自愿的方式随时进行(内核里:schedule、schedule_timeout;用户空间:pause、nanosleep),还可以非自愿的发生,即强制地发生在每次系统调用返回的前夕,以及每次从中断或异常处理返回到用户空间的前夕(只有在用户空间发生的中断或异常才会引起调度)。可参阅毛德操的《Linux内核源代码情景分析》上册的第4章关于进程调度的相关地方。 
    那我们就来看一下,__irq_usr在返回到usr模式(用户空间)前夕是如何强制进行进程调度的。 
    Line860,这是中断处理返回后,获取当前进程的task_struct指针,get_current_task是一个宏,它定义于arch/arm/kernel/entry-header.S中: 
    .macro get_current_task, rd 
    mov \rd, sp, lsr #13 
    mov \rd, \rd, lsl #13 
    .endm 
    该宏是先将sp的值右移13位,再左移13位,把结果返回给参数,其实也就是只保留sp值的高19位,这代表着把堆栈指针的地址round到8K地址边界上,这样它认为就得到了当前进程的task_struct数据结构了。它是因为内核在为每个进程分配一个task_struct结构时,实际上是分配两个连续的物理页面的(共8K),这两个页面的底部是用作进程的task_struct结构,而在结构的上面就用作进程的系统空间堆栈;数据结构task_struct的大小约为1K,进程系统空间堆栈大小就约为7K。当进程在系统空间运行时,常常需要访问当前进程自身的task_struct数据结构,为此内核中定义了一个宏操作current,提供指向当前进程task_struct结构的指针,它的实现实际上也与这里的get_current_task宏是差不多的。 
    /* include/asm-arm/current.h */ 
    static inline struct task_struct *get_current(void) 
    { 
    register unsigned long sp asm ("sp"); 
    return (struct task_struct *)(sp & ~0x1fff); 
    } 

    #define current (get_current()) 

    再回到lin860,get_current_task的参数是tsk,它实际上是r9寄存器,它也是定义于arch/arm/kernel/entry-header.S中的: 
    tsk .req r9 @ current task 
    这样r9寄存器就保存了当前进程的task_struct结构的指针了。 
    Line861,程序跳转到ret_to_user,以完成从中断处理到返回用户空间的过程,前面提到的进程重新调度将在那里得以体现。ret_to_user定义于arch/arm/entry-common.S中: 
    55 reschedule: 
    56 bl SYMBOL_NAME(schedule) 
    57 ret_disable_irq: 
    58 disable_irq r1 @ ensure IRQs are disabled 
    59 ENTRY(ret_to_user) 
    60 ret_slow_syscall: 
    61 ldr r1, [tsk, #TSK_NEED_RESCHED] 
    62 ldr r2, [tsk, #TSK_SIGPENDING] 
    63 teq r1, #0 @ need_resched => schedule() 
    64 bne reschedule 
    65 1: teq r2, #0 @ sigpending => do_signal() 
    66 bne __do_signal 
    67 restore: 
    68 restore_user_regs 
    69 
    70 __do_signal: 
    71 enable_irq r1 
    72 mov r0, #0 @ NULL 'oldset' 
    73 mov r1, sp @ 'regs' 
    74 mov r2, why @ 'syscall' 
    75 bl SYMBOL_NAME(do_signal) @ note the bl above sets lr 
    76 disable_irq r1 @ ensure IRQs are disabled 
    77 b restore 
    Line61,TSK_NEED_RESCHED值为20,它是task_struct结构中其成员变量need_resched相对于结构首地址的偏移量,所以此时r1的值就是当前进程task_struct结构里need_resched变量的值。同理在line62,r2存储就是task_struct->sigpenging的值。 
    从line63~64可见,只有在当前进程的task_struct结构中的need_resched字段为非0时才会转到reschedule处去调用schedule,那么,谁来设置这个字段呢?当然是内核,从用户空间是访问不到进程的task_struct结构的,那么,内核又是在什么情况下设置这个字段的呢?除当前进程通过系统调用自愿让出运行以及在系统调用中因某种原因受阻以外,主要就是当因某种原因唤醒一个进程的时候,以及在时钟中断服务程序发现当前进程已经连续运行太久的时候。(此段摘抄于Linux内核源代码情景分析》) 
    Line65~66,如果当前进程的task_struct结构中的sigpedding字段为非0时才会转到__do_signal处去调用do_signal处理信号。 
    Line68, restore_user_regs,它是一个宏定义于arch/arm/kernel/head-header.S中: 
    102 /* 
    103 * Must be called with IRQs already disabled. 
    104 */ 
    105 .macro restore_user_regs 
    106 ldr r1, [sp, #S_PSR] @ Get calling cpsr 
    107 ldr lr, [sp, #S_PC]! @ Get PC 
    108 msr spsr, r1 @ save in spsr_svc 
    109 ldmdb sp, {r0 - lr}^ @ Get calling r0 - lr 
    110 mov r0, r0 
    111 add sp, sp, #S_FRAME_SIZE - S_PC 
    112 movs pc, lr @ return & move spsr_svc into cpsr 
    113 .endm 



    17 and lr, lr, #15 
    18 ldr lr, [pc, lr, lsl #2] 
    19 movs pc, lr @ Changes mode and branches 
    20 
    21.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32) 
    22 .word __irq_invalid @ 1 (FIQ_26 / FIQ_32) 
    23 .word __irq_invalid @ 2 (IRQ_26 / IRQ_32) 
    24 .word __irq_svc @ 3 (SVC_26 / SVC_32) 

    这里有点疑惑要进入__irq_usr,则18行lr应该为pc+4那么向回推算第7行的mrs lr, spsr中spsr[3:0]应该为0b0001;如果要进入__irq_svc,则18行lr应该为pc+16,那么spsr[3:0]应该为0b0100; 
    而cprs[4:0]= 
    10000 User 模式 
    10011 SVC 模式 
    请达人指点迷津。。。。) 
    行19,跳转到相应入口,并且ARM寄存器r13和r14则切换到了SVC模式下的寄存器 




    这里第18行中的pc值正好是21行的.LCtab_irq,如果是在用户空间,User模式10000,逻辑左移两位为0x0=0b0000,即pc+0x0,恰好到了.word __irq_usr ,如果是在内核空间,svc模式10011,移位后为0xc=0b1100,及pc+0xc,正好到了.word __irq_svc,一点都没错(当然不可能错,系统不是跑得好好的吗) 
    注意,pc值是当前指令地址+8 
    关于get_irqnr_and_base宏中: 

    bics \irqstat, \irqstat, \irqnr 对照intmsk将intpnd中禁止的中断清0。因为intpnd在某一时刻只可以有一位为1,所以有一位被bics清0了,就会影响标志位从而beq跳转,return r0=0;从1001:开始所作的事情是循环查intpnd哪一位置为了1。有点疑惑的是tst 指令: 
    tst 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 tst 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。 
    那么这里的tst \irqstat, #1,当zero置1了表示有中断位,为什么下面是bne 1002f而不是beq?请教请教。。。。。。。) 



    没找到你看的内核版本中该宏的详细定义,我在我的2.6.12中pxa体系中的此宏中没找到tst指令,但想你的问题估计还是对tst的误解 
    pc值是当前指令地址+8 

    是因为armv5是三级流水线么?

    pxa的宏里面好像是没用tst,这里我引申到s3c2410的宏里面。 
    tst的定义我翻的是网上搜的arm指令集,里面是这么说的: 
    TST : 测试位 
    (Test bits) 

    TST{条件}{P} <op 1>, <op 2> 

    Status = op_1 AND op_2 

    TST 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 TST 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。象 CMP 那样,你不需要指定 S 后缀。 
    TST R0, #%1 ; 测试在 R0 中是否设置了位 0。 

    我觉得在这里是有点转不过弯来了,,,




    ARM linux的中断向量表初始化分析
    Author:     jimmy.li
     Time:        2007-06-09 

      本文分析基于linux2.4.19 source,pxa 270 cpu.

      ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表.
      
    /* arch/arm/kernel/traps.c */
    void __init trap_init(void)
    {
       extern void __trap_init(unsigned long);
       unsigned long base = vectors_base();

       __trap_init(base);
       if (base != 0)
          oopsprintk(KERN_DEBUG "Relocating machine vectors to 0x%08lx\n", base);
    #ifdef CONFIG_CPU_32
       modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
    #endif
    }

        vectors_base是一个宏,它的作用是获取ARM异常向量的地址,该宏在include/arch/asm-arm/proc-armv/system.h中定义:

    extern unsigned long cr_no_alignment; /* defined in entry-armv.S */
    extern unsigned long cr_alignment; /* defined in entry-armv.S */

    #if __LINUX_ARM_ARCH__ >= 4
    #define vectors_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0)
    #else
    #define vectors_base() (0)
    #endif

      对于ARMv4以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(control register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual)
      下面分析一下cr_alginment的值是在哪确定的,我们在arch/arm/kernel/entry-armv.S找到cr_alignment的定义:

                    .globl  SYMBOL_NAME(cr_alignment)
                    .globl  SYMBOL_NAME(cr_no_alignment)
    SYMBOL_NAME(cr_alignment):
                    .space  4
    SYMBOL_NAME(cr_no_alignment):
                    .space  4

      分析过head-armv.S文件的朋友都会知道,head-armv.S是非压缩内核的入口:
                   
    1               .section ".text.init",#alloc,#execinstr
    2               .type   stext, #function
    3ENTRY(stext)    
    4               mov     r12, r0
    5                
    6               mov     r0, #F_BIT | I_BIT | MODE_SVC   @ make sure svc mode
    7               msr     cpsr_c, r0                      @ and all irqs disabled
    8               bl      __lookup_processor_type         
    9               teq     r10, #0                         @ invalid processor?
    10               moveq   r0, #'p'                        @ yes, error 'p'
    11               beq     __error
    12               bl      __lookup_architecture_type
    13               teq     r7, #0                          @ invalid architecture?
    14               moveq   r0, #'a'                        @ yes, error 'a'
    15               beq     __error
    16               bl      __create_page_tables            
    17               adr     lr, __ret                       @ return address
    18               add     pc, r10, #12                    @ initialise processor
    19                                                       @ (return control reg)
    20
    21               .type   __switch_data, %object
    22__switch_data:  .long   __mmap_switched
    23                .long   SYMBOL_NAME(__bss_start)
    24                .long   SYMBOL_NAME(_end)
    25                .long   SYMBOL_NAME(processor_id)
    26                .long   SYMBOL_NAME(__machine_arch_type)
    27                .long   SYMBOL_NAME(cr_alignment)
    28                .long   SYMBOL_NAME(init_task_union)+8192
    29
    30                .type   __ret, %function
    31__ret:          ldr     lr, __switch_data
    32                mcr     p15, 0, r0, c1, c0
    33                mrc     p15, 0, r0, c1, c0, 0           @ read it back.
    34                mov     r0, r0
    35                mov     r0, r0
    36                mov     pc, lr

      这里我们关心的是从17行开始,17行code处将lr放置为__ret标号处的相对地址,以便将来某处返回时跳转到31行继续运行;
      18行,对于我所分析的pxa270平台,它将是跳转到arch/arm/mm/proc-xscale.S中执行__xscale_setup函数,在__xscale_setup中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其中包括设置V位=1),但在__xscale_setup中,r1寄存器并不立即写回到Cp15的control register中,而是在返回后的某个地方,接下来会慢慢分析到。__xscale_setup调用move pc, lr指令返回跳转到31行。
      31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。
      32,33行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。
      
      接下来再来看一下跳转到__mmap_switched处的代码:
    40 _mmap_switched:
    41                 adr     r3, __switch_data + 4
    42                 ldmia   r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat
    43                                                        @ sp = stack pointer
    44
    45                 mov     fp, #0                          @ Clear BSS (and zero fp)
    46 1:              cmp     r4, r5
    47                 strcc   fp, [r4],#4
    48                 bcc     1b
    49
    50                 str     r9, [r6]                        @ Save processor ID
    51                 str     r1, [r7]                        @ Save machine type
    52                 bic     r2, r0, #2                      @ Clear 'A' bit
    53                 stmia   r8, {r0, r2}                    @ Save control register values
    54                 b       SYMBOL_NAME(start_kernel)

      41~42行的结果是:r4=__bss_start,r5=__end,...,r8=cr_alignment,..,这里r8保存的是cr_alignment变量的地址.
      到了53行,由于之前r0保存的是cp15的control register(c1)的值,这里把r0的值写入r8指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。
      

      让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S:
          .align 5
    __stubs_start:
    vector_IRQ:
         ...
    vector_data:
         ....
    vector_prefetch:
         ...                                                                                                                        
    vector_undefinstr:
         ...
    vector_FIQ: disable_fiq
         subs pc, lr, #4
    vector_addrexcptn:
         b vector_addrexcptn        
          ...
    __stubs_end:
          .equ __real_stubs_start, .LCvectors + 0x200

    .LCvectors: swi SYS_ERROR0
          b __real_stubs_start + (vector_undefinstr - __stubs_start)
          ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
          b __real_stubs_start + (vector_prefetch - __stubs_start)
          b __real_stubs_start + (vector_data - __stubs_start)
          b __real_stubs_start + (vector_addrexcptn - __stubs_start)
          b __real_stubs_start + (vector_IRQ - __stubs_start)
          b __real_stubs_start + (vector_FIQ - __stubs_start)

    ENTRY(__trap_init)
          stmfd sp!, {r4 - r6, lr}  /* 压栈,保存数据*/

          /* 复制异常向量表(.LCvectors起始的8个地址)到r0指向的地址(异常向量地址),r0就是__trap_init(base)函数调用时传递的参数,不明白的请参考ATPCS*/
          adr r1, .LCvectors   @ set up the vectors
          ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
          stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}
      
          /* 在异常向量地址后的0x200偏移处,放置散转代码,即__stubs_start~__stubs_end之间的各个异常处理代码*/
          add r2, r0, #0x200
          adr r0, __stubs_start  @ copy stubs to 0x200
          adr r1, __stubs_end
    1:               ldr r3, [r0], #4
         str r3, [r2], #4
         cmp r0, r1
                      blt 1b
                      LOADREGS(fd, sp!, {r4 - r6, pc}) /*出栈,恢复数据,函数__trap_init返回*/

        __trap_init函数填充后的向量表如下:
        虚拟地址      异常              处理代码
        0xffff0000      reset              swi SYS_ERROR0
        0xffff0004      undefined       b __real_stubs_start + (vector_undefinstr - __stubs_start)
        0xffff0008      软件中断      ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
        0xffff000c      取指令异常  b __real_stubs_start + (vector_prefetch - __stubs_start)
        0xffff0010      数据异常      b __real_stubs_start + (vector_data - __stubs_start)
        0xffff0014      reserved         b __real_stubs_start + (vector_addrexcptn - __stubs_start)
        0xffff0018      irq                  b __real_stubs_start + (vector_IRQ - __stubs_start)
        0xffff001c      fiq                   b __real_stubs_start + (vector_FIQ - __stubs_start)
        
       当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。
     

    -------------------------参考资料--------------------
    1, 刘淼,嵌入式系统接口设计与Linux驱动程序开发,北京航天航空大学出版社,2006.
    2, ARM Architecture Reference Manual, ARM limited,2000. 


    http://jimmy-lee.blog.hexun.com/cate.aspx?cateid=7311&cate=ARM%26Linux

     

    展开全文
  • ARM Linux 中断向量表建立流程 .

    千次阅读 2013-08-30 18:29:44
    一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,...
    一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,因为在学习arm的时候,根本没去看arm中的协处理器CP15中的c1控制寄存器中的v位来控制,我们一般都使用默认的值0,则必须将中断向量表放在0x00000000~0x0000001c中。
      在看Linux内核对arm中的中断的初始化的时候,就一直对0xffff0000的地址有点怀疑,果然在网上发现这个地址不是随便写的,当我看到arm的协处理器进行控制,中断向量表的地址的时候,真的是哭笑不得啊!!
      有人肯定会问?v位是什么时候设置的呢?其实仔细的朋友就知道在head.S中,在创建完页表的时候,如add pc,r10,#PROCINFO_INITFUNC
    别急,r10保存在前面设置的procinfo的地址,但是很多人就觉得PROCINFO_INITFUNC的宏定义就不知道在哪找了,在include/asm/asm-offset.h中有定义。
      这些搞懂了,首先必须将中断向量表拷贝到0xffff0000的地址上去,把中断处理函数也拷贝到0xffff0200的地址上去,那么在中断向量表进行跳转的时候,如b vector_irq+stubs_offset,但是stubs_offset的偏移怎么设置呢?如果用b vector_irq的话,它就会跳转到原先的中断处理函数中去,因为它也拷贝到了0xffff0200的地址上去,所以将__vector_start-_stubs_start+0x200的话就转移到拷贝后的地址上去执行了。
      很多人应该会有点疑问吧,vector_irq好像找不到,别急,细心点,就在宏定义.macro vector_stubs,name,mode,correction中对各种处理函数有定义,所以很快就将中断向量表创建好了。

     

    Linux Version : 2.6.29

    1. start_kernel-->setup_arch-->early_trap_init

       1:  
        memcpy((void
     *)vectors, __vectors_start, __vectors_end - __vectors_start);
       2:  
        memcpy((void
     *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
       3:  
        memcpy((void
     *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

    对于第一行:

             __vectors_start 和 __vectors_end 定义在 arch/arm/kernel/entry-armv.S  , 它们之间保存了中断向量表。      

       1:  
        .globl    __vectors_start
       2:  
    __vectors_start:
       3:  
        swi    SYS_ERROR0   
       4:  
        b    vector_und + stubs_offset
       5:  
        ldr    pc, .LCvswi + stubs_offset
       6:  
        b    vector_pabt + stubs_offset
       7:  
        b    vector_dabt + stubs_offset
       8:  
        b    vector_addrexcptn + stubs_offset
       9:  
        b    vector_irq + stubs_offset
      10:  
        b    vector_fiq + stubs_offset
      11:  
     
      12:  
        .globl    __vectors_end
      13:  
    __vectors_end:

             vectors 的地址为CONFIG_VECTORS_BASE , 在.config中定义为0xffff0000

             所以 第1行就是把中断向量表拷贝到0xffff0000

      对于第二行: 

                 vector_stub是一个带参数的宏,第一个是name,第二个是arm excepiton mode,第三个是为了得到返回地址,lr需要减去的偏移

       1:  
        .macro    vector_stub, name, mode, correction=0
       2:  
        .align    5
       3:  
     
       4:  
    vector_/name:
       5:  
        .if
     /correction
       6:  
        sub    lr, lr, #/correction          @得到正确的返回地址
       7:  
        .endif
       8:  
     
       9:  
        @
      10:  
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
      11:  
        @ (parent CPSR)
      12:  
        @
      13:  
        stmia    sp, {r0, lr}        @ save r0, lr
      14:  
        mrs    lr, spsr
      15:  
        str    lr, [sp, #8]        @ save spsr
      16:  
     
      17:  
        @
      18:  
        @ Prepare for
     SVC32 mode.  IRQs remain disabled.
      19:  
        @ 
      20:  
        mrs    r0, cpsr
      21:  
        eor    r0, r0, #(/mode ^ SVC_MODE) @把cpsr内容与(mode^SVC_mode)异或,即r0里为SVC_MODE      
      22:  
        msr    spsr_cxsf, r0  @把r0的值写入整个spsr寄存器(cxsf表示要往哪个字节写入)
      23:  
     
      24:  
        @
      25:  
        @ the branch table must immediately follow this
     code
      26:  
        @
      27:  
        and    lr, lr, #0x0f  @lr为spsr_<exception>的值,此语句取到进入异常前的mode
      28:  
        mov    r0, sp         @ 
      29:  
        ldr    lr, [pc, lr, lsl #2] @lr=pc+mode*4,其中pc为紧接着30的指令,即vector_stub后的第一条指令
      30:  
        movs    pc, lr            @ movs会把spsr的值赋给cpsr,所以branch to handler in
     SVC mode
      31:  
    ENDPROC(vector_/name)
      32:  
        .endm

                再来看下vector 跳转表

       1:  
        .long
        __irq_usr            @  0  (USR_26 / USR_32)
       2:  
        .long
        __irq_invalid            @  1  (FIQ_26 / FIQ_32)
       3:  
        .long
        __irq_invalid            @  2  (IRQ_26 / IRQ_32)
       4:  
        .long
        __irq_svc            @  3  (SVC_26 / SVC_32)
       5:  
        .long
        __irq_invalid            @  4
       6:  
        .long
        __irq_invalid            @  5
       7:  
        .long
        __irq_invalid            @  6
       8:  
        .long
        __irq_invalid            @  7
       9:  
        .long
        __irq_invalid            @  8
      10:  
        .long
        __irq_invalid            @  9
      11:  
        .long
        __irq_invalid            @  a
      12:  
        .long
        __irq_invalid            @  b
      13:  
        .long
        __irq_invalid            @  c
      14:  
        .long
        __irq_invalid            @  d
      15:  
        .long
        __irq_invalid            @  e
      16:  
        .long
        __irq_invalid            @  f

                 这里只有usr 和svc 有入口,而其他都是invalid ,是因为linux只会从usr(application) 和svc(kernel)两种mode跳转到exception来

              __stubs_start 和 __stubs_end 之间的代码简化后为:

       1:  
    __stubs_start:
       2:  
       vector_irq:    @vector_stub    irq, IRQ_MODE, 4
       3:  
       vector_dabt:   @vector_stub    dabt, ABT_MODE, 8
       4:  
       vector_pabt:   @vector_stub    pabt, ABT_MODE, 4
       5:  
       vector_und:    @vector_stub    und, UND_MODE
       6:  
       vector_fiq:
       7:  
       vector_addrexcptn:
       8:  
       .LCvswi:
       9:  
    __stubs_end:

              由此可以知道 __stubs_start 和 __stubs_end 之间定义了各种异常的入口

             我们再来看为什么异常入口是“b vector_und + stubs_offset”, 同时为什么stubs_offset 的定义如下

    .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start

              arm 的跳转指令b 是跳转到相对于PC的一个偏移地址( offset ),汇编器在编译时会对label 减去PC 得到offset,同时vector 拷贝后是如下排列的

     

    __vectors_start

     

     

     

    B vector_<exception>

     

     

     

    __vectors_end

     

     

    +0x200

    __stubs_start

     

     

     

    vector_<exception>

     

     

     

    __stubs_end

                因此,"b vector_<exception>"  的label –PC =  offset, 而offset 为 b 指令与vector的offset,即

                             vector_<exception>-__stubs_start + ( 0x200 – ( PC_old – __vectors_start ) )

                           = vector_<exception> + __vectors_start + 0x200 – __stubs_start – PC_old

                    所以异常入口为“b vector_und + stubs_offset”, 同时stubs_offset= __vectors_start + 0x200 – __stubs_start

                我们可以通过objdump反汇编来验证:

     

    00000060 <vector_irq>: 
        .globl    __stubs_start 
    __stubs_start: 
    /* 
    * Interrupt dispatcher 
    */ 
        vector_stub    irq, IRQ_MODE, 4 
      60 :    e24ee004     sub    lr, lr, #4    ; 0x4 
      64:    e88d4001     stm    sp, {r0, lr}

     

    1d4:    e1a00000     .word    0xe1a00000 
    1d8:    e1a00000     .word    0xe1a00000 
    1dc:    e1a00000     .word    0xe1a00000

    000001e0 <vector_und>:

    /* 
    * Undef instr entry dispatcher 
    * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC 
    */

     

    __vectors_start: 
        swi    SYS_ERROR0 
    284:    ef9f0000     svc    0x009f0000 
        b    vector_und + stubs_offset 
     288:    ea0000dd     b    604 <vector_swi+0x604> 
        ldr    pc, .LCvswi + stubs_offset 
    28c:    e59ff410     ldr    pc, [pc, #1040]    ; 6a4 <vector_swi+0x6a4> 
        b    vector_pabt + stubs_offset 
    290:    ea0000bb     b    584 <vector_swi+0x584> 
        b    vector_dabt + stubs_offset 
    294:    ea00009a     b    504 <vector_swi+0x504> 
        b    vector_addrexcptn + stubs_offset 
    298:    ea0000fa     b    688 <vector_swi+0x688> 
        b    vector_irq + stubs_offset 
    29c:    ea000078     b    484 <vector_swi+0x484> 
        b    vector_fiq + stubs_offset 
    2a0:    ea0000f7     b    684 <vector_swi+0x684>

                                  0x1e0 – 0x60 + 0x200 – ( 0x288 + 8 ) – 0x284 = 0xdd*4

     

     

    ARM Linux外部中断处理过程

    最近在学习arm linux的整套外部中断的处理过程,在网上汇总了一些资料,整个过程差不多都了解到了。如果没有这些资料我真是没信心从汇编开始读代码,感谢 奔腾年代的jimmy.lee和 linux论坛的bx_bird。 
    在下面的的注释中有一些我读代码时遇到的问题,要是大家知道是怎么回事,希望多多回复。 

    ============================================= 
    一.ARM linux的中断向量表初始化分析 

    ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表. 

    /* arch/arm/kernel/traps.c */ 
    void __init trap_init(void) 
    { 
    extern void __trap_init(unsigned long); 
    unsigned long base = vectors_base(); 
    __trap_init(base); 
    if (base != 0) 
    oopsprintk(KERN_DEBUG "Relocating machine vectors to 0x%08lx\n", base); 
    #ifdef CONFIG_CPU_32 
    modify_domain(DOMAIN_USER, DOMAIN_CLIENT); 
    #endif 
    } 
    vectors_base是一个宏,它的作用是获取ARM异常向量的地址,该宏在include/arch/asm-arm/proc-armv/system.h中定义: 

    extern unsigned long cr_no_alignment; /* defined in entry-armv.S */ 
    extern unsigned long cr_alignment; /* defined in entry-armv.S */ 
    #if __LINUX_ARM_ARCH__ >= 4 
    #define vectors_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0) 
    #else 
    #define vectors_base() (0) 
    #endif 
      对于ARMv4以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(control register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual) 
      下面分析一下cr_alginment的值是在哪确定的,我们在arch/arm/kernel/entry-armv.S找到cr_alignment的定义: 

    .globl SYMBOL_NAME(cr_alignment) 
    .globl SYMBOL_NAME(cr_no_alignment) 
    SYMBOL_NAME(cr_alignment): 
    .space 4 
    SYMBOL_NAME(cr_no_alignment): 
    .space 4 

      分析过head-armv.S文件的朋友都会知道,head-armv.S是非压缩内核的入口: 

    1 .section ".text.init",#alloc,#execinstr 
    2 .type stext, #function 
    3ENTRY(stext) 
    4 mov r12, r0 
     
    6 mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode 
    7 msr cpsr_c, r0 @ and all irqs disabled 
    8 bl __lookup_processor_type 
    9 teq r10, #0 @ invalid processor? 
    10 moveq r0, #'p' @ yes, error 'p' 
    11 beq __error 
    12 bl __lookup_architecture_type 
    13 teq r7, #0 @ invalid architecture? 
    14 moveq r0, #'a' @ yes, error 'a' 
    15 beq __error 
    16 bl __create_page_tables 
    17 adr lr, __ret @ return address 
    18 add pc, r10, #12 @ initialise processor 
    19 @ (return control reg) 
    20 
    21 .type __switch_data, %object 
    22__switch_data: .long __mmap_switched 
    23 .long SYMBOL_NAME(__bss_start) 
    24 .long SYMBOL_NAME(_end) 
    25 .long SYMBOL_NAME(processor_id) 
    26 .long SYMBOL_NAME(__machine_arch_type) 
    27 .long SYMBOL_NAME(cr_alignment) 
    28 .long SYMBOL_NAME(init_task_union)+8192 
    29 
    30 .type __ret, %function 
    31__ret: ldr lr, __switch_data 
    32 mcr p15, 0, r0, c1, c0 
    33 mrc p15, 0, r0, c1, c0, 0 @ read it back. 
    34 mov r0, r0 
    35 mov r0, r0 
    36 mov pc, lr 
    这里我们关心的是从17行开始,17行code处将lr放置为__ret标号处的相对地址,以便将来某处返回时跳转到31行继续运行18行,对于我所分析的pxa270平台,它将是跳转到arch/arm/mm/proc-xscale.S中执行__xscale_setup函数,(在s3c2410平台中,它跳转到arch/arm/mm/proc-arm920.S,在 
    type __arm920_proc_info,#object 
    __arm920_proc_info: 
    .long 0x41009200 
    .long 0xff00fff0 
    .long 0x00000c1e @ mmuflags 
    b __arm920_setup 
    .long cpu_arch_name 
    .long cpu_elf_name 
    .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB 
    .long cpu_arm920_info 
    .long arm920_processor_functions 
    可以知道add pc, r10, #12 的#12意思是跳过3个指令,执行b _arm920_setup 
    在arm920_setup设置完协处理器和返回寄存器r0之后,跳回到__ret:(31行)。 
    在__xscale_setup中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其中包括设置V位=1),但在__xscale_setup中,r1寄存器并不立即写回到Cp15的control register中,而是在返回后的某个地方,接下来会慢慢分析到。__xscale_setup调用move pc, lr指令返回跳转到31行。 
      31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。 
      32,33行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。 
       
      接下来再来看一下跳转到__mmap_switched处的代码: 
    40 _mmap_switched: 
    41 adr r3, __switch_data + 4 
    42 ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat 
    43 @ sp = stack pointer 
    44 
    45 mov fp, #0 @ Clear BSS (and zero fp) 
    46 1: cmp r4, r5 
    47 strcc fp, [r4],#4 
    48 bcc 1b 
    49 
    50 str r9, [r6] @ Save processor ID 
    51 str r1, [r7] @ Save machine type 
    52 bic r2, r0, #2 @ Clear 'A' bit 
    53 stmia r8, {r0, r2} @ Save control register values 
    54 b SYMBOL_NAME(start_kernel) 

    41~42行的结果是:r4=__bss_start,r5=__end,...,r8=cr_alignment,..,这里r8保存的是cr_alignment变量的地址. 
      到了53行,由于之前r0保存的是cp15的control register(c1)的值,这里把r0的值写入r8指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。 
       
      让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S: 
        .align 5 
    __stubs_start: 
    vector_IRQ: 
         ... 
    vector_data: 
        .... 
    vector_prefetch: 
         ... 
    vector_undefinstr: 
         ... 
    vector_FIQ: disable_fiq 
         subs pc, lr, #4 
    vector_addrexcptn: 
         b vector_addrexcptn 
        ... 
    __stubs_end: 
         .equ __real_stubs_start, .LCvectors + 0x200 
    .LCvectors: swi SYS_ERROR0 
         b __real_stubs_start + (vector_undefinstr - __stubs_start) 
         ldr pc, __real_stubs_start + (.LCvswi - __stubs_start) 
         b __real_stubs_start + (vector_prefetch - __stubs_start) 
         b __real_stubs_start + (vector_data - __stubs_start) 
         b __real_stubs_start + (vector_addrexcptn - __stubs_start) 
         b __real_stubs_start + (vector_IRQ - __stubs_start) 
         b __real_stubs_start + (vector_FIQ - __stubs_start) 
    ENTRY(__trap_init) 
        stmfd sp!, {r4 - r6, lr} /* 压栈,保存数据*/ 
        /* 复制异常向量表(.LCvectors起始的8个地址)到r0指向的地址(异常向量地址),r0就是__trap_init(base)函数调用时传递的参数,不明白的请参考ATPCS*/(传递参数顺次利用r0,r1,r2,r3) 
        adr r1, .LCvectors @ set up the vectors 
        ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr} 
         stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr} 

    /* 在异常向量地址后的0x200偏移处,放置散转代码,即__stubs_start~__stubs_end之间的各个异常处理代码*/ 
         add r2, r0, #0x200 
         adr r0, __stubs_start @ copy stubs to 0x200 
         adr r1, __stubs_end 
    1: ldr r3, [r0], #4 
         str r3, [r2], #4 
         cmp r0, r1 
    blt 1b 
    LOADREGS(fd, sp!, {r4 - r6, pc}) /*出栈,恢复数据,函数__trap_init返回*/ 
    __trap_init函数填充后的向量表如下: 
    虚拟地址 异常 处理代码 
    0xffff0000 reset swi SYS_ERROR0 
    0xffff0004 undefined b __real_stubs_start + (vector_undefinstr - __stubs_start) 
    0xffff0008 软件中断 ldr pc, __real_stubs_start + (.LCvswi - __stubs_start) 
    0xffff000c 取指令异常 b __real_stubs_start + (vector_prefetch - __stubs_start) 
    0xffff0010 数据异常 b __real_stubs_start + (vector_data - __stubs_start) 
    0xffff0014 reserved b __real_stubs_start + (vector_addrexcptn - __stubs_start) 
    0xffff0018 irq b __real_stubs_start + (vector_IRQ - __stubs_start) 
    0xffff001c fiq b __real_stubs_start + (vector_FIQ - __stubs_start) 

       当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。 
    二.ARM Linux中断处理过程分析(1) 

    在我的上一篇文章(ARM linux的中断向量表初始化分析)中已经分析了ARM Linux中断向量表是如何建立的,在这篇文章中,我将分析一下Linux内核的ARM体系下,中断处理是如何响应的一个过程。 
    在ARM体系架构下,定义了7种异常,每一种异常都有自己的入口地址,即异常向量表,当异常发生时,处理器会自动跳转到相应的入口处执行。对于ARMv4及其以上的版本,异常向量表的起始位置由协处理器15(cp15)的控制寄存器(c1)里的V位(bit13)有关,当V=0时,异常向量表的起始位置在0x00000000,而当V=1时,异常向量表就起始于0xffff0000位置。在上一篇文章中,我们已经分析知道异常向量表放置于0xffff0000起始位置,而IRQ中断处理入口地址为:0xffff0018,所以当发生一IRQ中断异常时,处理器会自动跳转到0xffff0018这个虚拟地址上。 
    0xffff0018这个虚拟地址上是一条跳转指令: 
    b __real_stubs_start + (vector_IRQ - __stubs_start) 

    所以对于IRQ的处理就是从vector_IRQ标号处开始的。在linux2.4.19内核中相应代码如下: 
    __stubs_start: 
    /* 
    * Interrupt dispatcher 
    * Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC 
    */说明其实linux只用到了arm的svc和usr模式,其他的几个模式都没怎么用。 
    1 vector_IRQ: @ 
    2 @ save mode specific registers 
    3 @ 
    4 ldr r13, .LCsirq 
    5 sub lr, lr, #4 
    6 str lr, [r13] @ save lr_IRQ 
    7 mrs lr, spsr 
    8 str lr, [r13, #4] @ save spsr_IRQ 
    9 @ 
    10 @ now branch to the relevent MODE handling routine 
    11 @ 
    12 mrs r13, cpsr 
    13 bic r13, r13, #MODE_MASK 
    14 orr r13, r13, #I_BIT | MODE_SVC 
    15 msr spsr_c, r13 @ switch to SVC_32 mode 
    16 
    17 and lr, lr, #15 
    18 ldr lr, [pc, lr, lsl #2] 
    19 movs pc, lr @ Changes mode and branches 
    20 
    21.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32) 
    22 .word __irq_invalid @ 1 (FIQ_26 / FIQ_32) 
    23 .word __irq_invalid @ 2 (IRQ_26 / IRQ_32) 
    24 .word __irq_svc @ 3 (SVC_26 / SVC_32) 
    25 .word __irq_invalid @ 4 
    26 .word __irq_invalid @ 5 
    27 .word __irq_invalid @ 6 
    28 .word __irq_invalid @ 7 
    29 .word __irq_invalid @ 8 
    30 .word __irq_invalid @ 9 
    31 .word __irq_invalid @ a 
    32 .word __irq_invalid @ b 
    33 .word __irq_invalid @ c 
    34 .word __irq_invalid @ d 
    35 .word __irq_invalid @ e 
    36 .word __irq_invalid @ f 
    首先,行4~8是保存进入IRQ模式之前的pc指针(在lr_IRQ)和CPSR(在SPSR_IRQ)到.LCsirq所指向的地址中。.LCsirq相关代码也是位于entry-armv.S中: 
    .LCsirq: .word __temp_irq 
     
    __temp_irq: .word 0 @ saved lr_irq 
    .word 0 @ saved spsr_irq 
    .word -1 @ old_r0 
    在这里补充一下ARM对于异常的处理过程,可以用下面的一段伪码来表示: 
    r14_<异常模式> = return link 
    SPSR_<异常模式> = CPSR 
    CPSR[4:0] = 异常模式编码 
    CPSR[5] = 0 ;运行于ARM状态 
    If<异常模式> == Reset or FIQ then{ 
    ;当复位或响应FIQ异常时,禁止新的fiq和irq异常 
    CPSR[6] = 1; 
    CPSR[7] = 1; 
    }else if<异常模式> == IRQ then{ 
    ;当响应IRQ异常时,禁止新的IRQ异常 
    CPSR[7] = 1; 
    } 
    PC = 异常向量地址 

    所以在运行到行4~8之前时,lr为进入IRQ之前的pc指针,spsr为进入IRQ之前的cpsr指针。 
    接着,行12~15更新spsr寄存器为SVR模式,并关闭IRQ,为从IRQ模式切换到SVR模式做准备。 
    行17,根据进入IRQ模式之前的psr(因为在行7,lr已经被置以spsr_irq),获取之前的处理器模式(psr &0b1111)。 
    行18,根据获取的进入IRQ之前的处理器模式,查找相应的跳转入口(__irq_usr 对应于之前是USR模式,__irq_svc对于之前是SVC模式,对于其它模式均跳转到__irq_invalid,在linux系统中处理器进入IRQ之前只有usr和svc两种模式,其它模式均不允许开启IRQ)。此行实际上是:lr = pc+lr<<2,pc指向当前指令地址值加8个字节的地址,即pc指向当前指令的下两条指令的地址,所以pc在此时指向的是.LCtab_irq地址。 
    (这里有点疑惑要进入__irq_usr,则18行lr应该为pc+4那么向回推算第7行的mrs lr, spsr中spsr[3:0]应该为0b0001;如果要进入__irq_svc,则18行lr应该为pc+16,那么spsr[3:0]应该为0b0100; 
    而cprs[4:0]= 
    10000 User 模式 
    10011 SVC 模式 
    请达人指点迷津。。。。) 
    行19,跳转到相应入口,并且ARM寄存器r13和r14则切换到了SVC模式下的寄存器

    三.ARM Linux中断处理过程分析(2) 
    续前文,让我们先分析进入IRQ之前的处理器模式为SVC时的情况,程序会跳转到__irq_svc继续运行,其相应代码如下: 
    20__irq_svc: sub sp, sp, #S_FRAME_SIZE 
    21 stmia sp, {r0 - r12} @ save r0 - r12 
    22 ldr r7, .LCirq 
    23 add r5, sp, #S_FRAME_SIZE 
    24 ldmia r7, {r7 - r9} 
    25 add r4, sp, #S_SP 
    26 mov r6, lr 
    27 stmia r4, {r5, r6, r7, r8, r9} @ save sp_SVC, lr_SVC, pc, cpsr, old_ro 
    28 1: get_irqnr_and_base r0, r6, r5, lr 
    29 movne r1, sp 
    30 @ 
    31 @ routine called with r0 = irq number, r1 = struct pt_regs * 
    32 @ 
    33 adrsvc ne, lr, 1b 
    34 bne asm_do_IRQ 
    35 ldr r0, [sp, #S_PSR] @ irqs are already disabled 
    36 msr spsr, r0 
    37 ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr 

    行20~27:保存进入中断之前的寄存器,把它们放在堆栈中。其中#S_FRAME_SIZE和#S_SP的定义在arch/arm/kernel/entry-header.S中: 
    #ifdef CONFIG_CPU_32 
    #define S_FRAME_SIZE 72 
    #define S_OLD_R0 68 
    #define S_PSR 64 
    #else 
    #define S_FRAME_SIZE 68 
    #define S_OLD_R0 64 
    #define S_PSR 60 
    #endif 

    #define S_PC 60 
    #define S_LR 56 
    #define S_SP 52 
    #define S_IP 48 
    #define S_FP 44 
    #define S_R10 40 
    #define S_R9 36 
    #define S_R8 32 
    #define S_R7 28 
    #define S_R6 24 
    #define S_R5 20 
    #define S_R4 16 
    #define S_R3 12 
    #define S_R2 8 
    #define S_R1 4 
    #define S_R0 0 
    #define S_OFF 8 

    .LCirq在entry-armv.S中是这样定义的: 
    .LCirq: .word __temp_irq 
    这与行4处的.LCsirq定义是一样的,可见整个过程利用__temp_irq作为中转,把进入中断之前的CPSR和PC(中断处理结束后要返回的地址)放入堆栈,以便中断返回时直接恢复。 
    行20~27执行的结果是: 
    r5-> old_r0 
    cpsr 
    pc 
    lr_svc 
    r4-> sp_svc 
    r12 
    r11 
     
    r1 
    sp-> r0 
    行28的get_irqnr_and_base,它是一个宏定义,作用是获取中断号(irq number),它将被保存在r0中。另外,get_irqnr_and_base还会改变cpsr寄存器中的Z位,如果确实找到了发生的中断号,则Z位被清除,否则Z位被置位。get_irqnr_and_base这个宏定义的实现是依赖具体的硬件的,对于pxa270 cpu,其实现如下: 
    .macro get_irqnr_and_base, irqnr, irqstat, base, tmp 
    mov \base, #io_p2v(0x40000000) @ IIR Ctl = 0x40d00000 
    add \base, \base, #0x00d00000 
    ldr \irqstat, [\base, #0] @ ICIP 
    ldr \irqnr, [\base, #4] @ ICMR 
    ands \irqstat, \irqstat, \irqnr 
    beq 1001f /* 没找到中断,跳转*/ 
    rsb \irqnr, \irqstat, #0 
    and \irqstat, \irqstat, \irqnr 
    clz \irqnr, \irqstat 
    rsb \irqnr, \irqnr, #(31 - PXA_IRQ_SKIP) 
    #ifdef CONFIG_CPU_BULVERDE 
    b 1002f 
    #endif 
    1001: 
    1002: 
    .endm 

    .macro irq_prio_table 
    .endm 
    bics \irqstat, \irqstat, \irqnr 对照intmsk将intpnd中禁止的中断清0。因为intpnd在某一时刻只可以有一位为1,所以有一位被bics清0了,就会影响标志位从而beq跳转,return r0=0;从1001:开始所作的事情是循环查intpnd哪一位置为了1。有点疑惑的是tst 指令: 
    tst 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 tst 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。 
    那么这里的tst \irqstat, #1,当zero置1了表示有中断位,为什么下面是bne 1002f而不是beq?请教请教。。。。。。。) 


    asm_do_IRQ是用C语言编码的函数,它在arch/arm/kernel/irq.c中被定义,其原型为: 
    asmlinkage void asm_do_IRQ(int irq, struct pt_regs *regs); 
    这里牵扯到一个问题就是,在汇编中如何调用C语言的函数,参数是如何传递的?为了让ARM的汇编代码可与C代码一起连接,在编写ARM汇编时,应遵循一套标准,这就是ATPCS(The ARM-Thumb Procedure Call Standard)。ATPCS定义{r0~r3}为参数传递和结果返回寄存器;若参数超过4个字型(32bit),则使用堆栈进行传递;头4个参数依次存于r0...r3,大于4个的后续字型参数通过栈传送。关于栈的使用,是使用满递减的堆栈标准,也就是栈是从高地址向低地址方向增长的(递减堆栈),栈指针寄存器指向的数据是最后压入堆栈内的有效数据(满堆栈)。 
    所以在跳转到asm_do_IRQ函数之前,r0就必须设置为中断号(行28get_irqnr_and_base把中断号放置于r0),r1就必须是指向pt_regs这样结构(定义于include/asm-arm/proc-armv/ptrace.h)的指针,而行29把sp指针赋予r1,就完成了这样的一个调用准备。 
    行35~37:恢复寄存器,返回到发生中断之前的代码中继续执行。 
    这就是整个ARM linux中断处理的过程。以后有时间,再继续展开asm_do_IRQ继续分析。对于进入中断前处理器模式是USR的中断处理过程(__irq_usr),这里就不再做分析,这与__irq_svc基本相同 
    asmlinkage void do_IRQ(int irq, struct pt_regs * regs) 
    { 
    struct irqdesc * desc; 
    struct irqaction * action; 
    int cpu; 

    irq = fixup_irq(irq);// 查找子中断号,如无子中断return 原irq 
    /* 
    * Some hardware gives randomly wrong interrupts. Rather 
    * than crashing, do something sensible. 
    */ 
    if (irq >= NR_IRQS) 
    goto bad_irq; 

    desc = irq_desc + irq; 

    spin_lock(&irq_controller_lock); 
    desc->mask_ack(irq); 
    /*---------------------------------- 
    void __init init_IRQ(void) 
    { 
    extern void init_dma(void); 
    int irq; 

    for (irq = 0; irq < NR_IRQS; irq++) { 
    irq_desc[irq].probe_ok = 0; 
    irq_desc[irq].valid = 0; 
    irq_desc[irq].noautoenable = 0; 
    irq_desc[irq].mask_ack = dummy_mask_unmask_irq; 
    irq_desc[irq].mask = dummy_mask_unmask_irq; 
    irq_desc[irq].unmask = dummy_mask_unmask_irq; 
    } 
    init_arch_irq(); 
    init_dma(); 
    } 
    init_arch_irq(); init_dma();最后被指向/mach-s3c2410中的s3c2410_init_irq(void)和s3c2410_init_dma(void), desc->mask_ack(irq);将在那里被填充。 

    --------------------------------*/ 

    spin_unlock(&irq_controller_lock); 

    cpu = smp_processor_id(); //#define smp_processor_id() 0 
    irq_enter(cpu, irq); 
    kstat.irqs[cpu][irq]++; 
    desc->triggered = 1; 

    /* Return with this interrupt masked if no action */ 
    action = desc->action; 
    /* 这个结构由driver通过request_irq()挂入,包括了具体的中断处理程序入口和flags.一个中断的irq_desc下面可能会挂几个action(一个action队列)来实现中断的复用。也就是说几个driver可以公用一个中断号。*/ 

    if (action) { 
    int status = 0; 

    if (desc->nomask) { 
    spin_lock(&irq_controller_lock); 
    desc->unmask(irq); 
    spin_unlock(&irq_controller_lock); 
    } 

    if (!(action->flags & SA_INTERRUPT)) 
    /* SA_INTERRUPT Disable local interrupts while processing 
    SA_SHIRQ is shared 
    这个flag可以一直追到request irq的action->flags = irq_flags(传递参数); 
    */ 
    __sti();//清除cpsr的I_bit,开中断。 
    /*如果在上面的nomask处判断后,没有执行unmask动作,那么这里的__sti只是允许不同中断通道(即icip上不同的位)上的嵌套*/ 
    do { 
    status |= action->flags; 
    action->handler(irq, action->dev_id, regs); 
    action = action->next; 
    } while (action); 
    /*值得注意的是:整个action队列都会被调用,所以在driver里要判定是否是属于自己的中断*/ 
    if (status & SA_SAMPLE_RANDOM) 
    add_interrupt_randomness(irq); 
    __cli(); 

    if (!desc->nomask && desc->enabled) { 
    spin_lock(&irq_controller_lock); 
    desc->unmask(irq); 
    spin_unlock(&irq_controller_lock); 
    } 
    } 

    unsigned int fixup_irq(int irq) { 
    unsigned int ret; 
    unsigned long sub_mask, ext_mask; 

    if (irq == OS_TIMER) 
    return irq; 

    switch (irq) { 
    case IRQ_UART0: 
    sub_mask = SUBSRCPND & ~INTSUBMSK; 
    ret = get_subIRQ(sub_mask, 0, 2, irq); 
    break; 
    case IRQ_UART1: 
    sub_mask = SUBSRCPND & ~INTSUBMSK; 
    ret = get_subIRQ(sub_mask, 3, 5, irq); 
    break; 
    case IRQ_UART2: 
    sub_mask = SUBSRCPND & ~INTSUBMSK; 
    ret = get_subIRQ(sub_mask, 6, 8, irq); 
    break; 
    case IRQ_ADCTC: 
    sub_mask = SUBSRCPND & ~INTSUBMSK; 
    ret = get_subIRQ(sub_mask, 9, 10, irq); 
    break; 
    case IRQ_EINT4_7: 
    ext_mask = EINTPEND & ~EINTMASK; 
    ret = get_extIRQ(ext_mask, 4, 7, irq); 
    break; 
    case IRQ_EINT8_23: 
    ext_mask = EINTPEND & ~EINTMASK; 
    ret = get_extIRQ(ext_mask, 8, 23, irq); 
    break; 
    default: 
    ret = irq; 
    } 
    这个函数一看就知道是找子中断号的, 
    inline unsigned int get_subIRQ(int irq, int begin, int end, int fail_irq) { 
    int i; 

    for(i=begin; i <= end; i++) { 
    if (irq & (1 << i)) 
    return (EXT_IRQ_OFFSET + i); 
    } 
    return fail_irq; 
    } 

    inline unsigned int get_extIRQ(int irq, int begin, int end, int fail_irq) { 
    int i; 

    for(i=begin; i <= end; i++) { 
    if (irq & (1 << i)) 
    return (NORMAL_IRQ_OFFSET - 4 + i); 
    } 
    return fail_irq; 
    } 
    #define NORMAL_IRQ_OFFSET 32 
    #define EXT_IRQ_OFFSET (20 +NORMAL_IRQ_OFFSET) 

    ========================================= 
    申请中断: 
    int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *), 
    unsigned long irq_flags, const char * devname, void *dev_id) 
    { 
    unsigned long retval; 
    struct irqaction *action; 

    if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler || 
    (irq_flags & SA_SHIRQ && !dev_id)) 
    return -EINVAL; 

    action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL); 
    if (!action) 
    return -ENOMEM; 

    action->handler = handler; 
    action->flags = irq_flags; 
    action->mask = 0; 
    action->name = devname; 
    action->next = NULL; 
    action->dev_id = dev_id; 

    retval = setup_arm_irq(irq, action); /* 把这个action挂到对应irq的action链表中*/ 

    if (retval) 
    kfree(action); 
    return retval; 
    } 

    int setup_arm_irq(int irq, struct irqaction * new) 
    { 
    int shared = 0; 
    struct irqaction *old, **p; /*这里的**p 用的太妙了*/ 
    unsigned long flags; 
    struct irqdesc *desc; 

    /* 
    * Some drivers like serial.c use request_irq() heavily, 
    * so we have to be careful not to interfere with a 
    * running system. 
    */ 
    if (new->flags & SA_SAMPLE_RANDOM) { 
    /* 
    * This function might sleep, we want to call it first, 
    * outside of the atomic block. 
    * Yes, this might clear the entropy pool if the wrong 
    * driver is attempted to be loaded, without actually 
    * installing a new handler, but is this really a problem, 
    * only the sysadmin is able to do this. 
    */ 
    rand_initialize_irq(irq); /*这个函数的作用是利用中断的随机性来产生随机数列*/ 
    } 

    /* 
    * The following block of code has to be executed atomically 
    */ 
    desc = irq_desc + irq; 
    spin_lock_irqsave(&irq_controller_lock, flags); 
    p = &desc->action; 
    if ((old = *p) != NULL) { 
    注意/* Can't share interrupts unless both agree to */ 
    if (!(old->flags & new->flags & SA_SHIRQ)) { 
    spin_unlock_irqrestore(&irq_controller_lock, flags); 
    return -EBUSY; 
    } 

    /* add new interrupt at end of irq queue */ 
    do { 
    p = &old->next; 
    old = *p; 
    } while (old);/*当没有下一个irqaction链表元素时,next就位null*/ 
    shared = 1; 
    } 

    *p = new; 

    if (!shared) { 
    desc->nomask = (new->flags & SA_IRQNOMASK) ? 1 : 0; 
    desc->probing = 0; 
    if (!desc->noautoenable) { 
    desc->enabled = 1; 
    desc->unmask(irq); 
    } 
    } 

    spin_unlock_irqrestore(&irq_controller_lock, flags); 
    return 0; 
    }
    四.ARM Linux中断处理过程分析(3) 

    在之前的文章中,我分析了进入IRQ之前处理器模式为SVC的情况,在本篇文章中,将要讨论的是进入IRQ之前处理器模式为USR的情形。 
    843 __irq_usr: sub sp, sp, #S_FRAME_SIZE 
    844 stmia sp, {r0 - r12} @ save r0 - r12 
    845 ldr r4, .LCirq 
    846 add r8, sp, #S_PC 
    847 ldmia r4, {r5 - r7} @ get saved PC, SPSR 
    848 stmia r8, {r5 - r7} @ save pc, psr, old_r0 
    849 stmdb r8, {sp, lr}^ 
    850 alignment_trap r4, r7, __temp_irq 
    851 zero_fp 
    852 1: get_irqnr_and_base r0, r6, r5, lr 
    853 movne r1, sp 
    854 adrsvc ne, lr, 1b 
    855 @ 
    856 @ routine called with r0 = irq number, r1 = struct pt_regs * 
    857 @ 
    858 bne asm_do_IRQ 
    859 mov why, #0 
    860 get_current_task tsk 
    861 b ret_to_user 
    __irq_usr关于中断处理的过程大体与__irq_svc是一样的,这里我们重点要分析中断处理返回时的不同。 
    研读过linux内核进程调度的朋友都知道,进程的调度可以自愿的方式随时进行(内核里:schedule、schedule_timeout;用户空间:pause、nanosleep),还可以非自愿的发生,即强制地发生在每次系统调用返回的前夕,以及每次从中断或异常处理返回到用户空间的前夕(只有在用户空间发生的中断或异常才会引起调度)。可参阅毛德操的《Linux内核源代码情景分析》上册的第4章关于进程调度的相关地方。 
    那我们就来看一下,__irq_usr在返回到usr模式(用户空间)前夕是如何强制进行进程调度的。 
    Line860,这是中断处理返回后,获取当前进程的task_struct指针,get_current_task是一个宏,它定义于arch/arm/kernel/entry-header.S中: 
    .macro get_current_task, rd 
    mov \rd, sp, lsr #13 
    mov \rd, \rd, lsl #13 
    .endm 
    该宏是先将sp的值右移13位,再左移13位,把结果返回给参数,其实也就是只保留sp值的高19位,这代表着把堆栈指针的地址round到8K地址边界上,这样它认为就得到了当前进程的task_struct数据结构了。它是因为内核在为每个进程分配一个task_struct结构时,实际上是分配两个连续的物理页面的(共8K),这两个页面的底部是用作进程的task_struct结构,而在结构的上面就用作进程的系统空间堆栈;数据结构task_struct的大小约为1K,进程系统空间堆栈大小就约为7K。当进程在系统空间运行时,常常需要访问当前进程自身的task_struct数据结构,为此内核中定义了一个宏操作current,提供指向当前进程task_struct结构的指针,它的实现实际上也与这里的get_current_task宏是差不多的。 
    /* include/asm-arm/current.h */ 
    static inline struct task_struct *get_current(void) 
    { 
    register unsigned long sp asm ("sp"); 
    return (struct task_struct *)(sp & ~0x1fff); 
    } 

    #define current (get_current()) 

    再回到lin860,get_current_task的参数是tsk,它实际上是r9寄存器,它也是定义于arch/arm/kernel/entry-header.S中的: 
    tsk .req r9 @ current task 
    这样r9寄存器就保存了当前进程的task_struct结构的指针了。 
    Line861,程序跳转到ret_to_user,以完成从中断处理到返回用户空间的过程,前面提到的进程重新调度将在那里得以体现。ret_to_user定义于arch/arm/entry-common.S中: 
    55 reschedule: 
    56 bl SYMBOL_NAME(schedule) 
    57 ret_disable_irq: 
    58 disable_irq r1 @ ensure IRQs are disabled 
    59 ENTRY(ret_to_user) 
    60 ret_slow_syscall: 
    61 ldr r1, [tsk, #TSK_NEED_RESCHED] 
    62 ldr r2, [tsk, #TSK_SIGPENDING] 
    63 teq r1, #0 @ need_resched => schedule() 
    64 bne reschedule 
    65 1: teq r2, #0 @ sigpending => do_signal() 
    66 bne __do_signal 
    67 restore: 
    68 restore_user_regs 
    69 
    70 __do_signal: 
    71 enable_irq r1 
    72 mov r0, #0 @ NULL 'oldset' 
    73 mov r1, sp @ 'regs' 
    74 mov r2, why @ 'syscall' 
    75 bl SYMBOL_NAME(do_signal) @ note the bl above sets lr 
    76 disable_irq r1 @ ensure IRQs are disabled 
    77 b restore 
    Line61,TSK_NEED_RESCHED值为20,它是task_struct结构中其成员变量need_resched相对于结构首地址的偏移量,所以此时r1的值就是当前进程task_struct结构里need_resched变量的值。同理在line62,r2存储就是task_struct->sigpenging的值。 
    从line63~64可见,只有在当前进程的task_struct结构中的need_resched字段为非0时才会转到reschedule处去调用schedule,那么,谁来设置这个字段呢?当然是内核,从用户空间是访问不到进程的task_struct结构的,那么,内核又是在什么情况下设置这个字段的呢?除当前进程通过系统调用自愿让出运行以及在系统调用中因某种原因受阻以外,主要就是当因某种原因唤醒一个进程的时候,以及在时钟中断服务程序发现当前进程已经连续运行太久的时候。(此段摘抄于Linux内核源代码情景分析》) 
    Line65~66,如果当前进程的task_struct结构中的sigpedding字段为非0时才会转到__do_signal处去调用do_signal处理信号。 
    Line68, restore_user_regs,它是一个宏定义于arch/arm/kernel/head-header.S中: 
    102 /* 
    103 * Must be called with IRQs already disabled. 
    104 */ 
    105 .macro restore_user_regs 
    106 ldr r1, [sp, #S_PSR] @ Get calling cpsr 
    107 ldr lr, [sp, #S_PC]! @ Get PC 
    108 msr spsr, r1 @ save in spsr_svc 
    109 ldmdb sp, {r0 - lr}^ @ Get calling r0 - lr 
    110 mov r0, r0 
    111 add sp, sp, #S_FRAME_SIZE - S_PC 
    112 movs pc, lr @ return & move spsr_svc into cpsr 
    113 .endm 



    17 and lr, lr, #15 
    18 ldr lr, [pc, lr, lsl #2] 
    19 movs pc, lr @ Changes mode and branches 
    20 
    21.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32) 
    22 .word __irq_invalid @ 1 (FIQ_26 / FIQ_32) 
    23 .word __irq_invalid @ 2 (IRQ_26 / IRQ_32) 
    24 .word __irq_svc @ 3 (SVC_26 / SVC_32) 

    这里有点疑惑要进入__irq_usr,则18行lr应该为pc+4那么向回推算第7行的mrs lr, spsr中spsr[3:0]应该为0b0001;如果要进入__irq_svc,则18行lr应该为pc+16,那么spsr[3:0]应该为0b0100; 
    而cprs[4:0]= 
    10000 User 模式 
    10011 SVC 模式 
    请达人指点迷津。。。。) 
    行19,跳转到相应入口,并且ARM寄存器r13和r14则切换到了SVC模式下的寄存器 




    这里第18行中的pc值正好是21行的.LCtab_irq,如果是在用户空间,User模式10000,逻辑左移两位为0x0=0b0000,即pc+0x0,恰好到了.word __irq_usr ,如果是在内核空间,svc模式10011,移位后为0xc=0b1100,及pc+0xc,正好到了.word __irq_svc,一点都没错(当然不可能错,系统不是跑得好好的吗) 
    注意,pc值是当前指令地址+8 
    关于get_irqnr_and_base宏中: 

    bics \irqstat, \irqstat, \irqnr 对照intmsk将intpnd中禁止的中断清0。因为intpnd在某一时刻只可以有一位为1,所以有一位被bics清0了,就会影响标志位从而beq跳转,return r0=0;从1001:开始所作的事情是循环查intpnd哪一位置为了1。有点疑惑的是tst 指令: 
    tst 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 tst 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。 
    那么这里的tst \irqstat, #1,当zero置1了表示有中断位,为什么下面是bne 1002f而不是beq?请教请教。。。。。。。) 



    没找到你看的内核版本中该宏的详细定义,我在我的2.6.12中pxa体系中的此宏中没找到tst指令,但想你的问题估计还是对tst的误解 
    pc值是当前指令地址+8 

    是因为armv5是三级流水线么?

    pxa的宏里面好像是没用tst,这里我引申到s3c2410的宏里面。 
    tst的定义我翻的是网上搜的arm指令集,里面是这么说的: 
    TST : 测试位 
    (Test bits) 

    TST{条件}{P} <op 1>, <op 2> 

    Status = op_1 AND op_2 

    TST 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 TST 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。象 CMP 那样,你不需要指定 S 后缀。 
    TST R0, #%1 ; 测试在 R0 中是否设置了位 0。 

    我觉得在这里是有点转不过弯来了,,,




    ARM linux的中断向量表初始化分析
    Author:     jimmy.li
     Time:        2007-06-09 

      本文分析基于linux2.4.19 source,pxa 270 cpu.

      ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表.
      
    /* arch/arm/kernel/traps.c */
    void __init trap_init(void)
    {
       extern void __trap_init(unsigned long);
       unsigned long base = vectors_base();

       __trap_init(base);
       if (base != 0)
          oopsprintk(KERN_DEBUG "Relocating machine vectors to 0x%08lx\n", base);
    #ifdef CONFIG_CPU_32
       modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
    #endif
    }

        vectors_base是一个宏,它的作用是获取ARM异常向量的地址,该宏在include/arch/asm-arm/proc-armv/system.h中定义:

    extern unsigned long cr_no_alignment; /* defined in entry-armv.S */
    extern unsigned long cr_alignment; /* defined in entry-armv.S */

    #if __LINUX_ARM_ARCH__ >= 4
    #define vectors_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0)
    #else
    #define vectors_base() (0)
    #endif

      对于ARMv4以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(control register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual)
      下面分析一下cr_alginment的值是在哪确定的,我们在arch/arm/kernel/entry-armv.S找到cr_alignment的定义:

                    .globl  SYMBOL_NAME(cr_alignment)
                    .globl  SYMBOL_NAME(cr_no_alignment)
    SYMBOL_NAME(cr_alignment):
                    .space  4
    SYMBOL_NAME(cr_no_alignment):
                    .space  4

      分析过head-armv.S文件的朋友都会知道,head-armv.S是非压缩内核的入口:
                   
    1               .section ".text.init",#alloc,#execinstr
    2               .type   stext, #function
    3ENTRY(stext)    
    4               mov     r12, r0
    5                
    6               mov     r0, #F_BIT | I_BIT | MODE_SVC   @ make sure svc mode
    7               msr     cpsr_c, r0                      @ and all irqs disabled
    8               bl      __lookup_processor_type         
    9               teq     r10, #0                         @ invalid processor?
    10               moveq   r0, #'p'                        @ yes, error 'p'
    11               beq     __error
    12               bl      __lookup_architecture_type
    13               teq     r7, #0                          @ invalid architecture?
    14               moveq   r0, #'a'                        @ yes, error 'a'
    15               beq     __error
    16               bl      __create_page_tables            
    17               adr     lr, __ret                       @ return address
    18               add     pc, r10, #12                    @ initialise processor
    19                                                       @ (return control reg)
    20
    21               .type   __switch_data, %object
    22__switch_data:  .long   __mmap_switched
    23                .long   SYMBOL_NAME(__bss_start)
    24                .long   SYMBOL_NAME(_end)
    25                .long   SYMBOL_NAME(processor_id)
    26                .long   SYMBOL_NAME(__machine_arch_type)
    27                .long   SYMBOL_NAME(cr_alignment)
    28                .long   SYMBOL_NAME(init_task_union)+8192
    29
    30                .type   __ret, %function
    31__ret:          ldr     lr, __switch_data
    32                mcr     p15, 0, r0, c1, c0
    33                mrc     p15, 0, r0, c1, c0, 0           @ read it back.
    34                mov     r0, r0
    35                mov     r0, r0
    36                mov     pc, lr

      这里我们关心的是从17行开始,17行code处将lr放置为__ret标号处的相对地址,以便将来某处返回时跳转到31行继续运行;
      18行,对于我所分析的pxa270平台,它将是跳转到arch/arm/mm/proc-xscale.S中执行__xscale_setup函数,在__xscale_setup中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其中包括设置V位=1),但在__xscale_setup中,r1寄存器并不立即写回到Cp15的control register中,而是在返回后的某个地方,接下来会慢慢分析到。__xscale_setup调用move pc, lr指令返回跳转到31行。
      31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。
      32,33行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。
      
      接下来再来看一下跳转到__mmap_switched处的代码:
    40 _mmap_switched:
    41                 adr     r3, __switch_data + 4
    42                 ldmia   r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat
    43                                                        @ sp = stack pointer
    44
    45                 mov     fp, #0                          @ Clear BSS (and zero fp)
    46 1:              cmp     r4, r5
    47                 strcc   fp, [r4],#4
    48                 bcc     1b
    49
    50                 str     r9, [r6]                        @ Save processor ID
    51                 str     r1, [r7]                        @ Save machine type
    52                 bic     r2, r0, #2                      @ Clear 'A' bit
    53                 stmia   r8, {r0, r2}                    @ Save control register values
    54                 b       SYMBOL_NAME(start_kernel)

      41~42行的结果是:r4=__bss_start,r5=__end,...,r8=cr_alignment,..,这里r8保存的是cr_alignment变量的地址.
      到了53行,由于之前r0保存的是cp15的control register(c1)的值,这里把r0的值写入r8指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。
      

      让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S:
          .align 5
    __stubs_start:
    vector_IRQ:
         ...
    vector_data:
         ....
    vector_prefetch:
         ...                                                                                                                        
    vector_undefinstr:
         ...
    vector_FIQ: disable_fiq
         subs pc, lr, #4
    vector_addrexcptn:
         b vector_addrexcptn        
          ...
    __stubs_end:
          .equ __real_stubs_start, .LCvectors + 0x200

    .LCvectors: swi SYS_ERROR0
          b __real_stubs_start + (vector_undefinstr - __stubs_start)
          ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
          b __real_stubs_start + (vector_prefetch - __stubs_start)
          b __real_stubs_start + (vector_data - __stubs_start)
          b __real_stubs_start + (vector_addrexcptn - __stubs_start)
          b __real_stubs_start + (vector_IRQ - __stubs_start)
          b __real_stubs_start + (vector_FIQ - __stubs_start)

    ENTRY(__trap_init)
          stmfd sp!, {r4 - r6, lr}  /* 压栈,保存数据*/

          /* 复制异常向量表(.LCvectors起始的8个地址)到r0指向的地址(异常向量地址),r0就是__trap_init(base)函数调用时传递的参数,不明白的请参考ATPCS*/
          adr r1, .LCvectors   @ set up the vectors
          ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
          stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}
      
          /* 在异常向量地址后的0x200偏移处,放置散转代码,即__stubs_start~__stubs_end之间的各个异常处理代码*/
          add r2, r0, #0x200
          adr r0, __stubs_start  @ copy stubs to 0x200
          adr r1, __stubs_end
    1:               ldr r3, [r0], #4
         str r3, [r2], #4
         cmp r0, r1
                      blt 1b
                      LOADREGS(fd, sp!, {r4 - r6, pc}) /*出栈,恢复数据,函数__trap_init返回*/

        __trap_init函数填充后的向量表如下:
        虚拟地址      异常              处理代码
        0xffff0000      reset              swi SYS_ERROR0
        0xffff0004      undefined       b __real_stubs_start + (vector_undefinstr - __stubs_start)
        0xffff0008      软件中断      ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
        0xffff000c      取指令异常  b __real_stubs_start + (vector_prefetch - __stubs_start)
        0xffff0010      数据异常      b __real_stubs_start + (vector_data - __stubs_start)
        0xffff0014      reserved         b __real_stubs_start + (vector_addrexcptn - __stubs_start)
        0xffff0018      irq                  b __real_stubs_start + (vector_IRQ - __stubs_start)
        0xffff001c      fiq                   b __real_stubs_start + (vector_FIQ - __stubs_start)
        
       当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。
     

    -------------------------参考资料--------------------
    1, 刘淼,嵌入式系统接口设计与Linux驱动程序开发,北京航天航空大学出版社,2006.
    2, ARM Architecture Reference Manual, ARM limited,2000. 


    http://jimmy-lee.blog.hexun.com/cate.aspx?cateid=7311&cate=ARM%26Linux

    展开全文
  • 我在这里用一些篇幅来描述一下arm体系结构下Linux中怎样来初始化中断向量表的,因为这个方法很具有通用性,我把它叫做代码大挪移。您说搬代码谁不会阿,不就是拷贝吗,的确如此,但是拷贝也有技巧。拷贝很简单啦,...


    转载:http://chongsoft.bokee.com/6872424.html


    Preface 引言

    我在这里用一些篇幅来描述一下arm体系结构下Linux中怎样来初始化中断向量表的,因为这个方法很具有通用性,我把它叫做代码大挪移。您说搬代码谁不会阿,不就是拷贝吗,的确如此,但是拷贝也有技巧。拷贝很简单啦,其实就是memcpy,这不用提,我在这里想说的是,你怎么把你的代码设计成能随便拷贝的,换句专业的术语,叫与位置无关的代码,拷到哪都能用。我以前也用过类似的方法作启动,今天拿来说说。

    Scenario 1  第一场景 copy

    我们先看实际复制动作。代码的位置在arch/arm/traps.c中,kernel version: 2.6.27 这个是初始化部分的代码,setup_arch()->early_trap_init(). 熟悉初始化部分的朋友们可能见到过这段代码。

     

    void __init early_trap_init(void)

    {

           unsigned long vectors = CONFIG_VECTORS_BASE;

           extern char __stubs_start[], __stubs_end[];

      &nsp;    extern char __vectors_start[], __vectors_end[];

           extern char __kuser_helper_start[], __kuser_helper_end[];

           int kuser_sz = __kuser_helper_end - __kuser_helper_start;

     

           /*

            * Copy the vectors, stubs and kuser helpers (in entry-armv.S)

            * into the vector page, mapped at 0xffff0000, and ensure these

            * are visible to the instruction stream.

            */

           memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

           memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

           memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

           …

    }

     

    实际copy动作一目了然,就是两个memcpy(第三个实际上是拷贝一些别的东西,原理是一样的,这里不提了). Copy的源是vectors,这个值是CONFIG_VECTORS_BASE一般来讲,是0xffff0000,当然你可以根据硬件的设定自己配制这个值。把什么东西往那copy?第一部分是从__vectors_start__vectors_end之间的代码,第二部分是从__stubs_start__stubs_end之间的代码,而第二部分是copyvectors + 0x200起始的位置。也就是说,两部分之间的距离是0x200,512个字节。

    我们来看__vectors_start__vectors_endfont face="Times New Roman">__stubs_start__stubs_end到底是什么东西,只要知道它们在哪里定义的,就知道怎么回事了。

    Scenario 2  第二场景 主角闪亮登场

    它们埋伏在arch/arm/kernel/entry-armv.S中,这个文件是arm中各个模式的入口代码,熟悉arm的朋友们知道arm有几种模式,不知道的自己查查,不说了。我们取一个片断,和我们的阐述相关的部分。为了让大家看得更清楚,我删掉了部分代码和注释,把主干凸显出来。有兴趣的朋友可以查看源代码,研究全部,里面还是比较有内涵的。

     

           .globl      __stubs_start

    __stubs_start:

    /*

     * Interrupt dispatcher

     */

           vector_stub    irq, IRQ_MODE, 4

    // 请注意这里:vector_stub是一个宏,展开后是一块代码,下面是个跳转表,我们将代码结// 构展开,大致是这样的结构: (后面的vector_stub    dabt, ABT_MODE, 8等展开过程全一样,在此略过不提)

    // -------------------------------- begin 展开

    .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

           mrs  lr, spsr

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

          

           @ Prepare for SVC32 mode.  IRQs remain disabled.

           @

           mrs  r0, cpsr

           eor   r0, r0, IRQ_MODE ^ SVC_MODE)

           msr  spsr_cxsf, r0

          

           @ the branch table must immediately follow this code

           @

           and  lr, lr, #0x0f

           mov r0, sp

           ldr   lr, [pc, lr, lsl #2]

           movs      pc, lr                     @ branch to handler in SVC mode

           // -------------------------------- end 展开

     

           .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                 @  f

     

    /*

     * Data abort dispatcher

     * Enter in ABT mode, spsr = USR CPSR, lr = USR PC

     */

           vector_stub    dabt, ABT_MODE, 8

     

           .long       __dabt_usr                   @  0  (USR_26 / USR_32)

           .long       __dabt_invalid               @  1  (FIQ_26 / FIQ_32)

           .long       __dabt_invalid               @  2  (IRQ_26 / IRQ_32)

           .long       __dabt_svc                   @  3  (SVC_26 / SVC_32)

        。。。

           .long       __dabt_invalid               @  f

     

    /*

     * Prefetch abort dispatcher

     * Enter in ABT mode, spsr = USR CPSR, lr = USR PC

     */

           vector_stub    pabt, ABT_MODE, 4

     

           .long       __pabt_usr                   @  0 (USR_26 / USR_32)

           .long       __pabt_invalid               @  1 (FIQ_26 / FIQ_32)

           .long       __pabt_invalid               @  2 (IRQ_26 / IRQ_32)

           .long       __pabt_svc                   @  3 (SVC_26 / SVC_32)

    。。。

           .long       __pabt_invalid               @  f

     

    /*

     * Undef instr entry dispatcher

     * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC

     */

           vector_stub    und, UND_MODE

     

           .long       __und_usr                    @  0 (USR_26 / USR_32)

           .long       __und_invalid                @  1 (FIQ_26 / FIQ_32)

           .long       __und_invalid                @  2 (IRQ_26 / IRQ_32)

           .long       __und_svc                    @  3 (SVC_26 / SVC_32)

        。。。

           .long       __und_invalid                @  f

     

           .align       5

     

    vector_fiq:

           disable_fiq

           subs pc, lr, #4

     

    vector_addrexcptn:

           b     vector_addrexcptn

    /*

     * We group all the following data together to optimise

     * for CPUs with separate I & D caches.

     */

           .align       5

     

    .LCvswi:

           .word      vector_swi

     

           .globl      __stubs_end

    __stubs_end:

     

           .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

     

           .globl      __vectors_start

    __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:

     

    为了让大家看得更清,我把代码的结构再次简化成这样:

           .globl      __stubs_start

    __stubs_start:

    .align       5

    vector_irq:

    [code part]                // 展开代码

    [jump table part]           // 地址跳转表

    。。。

    .align       5

    vector_dabt:

    [code part]

    [jump table part]

     

    。。。

    .align       5

    vector_ pabt:

    [code part]

    [jump table part]

     

    。。。

    .align       5

    vector_und:

    [code part]

    [jump table part]

     

    。。。

    .align       5

    vector_fiq:

    。。。

     

           .globl      __stubs_end

    __stubs_end:

     

           .globl      __vectors_start

    __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:

     

    在这里我不花过多的篇幅去解释代码的意思,这不是本文的目的,只要你把结构看清,就达到目的了。但我会花点时间研究一下展开代码部分(蓝色)的特征,这部分代码是与位置无关的代码,我们稍微研究一下,它为什么会这么写。

    .align       5

    vector_irq:

    [code part]                // 展开代码

    [jump table part]           // 地址跳转表

    。。。

     

    首先这部分代码大致都是一样的结构,前面是一些代码,后面跟着一个跳转表。跳转表里面定义了一些地址。我们截取这部分看

    。。。

    @ the branch table must immediately follow this code

    @

    and  lr, lr, #0x0f      (1)       // lr中当前存储了上一个状态寄存器的值,对后几位做与,

    // 就是取在中断前处在用户态还是核心态,这个值用作跳

    // 转表的索引

    mov r0, sp            (2)  // 用做他用,sp值当第一个参数传给后面函数

    ldr   lr, [pc, lr, lsl #2]   (3)  // pc是当前执行指令地址加8,即跳转表的基地址,lr是索引

                             // 很好的技巧,取pc找当前地址什么时候都没错

    mov      pc, lr                     @ branch to handler in SVC mode

     

    [jump table]

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

     

     

     

    真正的跳转在最后一句完成,大家都看得很清楚。跳到哪里去了,如果中断以前是svc模式,就会跳到__irq_svc。我们发现这里不会直接用b(bl,bx)个,

    ü         一是b跳转后面是个偏移,而这个偏移是有限制的,不能太大

    ü         二是b跳转后面的偏移你不知道在代码拷贝后还是不是那个样子,因为我们要搬移代码,所以如果你不能确定搬移后的偏移不变,那你就用绝对地址,而上面的代码前三句就是算出绝对地址来,然后用绝对地址赋值给pc直接完成跳转。

     

    这些都是一些技巧,总之你要注意的是写位置无关的代码时涉及到跳转部分,用b跳转还是直接赋成绝对地址(通过跳转表实现),如果你不能保证搬移后的偏移一致,写这部分就要注意了,要用一些技巧的。

    大家可以去用gcc -fPIC-S选项汇编一个小的函数看看,fPIC就是与位置无关选项,相信编译过动态库的人都熟悉,看看它是怎么做的。你会发现异曲同工。

    Scenario 3  第三场景 大搬移

    我用一个章节来介绍大搬移的过程,以及一些在搬移中Linux出现的问题及解决方案。我把整个的搬移过程做成一张图里,然后讨论了一些技术细节。我们看到这是一个巨大无比的图,我们这章节的所阐述的内容都在图里。

     大搬移

    我们将搬移前的代码组织称为Code/Load 视图,因为这是代码中的(或image中的)组织情况,把搬移后的代码组织称为Exec视图,反映的是代码执行时代码在内存中的情况。我刚才讲过了第一场景的情况,忘了的回到第一场景中去看,两个memcpy的执行过程在图中也有表示,就是蓝色和红色的带箭头的虚线,这就是代码从code viewexec view的拷贝过程,一目了然,不用多说。

    现在出现了一个问题,就是我们发现在__vector_start__vector_end之间的代码有点怪异,我们再次摘到这里来看:

     

    .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

           .globl      __vectors_start

    __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跳转。举个例子,b      vector_dabt + stubs_offset(vector_dabt__stubs_start__stubs_end之间),如果你用b vector_dabt, 这肯定是有问题的,因为copy之后exec view的组织(map)是不一样的,所以b后这个偏移就不对了。这里面,我们就要对这个偏移进行一次调整。Stubs_offset就是这个调整值,是可以计算出来的,具体的计算过程在图中讲得比较清楚,这里不提了。大家可以在图中看到详细的推导过程。

    其实尽管ldr   pc, .LCvswi + stubs_offset这条指令用的是绝对地址跳转,用得跳转表的方法,但找地址的过程也用到了这个技术。我们看到

           .align       5

    .LCvswi:

           .word      vector_swi

    .LCvswi这个位置存储的是一个地址,就是要跳到这个地方。.align 5的意思是32字节对齐,这个是保证cache line对齐的,不提了。在exec view中找这个地址,就得加上个offset.原理是一样的,因为.LCvswi__stubs_start__stubs_end之间,这个区域被搬移了,不能直接用这个标签地址了,vector_swi没有被搬移,所以可以直接用。

     

    总结一下。我觉得我要讲的东西虽然是Linux中的技术细节,描述的确是代码搬移过程原理和注意事项。其实更重要的是,我们如何把这一个过程倒过来,即在涉及到代码搬移的场合中如何进行设计,如何运用这些技术实现这一设计过程。你可以遵循这样的指导步骤:

    1.      画出那个大图来,按自己的要求确定Code viewExec view, 设计搬移区段和设计搬移的位置

    2.      写出要搬移的代码,运用位置无关的技术(上面提到的)进行编码和检验

    3.      用类似memcpy的代码进行搬移


    展开全文
  • 上面的代码是arm-linux中断向量表中的内容,比如:中断发生后悔就会跳转到标红的那条指令开始去执行中断处理过程。在我们编译内核的时候,上面的代码有一个链接地址,但是大家都知道,在内核中中断向量表位于0...

    讲解ARM汇编指令的书籍中,很多在讲到B指令的时候会说这条指令时一条绝对跳转指令,这是不负责任的说法。这条指令的迷惑之处在于,B指令的汇编形式为:

        b label        :        :label:    mov r1,r2    mov r2,r3

    从汇编指令的书写形式上,确实是一条绝对跳转指令,但是实际上汇编在将“B label”这条指令翻译为机器指令的时候,会计算label相对于当前PC的偏移,将这个偏移值放到机器码中,所以实际上,B跳转指令为相对跳转指令。用相对跳转指令就可以编写位置无关的代码。

    什么是位置无关的代码,其实位置无关的意思就是说编译好的一段代码可以放到内存的任何位置运行而结果不会变。了解arm-linux内核中中断向量表建立过程的同学大概都熟悉以下的一段代码:

    .globl __vectors_start__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:

    上面的代码是arm-linux中中断向量表中的内容,比如:中断发生后悔就会跳转到标红的那条指令开始去执行中断处理过程。在我们编译内核的时候,上面的代码有一个链接地址,但是大家都知道,在内核中中断向量表位于0xFFFF000开始的位置,内核在early_trap_init函数中会将上面的代码“搬移”到0xFFFF000开始的位置。问题就来了,b指令是怎么找到目的地址的?

    下面的图很详细的解释了这个问题:

    上图下面的线显示了代码被“搬移”之前相对位置,上面的线显示了“搬移”后代码的相对位置。假设现在发生了一个数据预取异常,PC会先跳转到t1的位置执行b vector_irq + stubs_offset 然后跳转到E(vector_dabt)去执行对应的处理语句。我们怎样在写代码的时候确定将b vector_irq + stubs_offset ”这样的代码放到中断向量表中,然后在进行代码“搬移”工作之后,发生异常事件的时候,在固定的位置执行一条指令,这条指令中包含了相对于当前PC值得偏移,进而跳转到一个新的位置开始执行后面的处理。要记住,B指令实际上是一条相对跳转指令,而且代码在编译的时候与“搬移”之后的代码在相对位置上是确定的,这里就有了位置无关的代码的用武之地。

    这样解释之后,大家可以静下心来,看着linux中的源码(arch/arm/kernel/entry-armv.S),读读上面的英文(很简单),至于上面的计算公式,我相信一个初中能够正常毕业的人就能看懂。

    当你看到offset=vector_dabt+stub_offset-t2的时候,你要这样理解,内核编译到t2这条指令的时候b vector_dabt + stubs_offset )其实需要确定的是代码“搬移”之后的一个偏移,这个偏移是固定的上图中的L1+L2,而且这个偏移在执行的时候,也就是代码“搬移”之后才有意义),偏移加上当前的PC(t2)的值是vector_dabt + stubs_offset,所以中断向量表中的这一项就写成了b vector_dabt + stubs_offset ”



    from: http://www.chengxuyuans.com/Linux/55825.html

    展开全文
  •  上面的代码是arm-linux中断向量表中的内容,比如:中断发生后悔就会跳转到标红的那条指令开始去执行中断处理过程。在我们编译内核的时候,上面的代码有一个链接地址,但是大家都知道,在内核中中断向量表位于0...
  • Linux内核ARM架构异常中断向量表

    千次阅读 2020-07-18 13:21:34
    Linux内核ARM架构异常中断向量表说明ARM中异常中断的种类ARM异常中断向量表内核异常向量表异常向量表跳转vector_srub宏内核启动建立异常向量表   当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断...
  • 本文分析了Arm Linux中断Vector向量表的建立流程。
  • Linux ARMv7启动阶段对中断向量表的搬移 1、中断向量表和中断处理部分代码的搬移 经历过kernel的汇编阶段,进入C语言start_kernel后对中断向量表的位置进行搬移,搬移函数是early_trap_init。 early_trap_init...
  • 中断向量表

    2021-01-21 13:34:06
    中断向量表(interrupt vector table,IVT)是中断源的识别标志,可用来形成相应的中断服务程序的入口地址或存放中断服务程序的首地址称为中断向量。在Pc/AT中由硬件产生的中断标识码被称为中断类型号(当然,中断类...
  • 中断有两种,一种是由CPU外部产生的,对于执行中的软件来说,这种中断的发生完全是“异步”的,根本无法预料此类中断会在什么时候发生,一般由其他硬件设备产生(例如键盘中断);另一种是由CPU本身在执行程序的过程...
  • arm中断概念在《ARM体系结构与编程》第9章中说到,ARM 中有个概念叫做“异常中断”,也就是包括外部中断在内的各种...arm的异常中断向量表非向量化中断ARM体系定义了7种异常中断,在《ARM体系结构与编程》第9章的表9.
  • ARM linux中断向量表初始化分析

    千次阅读 2010-06-29 13:53:00
     ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表. <br /> /* arch/arm/kernel/traps.c */ void __init trap_init(void) {  ...
  • 异常向量表和中断向量表的关系 什么是异常? 异常:因为内部或者外部的一些事件,导致处理器停下正在处理的工作,转而去处理这些发生的事件。 当正常的程序执行流程发生暂时的停止时,称之为异常,例如处理一个...
  • ARM中断向量表与响应流程,从硬件到软件的过程
  • 中断向量表03. 中断向量表实现04. 设置Vectors bit05. 主函数实现06. 附录 01. 开发环境 开发板:Tiny4412SDK标准版 「Tiny4412 SDK 1506」 工具:「arm-linux-gcc-4.5.1」 「minicom」 「dnw」 平台:Ubuntu ...
  • 中断向量表 1 异常向量表 arch/arm/kernel/entry-armv.S 中_vectors_start和__vectors_end之间保存了异常向量表。 [cpp]   view plain copy  .equ stubs_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,254
精华内容 3,301
关键字:

linux中断向量表

linux 订阅