Arm linux 中断的处理过程
Arm处理器触发外部中断后,处理器进入到状态Arm的中断异常是由外部的中断输入产生的。它的优先级比快速中断要低。在快速中断产生后,中断被屏蔽掉。
当CPSR中断I bit被设置后,中断被禁止掉。当I bit被清除掉后,中断被打开。
----------------------------------------------Note----------------------------------------------------
I bit位只有在处理器的特权状态中才能被更改
--------------------------------------------------------------------------------------------------------
当中断被触发之后,处理器进入下面的状态
Cpu进入irq状态
R14_irq = 下一条指令 + 4 的地址
SPSR_irq = CPSR
CPSR(4:0) = 0b10010
CPSR(5) = 0
CPSR(7) = 1 //disable normal interrupt
CPSR(8) = 1
CPSR(9) = CP15_reg1_EEbit
If VE=0
If high vectors configured then
PC = 0xffff0018
Else
PC = 0x00000018
Else
PC = 不可预测
从上面的分析可知,arm 处理器触发中断置后进入到状态可以总结为
1) 进入了处理器的IRQ模式
2) R14_irq 保存了中断之前的指令地址,SPSR_irq保存了中断之前的寄存器状态
3) R13_irq中断状态下的堆栈
中断处理第一步,跳转执行异常向量表中的中断向量在arm体系中,中断属于处理器的一种异常状态,当处理器触发外部中断的时候,处理器会直接跳转到一个异常向量表中取中断异常入口点的指令执行。在ARM V4及V4T以后的大部分处理器中,异常向量表的位置可以有两个位置:一个是0,另一个是0xffff0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的对应关系如下:
V=0 ~ 0x00000000~0x0000001C
V=1 ~ 0xffff0000~0xffff001C
Linux中选择的是设置0xffff0000为异常向量表的位置
early_trap_init函数对异常向量表进行了初始化操作
void __init early_trap_init(void) { unsigned long vectors = CONFIG_VECTORS_BASE; 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. */ memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz); /* * Copy signal return handlers into the vector page, and * set sigreturn to be a pointer to these. */ memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes, sizeof(sigreturn_codes)); flush_icache_range(vectors, vectors + PAGE_SIZE); modify_domain(DOMAIN_USER, DOMAIN_CLIENT); } |
异常向量表的定义如下,当处理器触发了外部中断之后,跳转到红色的中断向量执行中断处理过程。
.globl __vectors_start __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:
|
保存信息跳转到svc状态1) 将lr的地址减4存入到lr中,这时lr中的地址就是发生中断时执行指令的下一条指令
2) 将r0 和lr的地址保存到irq的栈中,将spsr的数值存到lr中,在将这个数值保存到中断的栈中。
3) 将原来的IRQ_MODE清除掉,将SVC_MODE设置到r0寄存器,然后将r0值写入到spsr_cxsf寄存器。
4) and lr, lr, #0x0f 确定中断之前是处理器处于什么样的状态,一般只能处于USER状态或者是SVC状态
5) mov r0, sp 将irq状态下的栈指针通过r0传递到SVC状态下的中断处理程序
6) ldr lr, [pc, lr, lsl #2] 计算跳转地址,PC指针加0或者加12,如果中断前是user模式就加0,如果中断前是svc模式就增加12
7) movs pc, lr 将__irq_svc地址载入到PC,模式切换到SVC模式
中断处理的跳转表就挨在vector_stub之后,定义如下。
vector_stub irq, IRQ_MODE, 4 .macro vector_stub, name, mode, correction=0 .align 5 vector_\name: .if \correction sub lr, lr, #\correction .endif 将lr的地址减4存入到lr中,这时lr中的地址就是发生中断时执行指令的下一条指令 @ @ Save r0, lr_ (parent PC) and spsr_ @ (parent CPSR) @ stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, [sp, #8] @ save spsr 将r0 和lr的地址保存到irq的栈中,将spsr的数值存到lr中,在将这个数值保存到中断的栈中。 @ @ Prepare for SVC32 mode. IRQs remain disabled. @ mrs r0, cpsr eor r0, r0, #(\mode ^ SVC_MODE) msr spsr_cxsf, r0 将原来的IRQ_MODE清除掉,将SVC_MODE设置到r0寄存器,然后将r0值写入到spsr_cxsf寄存器。 @ @ 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 ENDPROC(vector_\name) .endm |
__irq_svc的处理流程Svc_enctry紧接着后面会做分析,它的作用就是将cpu相应的寄存器保存到堆栈,并将irq状态中保存的spsr_riq lr_irq等也保存到svc状态下的栈中。
get_thread_info tsk取得thread_info结构体,增加preempt计数。
执行irq_handler,它的主要处理就是读取中断相关的寄存器,读出相应的中断号,然后传递给函数asm_do_IRQ函数进行中断处理。asm_do_IRQ是一个c语言实现的函数,到了linux内核层面,基本上和体系不相关。之后的分析中再介绍
从asm_do_IRQ函数中返回后,检查是否发生抢占,如果满足抢占的条件,就调用svc_preempt。
最后完成了中断的处理之后,恢复处理器中断之前的状态
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
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 |
Svc_entry保存中断现场的处理过程Svc_entry主要将cpu相应的寄存器保存到堆栈,并将irq状态中保存的spsr_riq lr_irq等也保存到svc状态下的栈中。
保存中断现场寄存器处理过程中,将这些寄存器保存在栈中,相应的寄存器组织结构如下图所示。
__irq_svc: svc_entry #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_off #endif #ifdef CONFIG_PREEMPT get_thread_info tsk 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 str r8, [tsk, #TI_PREEMPT] @ restore preempt count ldr r0, [tsk, #TI_FLAGS] @ get flags teq r8, #0 @ if preempt count != 0 movne r0, #0 @ force flags to 0 tst r0, #_TIF_NEED_RESCHED blne svc_preempt #endif ldr r0, [sp, #S_PSR] @ irqs are already disabled msr spsr_cxsf, r0 #ifdef CONFIG_TRACE_IRQFLAGS tst r0, #PSR_I_BIT bleq trace_hardirqs_on #endif ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr ENDPROC(__irq_svc) |