从源码分析arm64中断与GIC-400
写在前面,本文将一步步从手册到源码,深入解析 ARMv8 架构下异常发生、被检测、再到被处理的全过程。相比于冗长的文字描述,代码往往更具说服力。因此,本文将结合 手册理论 + 代码实践,力求通过 可复现的代码 直观展示整个异常处理流程。
文中所有代码均由本人编写并经过测试,如果发现 bug 或者有更优的实现方式,欢迎指正与交流~
参考资料:Linux4.10 《arm64体系结构编程与实践》
本文以树莓派4b(armv8)来实现,4b支持两种
- 传统的中断控制器
- gic-400 但是使用的qemu和实际的板子都是默认支持gic-400的,所以主要是借助gic-400实现中断的功能
异常处理
相关寄存器
- PSTATE 就是cpu状态
- DAIF 调试异常 SError(系统异常) IRQ(中断) FIQ(快速中断)
- esr_elx 用来保存返回地址
- spsr_elx 用来保存对应级别的PSTATE
- elr_elx 用来保存异常的原因
处理异常时自动发生的事
CPU捕获到异常时
- 将PSTATE保存到对应的SPSR_ELx中
- 返回地址保存到ELR_ELx中
- PSTATE DAIF关闭
- 如果是同步异常把原因写入ESR_ELx,如果是中断,原因保存在GIC-400的寄存器中
- 切换SP到对应的SP_ELx中
- 跳转到中断向量表里
- 执行对应的处理函数
CPU处理完异常执行eret
,会
- ELR中恢复PC
- SPSR中恢复PSTATE (DAIF也会变)
从源码分析arm64中断与GIC
中断向量表
可见通过保存到对应异常的vbar中,CPU就可以在对应级别是发生异常进入中断向量表中 由于内核目前一直在EL1阶段,所以在el1的初始化函数el1_entry
中
el1_entry:
// 加入向量表
adr x0, vectors
msr vbar_el1, x0
其中vector的实现是参考linux的实现
.macro kernel_ventry, el, label, regsize=64
.align 7
sub sp, sp, #S_FRAME_SIZE
b el\()\el\()_\label
.endm
.pushsection ".entry.text", "ax"
.align 11
ENTRY(vectors)
kernel_ventry 1, sync_invalid // Synchronous EL1t
kernel_ventry 1, irq_invalid // IRQ EL1t
kernel_ventry 1, fiq_invalid // FIQ EL1t
kernel_ventry 1, error_invalid // Error EL1t
kernel_ventry 1, sync_invalid // Synchronous EL1h
kernel_ventry 1, irq // IRQ EL1h
kernel_ventry 1, fiq_invalid // FIQ EL1h
kernel_ventry 1, error_invalid // Error EL1h
kernel_ventry 0, sync_invalid // Synchronous 64-bit EL0
kernel_ventry 0, irq_invalid // IRQ 64-bit EL0
kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
kernel_ventry 0, error_invalid // Error 64-bit EL0
kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
END(vectors)
kernel_ventry
值得说一下 el\()\el\()_\label
中\()
表示一个符号的结尾,el 然后是结尾\()
,接着\el
这个就是传入宏第一个的参数,然后是结尾\()
,然后是_
,在跟着\label
也就是第二个参数
vectors
就是个位置,依次放着各个异常的入口,只要依次实现这些入口函数,CPU发生异常的时候就会进入对应的处理函 数。
以el0_sync_invalid
为例,现在先忽略kernel_entry
,这个是用来现场保护的,最后会到bad_mode
中进行异常的处理
/*
* Invalid mode handlers
*/
.macro inv_entry, el, reason, regsize = 64
bl kernel_entry
mov x0, sp
mov x1, #\reason
mrs x2, esr_el1
b bad_mode
.endm
el0_sync_invalid:
inv_entry 0, BAD_SYNC
ENDPROC(el0_sync_invalid)
中断现场保护和恢复
这时候就不得不说中断发生时的现场保护了 与异常不同最后需要恢复现场
el1_irq:
bl kernel_entry
bl irq_handle
bl kernel_exit
ENDPROC(el1_irq)
一个线框的定义是
struct pt_regs {
struct {
u64 regs[31];
u64 sp;
u64 pc;
u64 pstate;
};
u64 orig_x0;
#ifdef __AARCH64EB__
u32 unused2;
s32 syscallno;
#else
s32 syscallno;
u32 unused2;
#endif
u64 orig_addr_limit;
u64 unused; // maintain 16 byte alignment
u64 stackframe[2];
};
主要就是32个寄存器和SP、PC、PSTATE,所以只要按照这个顺序依次保存就可以
// 保护 pt_regs
kernel_entry:
//开辟空间
sub sp, sp, #S_FRAME_SIZE
// 保存普通寄存器
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
//保存最开始sp的位置到x21
add x21, sp, #S_FRAME_SIZE
mrs x22, elr_el1
mrs x23, spsr_el1
stp lr, x21, [sp, #S_LR]
stp x22, x23, [sp, #S_PC]
ret
stp
会依次保存两个寄存器到 sp+第二个数的位置
接下来就是恢复时候
// 恢复 pt_regs
kernel_exit:
// 先把pc 和 pstate恢复
ldp x22, x23, [sp, #S_PC]
msr elr_el1, x22 // set up the return data
msr spsr_el1, x23
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
1. 自我介绍:高通、Oppo(sp)、vivo(sp)、小米(ssp)、荣耀(26k*12+80k)、华子(报批中)、美团、韶音、经纬恒润、乐鑫、中兴、TP 2. 内容: 1.嵌入式学习的资料和路径 2.所有面试的题目和解答(持续更新)、对评论的快速解答 3.各种碎碎念 3.整理不易,buy me coffee☕️,为了回馈牛客和各个粉丝,文章都会先试读几天,热度过了再收录~