代码分析
分析完el2_setup的注释和代码段的位置之后,我们正式开始学习el2_setup的实现
SYM_FUNC_START(el2_setup)
msr SPsel, #1 // We want to use SP_EL{1,2}
mrs x0, CurrentEL
cmp x0, #CurrentEL_EL2
b.eq 1f
mov_q x0, (SCTLR_EL1_RES1 | ENDIAN_SET_EL1)
msr sctlr_el1, x0
mov w0, #BOOT_CPU_MODE_EL1 // This cpu booted in EL1
isb
ret
1: mov_q x0, (SCTLR_EL2_RES1 | ENDIAN_SET_EL2)
msr sctlr_el2, x0
异常等级
第二行需要参考ARM64的芯片手册,SPsel的值为0表示在任何异常等级下,都选择SP_EL0作为堆栈寄存器;
值为1表示选择异常等级对应的SP_ELX作为堆栈寄存器,X表示当前的异常等级
第三行表示将当前的异常等级存入到x0寄存器。
第四行表示将当前的异常等级与EL2比较。
第五行表示如果当前的等级就是EL2则跳转到标签为1的代码去执行;
否则如果不相等,也就是说,如果当前的异常等级为EL1,则继续执行。
设置SCTLR_ELX
第六行涉及到了一个系统寄存器SCTLR_EL1, System Control Register (EL1),我们又要查看芯片手册:
根据芯片手册手册描述,该寄存器提供了在EL0和EL1异常等级下,系统的异常级别控制,这里的系统,包括内存系统;
SCTLR_EL1_RES1表示该系统寄存器的保留位,这些保留位需要设置为1;
而ENDIAN_SET_EL1在代码的定义如下:
#ifdef CONFIG_CPU_BIG_ENDIAN
#define ENDIAN_SET_EL1 (SCTLR_EL1_E0E | SCTLR_ELx_EE)
#else
#define ENDIAN_SET_EL1 0
#endif
$ grep -rnw SCTLR_EL1_E0E arch/arm64/
arch/arm64/include/asm/sysreg.h:607:#define SCTLR_EL1_E0E (BIT(24))
$ grep -rnw SCTLR_ELx_EE arch/arm64/
arch/arm64/include/asm/sysreg.h:571:#define SCTLR_ELx_EE (BIT(25))
其中,E0E 表示设置 EL0下访问数据使用大端还是小端,0表示小端,1表示大端;
EE表示设置EL1下访问数据使用大端还是小端
对于ARM64架构,Linux内核会采用小端模式来处理数据,以确保与硬件平台的兼容性。
所以,在ARM64 Linux Kernel的配置里面,并没有使能该宏定义
$ cat .config | grep CONFIG_CPU_BIG_ENDIAN
# CONFIG_CPU_BIG_ENDIAN is not set
所以ENDIAN_SET_EL1表示使用小端。
紧接着第七行就表示,把刚才设置的值写入到 SCTLR_EL1 寄存器。
第八行表示,w0寄存器写入EL1,上一章节也就是el2_setup注释部分章节我们查看注释,w0作为出参,带出启动时的异常等级;
回到当前代码,由于当前是EL1异常等级,所以w0赋值为EL1是正确的;
紧接着第10行,如果当前的异常等级是EL1,w0也赋了正确的值,那也没有别的好说的了,直接返回。也就是说,如果当前的异常等级是EL1,设置完 SCTLR寄存器为小端即可。
但是如果当前的异常等级不是EL1,而是EL2则还要继续说道说道。
第12和第13行与第6和第7行很类似,只不过是将EL1换成了EL2;
查看芯片手册,该寄存器的含义是该寄存器提供了在EL2异常等级下,系统的级别控制,这里的系统,包括内存系统。
所以,同样需要将 SCTLR_EL2 寄存器相应的bit位设置为小端
#ifdef CONFIG_CPU_BIG_ENDIAN
#define ENDIAN_SET_EL2 SCTLR_ELx_EE
#else
#define ENDIAN_SET_EL2 0
#endif
$ grep -rn SCTLR_ELx_EE arch/arm64/
arch/arm64/include/asm/sysreg.h:571:#define SCTLR_ELx_EE (BIT(25))
参考
参考1
Linux内核源码 ---- el2_setup源码解析
https://blog.csdn.net/aigourensheng/article/details/125704613