linux irq中断过程解析(基于ARM处理器)
.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
.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)
/*
* 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
.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
.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
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)
.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
.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)
.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)
.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