源码:
#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_hangproc_hang: b proc_hangmaster:/* 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_EL3b.eq el3_entry//否则调用EL2的处理b el2_entryel3_entry:eretel2_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_FLAGSmsr hcr_el2, x0//EL1页表还未建立,因此需要关闭MMUldr x0, =SCTLR_VALUE_MMU_DISABLEDmsr 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_EL1msr spsr_el2, x0/*PC相对地址加载指令,将el1的入口函数地址赋值给x0寄存器,这里注意和adrp的区别,adrp返回的地址是4K对齐,这样的话就不是函数的地址了*/adr x0, el1_entry/*将x0寄存器赋值给elr_el2,这里是设置el2的异常返回地址*/msr elr_el2, x0/*el2异常返回,程序流程会进入到el1*/eretel1_entry:bl print_el/* 设置异常向量表基地址到vbar寄存器 */ldr x5, =vectorsmsr vbar_el1, x5isb//bss段清零adr x0, _bssadr x1, _ebsssub x1, x1, x0bl memzero/*预留栈空间*/mov sp, #LOW_MEMORY /*跳转到C函数*/bl kernel_mainb proc_hang // should never come hereprint_el://先保存LR寄存器,保存子程序的返回地址,避免被破坏mov x10, x30/*print EL*/adrp x0, el_string1add x0, x0, :lo12:el_string1bl put_string_uartmrs x5, CurrentEL/* get the currentEL value *//*CurrentEL寄存器的 [2:3] 保存的是当前异常等级,因此这里右移2位后赋值给x2寄存器*/lsr x2, x5, #2//48指的是ASCII码为0mov x0, #48// ‘0’ + 具体的EL值,得到相应EL等级的ASCII码add x0, x0, x2// 将x0(EL具体值)作为入参传达给串口打印出来bl put_uart/* print the new line tab *///打印换行符mov x0, #10bl put_uart//恢复LR寄存器mov x30, x10ret
异常向量表的实现:
#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 7b \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_invalidvtentry el1_irq_invalidvtentry el1_fiq_invalidvtentry el1_error_invalid/* Current EL with SPx当前系统运行在EL1时使用EL1的栈指针SP这说明系统在内核态发生了异常Note: 我们暂时只实现IRQ中断*/vtentry el1_sync_invalidvtentry el1_irq_invalidvtentry el1_fiq_invalidvtentry el1_error_invalid/* Lower EL using AArch64在用户态的aarch64的程序发生了异常*/vtentry el0_sync_invalidvtentry el0_irq_invalidvtentry el0_fiq_invalidvtentry el0_error_invalid/* Lower EL using AArch32在用户态的aarch32的程序发生了异常*/vtentry el0_sync_invalidvtentry el0_irq_invalidvtentry el0_fiq_invalidvtentry el0_error_invalidel1_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_ERRORstring_test:.string "t".global trigger_alignment
trigger_alignment:ldr x0, =0x80002ldr x1, [x0]ret
不同异常问题构造:
这里.string “t” 只会占用2个字节,因此紧随其后的 “trigger_alignment” 会触发指令不对齐异常