天天看點

linux irq中斷過程解析(基于ARM處理器)1、中斷向量2、中斷處理

linux irq中斷過程解析(基于ARM處理器)

1、中斷向量

1.1、__vectors_start

        .section .vectors, "ax", %progbits
__vectors_start:
        W(b)    vector_rst
        W(b)    vector_und
        W(ldr)  pc, __vectors_start + 0x1000
        W(b)    vector_pabt
        W(b)    vector_dabt
        W(b)    vector_addrexcptn
         W(b)    vector_irq // irq中斷向量入口
        W(b)    vector_fiq
           

1.2、vector_stub

        .macro  vector_stub, name, mode, correction=0
        .align  5


vector_\name:   
        .if \correction
sub     lr, lr, #\correction //中斷傳回位址修正,ARM處理器有取指、譯碼、執行等多級流水線,pc指向取值的指令位址,硬體中斷會将pc-4存在lr,此時lr指向譯碼指令位址而不是正在執行的指令位址,是以需要再修正中斷傳回位址
        .endif


        @
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
        @ (parent CPSR)
        @
 stmia   sp, {r0, lr}            @ save r0, lr // 儲存r0, lr到irq棧
mrs     lr, spsr // 儲存中斷前的cpsr(中斷時硬體自動将中斷前的cpsr儲存到spsr)
str     lr, [sp, #8]            @ save spsr // 儲存cpsr到irq棧,此時irq棧的内容為r0,lr,cpsr


        @
        @ Prepare for SVC32 mode.  IRQs remain disabled.
        @
        mrs     r0, cpsr // 此時IRQs仍然處于禁止狀态(中斷的時候由硬體禁止)
        eor     r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) // 設定svc狀态位
        msr     spsr_cxsf, r0 // 儲存cpsr到spsr


        @
        @ the branch table must immediately follow this code
        @
