linux irq中断过程解析(基于ARM处理器)

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_ (parent PC) and spsr_
        @ (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_, already fixed up for correct return/restart
        @  r5 - spsr_
        @  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

 

你可能感兴趣的:(Kernel)