在看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>: … |
|
1d4: e1a00000 .word 0xe1a00000 000001e0 <vector_und>: /* … |
|
__vectors_start: |
0x1e0 – 0x60 + 0x200 – ( 0x288 + 8 ) – 0x284 = 0xdd*4
ARM Linux外部中斷處理過程
在下面的的註釋中有一些我讀代碼時遇到的問題,要是大家知道是怎麼回事,希望多多回復。
=============================================
一.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)
所以對於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