1. 目标
1)中断向量概念
2)中断向量的加载
3)中断硬件准备
3) 中断处理服务vector_irq的定义
4) 中断处理服务 vector_irq的处理流程
5) 如果进入中断前是svc模式处理场景
6)如果进入中断钱是usr模式处理场景
内核版本:4.4.17
2. 中断向量表
中断向量表中保存所有中断向量的入口,__vectors_start是一个中断向量表,其中的一项:W(b) vector_irq,就是放的一个跳转指令,跳转到vector_irq。
在arch/arm/kernel/entry-armv.S中的__vectors_start的定义如下:
1209 __vectors_start:
1210 W(b) vector_rst
1211 W(b) vector_und
1212 W(ldr) pc, __vectors_start + 0x1000
1213 W(b) vector_pabt
1214 W(b) vector_dabt
1215 W(b) vector_addrexcptn
1216 W(b) vector_irq //b是跳转指令
1217 W(b) vector_fiq
1218
1219 .data
地址 | 异常种类 |
---|---|
0xffff 0000 | 复位 |
0xffff 0004 | 未定义指令 |
0xffff 0008 | 软中断(SWI) |
0xffff 000c | prefetch abort |
0xffff 0010 | data abort |
0xffff 0014 | address exception |
0xffff 0018 | irq |
0xffff 001c | fiq |
3. 中断向量加载
arch/arm/kernel/traps.c中,early_trap_init函数中:
802 void __init early_trap_init(void *vectors_base)
803 {
804 #ifndef CONFIG_CPU_V7M
805 unsigned long vectors = (unsigned long)vectors_base;
806 extern char __stubs_start[], __stubs_end[];
807 extern char __vectors_start[], __vectors_end[];
808 unsigned i;
809
810 vectors_page = vectors_base;
811
812 /*
813 * Poison the vectors page with an undefined instruction. This
814 * instruction is chosen to be undefined for both ARM and Thumb
815 * ISAs. The Thumb version is an undefined instruction with a
816 * branch back to the undefined instruction.
817 */
818 for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
819 ((u32 *)vectors_base)[i] = 0xe7fddef1;
820
821 /*
822 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
823 * into the vector page, mapped at 0xffff0000, and ensure these
824 * are visible to the instruction stream.
825 */
826 memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
827 memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
828 printk(KERN_ERR "tom __vectors_start=%x __vectors_end=%x\n",__vectors_start,__vectors_end);
printk(KERN_ERR "tom %x %x %x %x\n",*((char *)vectors+24),*((char *)vectors+25),*((char *)vectors+26),*((char *)vectors+27));
838 kuser_init(vectors_base);
839
840 flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
841 #else /* ifndef CONFIG_CPU_V7M */
842 /*
843 * on V7-M there is no need to copy the vector table to a dedicated
844 * memory area. The address is configurable and so a table in the kernel
845 * image can be used.
846 */
847 #endif
848 }
打印信息是:
tom __vectors_start=80630000 __vectors_end=80630020
tom 0 4 0 ea
在System.map中有__vectors_start的符号是0x80630000 ,__vectors_end是 0x80630020和打印信息一致。
W(b) vector_irq在内存的内容分别是: 0x00 0x04 0x00 0xea,这些是机器码,转换为整数是:0xea000400,0xea是跳转b的机器码,0x400是偏移量,怎么算出偏移量呢?
在objdump -d vmlinux中输出信息中:
- 场景是:程序中执行到0x18: b vector_irq,跳转到0x1020中的vector_irq中的偏移量怎么算?
偏移量公式: (目标地址 - 指令地址 - 8)/ 4 = 偏移
则: (0x1020-0x18-8)/4=0x400
2)静态分析vmlinux中b vector_irq的机器码就是0xea00400,和内存运行时的内容一样的。
3 vector_irq定义
vector_stub的定义如下:
1027 .macro vector_stub, name, mode, correction=0 //定义vector_stub有3个参数name,mode和correction
1028 .align 5
1030 vector_\name:
1031 .if \correction
1032 sub lr, lr, #\correction
1033 .endif
1034
1035 @
1036 @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
1037 @ (parent CPSR)
1038 @
1039 stmia sp, {r0, lr} @ save r0, lr
1040 mrs lr, spsr
1041 str lr, [sp, #8] @ save spsr
1042
1043 @
1044 @ Prepare for SVC32 mode. IRQs remain disabled.
1045 @
1046 mrs r0, cpsr
1047 eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
1048 msr spsr_cxsf, r0
1049
1050 @
1051 @ the branch table must immediately follow this code
1052 @
1053 and lr, lr, #0x0f
1054 THUMB( adr r0, 1f )
1055 THUMB( ldr lr, [r0, lr, lsl #2] )
1056 mov r0, sp
1057 ARM( ldr lr, [pc, lr, lsl #2] )
1058 movs pc, lr @ branch to handler in SVC mode
1059 ENDPROC(vector_\name)
vector_irq的定义如下:
1080 vector_stub irq, IRQ_MODE, 4
1081
1082 .long __irq_usr @ 0 (USR_26 / USR_32)
1083 .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
1084 .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
1085 .long __irq_svc @ 3 (SVC_26 / SVC_32)
1086 .long __irq_invalid @ 4
1087 .long __irq_invalid @ 5
1088 .long __irq_invalid @ 6
1089 .long __irq_invalid @ 7
1090 .long __irq_invalid @ 8
1091 .long __irq_invalid @ 9
1092 .long __irq_invalid @ a
1093 .long __irq_invalid @ b
1094 .long __irq_invalid @ c
1095 .long __irq_invalid @ d
1096 .long __irq_invalid @ e
1097 .long __irq_invalid @ f
1098
中断硬件准备
当一切准备好之后,一旦打开处理器的全局中断就可以处理来自外设的各种中断事件了。
当外设(SOC内部或者外部都可以)检测到了中断事件,就会通过interrupt requestion line上的电平或者边沿(上升沿或者下降沿或者both)通知到该外设连接到的那个中断控制器,而中断控制器就会在多个处理器中选择一个,并把该中断通过IRQ(或者FIQ,本文不讨论FIQ的情况)分发给该processor。ARM处理器感知到了中断事件后,会进行下面一系列的动作:
- 修改CPSR(Current Program Status Register)寄存器中的M[4:0]。改变CPSR中M[4:0],把模式切换成IRQ MODE模式。
2)保存发生中断那一点的CPSR值(step 1之前的状态)和PC值。(比如在用户空间,就保存用户空间的CPSR和PC;在内核空间,就保存内核空间的CPSR和PC)
2.1 中断那一刻的CPSR,保存到IRQ MODE的SPSR中
2.3 中断那一一刻的PC,保存到LR_IRQ中
对于thumb state,lr_irq = PC
对于ARM state,lr_irq = PC - 4
3、mask IRQ exception。也就是设定CPSR.I = 1
4、设定PC值为IRQ exception vector。
系统现在从发生中断之前的模式(比如用户或者内核模式)切换成IRQ模式。
struct stack {
u32 irq[3];
u32 abt[3];
u32 und[3];
} ____cacheline_aligned;
static struct stack stacks[NR_CPUS];
4. vector_irq的处理流程
跳转到vector_irq模式,这个模式下 lr_irq保存发生中断时刻的PC 指针,IRQ_SPSR保存发生中断时刻的CPSR。
1)lr_irq= PC -4 (中断时刻PC寄存器) IRQ_SPSR= CPSR(中断时刻的CPSR)
2)在cpu_init中SP_IRQ指向stacks->irq地址,通过stmia sp, {r0, lr} 指令,stacks->irq[0]=r0(中断前的r0),stacks->irq[1]=LR_IRQ=PC-8(中断时刻下一条要执行的命令),通过 mrs lr, spsr和str lr, [sp, #8],stacks->irq[2]=LR_SPSR=CPSR(中断时刻的CPSR)
3)把SP_IRQ,存放到r0中。
4)PC设置为根据发生中断时刻的状态的处理函数地址,并且把IRQ模式切换为SVC模式。
1030 vector_\name:
1031 .if 4
1032 sub lr, lr, 4 // /*计算返回地址(在arm流水线中,lr=pc+8,但是pc+4只译码没有执行,所以lr=lr-4) */
1033 .endif
1034
1035 @
1036 @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
1037 @ (parent CPSR)
1038 @
1039 stmia sp, {r0, lr} @ save r0, lr //把r0和lr寄存器的内容保存到sp寄存器保存的地址中
1040 mrs lr, spsr //把状态寄存器spsr的内容保存到lr寄存器器中
1041 str lr, [sp, #8] @ save spsr
// 将lr的内容保存到SP指向的地址加上8个字节后的地址中,就是栈指针SP+8保存的内容是spsr。
1042
1043 @
1044 @ Prepare for SVC32 mode. IRQs remain disabled.
1045 @
1046 mrs r0, cpsr
//将cpsr的寄存器的值保存到r0寄存器中
1047 eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
//mode=IRQ_MODE=0x00000012 SVC_MODE=0x13 PSR_ISETSTATE=0x20
// #(\mode ^ SVC_MODE | PSR_ISETSTATE) =0x12^0x13|0x20=0x1|0x20=0x21
//eor是异或,r0和0x21异或后,保存到r0中,就是反转bit0和bit5,也就是把CPSR寄存器中的bit0和bit5反转后,保存到r0寄存器中。
1048 msr spsr_cxsf, r0
//把r0寄存器中的内容给SPSR,也就是把寄存器CPSR的bit0和bit5反转后,保存到SPSR寄存器。
//之前是THUMB模式转换为ARM模式,IRQ模式转换为SVC模式
1049
1050 @
1051 @ the branch table must immediately follow this code
1052 @
1053 and lr, lr, #0x0f
// 把lr寄存器中的内容保留bit3-bit0。 就是把spsr的模式位保存下来 就是在进入中断模式前,CPSR就是进中断的模式存到CPSR中。
1054 THUMB( adr r0, 1f ) //THUMB是在THUMB模式下才执行的命令
1055 THUMB( ldr lr, [r0, lr, lsl #2] )
1056 mov r0, sp //把sp占地保存到r0中。
1057 ARM( ldr lr, [pc, lr, lsl #2] ) /如果进入中断前是usr,则lr=0 如果是SVC,则lr=3
1058 movs pc, lr @ branch to handler in SVC mode //此时已经切换成SVC模式
1059 ENDPROC(vector_\name)
问题1:
THUMB或者ARM宏在哪种模式下执行?
THUMB宏在THUMB模式下执行。
ARM宏在 ARM模式下执行。
问题2:
1054 THUMB( adr r0, 1f ) //THUMB是在THUMB模式下才执行的命令
1055 THUMB( ldr lr, [r0, lr, lsl #2] )
如果lr=0,则ldr lr,[r0,lr,lsl #2]后,lr=r0+lr*4=r0,因为lr是标签1的地址,但是1:的 入口地址为什么是.long __irq_usr?
因为1:和__irq_usr之间的代码不占空间。
其他
1.1 SWI异常
在__vectors_start中W(ldr) pc, __vectors_start + 0x1000 ,指的软中断SWI。
如下图中所示,在System.map中查看,可以看到 __vectors_start + 0x1000,也就是 __stubs_start。
查看__stubs_start的定义如下,可以看到__stubs_start指向vector_swi标签。
1067 __stubs_start:
1068 @ This must be the first word
1069 .word vector_swi
1.2 CPSR寄存器
M[4:0] | 模式 |
---|---|
0b10000 | 用户 |
0b10001 | FIQ |
0b10010 | IRQ |
0b10011 | 管理模式 |
0b10111 | 中止 |
0b11011 | 未定义 |
0b11011 | 系统 |
bit5 | 工作状态 |
---|---|
1 | Thumb |
0 | ARM |
注:
所有处理器模式下都可访问当前程序状态寄存器CPSR。CPSR中包含条件码标志、中断禁止位、当前处理器模式以及其他状态和控制信息。在每种异常模式下都有一个对用的程序状态寄存器SPSR。当异常出现时,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。
c - control field mask byte(xPSR[7:0])
x - extension field mask byte(xPSR[15:8])
s - status field mask byte(xPSR[23:16)
f - flags field mask byte(xPSR[31:24]).
老式声明方式:cpsr_flg,cpsr_all在ADS中已经不在支持
cpsr_flg对应cpsr_f
cpsr_all对应cpsr_cxsf
参考
分析在linux中的中断是如何运行的,以及中断3大结构体:irq_desc、irq_chip、irqaction
ARM中断表与响应流程
Linux系统调用过程中user栈的保存与恢复
linux中断中(异常向量详解)