a.异常向量入口:(linux-4.19-rc3\arch\arm\kernel\entry-armv.S)
对于ARM处理器而言,当发生异常的时候,处理器会暂停当前指令的执行,保存现场,转而去执行对应的异常向量处的指令,当处理完该异常的时候,恢复现场,回到原来的那点去继续执行程序。系统所有的异常向量(共计8个)组成了异常向量表。向量表(vector table)的代码如下:
.section .vectors, "ax", %progbits
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq //中断向量宏
W(b) vector_fiq
异常向量表可能被安放在两个位置上:
(1)异常向量表位于0x0的地址。这种设置叫做Normal vectors或者Low vectors。
(2)异常向量表位于0xffff0000的地址。这种设置叫做high vectors
具体是low vectors还是high vectors是由ARM的一个叫做的SCTLR寄存器的第13个bit (vector bit)控制的。对于启用MMU的ARM Linux而言,系统使用了high vectors。
为什么不用low vector呢?对于linux而言,0~3G的空间是用户空间,如果使用low vector,那么异常向量表在0地址,那么则是用户空间的位置,因此linux选用high vector。当然,使用Low vector也可以,这样Low vector所在的空间则属于kernel space了。(也就是说,3G~4G的空间加上Low vector所占的空间属于kernel space)
为了防止这块区域被访问到,vector table所在的空间通常被设置为read only。
b.中断向量: vector_irq
/*
* Interrupt dispatcher
*/
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
vector_stub宏的定义如下:
.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 | PSR_ISETSTATE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp
ARM( ldr lr, [pc, lr, lsl #2] )
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
.align 2
@ handler addresses follow this label
1:
.endm
展开后得到
/*
* Interrupt dispatcher
*/
.align 5
vector_irq:
sub lr, lr, #4 @得到irq返回地址
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ r0写入sp地址 sp+=4 lr写入sp地址 sp+=4
mrs lr, spsr @ spsr取到lr
str lr, [sp, #8] @ 将spsr写入sp+8的地址
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr @取得cpsr
eor r0, r0, #(IRQ_MODE ^ SVC_MODE) @IRQ_MODE = 0x00000012 SVC_MODE = 0x00000013 两者异或得到0x01 将r0的的0x01位取反
msr spsr_cxsf, r0 @将r0重新写入spsr,设置为svc模式
@
@ the branch table must immediately follow this code
@
@lr的内容为spsr,spsr是发生IRQ时保存下来的CPSR 实际上就是根据CPSR.M[3:0]的值进行跳转
and lr, lr, #0x0f @获取spsr低4位(模式位),发生在用户空间为0,发生在内核空间为3
mov r0, sp
ldr lr, [pc, lr, lsl #2] @lr = pc + lr * 4 要么是__irq_usr的地址 ,要么是__irq_svc的地址
movs pc, lr @ 带s后缀的指令会把spsr赋值给cpsr 此时会进入管理模式 然后pc=lr 跳转执行对应函数
.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
最终实现的功能如下:
计算处理完异常的返回地址;
保存寄存器(r0,lr,spsr)
进入管理模式;
最后根据进入异常前的模式跳转到相应的某个分支。
关于spsr和cpsr的描述可以看看这篇博文https://www.cnblogs.com/lifexy/p/7101686.html
c. __irq_usr/__irq_svc
这2个函数的处理过程类似:
保存现场
调用 irq_handler
恢复现场
__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_svc:
svc_entry
irq_handler
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get 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
svc_exit r5, irq = 1 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
d.irq_handler: 将会调用C函数 handle_arch_irq
.macro irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
ldr r1, =handle_arch_irq @伪指令 得到handle_arch_irq函数的绝对地址
mov r0, sp
@设置返回地址 badr是一个宏定义在/arch/arm/include/asm/assembler.h中
badr lr, 9997f
ldr pc, [r1] @赋值pc 跳转执行
#else
arch_irq_handler_default
#endif
9997:
.endm
搜索handle_arch_irq可以知道,实际上是set_handle_irq()设置了这个函数指针。
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
return 0;
}
对于不同的芯片,会调用这个函数设置不同的handle_arch_irq。
对于S3C2440来说, drivers/irqchip/irq-s3c24xx.c中的s3c24xx_init_intc()函数调用了set_handle_irq()。
static struct s3c_irq_intc * __init s3c24xx_init_intc(struct device_node *np,
struct s3c_irq_data *irq_data,
struct s3c_irq_intc *parent,
unsigned long address)
{
.....
set_handle_irq(s3c24xx_handle_irq);
.....
}
s3c24xx_handle_irq()是用于处理中断的C语言入口函数,也是中断的总入口
asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs)
{
do {
if (likely(s3c_intc[0]))
if (s3c24xx_handle_intc(s3c_intc[0], regs, 0))
continue;
if (s3c_intc[2])
if (s3c24xx_handle_intc(s3c_intc[2], regs, 64))
continue;
break;
} while (1);
}
e. handle_arch_irq的处理过程:
1.读 int controller, 得到hwirq(硬件中断号)
2.根据hwirq得到virq(虚拟中断号,虚拟中断号一般是硬件中断号的偏移)
3.调用 irq_desc[virq].handle_irq
如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
1. 调用irq_desc[virq].handle_irq函数
2. 取出irq_desc[virq].action链表中的每个节点的handler, 执行它
3. 使用irq_desc[virq].irq_data.chip的函数清中断
如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作:
1. 读 sub int controller, 得到hwirq'
2. 根据hwirq'得到virq
3. 调用 irq_desc[virq].handle_irq,之后的流程等同于没有子中断的操作