基于Linux2.6.30.4分析IRQ中断的处理流程。
1.中断入口
/* arch/arm/kenel/entry-armv.S*/ b vector_irq + stubs_offset
2.vector_irq
vector_stub 宏展开即为vector_irq, 参考Linux异常体系之vector_stub宏解析。
/* * 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)
3.__irq_user
__irq_usr: usr_entry /*3.1*/ kuser_cmpxchg_check #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_off #endif get_thread_info tsk /*3.2*/ #ifdef CONFIG_PREEMPT /* *r8<--old preempt_count *r7<--preempt_count+1 *preempt_count<--r7 */ ldr r8, [tsk, #TI_PREEMPT] @ get preempt count add r7, r8, #1 @ increment it str r7, [tsk, #TI_PREEMPT] #endif irq_handler /*3.3*/ #ifdef CONFIG_PREEMPT /* *r0<--new preempt_count *preempt<--old preempt_count */ ldr r0, [tsk, #TI_PREEMPT] str r8, [tsk, #TI_PREEMPT] teq r0, r7 strne r0, [r0, -r0] #endif #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_on #endif mov why, #0 b ret_to_user /*3.4*/ UNWIND(.fnend ) ENDPROC(__irq_usr)
3.1__user_entry
.macro usr_entry UNWIND(.fnstart ) UNWIND(.cantunwind ) @ don't unwind the user space /* DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));*/ sub sp, sp, #S_FRAME_SIZE /**/ stmib sp, {r1 - r12} ldmia r0, {r1 - r3} add r0, sp, #S_PC @ here for interlock avoidance mov r4, #-1 @ "" "" "" "" str r1, [sp] @ save the "real" r0 copied @ from the exception stack @ @ We are now ready to fill in the remaining blanks on the stack: @ @ r2 - lr_<exception>, already fixed up for correct return/restart @ r3 - spsr_<exception> @ r4 - orig_r0 (see pt_regs definition in ptrace.h) @ @ Also, separately save sp_usr and lr_usr @ stmia r0, {r2 - r4} /*“^”符号表示访问user mode的寄存器*/ stmdb r0, {sp, lr}^ @ @ Enable the alignment trap while in kernel mode @ alignment_trap r0 @ @ Clear FP to mark the first stack frame @ zero_fp .endm
这里面用到pt_regs结构保存栈上的数据,8字节对齐
/* * This struct defines the way the registers are stored on the * stack during a system call. Note that sizeof(struct pt_regs) * has to be a multiple of 8. */ struct pt_regs { long uregs[18]; };
与之相关的宏定义如下
DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs)); DEFINE(S_R0, offsetof(struct pt_regs, ARM_r0)); DEFINE(S_R1, offsetof(struct pt_regs, ARM_r1)); DEFINE(S_R2, offsetof(struct pt_regs, ARM_r2)); DEFINE(S_R3, offsetof(struct pt_regs, ARM_r3)); DEFINE(S_R4, offsetof(struct pt_regs, ARM_r4)); DEFINE(S_R5, offsetof(struct pt_regs, ARM_r5)); DEFINE(S_R6, offsetof(struct pt_regs, ARM_r6)); DEFINE(S_R7, offsetof(struct pt_regs, ARM_r7)); DEFINE(S_R8, offsetof(struct pt_regs, ARM_r8)); DEFINE(S_R9, offsetof(struct pt_regs, ARM_r9)); DEFINE(S_R10, offsetof(struct pt_regs, ARM_r10)); DEFINE(S_FP, offsetof(struct pt_regs, ARM_fp)); DEFINE(S_IP, offsetof(struct pt_regs, ARM_ip)); DEFINE(S_SP, offsetof(struct pt_regs, ARM_sp)); DEFINE(S_LR, offsetof(struct pt_regs, ARM_lr)); DEFINE(S_PC, offsetof(struct pt_regs, ARM_pc)); DEFINE(S_PSR, offsetof(struct pt_regs, ARM_cpsr)); DEFINE(S_OLD_R0, offsetof(struct pt_regs, ARM_ORIG_r0)); #define ARM_cpsr uregs[16] #define ARM_pc uregs[15] #define ARM_lr uregs[14] #define ARM_sp uregs[13] #define ARM_ip uregs[12] #define ARM_fp uregs[11] #define ARM_r10 uregs[10] #define ARM_r9 uregs[9] #define ARM_r8 uregs[8] #define ARM_r7 uregs[7] #define ARM_r6 uregs[6] #define ARM_r5 uregs[5] #define ARM_r4 uregs[4] #define ARM_r3 uregs[3] #define ARM_r2 uregs[2] #define ARM_r1 uregs[1] #define ARM_r0 uregs[0] #define ARM_ORIG_r0 uregs[17]
3.2 get_thread_info tsk
tsk即r9寄存器的别名,内核中为寄存器声明的别名如下
/* * These are the registers used in the syscall handler, and allow us to * have in theory up to 7 arguments to a function - r0 to r6. * * r7 is reserved for the system call number for thumb mode. * * Note that tbl == why is intentional. * * We must set at least "tsk" and "why" when calling ret_with_reschedule. */ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info
get_thread_info tsk的作用是获取sp地址保存在tsk(r9)中,即r9中保存当前任务的thread_info结构的地址。
.macro get_thread_info, rd mov d, sp, lsr #13 /*获取sp地址*/ mov d, d, lsl #13 /*8KBytes对齐*/ .endm
3.3 irq_handler
irq_handler函数调用分析以后再写。
/* * Interrupt handling. Preserves r7, r8, r9 */ .macro irq_handler get_irqnr_preamble r5, lr 1: get_irqnr_and_base r0, r6, r5, lr movne r1, sp @ @ routine called with r0 = irq number, r1 = struct pt_regs * @ adrne lr, 1b bne asm_do_IRQ #ifdef CONFIG_SMP /* * XXX * * this macro assumes that irqstat (r6) and base (r5) are * preserved from get_irqnr_and_base above */ test_for_ipi r0, r6, r5, lr movne r0, sp adrne lr, 1b bne do_IPI #ifdef CONFIG_LOCAL_TIMERS test_for_ltirq r0, r6, r5, lr movne r0, sp adrne lr, 1b bne do_local_timer #endif #endif .endm
3.4 ret_to_user
ret_to_user函数调用分析以后再写。
/* * "slow" syscall return path. "why" tells us if this was a real syscall. */ ENTRY(ret_to_user) ret_slow_syscall: disable_irq @ disable interrupts ldr r1, [tsk, #TI_FLAGS] tst r1, #_TIF_WORK_MASK bne work_pending no_work_pending: /* perform architecture specific actions before user return */ arch_ret_to_user r1, lr @ slow_restore_user_regs ldr r1, [sp, #S_PSR] @ get calling cpsr ldr lr, [sp, #S_PC]! @ get pc msr spsr_cxsf, r1 @ save in spsr_svc ldmdb sp, {r0 - lr}^ @ get calling r0 - lr mov r0, r0 add sp, sp, #S_FRAME_SIZE - S_PC movs pc, lr @ return & move spsr_svc into cpsr ENDPROC(ret_to_user)