历史上的今天
返回首页

历史上的今天

今天是:2025年01月16日(星期四)

正在发生

2019年01月16日 | ARM中断向量表的简单分析

2019-01-16 来源:eefocus

一般编写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的地址上去,那么在中断向量表进行跳转的时候,如bvector_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_

  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_

  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_




__vectors_end



+0x200

__stubs_start




vector_




__stubs_end


因此,”b vector_” 的label –PC = offset, 而offset 为 b 指令与vector的offset,即


                         vector_

                       = vector_


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

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


00000060

    .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

/* 

* 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

    ldr    pc, .LCvswi + stubs_offset 

28c:    e59ff410     ldr    pc, [pc, #1040]    ; 6a4

    b    vector_pabt + stubs_offset 

290:    ea0000bb     b    584

    b    vector_dabt + stubs_offset 

294:    ea00009a     b    504

    b    vector_addrexcptn + stubs_offset 

298:    ea0000fa     b    688

    b    vector_irq + stubs_offset 

29c:    ea000078     b    484

    b    vector_fiq + stubs_offset 

2a0:    ea0000f7     b    684

                              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 

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函数,(在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) 

1

所以对于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_

SPSR_

CPSR[4:0] = 异常模式编码 

CPSR[5] = 0 ;运行于ARM状态 

If

;当复位或响应FIQ异常时,禁止新的fiq和irq异常 

CPSR[6] = 1; 

CPSR[7] = 1; 

}else if

;当响应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 

1

这与行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} ,


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)


15

  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)效率高。


推荐阅读

史海拾趣

联智(Celfras)公司的发展小趣事

江西联智集成电路有限公司(简称联智)自2016年成立以来,始终致力于无线充电技术的研发和创新。公司凭借其先进的无线充电TX/RX芯片解决方案,迅速在行业内崭露头角。联智不仅拥有由多名高校博士和高级研发人员组成的核心研发团队,还在中国及韩国设立了研发中心,确保技术的领先性和市场竞争力。

ELEKTRO-PLAST NASIELSK公司的发展小趣事

随着环保意识的提高和可持续发展理念的普及,电子行业对环保要求也越来越高。ELEKTRO-PLAST NASIELSK公司积极响应这一趋势,将绿色环保理念贯穿于产品研发和生产过程中。公司采用环保材料和工艺生产产品,并推动供应链中的合作伙伴共同践行环保理念。此外,公司还积极推广可回收利用和循环再生的理念,为保护环境做出了积极贡献。

力芯微(ETEK)公司的发展小趣事

2024年第一季度,力芯微公司实现了营业总收入2.20亿元,同比增长23.57%;归母净利润5347.70万元,同比增长93.35%。这一业绩的取得,不仅体现了公司强大的市场竞争力和盈利能力,也为公司未来的发展奠定了坚实的基础。同时,公司在资产结构、现金流量等方面也取得了显著的改善。

请注意,以上故事均基于事实描述,未对力芯微公司进行评价或褒贬。每个故事都力求全面、客观地展现力芯微公司在电子行业中的发展历程和成就。

Andersen Laboratories Inc公司的发展小趣事

为了更好地服务全球客户,AnalogicTech积极实施全球化运营策略。公司在亚洲和大中华地区建立了广泛的晶圆制造和后端合作伙伴网络,包括香港地区、上海和台湾地区的台北。此外,AnalogicTech还迁入香港科技园,将深圳地区纳入业务版图,提高在中国大陆的销售情况,特别是珠江三角洲地区。

Apacer公司的发展小趣事

1997年,Apacer宇瞻科技在台湾创立,初期专注于DRAM模组的专业供货。公司凭借对半导体垂直整合的完整内存模组技术能力和专业营销业务,迅速在全球市场上打响了Apacer的自有品牌。1999年,宇瞻科技更是跃升为全球第四大内存模组厂商,奠定了其在行业内的领先地位。

Galaxy Microelectronics公司的发展小趣事

江苏飞翼智能科技有限公司在无人机航模领域也有着深厚的积累。公司不仅自己购买零件组装各类航模,还积极参与国内航模比赛,凭借良好的飞机性能和一流的操控技术取得了不俗的成绩。尤其是涡喷航模,作为中高级的航模产品,其高难度的特技动作展示了飞翼智能在无人机技术方面的深厚功底。这些经验积累和技术创新为飞翼智能向无人机应用型企业转型奠定了坚实基础。

问答坊 | AI 解惑

北京2008安防展上的新动向---6

煤矿安全的产品热点依旧停留在前端传感探测和一级报警层面。作为应急联动的新概念,还有很大的研发开拓空间。除了矿、县、省要多级信息互通、联网联动的设备架构之外,决策指挥软件平台和支持首长决策指挥的各基础资料数据库系统,尚待投入庞大的人 ...…

查看全部问答>

FPGA管脚分配需要考虑的因素

FPGA管脚分配需要考虑的因素…

查看全部问答>

5月最后一天,明天六。一节到了

不知道,单位明天放假不。好陪陪小孩过节!…

查看全部问答>

内核启动失败

Uncompressing Linux............................................................. Linux version 2.6.18 (johan@johaness-laptop) (gcc version 3.4.2) #7 Mon Dec 28 9 CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177  &nb ...…

查看全部问答>

drawtext 怎么无效?

如题,小弟要在一个对话框中显示很多文字,想用drawtext但老是无效, 代码如下,             PAINTSTRUCT ps;                      &nb ...…

查看全部问答>

请教:串口接收不对??

各位大侠,我用51单片机的串口接收数据 在开始的几秒内接收是正确的,可是过一会就接收不正确了,为甚呢?? 串口以9600的波特率一直在接收数据,是不是接收时间长波特率就乱了? void serial() interrupt 4 using 1    {   &n ...…

查看全部问答>

WinCE下利用DeviceIoControl向SD卡发送apdu命令

本人正在开发一快SD接口的开发板,想在CE下向sd接口的设备发送指令进行控制,CTL_CODE(FILE_DEVICE_DISK, 0x700, METHOD_BUFFERED, FILE_ANY_ACCESS)这样设置CTL_CODE是否正确,尤其是那个0x700到底什么意思,msdn上也没有具体说明。本人qq21544201 ...…

查看全部问答>

求助啊!!!12864为什么打一个点,其后2个字节都显示出来了?

本人最近学习编程12864,显示图片文字都没问题。但打点画直线就出现问题了,假如画条垂直线其后16位都画出来了。有哪位遇到过同样的问题,知不知道怎么回事啊??小弟在线烦请高人解答。 [ 本帖最后由 nwx8899 于 2011-8-23 10:07 编辑 ]…

查看全部问答>

2401数字源表与吉时利其它数字源表的共性

触发链路接口 所有数字源表都包含吉时利独有的触发链路接口,以实现与吉时利许多其它仪器的高速、无缝通信。例如,使用触发链路接口连接数字源表与7000系列开关系统[1]实现完整的多点测试方案。通过触发链路,7000系列开关系统能独立于计算机和G ...…

查看全部问答>

TI发布非接触式红外温度测量扩展模块

这也是launchpad的一个boosterpack,主要是用来展示TI新的非接触式红外传感器TMP006,TMP006是一款非接触式的温度测量传感器,传感器通过吸收目标物体散发出来的红外线的的方式测量目标物体温度。这款红外传感器的工作温度为-40到125度,可以在一个 ...…

查看全部问答>