内核中断处理过程
S3C2410和Linux2.6.26内核为例讲解处理过程
1.中断向量表arch\arm\kernel\entry-armv.S
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
中断发生后,跳转到 b vector_irq + stubs_offset 的位置执行。注意现在的向
量表的初始位置是 0xffff0000。
2.向量表中找到入口位置(同一个文件中)
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4 @IRQ_MODE 在
include\asm\ptrace.h 中定义:0x12
.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)
.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
上面代码中 vector_stub 宏的定义为:
.macro vector_stub, name, mode, correction=0
.align 5(就在该文件呢)
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
andlr, lr, #0x0f @进入中断前的 mode 的后 4 位
@#define USR_MODE 0x00000010
@#define FIQ_MODE 0x00000011
@#define IRQ_MODE 0x00000012
@#define SVC_MODE 0x00000013
@#define ABT_MODE 0x00000017
@#define UND_MODE 0x0000001b
@#define SYSTEM_MODE 0x0000001f
mov r0, sp
ldrlr, [pc, lr, lsl #2] //根据user还是svc模式而不同,决定进入_irq_user还是_irq_svc
movs pc, lr @ branch to handler in SVC mode
.endm
.globl __stubs_start
__stubs_start:
/*
* 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)
用“irq, IRQ_MODE, 4”代替宏 vector_stub 中的“name, mode, correction”,
找到了我们中断处理的入口位置为 vector_irq(宏里面的 vector_\name)。
从上面代码中的注释可以看出,根据进入中断前的工作模式不同,程序下一步将
跳转到_irq_usr 、或__irq_svc 等位置。我们先选择__irq_usr 作为下一步跟踪
的目标。
3.__irq_usr的实现(同样的位置)
__irq_usr:
usr_entry
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_info tsk //获取进程中描述符threa_info的地址,存到寄存器里面
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler //重要的处理过程
#ifdef CONFIG_PREEMPT
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
bret_to_user //中断处理完成,返回中断产生位置
.ltorg
.align 5
①usr_entry
是一个宏:将usr模式下的寄存器和中断返回地址保存到堆栈(保护现场)
②ret_to_user中断返回过程 /arch/arm/kernel/entry-common.S
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
4.irq_handler的实现(同一个地址)
.macro irq_handler
get_irqnr_preamble r5, lr //include/asm/arch-s3c2410/entry-macro.s 中定义,为空操作
1: get_irqnr_and_base r0, r6, r5, lr //判断中断号,R0返回
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b
bneasm_do_IRQ //进入中断处理
①判断中断号
include/asm/arch-s3c2410/entry-macro.s
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov \base, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr \irqstat, [ \base, #INTPND ]
teq \irqstat, #0
beq 1002f
ldr \irqnr, [ \base, #INTOFFSET ] @通过判断 INTOFFSET 寄
存器得到中断位置
mov \tmp, #1
tst \irqstat, \tmp, lsl \irqnr
bne 1001f
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov \irqnr, #0 @@ start here
@@ work out which irq (if any) we got
movs \tmp, \irqstat, lsl#16
addeq \irqnr, \irqnr, #16
moveq \irqstat, \irqstat, lsr#16
tst \irqstat, #0xff
addeq \irqnr, \irqnr, #8
moveq \irqstat, \irqstat, lsr#8
tst \irqstat, #0xf
addeq \irqnr, \irqnr, #4
moveq \irqstat, \irqstat, lsr#4
tst \irqstat, #0x3
addeq \irqnr, \irqnr, #2
moveq \irqstat, \irqstat, lsr#2
tst \irqstat, #0x1
addeq \irqnr, \irqnr, #1
@@ we have the value
1001:
adds \irqnr, \irqnr, #IRQ_EINT0 @加上中断号的基准 数值,得到最
终的中断号,注意:此时没有考虑子中断的具体情况,(子中断的问题后面会
有讲解)。IRQ_EINT0 在 include/asm/arch- s3c2410/irqs.h 中定义.从这里可
以看出,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起寄存器
中的中断号是不等的。
1002:
@@ exit here, Z flag unset if IRQ
.endm
5.asm_do_IRQ 实现过程 arch/arm/kernel/irq.c
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq; //根据参数irq找到具体的中断号
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;trap_init
irq_enter(); //没用
desc_handle_irq(irq, desc); //根据中断号和desc结构进入中断处理
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
static inline void desc_handle_irq(unsigned int irq, struct irq_desc
*desc)
{
desc->handle_irq(irq, desc);//中断处理
}
①asmlinkage标志含义:
#include <asm/linkage.h>//各个具体处理器在此文件中定义 asmlinkage
#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif
#ifndef asmlinkage//如果以前没有定义 asmlinkage
#define asmlinkage CPP_ASMLINKAGE
#endif
对于 ARM 处理器的<asm/linkage.h>,没有定义 asmlinkage,所以没有意义(不
要以为参数是从堆栈传递的,对于 ARM 平台来说还是符合 ATPCS 过程调用标准,
通过寄存器传递的)。
但对于 X86 处理器的<asm/linkage.h>中是这样定义的:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
表示函数的参数传递是通过堆栈完成的。