上一节中,reset_sctrl将cpu设置为了小端,MMU 和 i/d cache 都关闭。
/*
* Could be EL3/EL2/EL1, Initial State:
* Little Endian, MMU Disabled, i/dCache Disabled
*/
adr x0, vectors
switch_el x1, 3f, 2f, 1f
3: msr vbar_el3, x0
mrs x0, scr_el3
orr x0, x0, #0xf /* SCR_EL3.NS|IRQ|FIQ|EA */
msr scr_el3, x0
msr cptr_el3, xzr /* Enable FP/SIMD */
#ifdef COUNTER_FREQUENCY
ldr x0, =COUNTER_FREQUENCY
msr cntfrq_el0, x0 /* Initialize CNTFRQ */
#endif
b 0f
2: msr vbar_el2, x0
mov x0, #0x33ff
msr cptr_el2, x0 /* Enable FP/SIMD */
b 0f
1: msr vbar_el1, x0
mov x0, #3 << 20
msr cpacr_el1, x0 /* Enable FP/SIMD */
首先将中断向量的地址保存到x0寄存器,然后判断当前的异常等级。并将中断向量的地址写到各个EL对应的VBAR寄存器中去。中断向量vectors的定义于 arch\arm\cpu\armv8\exceptions.S ,中断向量表的定义如下,开头就对首地址进行211也就是2k对齐,***为什么需要2K对齐***,是因为我们后面保存中断向量地址的VBAR寄存器低11位是不保存的,后面会看到这点。此外各个中断向量地址27次方也就是128字节对齐,为什么以128字节对齐?是因为arm定义的异常entry偏移是以128字节为单位。
/*
* Exception vectors.
*/
.align 11
.globl vectors
vectors:
.align 7
b _do_bad_sync /* Current EL Synchronous Thread */
.align 7
b _do_bad_irq /* Current EL IRQ Thread */
.align 7
b _do_bad_fiq /* Current EL FIQ Thread */
.align 7
b _do_bad_error /* Current EL Error Thread */
.align 7
b _do_sync /* Current EL Synchronous Handler */
.align 7
b _do_irq /* Current EL IRQ Handler */
.align 7
b _do_fiq /* Current EL FIQ Handler */
.align 7
b _do_error /* Current EL Error Handler */
可以看到每个中断向量根据中断类型的不同都是一条跳转指令,跳转到对应的中断处理函数,我们以_do_bad_sync为例
/*
* do_bad_sync handles the impossible case in the Synchronous Abort vector.
*/
void do_bad_sync(struct pt_regs *pt_regs, unsigned int esr)
{
efi_restore_gd();
printf("Bad mode in \"Synchronous Abort\" handler, esr 0x%08x\n", esr);
show_regs(pt_regs);
panic("Resetting CPU ...\n");
}
假设在栈初始化完成后出现了Synchronous Abort(如未定义的指令、data abort、prefetch instruction abort、前面提到的SP未对齐异常,debug exception等等),则会进入到该函数中,首先调用efi_restore_gd(),接着打印依据异常报错,随后将ARM CPU的各个寄存器值都打印出来方便定位问题,最后调用panic->do_reset->reset_cpu复位CPU,对于8mp来说最终会调用到下面。
void reset_cpu(void)
{
struct watchdog_regs *wdog = (struct watchdog_regs *)WDOG1_BASE_ADDR;
/* Clear WDA to trigger WDOG_B immediately */
writew((SET_WCR_WT(1) | WCR_WDT | WCR_WDE | WCR_SRS), &wdog->wcr);
while (1) {
/*
* spin for .5 seconds before reset
*/
}
}
将中断向量表的首地址也就是vectors标号对应的地址根据当前异常等级写到对应的VBAR_ELn中去。
vbar_el3:EL3异常配置
3: msr vbar_el3, x0
mrs x0, scr_el3
orr x0, x0, #0xf /* SCR_EL3.NS|IRQ|FIQ|EA */
msr scr_el3, x0
msr cptr_el3, xzr /* Enable FP/SIMD */
#ifdef COUNTER_FREQUENCY
ldr x0, =COUNTER_FREQUENCY
msr cntfrq_el0, x0 /* Initialize CNTFRQ */
#endif
b 0f
VBAR_ELn中的定义,可以看到,低11bit都为0,所以这符合之前中断向量表首地址分配前,先进行了2K对齐。
我们可以看到当异常等级为EL3时,orr x0, x0, #0xf操作将scr_el3寄存器的低4bit置为了1。
我们分析一下这个寄存器:低四位分别是EA、FIQ、IRQ、NS。EA置1表示在任何异常等级下发生的External Abort 和 SError 中断都路由到EL3进行处理;FIQ置1表示在任何异常等级下发生的物理FIQ(快速中断请求)路由到EL3进行处理;IRQ置1表示在任何异常等级下发生的物理IRQ(中断请求)路由到EL3进行处理;NS,置1表示EL0和EL1 这两个异常等级时非安全态,在这两个异常等级下的内存访问是不能访问安全域的内存空间的。
msr cptr_el3, xzr 将cptr_el3 的三个bit位清,打开了SIMD和FP浮点运算功能。这三位分别是:
TCPAC, bit [31] ,当清0时表示打开
EL2 异常等级访问 CPTR_EL2 or HCPTR.
EL2 和 EL1访问 CPACR_EL1 or CPACR.的权限
TFP, bit [10]:清0时 打开SIMD和FP浮点运算功能。
TTA, bit [20],打开系统寄存器访问trace寄存器权限。
最后根据是否定义COUNTER_FREQUENCY,来配置cntfrq_el0 系统时钟计数器的频率,这个频率我们经常可以用来进行比较精确的延时。8mp没有定义,所以使用的是外部时钟24mhz。
#ifdef COUNTER_FREQUENCY
ldr x0, =COUNTER_FREQUENCY
msr cntfrq_el0, x0 /* Initialize CNTFRQ */
#endif
b 0f
EL2和EL1等级下,之后的代码和EL3一样使能SIMD和FP功能。之所以EL2和EL1可以访问相应的寄存器,就是因为之前配置了 cptr_el3寄存器开放了访问权限。下一步就会继续往下分析,这一节主要介绍了EL3/2/1异常的配置。