and     lr, lr, #0x0f // 前面已經将中斷前的cpsr儲存到lr,此處擷取中斷前的模式,用于判斷是使用者态被中斷還是核心态被中斷
 THUMB( adr     r0, 1f                  )
 THUMB( ldr     lr, [r0, lr, lsl #2]    )
        mov     r0, sp // sp儲存到r0,切換模式後sp會改變成對應模式的sp,棧裡面已經儲存了部分中斷上下文r0,lr,cpsr
 ARM(   ldr     lr, [pc, lr, lsl #2]    ) 
 movs    pc, lr                  @ branch to handler in SVC mode // irq/fiq中斷向量表正好緊接目前指令之後,即pc等價于irq/fiq中斷向量表基位址,lr為中斷前模式,pc + lr * 4即得到對應模式的中斷入口函數位址,例如__irq_usr、__irq_svc,從不同模式進入中斷,處理流程有所不同,此處跳轉到對應模式的中斷處理程式
ENDPROC(vector_\name)
           

1.3、vector_irq

/*
 * Interrupt dispatcher
 */
        vector_stub     irq, IRQ_MODE, 4


        .long   __irq_usr                       @  0  (USR_26 / USR_32) // 使用者模式進入中斷
        .long   __irq_invalid                   @  1  (FIQ_26 / FIQ_32)
        .long   __irq_invalid                   @  2  (IRQ_26 / IRQ_32)
        .long   __irq_svc                       @  3  (SVC_26 / SVC_32) // svc模式進入中斷
        .long   __irq_invalid                   @  4
        .long   __irq_invalid                   @  5
        .long   __irq_invalid                   @  6
        .long   __irq_invalid                   @  7
        .long   __irq_invalid                   @  8
        .long   __irq_invalid                   @  9
        .long   __irq_invalid                   @  a
        .long   __irq_invalid                   @  b
        .long   __irq_invalid                   @  c
        .long   __irq_invalid                   @  d
        .long   __irq_invalid                   @  e
        .long   __irq_invalid                   @  f
           

2、中斷處理

2.1、__irq_usr(使用者模式進入中斷)

2.1.1、usr_entry

        .macro  usr_entry, trace=1, uaccess=1
 UNWIND(.fnstart        )
 UNWIND(.cantunwind     )       @ don't unwind the user space                                                                                                                                     
        sub     sp, sp, #S_FRAME_SIZE // 預留72位元組保留中斷上下文
 ARM(   stmib   sp, {r1 - r12}  ) // 1.2中已經儲存了r0,lr,cpsr
 THUMB( stmia   sp, {r0 - r12}  )


 ATRAP( mrc     p15, 0, r7, c1, c0, 0)
 ATRAP( ldr     r8, .LCcralign)


        ldmia   r0, {r3 - r5} // r0為切換模式前的sp,sp棧裡面儲存了中斷前的r0,lr,cpsr,即中斷上下文的一部分寄存器,将r0,lr,cpsr裝載到r3-r5
        add     r0, sp, #S_PC           @ here for interlock avoidance // sp + pc偏移 -> r0,r0指向棧位址用于儲存中斷傳回位址pc
        mov     r6, #-1                 @  ""  ""     ""        "" // r6 = 0xffffffff


        str     r3, [sp]                @ save the "real" r0 copied // 将中斷前的r0寄存器值儲存到但前棧,目前棧内容變為r0-r12
                                        @ from the exception stack


 ATRAP( ldr     r8, [r8, #0])


        @
        @ We are now ready to fill in the remaining blanks on the stack:
        @
        @  r4 - lr_<exception>, already fixed up for correct return/restart
        @  r5 - spsr_<exception>
        @  r6 - orig_r0 (see pt_regs definition in ptrace.h)
        @
        @ Also, separately save sp_usr and lr_usr
        @
        stmia   r0, {r4 - r6} // r4-r6從pc位址開始入棧,r4-r6的内容依次為lr(pc),cpsr,0xffffffff
 ARM(   stmdb   r0, {sp, lr}^                   ) // r0指向了pc儲存位址,sp、lr儲存位址介于r0-r12及pc之間,stmdb将使用者态的sp、lr儲存在棧中間,儲存之後棧内容依次是r0-r12,sp,lr,pc,cpsr,0xffffffff,此時中斷前的寄存器等都已經儲存到棧裡面了
 THUMB( store_user_sp_lr r0, r1, S_SP - S_PC    )


        .if \uaccess
        uaccess_disable ip
        .endif


        @ Enable the alignment trap while in kernel mode
 ATRAP( teq     r8, r7)
 ATRAP( mcrne   p15, 0, r8, c1, c0, 0)


        @
        @ Clear FP to mark the first stack frame
        @
        zero_fp


        .if     \trace
#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_off
#endif
        ct_user_exit save = 0
        .endif
        .endm
           

2.1.2、irq_handler

        .macro  irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
        ldr     r1, =handle_arch_irq // 處理器相關中斷處理函數位址
        mov     r0, sp // sp棧儲存了中斷上下文
        badr    lr, 9997f // handler_arch_irq傳回位址,即irq_handler的下一條指令位址,__irq_usr中的get_thread_info tsk指令
        ldr     pc, [r1] // 跳轉到irq中斷處理函數
#else
        arch_irq_handler_default
#endif
9997:
        .endm
           

2.1.3、ret_to_user_from_irq

ENTRY(ret_to_user_from_irq)
        ldr     r1, [tsk, #TI_FLAGS]
        tst     r1, #_TIF_WORK_MASK // 檢查是否有挂起的待處理任務,使用者程序被中斷後有可能需要重新排程等
        bne     slow_work_pending // 處理挂起的待處理任務
no_work_pending:
        asm_trace_hardirqs_on save = 0


        /* perform architecture specific actions before user return */
        arch_ret_to_user r1, lr
        ct_user_enter save = 0

        restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user_from_irq)
           

2.1.4 restore_user_regs

        .macro  restore_user_regs, fast = 0, offset = 0
        uaccess_enable r1, isb=0
#ifndef CONFIG_THUMB2_KERNEL
        @ ARM mode restore
        mov     r2, sp // sp儲存了中斷上下文,即使用者态寄存器、傳回位址等資訊
        ldr     r1, [r2, #\offset + S_PSR]      @ get calling cpsr // 擷取儲存的使用者态cpsr(裡面已經設定了使用者态模式标志位)
        ldr     lr, [r2, #\offset + S_PC]!      @ get pc // 擷取中斷傳回位址,使用者模式被中斷的指令執行位址
        msr     spsr_cxsf, r1                   @ save in spsr_svc // 使用者态cpsr儲存到spsr
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)
        @ We must avoid clrex due to Cortex-A15 erratum #830321
        strex   r1, r2, [r2]                    @ clear the exclusive monitor
#endif
        .if     \fast
        ldmdb   r2, {r1 - lr}^                  @ get calling r1 - lr
        .else
        ldmdb   r2, {r0 - lr}^                  @ get calling r0 - lr // sp棧裡面儲存的資料恢複到使用者态r0-lr (有部分寄存器svc模式與usr模式不共用)
        .endif
        mov     r0, r0                          @ ARMv5T and earlier require a nop
                                                @ after ldm {}^
        add     sp, sp, #\offset + S_FRAME_SIZE // 釋放sp中斷上下文所占用的記憶體
        movs    pc, lr                          @ return & move spsr_svc into cpsr // 跳轉到被中斷的使用者态指令位址,并且恢複spsr到cpsr(cpu模式切換)
......
#endif  /* !CONFIG_THUMB2_KERNEL */
        .endm
           

2.1.5、__irq_usr

        .align  5
__irq_usr:                                                                                                                                                                                        
        usr_entry // 儲存使用者态寄存器到棧裡面(儲存中斷上下文)
        kuser_cmpxchg_check
        irq_handler // 調用irq中斷處理函數
        get_thread_info tsk //- 擷取目前程序/線程的thread_info位址
        mov     why, #0
        b       ret_to_user_from_irq // 從irq模式傳回到使用者模式(中斷并沒有改變mmu,是以不需要切換mmu)
 UNWIND(.fnend          )
ENDPROC(__irq_usr)
           

2.2、__irq_svc(svc模式進入中斷)

2.2.1、__irq_svc

        .align  5
__irq_svc:                                                                                                                                                                                        
        svc_entry // 儲存中斷上下文,與使用者模式不同的是,中斷前後處于同一模式,使用同一套寄存器,儲存中斷上下文方法稍微有些不同,此處不過多解釋
        irq_handler // 中斷處理,與從使用者模式進入中斷是一樣的


#ifdef CONFIG_PREEMPT
        get_thread_info tsk
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        ldr     r0, [tsk, #TI_FLAGS]            @ get flags
        teq     r8, #0                          @ if preempt count != 0
        movne   r0, #0                          @ force flags to 0
        tst     r0, #_TIF_NEED_RESCHED
        blne    svc_preempt // 核心态搶占(需要打開CONFIG_PREEMPT開關)
#endif


        svc_exit r5, irq = 1                    @ return from exception // svc_entry已經将中斷前的cpsr儲存在r5寄存器中
 UNWIND(.fnend          )
ENDPROC(__irq_svc)
           

2.2.2、svc_exit

        .macro  svc_exit, rpsr, irq = 0
        .if     \irq != 0
......
        .else
        @ IRQs off again before pulling preserved data off the stack
        disable_irq_notrace
#ifdef CONFIG_TRACE_IRQFLAGS
        tst     \rpsr, #PSR_I_BIT
        bleq    trace_hardirqs_on
        tst     \rpsr, #PSR_I_BIT
        blne    trace_hardirqs_off
#endif
        .endif
        uaccess_restore


#ifndef CONFIG_THUMB2_KERNEL
        @ ARM mode SVC restore
        msr     spsr_cxsf, \rpsr // 中斷前cpsr儲存到spsr
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)
        @ We must avoid clrex due to Cortex-A15 erratum #830321
        sub     r0, sp, #4                      @ uninhabited address
        strex   r1, r2, [r0]                    @ clear the exclusive monitor
#endif
        ldmia   sp, {r0 - pc}^                  @ load r0 - pc, cpsr // 從棧裡面恢複中斷上下文,并且恢複cpsr
#else
......
#endif
        .endm
           

繼續閱讀