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%08lxn”, 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)效率高。