目录
- 背景
- 信息打印
- 异常等级切换实现
- 等级切换
- 代码实现
- 打印异常等级
背景
上一小节中介绍了树莓派上输出的第一个裸机程序“Hello, world!”。本节内容将了解并介绍怎么配置正确的异常等级(exception level),关于异常等级的介绍可以参考:AArch64 异常等级(exception level)。以上介绍了通常操作系统运行在EL1等级,所以我们本节也会将操作系统切换到EL1。
基于前面的介绍可以实现uart驱动,并通过串口可以和系统交互。
信息打印
由于后面系统的打印会越来越多,所以我们需要实现通用的printf接口,printf是开源代码,避免做无意义的函数实现,这里我们直接拿来用了,这样我们就能轻松实现变量或者指定地址内容的打印,方便系统调试。
实现printf的api, 需要我们实现putc函数,以完成printf函数的初始化。putc函数主要调用我们上一节中实现的uart_send_char函数,具体内容如下
关于print的代码实现可以看本文所涉及的源代码,放在文章末尾处。
异常等级切换实现
系统上电后默认会进入EL3或者EL2(我本地的板子进入到了EL2,与板子早期初始化有关)。
等级切换
如AArch64 异常等级(exception level)文章中介绍,aarch64架构为实现等级管理,出于功能安全上的考虑,程序无法主动提高自己的异常等级,除非发生异常,类似我们通用的linux操作系统,用户空间(EL0)的程序无法主动进入内核空间(EL1),但当系统除以0,软件中断,访问非法地址等异常发生时,会进入更高的异常等级,执行相应的异常处理函数。
另外,arch64的指令集提供了svc命令,主动产生异常,进入更高异常等级。
异常发生后系统通常会有一下几个动作(假设异常发生在EL0):
- 当前指令位置保存到elr_el0寄存器。
- 当前处理器的状态保存在spsr_el0寄存器中。
- 保存其它寄存器,比如通用寄存器的值。
- 执行EL1中的异常处理程序,程序最终调用eret返回到EL0,同时恢复EL0程序中的上下文如x0-x30等寄存器。
- EL0处理器从spsr_el0寄存器中恢复处理器的状态,并在elr_el0寄存器保存的地址开始恢复执行。
其它异常等级程序运行时发生异常也是这个过程,比如EL1的程序发生异常就会跳转到EL2执行异常处理程序,并最终返回EL1
实际上的系统大致是这么过程。实际实现中,elr_el0寄存器和spsr_el0寄存器,都可被更高异常等级执行的异常处理函数修改。根据这一特性可以实现高异常等级切换到更低异常等级,并指定低异常等级执行的程序位置和处理器状态。
代码实现
打印异常等级
要想实现异常等级的切换,首先需要确认程序所处的异常等级,所以首先需要一个可以打印异常等级的函数。
在aarch64架构中,存在CurrentEL变量保存异常等级信息,且每个异常等级都有权访问,因此可以通过以下汇编代码实现异常等级读取功能
.global get_el
get_el:
mrs x0, CurrentEL
lsr x0, x0, #2
ret
上诉代码实现的主要功能是将CurrentEL寄存器的值读取到x0通用寄存器中,并右移两位(CurrentEL前两位为空,没有意义,异常等级保存在第3,4位),之后函数返回x0的内容。
调用方法也非常简单,如下
imt el = get_el();
待续 。。。