U-Boot ARMv8 平台异常处理机制解析
入口点:arch/arm/cpu/armv8/start.S
1. 判断是否定义了钩子,如有则执行,否则往下走。执行save_boot_params,本质就是保存一些寄存器的值。
2. 对齐修复位置无关码的偏移
假设U-Boot链接时基址为0x10000,但实际加载到0x20000:
基址偏移 x9 = 0x20000 - 0x10000 = 0x10000。
若某个重定位项的r_offset = 0x11000(链接时地址),r_addend = 0x200:
运行时r_offset 修正为0x11000 + 0x10000 = 0x21000。
3. 若定义了CONFIG_SYS_RESET_SCTRL,则执行reset_sctrl,也是一个钩子函数,复位操作相关。
4. 异常向量表配置及EL3常规配置
a. 异常向量表地址(即vector)存到x0
b. 判断当前EL几,然后执行对应的分支,以EL3举例
1)把x0的值,写到vbar_el3。
2)配置scr_el3 的低4位,包括安全位的设置,以及配置“物理IRQ中断会不会被路由到EL3”“物理 FIQ 中断会不会被路由到EL3”“external aborts和SError中断会不会被路由到EL3”,以上全部配置为“会”。
3)使能浮点计算;使能单指令多数据。
4)初始cntfrq_el的值。这个变量是咱们用的定时器的那个频率。在Uboot中,不是测量出来的,是直接写死的。
5. 配置sctlr,使能指令cache
c. CR_I,一个宏定义,表示指令cache在sctlr中的bit位。
d. 通过switch_el宏指令,判断当前是EL几,然后进入相应分支。以el3举例。
e. 把CR_I写入sctlr,即,使能指令cache。这个指令cache,是EL0和EL1的使能位。
6. 使能多核之间的数据一致性
f. 先是isb指令,流水线同步操作。
g. 然后操作cpuectlr寄存器,bit6使能,即保证多核数据一致性。
7. 截至这里,cache、TLB都是无效的。执行lowlevel_init
h. 执行bl lowlevel_init时,bl指令,会把bl后边的指令的地址,存在LR中,以便执行完lowlevel_init的ret时返回。但是由于lowlevel_init中,也会调用bl,导致lr会被覆盖,所以在lowlevel_init中,先存一下LR的值。可以看到,上图最后3行,在执行ret 时,先把x29的值,恢复到lr中,在执行ret。
i. 如果IRQ被设置为y,则进行中断控制器的配置:branch_if_slave可以判断是否是主核,若是主核,则执行ldr x0, = GICD_BASE 和bl gic_init_secure,然后跳转;若不是主核,则直接跳转。
1)若是主核,x0存储了中断控制器寄存器基地址。
2)x0作为参数,调用gic_init_secure:arch/arm/lib/gic_64.S。
3)上图代码走GICV2分支。先是gicd_ctlr的bit0和bit1置1,表示使能Grp0和Grp1的中断上送。
4)读取GICD_TYPER中的ITLinesNumber。
需要了解的是,当ITLinesNumber = 0时,表示最大中断数为32 * (0 + 1) = 32,即 ID 为0 ~ 31。但只有32个中断,就意味着没有SPI(共享中断),如下图,SPI的编号,从32开始。
所以,cbz w10 1f 指令的含义,是当没有SPI中断时,跳转到1:处,直接返回。
5)通过循环,将所有中断都划到group1中。
6)gic_init_secure结束,返回上一层。跳转到gic_ini_secure_percpu去,同时传进去的参数分别是GIC distributor寄存器组的基地址和cpu interface组的基地址。把SGI 和PPI划分到group1(其实在上一个函数里,已经做过一遍了)。使能SGI(多核通信要用);使能通过interface上报中断。
j. 从核等待主核SGI0中断唤醒。
8. 最后进入_main
异常触发时,根据异常的类型(SE同步异常、irq中断、fiq快速中断等),硬件会跳转到vector 的偏移。比如,IRQ跳转到vector+0x80*5,其实就是,vector中的条目,每个条目按128字节对齐(align 7),所以就是,每个条目最多128字节。