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