linux系統下驅動中,中斷異常的處理過程,與裸機開發中斷處理過程很是相似。經過簡單的回顧裸機開發中斷處理部分,來參考學習linux系統下中斷處理流程。html
1、ARM裸機開發中斷處理過程linux
以S3C2440的裸機開發啓動文件中,有關irq中斷部分代碼爲例進行說明:函數
.extern main .text .global _start _start: b Reset HandleUndef: b HandleUndef HandleSWI: b HandleSWI HandlePrefetchAbort: b HandlePrefetchAbort HandleDataAbort: b HandleDataAbort HandleNotUsed: b HandleNotUsed b HandleIRQ HandleFIQ: b HandleFIQ Reset: ldr sp, =4096 @ 設置棧指針,如下都是C函數,調用前須要設好棧 bl disable_watch_dog @ 關閉WATCHDOG,不然CPU會不斷重啓 msr cpsr_c, #0xd2 @ 進入中斷模式 ldr sp, =3072 @ 設置中斷模式棧指針 msr cpsr_c, #0xdf @ 進入系統模式 ldr sp, =4096 @ 設置系統模式棧指針 bl init_led @ 初始化LED的GPIO管腳 bl init_irq @ 調用中斷初始化函數,在init.c中 msr cpsr_c, #0x5f @ 設置I-bit=0,開IRQ中斷 ldr lr, =halt_loop @ 設置返回地址 ldr pc, =main @ 調用main函數 halt_loop: b halt_loop HandleIRQ: sub lr, lr, #4 @ 計算返回地址 stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器 @ 注意,此時的sp是中斷模式的sp,初始值是上面設置的3072 ldr lr, =int_return @ 設置調用ISR即EINT_Handle函數後的返回地址 ldr pc, =EINT_Handle @ 調用中斷服務函數,在interrupt.c中 int_return: ldmia sp!, { r0-r12,pc }^ @ 中斷返回, ^表示將spsr的值複製到cpsr
當irq中斷髮生時,一些列的處理流程以下:oop
一、硬件自動令PC置爲irq的中斷向量,從而執行跳轉指令「b HandleIRQ」。學習
其實,以前還伴隨着保存中斷斷點地址到lr(還要換算);CPSR的值到SPSR;將CPSR切換到異常模式。fetch
二、保存中斷現場spa
sub lr, lr, #4 @ 計算返回地址 stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器
三、執行中斷服務程序指針
ldr lr, =int_return @ 設置調用ISR即EINT_Handle函數後的返回地址
ldr pc, =EINT_Handle @ 調用中斷服務函數,在interrupt.c中
四、從中斷異常工做模式返回code
int_return: ldmia sp!, { r0-r12,pc }^ @ 中斷返回, ^表示將spsr的值複製到cpsr
2、linux系統中斷處理流程htm
具體的代碼細節沒有分析,主要是爲了理清中斷處理的總體脈絡。
一、ARM異常向量表
arch/arm/kernel/entry-armv.S
.globl __vectors_start __vectors_start: swi SYS_ERROR0 /* 復位時,執行這條指令 */ b vector_und + stubs_offset /* 未定義異常 */ ldr pc, .LCvswi + stubs_offset /* swi異常 */ b vector_pabt + stubs_offset /* 指令預取異常 */ b vector_dabt + stubs_offset /* 數據訪問終止 */ b vector_addrexcptn + stubs_offset /* 沒有用 */ b vector_irq + stubs_offset /* irq異常 */ b vector_fiq + stubs_offset /* fiq異常 */ .globl __vectors_end __vectors_end:
異常向量表,無非仍是一些跳轉指令。當發生irq中斷,執行指令「b vector_irq + stubs_offset」,也就是跳轉到vector_irq代碼段繼續執行。
在linux內核初始化階段,start_kernel函數(init/main.c)會調用trap_init、init_IRQ兩個函數來初始化異常向量相關處理函數。簡要說明就是,將異常向量表拷貝到地址0xffff0000處(ARM體系協處理器寄存器c1能設置異常向量的基地址爲0xffff0000),再把異常向量表中異常處理的進一步函數代碼段拷貝到0xffff0200位置(vector_und、vector_irq等)。
二、異常處理進一步函數----vector_irq
arch/arm/kernel/entry-armv.S
1 .globl __stubs_start 2 __stubs_start: 3 vector_stub irq, IRQ_MODE, 4 4 .long __irq_usr @ 0 (USR_26 / USR_32) 5 .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) 6 .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) 7 .long __irq_svc @ 3 (SVC_26 / SVC_32) 8 .long __irq_invalid @ 4 9 .long __irq_invalid @ 5 10 .long __irq_invalid @ 6 11 .long __irq_invalid @ 7 12 .long __irq_invalid @ 8 13 .long __irq_invalid @ 9 14 .long __irq_invalid @ a 15 .long __irq_invalid @ b 16 .long __irq_invalid @ c 17 .long __irq_invalid @ d 18 .long __irq_invalid @ e 19 .long __irq_invalid @ f
從第4行到第19行,記錄了(代碼連接階段填入的地址數據)在各個模式下遇到irq中斷時,發生異常的處理分支。好比第4行__irq_usr表示用戶模式下發生irq中斷時,由__irq_usr對應的代碼段來處理這種狀況。
vector_stub是一個宏,將宏展開內容以下:
vector_irq: sub lr, lr, #4 stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, [sp, #8] @ save spsr mrs r0, cpsr eor r0, r0, #(IRQ_MODE ^ SVC_MODE) msr spsr_cxsf, r0 and lr, lr, #0x0f mov r0, sp ldr lr, [pc, lr, lsl #2] movs pc, lr @ branch to handler in SVC mode .endm
這個宏的目的就是,根據進入irq中斷前處理器所處的模式,將緊接着其下邊的16個地址池中對應位置的處理向量,取出來賦給PC,完成進一步跳轉。這裏咱們選擇讓程序跳轉到__irq_usr代碼段繼續執行。
三、異常處理進一步函數----__irq_usr
arch/arm/kernel/entry-armv.S
__irq_usr: usr_entry @將usr模式下的寄存器、中斷返回地址保存到堆棧中 get_thread_info tsk @獲取當前進程的進程描述符中的成員變量thread_info的地址,並將該地址保存到寄存器tsk等於r9 irq_handler @中斷處理 mov why, #0 b ret_to_user @中斷處理完成,返回中斷產生的位置
四、irq_handler
irq_handler是一個宏,將其內容展開以下:
arch/arm/kernel/entry-armv.S
.macro irq_handler get_irqnr_preamble r5, lr 1: get_irqnr_and_base r0, r6, r5, lr movne r1, sp adrne lr, 1b bne asm_do_IRQ
.endm
因而可知,進入asm_do_IRQ函數開始具體的中斷處理。須要指出的是,asm_do_IRQ是中斷的C語言總入口函數。asm_do_IRQ函數原型爲:
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
在彙編處理階段,會爲asm_do_IRQ傳入兩個參數irq(中斷號)和regs,中斷號對應着發生了什麼樣的中斷事件,因而能夠採起什麼樣的中斷服務程序進行處理。
五、get_irqnr_and_base
include/asm-arm/arch-s3c2410/entry-macro.s
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp mov \base, #S3C24XX_VA_IRQ @@ try the interrupt offset register, since it is there ldr \irqstat, [ \base, #INTPND ] teq \irqstat, #0 beq 1002f ldr \irqnr, [ \base, #INTOFFSET ] mov \tmp, #1 tst \irqstat, \tmp, lsl \irqnr bne 1001f @@ the number specified is not a valid irq, so try @@ and work it out for ourselves mov \irqnr, #0 @@ start here @@ work out which irq (if any) we got movs \tmp, \irqstat, lsl#16 addeq \irqnr, \irqnr, #16 moveq \irqstat, \irqstat, lsr#16 tst \irqstat, #0xff addeq \irqnr, \irqnr, #8 moveq \irqstat, \irqstat, lsr#8 tst \irqstat, #0xf addeq \irqnr, \irqnr, #4 moveq \irqstat, \irqstat, lsr#4 tst \irqstat, #0x3 addeq \irqnr, \irqnr, #2 moveq \irqstat, \irqstat, lsr#2 tst \irqstat, #0x1 addeq \irqnr, \irqnr, #1 @@ we have the value 1001: adds \irqnr, \irqnr, #IRQ_EINT0 @加上中斷號的基準數值,獲得最終的中斷號 1002: @@ exit here, Z flag unset if IRQ .endm
linux系統中斷號判斷過程,是與硬件平臺相關的。例如S3C2410的中斷號判斷過程,是根據INTOFFSET來判斷的。可是,須要注意的是,中斷號的具體值是有平臺相關的代碼決定的,和硬件中斷掛起寄存器中的中斷號是不等的。
#define S3C2410_CPUIRQ_OFFSET (16) #define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET) /* main cpu interrupts */ #define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */ #define IRQ_EINT1 S3C2410_IRQ(1) /* 17 */ #define IRQ_EINT2 S3C2410_IRQ(2) /* 18 */ #define IRQ_EINT3 S3C2410_IRQ(3) /* 19 */ ...............