飞腾CPU虚拟化相关代码分析(二)——EL2异常向量表
- 飞腾CPU采用ARMv8体系结构,在非安全态有四个权限级EL0/1/2/3,其中EL2是Hypervisor权限级,虚拟机监控器VMM代码运行在EL2权限级上。
- ARMv8的早期版本只能支持Hyp模式的EL2,即EL2权限级的实现还不能完全支撑虚拟机内核和应用,需要运行在EL1的宿主OS协助完成。(备注:ARMv8的扩展版本支持VHE模式,即宿主OS可以直接运行在EL2模式下,此时VMM和宿主机内核可以共用一个EL2权限级上的异常向量表)
- 下面列出了Hyp分裂模式的EL2的异常向量表:
ENTRY(__hyp_stub_vectors)
ventry el2_sync_invalid // Synchronous EL2t
ventry el2_irq_invalid // IRQ EL2t
ventry el2_fiq_invalid // FIQ EL2t
ventry el2_error_invalid // Error EL2t
ventry el2_sync_invalid // Synchronous EL2h
ventry el2_irq_invalid // IRQ EL2h
ventry el2_fiq_invalid // FIQ EL2h
ventry el2_error_invalid // Error EL2h
ventry el1_sync // Synchronous 64-bit EL1
ventry el1_irq_invalid // IRQ 64-bit EL1
ventry el1_fiq_invalid // FIQ 64-bit EL1
ventry el1_error_invalid // Error 64-bit EL1
ventry el1_sync_invalid // Synchronous 32-bit EL1
ventry el1_irq_invalid // IRQ 32-bit EL1
ventry el1_fiq_invalid // FIQ 32-bit EL1
ventry el1_error_invalid // Error 32-bit EL1
ENDPROC(__hyp_stub_vectors)
- 根据触发异常的权限级和CPU状态,EL2异常向量表定义了四组:
- EL2t,当CPU在EL2且堆栈寄存器为SP_EL0时,触发了异常。
- EL2h,当CPU在EL2且堆栈寄存器为SP_EL2时,触发了异常。
- AArch64_EL1,当CPU在EL0/1且为AArch64模式,触发了异常。
- AArch32_EL1,当CPU在EL0/1且为AArch32模式,触发了异常。
说明:1~2,CPU权限级没有发生变化。
3~4,CPU权限级升高了。
这四组是按照位置顺序来定义的。
- 根据异常的类型,每一组又分为四个入口点
- 同步指令类型,例如系统调用指令、访存的缺页故障等
- IRQ中断类型,中断控制器GIC控制的一组外部设备中断
- FIQ中断类型,中断控制器GIC控制的另一组外部设备中断
- 系统错误类型,片上网络等相关部件故障中断(一般由具体CPU厂商定义的)
说明:
这四种类型是按照位置顺序来定义的。
- 根据上述EL2异常向量表,当前Hyp模式下虚拟机监控器VMM仅仅支持AArch64模式的虚拟机的同步指令触发的异常。
1.
ventry el1_sync
2.*_invalid
ventry
代码分析,这是汇编宏。
.macro ventry label
.align 7/*必须按照0x80长度对齐*/
b \label/*虽然可以容纳0x80/4 =0x20条指令,但软件实现为直接跳转*/
.endm
el1_sync
代码分析,无返回无条件跳转到el1_sync中,返回采用异常返回eret指令,异常链接寄存器elr_el2已经保存了返回地址,即EL1中触发同步异常的下一条指令地址。
el1_sync:
...
ENDPROC(el1_sync)
cmp x0, #HVC_SET_VECTORS /*判断是否为HVC_SET_VECTORS调用*/
b.ne 2f
...
2: cmp x0, #HVC_SOFT_RESTART /*判断是否为HVC_SOFT_RESTART调用*/
b.ne 3f
...
3: cmp x0, #HVC_RESET_VECTORS/*判断是否为HVC_RESET_VECTORS调用*/
beq 9f
...
9:...
- 第一个参数x0是hcall的系统调用号,当前hcall的系统系统调用号为0/1/2
#define HVC_SET_VECTORS 0
设置新的异常向量表,参数x1为新表物理地址。
#define HVC_SOFT_RESTART 1
CPU软件启动
#define HVC_RESET_VECTORS 2
将异常向量表恢复到原始__hyp_stub_vectors
#define HVC_STUB_HCALL_NR 3
#define HVC_STUB_ERR 0xbadca11
- 如果调用号为HVC_SET_VECTORS(0), 设置新的异常向量表调用
msr vbar_el2, x1
设置寄存器vbar_el2
mov x0, xzr
正常返回零
eret
- 如果调用号为HVC_RESET_VECTORS(2),恢复异常向量表(其实什么也没有做,直接返回)
mov x0, xzr
eret
- 如果调用号为HVC_SOFT_RESTART,CPU软件启动
执行下列代码之前,
x1是CPU软件启动函数的首地址;
x2/x3/x4是CPU软件启动函数的第一、二、三个参数
mov x0, x2
mov x2, x4
mov x4, x1
mov x1, x3
br x4
调用CPU软件启动函数之前,x0/x1/x2已经是第一、二、三个参数了。
mov x0, xzr
eret
- 出错返回
ldr x0, =HVC_STUB_ERR
eret
- 我们再看看虚拟机的内核如何实现HVC_SET/RESET_VECTORS
ENTRY(__hyp_set_vectors)
mov x1, x0
mov x0, #HVC_SET_VECTORS
hvc #0
ret
ENDPROC(__hyp_set_vectors)
ENTRY(__hyp_reset_vectors)
mov x0, #HVC_RESET_VECTORS
hvc #0
ret
ENDPROC(__hyp_reset_vectors)
- 最后我们看看虚拟机的内核如何实现HVC_SOFT_RESTART
- 主要是调用__cpu_soft_restart(el2_switch, entry, arg0, arg1, arg2)
- el2_switch表示CPU是否需要进入EL2权限级
- entry表示软件启动CPU的代码地址
- arg0/1/2软件启动CPU函数的参数
- __cpu_soft_restart代码分析
ENTRY(__cpu_soft_restart)
mrs x12, sctlr_el1
ldr x13, =SCTLR_ELx_FLAGS
bic x12, x12, x13
pre_disable_mmu_workaround
msr sctlr_el1, x12
isb
上面代码主要是将寄存器sctlr_el1的SCTLR_ELx_FLAGS对应的位清零
cbz x0, 1f // el2_switch?
mov x0, #HVC_SOFT_RESTART
hvc #0 // no return
1:
下面代码是因为不希望CPU陷入EL2来处理。
mov x18, x1 // entry
mov x0, x2 // arg0
mov x1, x3 // arg1
mov x2, x4 // arg2
br x18
ENDPROC(__cpu_soft_restart)