从源码分析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捕获到异常时

  1. 将PSTATE保存到对应的SPSR_ELx中
  2. 返回地址保存到ELR_ELx中
  3. PSTATE DAIF关闭
  4. 如果是同步异常把原因写入ESR_ELx,如果是中断,原因保存在GIC-400的寄存器中
  5. 切换SP到对应的SP_ELx中
  6. 跳转到中断向量表里
  7. 执行对应的处理函数

CPU处理完异常执行eret,会

  1. ELR中恢复PC
  2. SPSR中恢复PSTATE (DAIF也会变) alt 从源码分析arm64中断与GIC

中断向量表

alt

可见通过保存到对应异常的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☕️,为了回馈牛客和各个粉丝,文章都会先试读几天,热度过了再收录~

全部评论
哥 需不需要单独学arm架构呢
点赞 回复 分享
发布于 02-25 19:54 黑龙江

相关推荐

不愿透露姓名的神秘牛友
07-10 12:10
点赞 评论 收藏
分享
06-19 19:06
门头沟学院 Java
码农索隆:别去东软,真学不到东西,真事
点赞 评论 收藏
分享
07-10 13:59
门头沟学院 Java
点赞 评论 收藏
分享
评论
点赞
7
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务