源码:
#include "mm.h"
#include "sysregs.h"
.section .rodata
.align 3
.globl el_string1
el_string1:
.string "Booting at EL"
.section ".text.boot"
.globl _start
_start:
//读取mpidr_el1寄存器的值,该寄存器决定了代码运行在哪个核心上
mrs x0, mpidr_el1
//提取mpidr_el1寄存器的低8位
and x0, x0,#0xFF // Check processor id
//如果全零,说明代码运行在CPU0上
cbz x0, master // Hang for all non-primary CPU
//如果代码不是运行在CPU0上则挂死
b proc_hang
proc_hang:
b proc_hang
master:
/* init uart and print the string*/
bl __init_uart
/* init uart and print the string*/
//初始化串口打印
bl __init_uart
//读取当前EL状态
mrs x5, CurrentEL
//如果是处于EL3说明异常了,调用异常返回
cmp x5, #CurrentEL_EL3
b.eq el3_entry
//否则调用EL2的处理
b el2_entry
el3_entry:
eret
el2_entry:
bl print_el
/* The Execution state for EL1 is AArch64
HCR_EL2寄存器中有一个RW域(Bit[31]),它记录了异常发生后ELI要处在哪个执行 状态下。
□ 1表示在AArch64执行状态下。
□ 0表示在AArch32执行状态下。
*/
ldr x0, =HCR_HOST_NVHE_FLAGS
msr hcr_el2, x0
//EL1页表还未建立,因此需要关闭MMU
ldr x0, =SCTLR_VALUE_MMU_DISABLED
msr sctlr_el1, x0
/*因为当前处于EL2,所以发生异常前(EL2切换到EL1本质上是一种异常发生),需要填充当前等级的SPSR,也就是SPSR_EL2,当异常返回的时候(eret)时,再从SPSR_EL2恢复到PSTATE*/
/*关闭D,A,I,F(关闭中断等),同时设置异常返回的等级,因为我们希望切换到EL1,因此这里设置EL2的异常返回等级为EL1h,这里的 h 代表使用的是SP_EL1作为栈指针*/
ldr x0, =SPSR_EL1
msr spsr_el2, x0
/*PC相对地址加载指令,将el1的入口函数地址赋值给x0寄存器,这里注意和adrp的区别,adrp返回的地址是4K对齐,这样的话就不是函数的地址了*/
adr x0, el1_entry
/*将x0寄存器赋值给elr_el2,这里是设置el2的异常返回地址*/
msr elr_el2, x0
/*el2异常返回,程序流程会进入到el1*/
eret
el1_entry:
bl print_el
/* 设置异常向量表基地址到vbar寄存器 */
ldr x5, =vectors
msr vbar_el1, x5
isb
//bss段清零
adr x0, _bss
adr x1, _ebss
sub x1, x1, x0
bl memzero
/*预留栈空间*/
mov sp, #LOW_MEMORY
/*跳转到C函数*/
bl kernel_main
b proc_hang // should never come here
print_el:
//先保存LR寄存器,保存子程序的返回地址,避免被破坏
mov x10, x30
/*
print EL
*/
adrp x0, el_string1
add x0, x0, :lo12:el_string1
bl put_string_uart
mrs x5, CurrentEL
/* get the currentEL value */
/*CurrentEL寄存器的 [2:3] 保存的是当前异常等级,因此这里右移2位后赋值给x2寄存器*/
lsr x2, x5, #2
//48指的是ASCII码为0
mov x0, #48
// ‘0’ + 具体的EL值,得到相应EL等级的ASCII码
add x0, x0, x2
// 将x0(EL具体值)作为入参传达给串口打印出来
bl put_uart
/* print the new line tab */
//打印换行符
mov x0, #10
bl put_uart
//恢复LR寄存器
mov x30, x10
ret
异常向量表的实现:
#define BAD_SYNC 0
#define BAD_IRQ 1
#define BAD_FIQ 2
#define BAD_ERROR 3
/*
处理无效的异常向量
*/
.macro inv_entry el, reason
//kernel_entry el
//第一个参数是pt_regs(寄存器框架大小),这个实验并没有用到
mov x0, sp
//"\reason" 汇编中表示引用入参
mov x1, #\reason
//根据寄存器esr_el1来解析同步异常
mrs x2, esr_el1
//跳转到C代码
b bad_mode
.endm
/*
vector table entry
每个表项是128字节, align 7表示128字节对齐
*/
.macro vtentry label
.align 7
b \label
.endm
/*
* Vector Table
*
* ARM64的异常向量表一共占用2048个字节
* 分成4组,每组4个表项,每个表项占128字节
* 参见ARMv8 spec v8.6第D1.10节
* align 11表示2048字节对齐
*/
.align 11
.global vectors
vectors:
/* Current EL with SP0
当前系统运行在EL1时使用EL0的栈指针SP
这是一种异常错误的类型
*/
vtentry el1_sync_invalid
vtentry el1_irq_invalid
vtentry el1_fiq_invalid
vtentry el1_error_invalid
/* Current EL with SPx
当前系统运行在EL1时使用EL1的栈指针SP
这说明系统在内核态发生了异常
Note: 我们暂时只实现IRQ中断
*/
vtentry el1_sync_invalid
vtentry el1_irq_invalid
vtentry el1_fiq_invalid
vtentry el1_error_invalid
/* Lower EL using AArch64
在用户态的aarch64的程序发生了异常
*/
vtentry el0_sync_invalid
vtentry el0_irq_invalid
vtentry el0_fiq_invalid
vtentry el0_error_invalid
/* Lower EL using AArch32
在用户态的aarch32的程序发生了异常
*/
vtentry el0_sync_invalid
vtentry el0_irq_invalid
vtentry el0_fiq_invalid
vtentry el0_error_invalid
el1_sync_invalid:
inv_entry 1, BAD_SYNC
el1_irq_invalid:
inv_entry 1, BAD_IRQ
el1_fiq_invalid:
inv_entry 1, BAD_FIQ
el1_error_invalid:
inv_entry 1, BAD_ERROR
el0_sync_invalid:
inv_entry 0, BAD_SYNC
el0_irq_invalid:
inv_entry 0, BAD_IRQ
el0_fiq_invalid:
inv_entry 0, BAD_FIQ
el0_error_invalid:
inv_entry 0, BAD_ERROR
string_test:
.string "t"
.global trigger_alignment
trigger_alignment:
ldr x0, =0x80002
ldr x1, [x0]
ret
不同异常问题构造:
这里.string “t” 只会占用2个字节,因此紧随其后的 “trigger_alignment” 会触发指令不对齐异常