在裸板程序中,按键中断的head.S如下:
@******************************************************************************
@ File:head.S
@ 功能:初始化,设置中断模式、管理模式的栈,设置好中断处理函数
@******************************************************************************
.extern main
.text
.global _start
_start:
@******************************************************************************
@ 异常向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
@******************************************************************************
b Reset
@ 0x04: 未定义指令中止模式的向量地址
HandleUndef:
b HandleUndef
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
HandleSWI:
b HandleSWI
@ 0x0c: 指令预取终止导致的异常的向量地址
HandlePrefetchAbort:
b HandlePrefetchAbort
@ 0x10: 数据访问终止导致的异常的向量地址
HandleDataAbort:
b HandleDataAbort
@ 0x14: 保留
HandleNotUsed:
b HandleNotUsed
@ 0x18: 中断模式的向量地址
b HandleIRQ
@ 0x1c: 快中断模式的向量地址
HandleFIQ:
b HandleFIQ
Reset:
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
msr cpsr_c, #0xd2 @ 进入中断模式
ldr sp, =3072 @ 设置中断模式栈指针
msr cpsr_c, #0xd3 @ 进入管理模式
ldr sp, =4096 @ 设置管理模式栈指针,
@ 其实复位之后,CPU就处于管理模式,
@ 前面的“ldr sp, =4096”完成同样的功能,此句可省略
bl init_led @ 初始化LED的GPIO管脚
bl init_irq @ 调用中断初始化函数,在init.c中
msr cpsr_c, #0x53 @ 设置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
由此可见中断处理的过程为:
1.按键按下,
2.cpu核自动完成下面3件事
①将cpsr的值复制到异常模式的spsr
②将cpsr的工作模式设为异常中断模式
③PC值等于异常向量表的地址,比如如上head.S中,PC=0x18即执行b HandleIRQ指令, 进入中断入口
3.进入中断入口HandleIRQ处执行又分为:
①保存被中断的现场(比如管理模式下的r0-r12寄存器,返回地址)
②调用中断处理函数
③恢复被中断的现场(比如将保存在中断栈中的管理模式的模式的寄存器r0,r12,被中断指令的下一条地址lr,spsr的值复制到cpsr)
编码时需要
①设置异常向量表,设置各种模式的栈地址,init.c相应的设置中断引脚,中断打开,中断优先级,总中断cpsr的值设置等等
②跳转到异常入口处
b HandleIRQ
③跳到异常入口处执行①保存被中断的现场(比如管理模式下的r0-r12寄存器,返回地址)
②调用中断处理函数
③恢复被中断的现场(比如将保存在中断栈中的管理模式的模式的寄存器r0,r12,被中断指令的下一条地址lr,spsr的值复制到cpsr)
其中调用中断处理函数分为:
①获得中断号
②对不同的中断,进行相应处理或者调用相应中断服务函数
③清中断,清中断在②之前或之后要根据中断是否发生或者是否可忽略来定
对linux2.6.22来说,trap.c中有函数
void __init early_trap_init(void)
函数调用memcpy将异常向量表代码复制到CONFIG_VECTORS_BASE即虚拟地址0xffff0000处,跳转目的代码复制到0xffff0000+0x200 memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
unsigned long vectors = CONFIG_VECTORS_BASE;//xyc: .config中CONFIG_VECTORS_BASE=0xffff0000
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
//xyc:__vectors_start,异常向量入口在entry-armv.S文件中
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
开始异常向量表代码在entry-armv.S (arch\arm\kernel)中,异常向量表保存在__vectors_start全局变量地址处,异常向量表如下:<pre name="code" class="cpp"> .globl __stubs_start
__vectors_start:swi SYS_ERROR0b vector_und + stubs_offsetldr pc, .LCvswi + stubs_offsetb vector_pabt + stubs_offsetb vector_dabt + stubs_offsetb vector_addrexcptn + stubs_offsetb vector_irq + stubs_offsetb vector_fiq + stubs_offset.globl __vectors_end__vectors_end:
比如发生中断,执行(vector_irq由宏vector_stub实现),其中stubs_offset=__vectors_start + 0x200 - __stubs_start=vectors+ 0x200 - (vectors + 0x200)=0 b vector_irq + stubs_offset
而vector_irq由宏定义
vector_stub irq, IRQ_MODE, 4
vector_stub irq, IRQ_MODE, 4
<pre name="code" class="cpp"> .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
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
因此vector_irq展开为(①中断入口):
vector_irq:
sub lr, lr, #4
@
@ 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
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
a进程产生了中断b,中断b处理中又产生了中断c,a/b/c 共享一个cpsr如果b,c为父子进程,则b c共享一个b_bpsr,所以a的cpsr保存在b_bpsr中,b中断中产生c的话,b的cpsr值自动保存在b_cpsr,而a的cpsr已经保存在b_bpsr中,所以需要将保存在b_bpsr的a的cpsr 值压栈,
然后跳到:
.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
__irq_usr表示在用户态发生了中断:即跳到__irq_usr标号地址处:
__irq_usr:
usr_entry
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_info tsk
#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
b ret_to_user
.ltorg
.align 5
usr_entry是个宏(②保存寄存器的值):
.macro usr_entry
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
#if __LINUX_ARM_ARCH__ < 6 && !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
#ifndef CONFIG_MMU
#warning "NPTL on non MMU needs fixing"
#else
@ make sure our user space atomic helper is aborted
cmp r2, #TASK_SIZE
bichs r3, r3, #PSR_Z_BIT
#endif
#endif
@
@ 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}
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
irq_handler
irq_handler是一个宏 .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
最后调用asm_do_IRQ中断处理函数,处理完中断处理,以下的被中断的现场恢复就不分析了
总结异常处理:
⑴cpu进入异常模式
⑵ b
vector_irq + stubs_offset
⑶ 调用中断处理函数 asm_do_IRQ