对于arm的设备,有七个异常中断向量+一个保留向量一共8个。
是按照顺序排列的。每个向量地址对应一个字大小的空间用于跳转到特定的处理函数。
1 复位向量 0X00000000/0XFFFF0000
2 未定义指令终止向量 0X00000004/0XFFFF0004
3SWI向量 0X00000008/0XFFFF0008
4预取指令终止向量 0X0000000c/0XFFFF000c
5数据访问终止向量 0X00000010/0XFFFF0010
6保留 0X00000014/0XFFFF0014
7外部中断向量 0X00000018/0XFFFF0018
8快速中断向量 0X0000001c/0XFFFF001c
地址一般是固定的
从0X00000000-0x0000001c或者 0XFFFF0000-0XFFFF001C。通过CP15协处理器的寄存器的某个寄存器可以控制。
当然也有可以自定义的。比如树莓派的BCM2835芯片armv7就可以使用CP2来自定义异常中断向量的地址。
当CPU遇到特定的异常的时候就会跳转到对应的地址上,执行指令,比如当前CPU执行的指令是一个未定义的指令,CPU出现异常。就会跳到未定义指令终止向量(0x00000004/0xffff0004)去执行这里的指令。访问数据出现错误的时候就会跳转到5数据访问终止向量 (0X00000010/0XFFFF0010)这里执行。
外部中断就会跳转到 (外部中断向量 0X00000018/0XFFFF0018)。比如插入了一个USB设备,网卡接收到了数据,就会跳转到这里来执行。
7 外部中断向量IRQ 和快速中断向量FIQ的区别是。FIQ一般用于处理耗时比较短的一些中断,因为FIQ有R8-R14寄存器,比IRQ的R13和R14寄存器要多一些,IRQ例程里面可能要先保护R8-R12,执行完中断函数后再恢复,FIQ就不需要,中断如何区分是插入了USB设备还是网卡接收到了数据呢?这就要通过中断号来区分了。后续再看。
这里先看这几个例程的定义。
arm\kernel\entry-armv.S里面定义了这几个向量 。
__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
W(b) vector_fiq
b表示跳转到xxx。
比如W(b) vector_irq
vector_irq的定义是
vector_stub irq, IRQ_MODE, 4
vector_stub是一个宏,可以展开。
在进入这些模式之前,会执行一系列的操作,并且这些操作不可以被打断,可以参看<深度探索嵌入式操作系统>page44-46。
进入irq模式之前
1将处理器设置成ARM状态和外部中断模式
2把中断后将要执行的第二条指令(参看ARM流水线)的地址放到中断模式下的R14(lr)寄存器中。将CPSR放到外部中断模式的SPSR寄存器中
3禁止外部设备中断
4设置R15位外部中断向量。也就是PC=vector_irq 跳转到这里执行
看一下vector_irq函数执行的操作。展开vector_stub irq, IRQ_MODE, 4以后。
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction correction=4 因为执行当前指令发生中断的时候,PC已经指向了后续第2条指令(参看ARM指令流水线),所以这里lr=lr-4,处理完毕以后。回到当前中断的下一条指令执行,注意这里的LR已经是IRQ模式下的LR寄存器了。
.endif
@
@ Save r0, lr_ (parent PC) and spsr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr 保存R0和R14到堆栈(IRQ模式的堆栈)中。保存被中断的下一条指令lr,r0和lr都用于后续的临时操作r0也要保存。
mrs lr, spsr 将SPSR放到LR中
str lr, [sp, #8] @ save spsr 将SPSR放到SP+8的地址里面 地址从低到高分别是R0 LR SPSR(中断前的CPSR)
@
@ Prepare for SVC32 mode. IRQs remain disabled. 准备从中断模式切换到管理模式(切换过去以后,SP和LR不一样了)
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0 切换CPSR进入SVC模式
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f lr=IRQ模式spsr=中断前的CPSR。这里是获取中断之前的模式.取得cpsr的后四位。
mov r0, sp 让R0指向IRQ中断模式的堆栈指针
ldr lr, [pc, lr, lsl #2] 根据之前的模式取得将要跳转的地址。
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
首先堆栈如下
spsr (中断前的cpsr)
lr
r0 <-------- sp
执行 ldr lr,[pc,lr,lsl#2]时。PC等于 movs pc,lr下面一条指令。也就是此时的PC指向.long __irq_usr
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)
.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
此时的lr是cpsr的后四位 ,0-0xf 跳转到下面的@对应的位置上
ldr lr,[pc,lr,lsl#2] 这句意思是 先把 lr右移两位(也就是乘4,移动一个long的长度)再把PC+lr里面的值赋值给lr。
也就是根据之前的模式跳转到特定的函数位置。如果之前是usr模式0。那么跳转到__irq_usr。如果是3 跳转到__irq_svc。
假设之前是usr模式。那么就跳转到__irq_usr执行
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq
UNWIND(.fnend )
ENDPROC(__irq_usr)
.macro usr_entry, trace=1
sub sp, sp, #S_FRAME_SIZE 预留一部分栈空间 (我这里大小是0X48)
ARM( stmib sp, {r1 - r12} ) 保护用户空间的R1-R12寄存器
ATRAP( mrc p15, 0, r7, c1, c0, 0)
ATRAP( ldr r8, .LCcralign)
ldmia r0, {r3 - r5}
add r0, sp, #S_PC @ here for interlock avoidance
mov r6, #-1 @ "" "" "" ""
str r3, [sp] @ save the "real" r0 copied
@ 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}
ARM( stmdb r0, {sp, lr}^ )
THUMB( store_user_sp_lr r0, r1, S_SP - S_PC )
@ 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_IRQSOFF_TRACER
bl trace_hardirqs_off
#endif
ct_user_exit save = 0
.endif
.endm
目前由于对arm中断了解得不多。汇编部分 主要参看。。。。到时候具体处理再来看
http://blog.csdn.net/xiaojsj111/article/details/14129661
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq
UNWIND(.fnend )
ENDPROC(__irq_usr)
这里可以简单理解为 保存之前模式的寄存器。执行irq_handler函数。恢复寄存器,跳转到原来的指令的下一条来执行。
后续把重点放在irq_handler里面
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
一下根据树莓派来确定执行流程。
我的树莓派是单个处理irq。因此跳转到arch_irq_handler_default执行
arch_irq_handler_default定义在arch\arm\include\asm\entry-macro-multi.S的line7
.macro arch_irq_handler_default
get_irqnr_preamble r6, lr
1: get_irqnr_and_base r0, r2, r6, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, BSYM(1b)
bne asm_do_IRQ
#ifdef CONFIG_SMP
/*
* XXX
*
* this macro assumes that irqstat (r2) and base (r6) are
* preserved from get_irqnr_and_base above
*/
ALT_SMP(test_for_ipi r0, r2, r6, lr)
ALT_UP_B(9997f)
movne r1, sp
adrne lr, BSYM(1b)
bne do_IPI
#endif
9997:
.endm
上面的代码使用到的宏都定义在
arch\arm\mach-bcm2708\include\mach\entry-macro.S里面
.macro get_irqnr_preamble, base, tmp
ldr \base, =IO_ADDRESS(ARMCTRL_IC_BASE)
.endm
get_irqnr_and_base得到了中断的号码等等信息。随后调用asm_do_IRQ,在编译链接的时候会连接成另外一个名字__irqentry_text_start
在arch\arm\kernel\irq.c里面
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
__handle_domain_irq(NULL, irq, false, regs);
}
/*
* asm_do_IRQ is the interface to be used from assembly code.
*/
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
handle_IRQ(irq, regs);
}
asm_do_IRQ->handle_IRQ->__handle_domain_irq(arch\arm\kernel\irqdesc.c)执行
详细看一下 树莓派中
get_irqnr_preamble r6, lr
1: get_irqnr_and_base r0, r2, r6, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, BSYM(1b)
bne asm_do_IRQ
展开以后的代码如下
.text:C05340E4 LDR R6, =0xF200B200 这里R6=0XF200B200的来源参看BCM2835手册。外设的物理地址是0X20000000,映射到虚拟地址后是F2000000(Page6.(page 112)7.5 Registers定义了bcm2835中断寄存器的基地址为0XB000。而IRQ BASIC PENDING的偏移是0X200,因此这里就是取得IRQbasic Pending寄存器的位置
.text:C05340E8
.text:C05340E8 loc_C05340E8 ; DATA XREF: __irq_usr+A0o
.text:C05340E8 LDR R2, [R6]
.text:C05340EC MOV R0, #0x5F ; '_'
.text:C05340F0 AND LR, R2, #0x300
.text:C05340F4 BICS R2, R2, #0x300
.text:C05340F8 BNE loc_C053412C
.text:C05340FC TST LR, #0x100
.text:C0534100 LDRNE R2, [R6,#4]
.text:C0534104 MOVNE R0, #0x1F
.text:C0534108 BICNE R2, R2, #0x680
.text:C053410C BICNE R2, R2, #0xC0000
.text:C0534110 BNE loc_C053412C
.text:C0534114 TST LR, #0x200
.text:C0534118 LDRNE R2, [R6,#8]
.text:C053411C MOVNE R0, #0x3F ; '?'
.text:C0534120 BICNE R2, R2, #0x3E00000
.text:C0534124 BICNE R2, R2, #0x40000000
.text:C0534128 BEQ loc_C053413C
.text:C053412C
.text:C053412C loc_C053412C ; CODE XREF: __irq_usr+58j
.text:C053412C ; __irq_usr+70j
.text:C053412C SUB LR, R2, #1
.text:C0534130 EOR R2, R2, LR
.text:C0534134 CLZ LR, R2
.text:C0534138 SUB R0, R0, LR
.text:C053413C
.text:C053413C loc_C053413C ; CODE XREF: __irq_usr+88j
.text:C053413C MOVNE R1, SP
.text:C0534140 ADRNE LR, loc_C05340E8
.text:C0534144 BNE __irqentry_text_start
具体的irq注册和会有第二篇
看到这里我明白了,当有外部设备发生中断的时候。比如USB插入。键盘按键。CPU接收到信号。进入中断向量中。获取中断的号码等信息。后续就是根据中断的号码执行对应的函数了