2008年8月5日 星期二

ARM中斷流程

程式的執行過程中,CPU經常會收到硬體中斷的訊號,告知硬體有些事情需要去處理,比如:OS的System Clock...等等。而ARM的外部硬體中斷訊號由IRQ及FIQ兩支訊號腳來控制,當IRQ(Interrupt Request)或FIQ(Fast Interrupt Request),當IRQ或FIQ被觸發時,則ARM CPU將會強制的進入IRQ或FIQ模式,在ARM裏IRQ有r13/r14/spsr是banked register,FIQ則有r8/r9/r10/r11/r12/r13/r14/spsr,所以沒有banked register的部份,程式就必需自行處理保存資料,所以FIQ可提供比IRQ還要好的效能。

先看一下中斷向量表IVT:interrupt vector table
在x86的IVT範圍是:0x00000000~0x000003FF
而ARM的IVT則是0x00000000~0x0000001F
0x00000000 硬體重置SVC 10011
0x00000004 未定義指令Undefined Mode 11011
0x00000008 軟體中斷指令(SWI) SVC 10011
0x0000000C 預先擷取中止Abort Mode 10111
0x00000010 資料中止Abort Mode 10111
0x00000014 保留reserved 11011
0x00000018 IRQ IRQ Mode 10010
0x0000001C FIQ FIQ Mode 10001
0x0000001F 保留reserved

說明如下:
  1. 重置向量 (Reset Interrupt Vector) :重置向量是處理器開電後執行的第一道指令的位址,這條指令將使處理器跳躍到初始化程式碼處。

  2. 未定義指令向量(Undefined Interrupt Vector):未定義指令向量是在處理器不能對第一道指令解碼時使用。通常當我們在開發新的DSP指令時,可以使用這個部份來進行模擬測試

  3. 軟體中斷向量(Software Interrupt Vector:Trap):軟體中斷向量是執行 SWI 指令時被呼叫的。 SWI 指令經常用於呼叫一個作業系統常式的機制(System Call)。

  4. 預先存取中止向量(Prefetch Abort Interrupt Vector):發生在試圖從一個未獲得正確存取許可權的位址去存取時,而實際上中止發生在解碼階段。

  5. 資料中止向量(Data Abort Interrupt Vector):和預先存取中止向量類似,發生在一道指令試圖存取未獲得正確存取許可權的資料記憶體時。

  6. 中斷請求向量(Interrupt Request Vector):用於外部硬體中斷處理器的正常執行流,只有當 CPSR 中的 IRQ 位元未被遮罩時才發生。

  7. 快速中斷請求向量(Fast Interrupt RequestVector):和中斷請求向量累類似,是為了要求更快速的中斷反應時間,有當 CPSR 中的 FIQ 位元未被遮罩時才發生。

微處理器進入中斷時(硬體本身的中斷處理動作):
(1)自動將原程式中下一個準備要執行的指令位址儲存至R14(Link Register)
(2)將CPSR複製到適當的SPSR
(3)依中斷的要求設定CPSR mode bits
(4)強迫PC擷取相對應的中斷向量指令。

微處理器從中斷返回時(硬體本身的中斷處理動作):
(1)將Link Register減去相對應之偏移值後寫入PC
(2)將SPSR值搬回CPSR
(3)如果在中斷進入點設定了interrupt disable flags,此時還原。

當系統重置有效後會使微處理器執行(硬體本身的中斷處理動作):
(1)將目前之PC及CPSR覆蓋R14_svc及SPSR_svc
(2)CPSR[4:0]=0b10011(進入supervisor模式)、CPSR[5]=0(在ARM模式下執行)、CPSR[6]=1(disable FIQ)、CPSR[7]=1(disable IRQ)
(3)強迫PC擷取位址0x0之中斷向量指令
(4)回到ARM模式下繼續執行。

所以通常在0x0~0x1F我們會放如下的程式碼:
b HandlerReset
b HandlerUndef
b HandlerSWI
b HandlerPabort
b HandlerDabort
b .;保留沒有使用
b HandlerIRQ
b HandlerFIQ

當相對應的中斷發生時,CPU切換模式,然後fetch IVT裏面的程式,然後跳到你自己定義的位址去執行你要處理的事情。到目前為止處理的步驟我們通常稱之為一級中斷向量表,但一個中斷向量表應該都不夠用,因為像IRQ,通常硬體不可能只會有一個,所以還會有一個中斷控制器(interrupt controller)去管更多的硬體,而這麼多的硬體中斷則由二級中斷向量表去處理,也就是進入HandlerIRQ後,要再去判斷現在是什麼硬體發出中斷的,再跳去該硬體的中斷服務常式去處理。

我們拿個u-boot loader來當範例看一下IRQ的部份
底下是IVT定義,_start當然是0x0開始囉!
_start: b reset
ldr pc,=HandleUndef
ldr pc,=HandleSWI
ldr pc,=HandlePabort
ldr pc,=HandleDabort
b .
ldr pc,=HandleIRQ;你要自己定義HandleIRQ的實際位置在那裏
ldr pc,=HandleFIQ

real_vectors的位置也是自己定義,但要跟上面的HandleXXXX對映起來
real_vectors:
ldr pc,=reset
ldr pc,=undefined_instruction
ldr pc,=software_interrupt
ldr pc,=prefetch_abort
ldr pc,=data_abort
ldr pc,=not_used
ldr pc,=irq
ldr pc,=fiq

這裏的IRQ處理取得IRQ的堆疊指標,備份暫存器,執行真正的IRQ ISR,恢復暫存器值
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs

底下的部份就是上面irq裏使用的程式碼
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
add r8, sp, #S_PC
stmdb r8, {sp, lr}^ @ Calling SP, LR
str lr, [r8, #0] @ Save calling PC
mrs r6, spsr
str r6, [r8, #4] @ Save CPSR
str r0, [r8, #8] @ Save OLD_R0
mov r0, sp
.endm

.macro irq_restore_user_regs
ldmia sp, {r0 - lr}^ @ Calling r0 - lr
mov r0, r0
ldr lr, [sp, #S_PC] @ Get PC
add sp, sp, #S_FRAME_SIZE
subs pc, lr, #4 @ return & move spsr_svc into cpsr
.endm

.macro get_irq_stack @ setup IRQ stack
ldr sp, =STACK_BOTTOM @IRQ_STACK_START
/*ldr sp, =IRQ_STACK_START @IRQ_STACK_START*/
.endm
上圖是一張我蠻喜歡的圖,呈現了ARM裏面各中斷與各模式的關係圖上面這張則是介紹當IRQ引發後(1)先跳到0x18的位址(2)由0x18再轉跳到ISR去。

1 則留言:

Aspirinnnnn 提到...

写得很清楚,谢谢你的分享