linux 中断(一)

对于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)

user_entry是一个宏。主要是保护寄存器

	.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_handler。跳转到handle_arch_irq执行。对于单个handler就是arch_irq_handler_default

一下根据树莓派来确定执行流程。

我的树莓派是单个处理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接收到信号。进入中断向量中。获取中断的号码等信息。后续就是根据中断的号码执行对应的函数了




你可能感兴趣的:(linux_kernel)