linux系统下驱动中,中断异常的处理过程,与裸机开发中断处理过程非常类似。通过简单的回顾裸机开发中断处理部分,来参考学习linux系统下中断处理流程。
一、ARM裸机开发中断处理过程
以S3C2440的裸机开发启动文件中,有关irq中断部分代码为例进行说明:
.extern main .text .global _start _start: b Reset HandleUndef: b HandleUndef HandleSWI: b HandleSWI HandlePrefetchAbort: b HandlePrefetchAbort HandleDataAbort: b HandleDataAbort HandleNotUsed: b HandleNotUsed b HandleIRQ HandleFIQ: b HandleFIQ Reset: ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈 bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启 msr cpsr_c, #0xd2 @ 进入中断模式 ldr sp, =3072 @ 设置中断模式栈指针 msr cpsr_c, #0xdf @ 进入系统模式 ldr sp, =4096 @ 设置系统模式栈指针 bl init_led @ 初始化LED的GPIO管脚 bl init_irq @ 调用中断初始化函数,在init.c中 msr cpsr_c, #0x5f @ 设置I-bit=0,开IRQ中断 ldr lr, =halt_loop @ 设置返回地址 ldr pc, =main @ 调用main函数 halt_loop: b halt_loop HandleIRQ: sub lr, lr, #4 @ 计算返回地址 stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器 @ 注意,此时的sp是中断模式的sp,初始值是上面设置的3072 ldr lr, =int_return @ 设置调用ISR即EINT_Handle函数后的返回地址 ldr pc, =EINT_Handle @ 调用中断服务函数,在interrupt.c中 int_return: ldmia sp!, { r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
当irq中断发生时,一些列的处理流程如下:
1、硬件自动令PC置为irq的中断向量,从而执行跳转指令“b HandleIRQ”。
其实,之前还伴随着保存中断断点地址到lr(还要换算);CPSR的值到SPSR;将CPSR切换到异常模式。
2、保存中断现场
sub lr, lr, #4 @ 计算返回地址 stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器
3、执行中断服务程序
ldr lr, =int_return @ 设置调用ISR即EINT_Handle函数后的返回地址
ldr pc, =EINT_Handle @ 调用中断服务函数,在interrupt.c中
4、从中断异常工作模式返回
int_return: ldmia sp!, { r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
二、linux系统中断处理流程
具体的代码细节没有分析,主要是为了理清中断处理的整体脉络。
1、ARM异常向量表
arch/arm/kernel/entry-armv.S
.globl __vectors_start __vectors_start: swi SYS_ERROR0 /* 复位时,执行这条指令 */ b vector_und + stubs_offset /* 未定义异常 */ ldr pc, .LCvswi + stubs_offset /* swi异常 */ b vector_pabt + stubs_offset /* 指令预取异常 */ b vector_dabt + stubs_offset /* 数据访问终止 */ b vector_addrexcptn + stubs_offset /* 没有用 */ b vector_irq + stubs_offset /* irq异常 */ b vector_fiq + stubs_offset /* fiq异常 */ .globl __vectors_end __vectors_end:
异常向量表,无非还是一些跳转指令。当发生irq中断,执行指令“b vector_irq + stubs_offset”,也就是跳转到vector_irq代码段继续执行。
在linux内核初始化阶段,start_kernel函数(init/main.c)会调用trap_init、init_IRQ两个函数来初始化异常向量相关处理函数。简要说明就是,将异常向量表拷贝到地址0xffff0000处(ARM体系协处理器寄存器c1能设置异常向量的基地址为0xffff0000),再把异常向量表中异常处理的进一步函数代码段拷贝到0xffff0200位置(vector_und、vector_irq等)。
2、异常处理进一步函数----vector_irq
arch/arm/kernel/entry-armv.S
1 .globl __stubs_start 2 __stubs_start: 3 vector_stub irq, IRQ_MODE, 4 4 .long __irq_usr @ 0 (USR_26 / USR_32) 5 .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) 6 .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) 7 .long __irq_svc @ 3 (SVC_26 / SVC_32) 8 .long __irq_invalid @ 4 9 .long __irq_invalid @ 5 10 .long __irq_invalid @ 6 11 .long __irq_invalid @ 7 12 .long __irq_invalid @ 8 13 .long __irq_invalid @ 9 14 .long __irq_invalid @ a 15 .long __irq_invalid @ b 16 .long __irq_invalid @ c 17 .long __irq_invalid @ d 18 .long __irq_invalid @ e 19 .long __irq_invalid @ f
从第4行到第19行,记录了(代码链接阶段填入的地址数据)在各个模式下遇到irq中断时,发生异常的处理分支。比如第4行__irq_usr表示用户模式下发生irq中断时,由__irq_usr对应的代码段来处理这种情况。
vector_stub是一个宏,将宏展开内容如下:
vector_irq: sub lr, lr, #4 stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, [sp, #8] @ save spsr mrs r0, cpsr eor r0, r0, #(IRQ_MODE ^ SVC_MODE) msr spsr_cxsf, r0 and lr, lr, #0x0f mov r0, sp ldr lr, [pc, lr, lsl #2] movs pc, lr @ branch to handler in SVC mode .endm
这个宏的目的就是,根据进入irq中断前处理器所处的模式,将紧接着其下边的16个地址池中对应位置的处理向量,取出来赋给PC,完成进一步跳转。这里我们选择让程序跳转到__irq_usr代码段继续执行。
3、异常处理进一步函数----__irq_usr
arch/arm/kernel/entry-armv.S
__irq_usr: usr_entry @将usr模式下的寄存器、中断返回地址保存到堆栈中 get_thread_info tsk @获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk等于r9 irq_handler @中断处理 mov why, #0 b ret_to_user @中断处理完成,返回中断产生的位置
4、irq_handler
irq_handler是一个宏,将其内容展开如下:
arch/arm/kernel/entry-armv.S
.macro irq_handler get_irqnr_preamble r5, lr 1: get_irqnr_and_base r0, r6, r5, lr movne r1, sp adrne lr, 1b bne asm_do_IRQ
.endm
由此可见,进入asm_do_IRQ函数开始具体的中断处理。需要指出的是,asm_do_IRQ是中断的C语言总入口函数。asm_do_IRQ函数原型为:
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
在汇编处理阶段,会为asm_do_IRQ传入两个参数irq(中断号)和regs,中断号对应着发生了什么样的中断事件,于是可以采取什么样的中断服务程序进行处理。
5、get_irqnr_and_base
include/asm-arm/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 ] 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 @加上中断号的基准数值,得到最终的中断号 1002: @@ exit here, Z flag unset if IRQ .endm
linux系统中断号判断过程,是与硬件平台相关的。例如S3C2410的中断号判断过程,是根据INTOFFSET来判断的。但是,需要注意的是,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起寄存器中的中断号是不等的。
#define S3C2410_CPUIRQ_OFFSET (16) #define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET) /* main cpu interrupts */ #define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */ #define IRQ_EINT1 S3C2410_IRQ(1) /* 17 */ #define IRQ_EINT2 S3C2410_IRQ(2) /* 18 */ #define IRQ_EINT3 S3C2410_IRQ(3) /* 19 */ ...............