linux驅動之中斷處理過程彙編部分

      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 */
      ...............

 

參考資料:linux-2.6.26內核中ARM中斷實現詳解(轉)

相關文章
相關標籤/搜索