ARM處理器架構-----異常/中斷處理
中斷是我們嵌入式開發很常用到的一種資源和程式設計手段。這篇文章重點分析arm的中斷處理流程。
首先,中斷是異常的一種。當發生一種異常時,處理器會進入不同的工作模式。ARM的異常和相應的模式之間的對應關係見下表:
當一個異常導致模式的改變時,ARM核自動地:
1、把cpsr儲存到相應模式下的spsr
2、把pc儲存到相應模式下的lr
3、設定cpsr為相應異常模式
4、設定pc為相應異常處理程式的入口地址
對於IRQ或者FIQ而言,還多一項變化:禁用相關的中斷IRQ或FIQ,禁止同類型的其他中斷被觸發。(這也是自動實現的,因此正常情況下,ARM中斷不可巢狀)
從異常中斷處理程式退出時,需要我們在程式中用軟體實現下面兩個操作
1、從spsr_mode中恢復資料到cpsr中
2、從lr_mode中恢復內容到pc中,返回到異常中斷的指令的下一條政令處執行.
2440預設的有一個異常向量表,即發生某一個異常後,會根據異常向量表設定pc為相應的處理函式入口地址。
地址 |
異常名稱 |
指令 |
0x00 |
復位異常 |
B RestHandler |
0x04 |
未定義指令異常 |
B HandlerUndef |
0x08 |
軟體中斷異常 |
B HandlerSWI |
0x0C |
指令預取異常 |
B HandlerPabort |
0x10 |
資料預取異常 |
B HandlerDabort |
0x14 |
保留 |
|
0x18 |
IRQ中斷異常 |
B HandlerIRQ |
0x1C |
FIQ中斷異常 |
B HandlerFIQ |
上表中的 指令都是在2440init.s中的程式表示。
下面,我們結合板子自帶的2440test原始碼中的2440init.s中的異常處理,來分析arm中斷處理的實現。
首先,在2440init.s中有:
__ENTRY b ResetHandler b HandlerUndef ;handler for Undefined mode b HandlerSWI ;handler for SWI interrupt b HandlerPabort ;handler for PAbort b HandlerDabort ;handler for DAbort b . ;reserved b HandlerIRQ ;handler for IRQ interrupt b HandlerFIQ ;handler for FIQ interrupt
這裡就是相應的 異常處理向量表。程式正常啟動就跳轉到resethandler,如果是發生中斷就跳轉到handlerIRQ。對於handlerIRQ,它是用一個巨集實現的。
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4 ;decrement sp(to store jump address)
stmfd sp!,{r0} ;PUSH the work register to stack(lr does't push because it return to original address)
ldr r0,=$HandleLabel;load the address of HandleXXX to r0
ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX
str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack
ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)
MEND
上面是巨集的宣告。下面是具體用到巨集的地方。HandlerFIQ HANDLER HandleFIQ
HandlerIRQ HANDLER HandleIRQ
HandlerUndef HANDLER HandleUndef
HandlerSWI HANDLER HandleSWI
HandlerDabort HANDLER HandleDabort
HandlerPabort HANDLER HandlePabort
上面的這段程式在編譯的時候會被編譯器展開,我們可以將其中的IRQ相關的展開如下:
HandlerIRQ HANDLER HandleIRQ 會被下面的程式碼段替換:
HandlerIRQ
sub sp,sp,#4 ;decrement sp(to store jump address)
stmfd sp!,{r0} ;PUSH the work register to stack(lr does''t push because it return to original address)
ldr r0,=HandleIRQ ;load the address of HandleXXX to r0
ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX
str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack
ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)
因此,發生中斷時,就會b HandlerIRQ,跳轉到上面的程式碼進行執行。按照上面的流程,處理器會把HandleIRQ地址中所儲存的數 付給pc指標,作為下一條指令的地址,然後執行。那麼HandleIRQ地址中儲存的數是什麼呢?
在2440init.s中有這樣一段程式:
ldr r0,=HandleIRQ ;This routine is needed
ldr r1,=IsrIRQ ;if there isn't 'subs pc,lr,#4' at 0x18, 0x1c
str r1,[r0]
從這裡,可以看出,HandleIRQ中存的是IsrIRQ。所以處理器會跳轉到isrIRQ中執行。
IsrIRQ
sub sp,sp,#4 ;reserved for PC
stmfd sp!,{r8-r9}
ldr r9,=INTOFFSET
ldr r9,[r9]
ldr r8,=HandleEINT0
add r8,r8,r9,lsl #2
ldr r8,[r8]
str r8,[sp,#8]
ldmfd sp!,{r8-r9,pc}
在上面的程式中,INTOFFSET表示的是中斷號對於EINT0的偏移號。這樣計算得到中斷向量號之後,跳轉到中斷函式進行處理。對於,上面的程式奇怪的一點是沒有看到恢復cpsr和pc指標。因此,可以推斷,對於中斷函式在ADS中有特殊的宣告方式,如:static void __irq Uart_DMA_ISR(void)。像這種宣告方式,在編譯的時候,編譯器會自動在函式的末尾新增恢復cpsr和pc的語句。另外, 暫存器r0-r12也是需要保護的,因為在中斷函式和原來的上下文中都會用到,所以,我認為 編譯器在中斷處理函式中對r0-r12也進行了保護和恢復。
另外,在ucosII中,對IsrIRQ函式進行了修改,我們後面再進行分析。
另外,我們用軟體實現了一套中斷向量表:
ALIGN
AREA RamData, DATA, READWRITE
^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00
HandleReset # 4
HandleUndef # 4
HandleSWI # 4
HandlePabort # 4
HandleDabort # 4
HandleReserved # 4
HandleIRQ # 4
HandleFIQ # 4
;Don''t use the label 'IntVectorTable',
;The value of IntVectorTable is different with the address you think it may be.
;IntVectorTable
;@0x33FF_FF20
HandleEINT0 # 4
HandleEINT1 # 4
HandleEINT2 # 4
HandleEINT3 # 4
HandleEINT4_7 # 4
HandleEINT8_23 # 4
HandleCAM # 4 ; Added for 2440.
HandleBATFLT # 4
HandleTICK # 4
HandleWDT # 4
HandleTIMER0 # 4
HandleTIMER1 # 4
HandleTIMER2 # 4
HandleTIMER3 # 4
HandleTIMER4 # 4
HandleUART2 # 4
;@0x33FF_FF60
HandleLCD # 4
HandleDMA0 # 4
HandleDMA1 # 4
HandleDMA2 # 4
HandleDMA3 # 4
HandleMMC # 4
HandleSPI0 # 4
HandleUART1 # 4
HandleNFCON # 4 ; Added for 2440.
HandleUSBD # 4
HandleUSBH # 4
HandleIIC # 4
HandleUART0 # 4
HandleSPI1 # 4
HandleRTC # 4
HandleADC # 4
;@0x33FF_FFA0
之前,我們在發生中斷時,pc指標就跳轉到了 HandleIRQ地址中所儲存的數 出執行。也就是說在HandleIRQ中存的是異常處理函式的入口地址。這就是異常處理向量表的作用。所以,我們可以看出,對於2440init.s實現的異常處理,採用的是兩級向量表機制。 第一級向量表是arm核自己實現的,發生相應的異常時,pc指標跳轉到0x18地址中存的數,作為入口地址。 第二級向量表是由Handler巨集實現的,繼續跳轉到 HandleIRQ地址中存的數,繼續執行。
而對於IRQ來講,還有第三級的向量表,在IsrIRQ中,又會根據中斷號,比如uart2的中斷,跳轉到 HandleUart2地址中 存的數,繼續執行。
在2440init.s中,並沒有給HandleUndef等這些地址處賦值,因此,一旦執行到,程式就會跑飛。