ARM Linux 中断向量表建立流程
d 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模式下的寄存器
ARMLinux中断向量 相关文章:
- ARM Linux中断向量表搬移设计过程(11-09)
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)