飞腾CPU虚拟化相关代码分析(二)——EL2异常向量表

  1. 飞腾CPU采用ARMv8体系结构,在非安全态有四个权限级EL0/1/2/3,其中EL2是Hypervisor权限级,虚拟机监控器VMM代码运行在EL2权限级上。
  2. ARMv8的早期版本只能支持Hyp模式的EL2,即EL2权限级的实现还不能完全支撑虚拟机内核和应用,需要运行在EL1的宿主OS协助完成。(备注:ARMv8的扩展版本支持VHE模式,即宿主OS可以直接运行在EL2模式下,此时VMM和宿主机内核可以共用一个EL2权限级上的异常向量表)
  3. 下面列出了Hyp分裂模式的EL2的异常向量表:
  1. ENTRY(__hyp_stub_vectors)
  1. ventry el2_sync_invalid // Synchronous EL2tventry el2_irq_invalid // IRQ EL2tventry el2_fiq_invalid // FIQ EL2tventry el2_error_invalid // Error EL2t
  2. ventry el2_sync_invalid // Synchronous EL2hventry el2_irq_invalid // IRQ EL2hventry el2_fiq_invalid // FIQ EL2hventry el2_error_invalid // Error EL2h
  3. ventry el1_sync // Synchronous 64-bit EL1ventry el1_irq_invalid // IRQ 64-bit EL1ventry el1_fiq_invalid // FIQ 64-bit EL1ventry el1_error_invalid // Error 64-bit EL1
  4. ventry el1_sync_invalid // Synchronous 32-bit EL1ventry el1_irq_invalid // IRQ 32-bit EL1ventry el1_fiq_invalid // FIQ 32-bit EL1ventry el1_error_invalid // Error 32-bit EL1
  1. ENDPROC(__hyp_stub_vectors)
  1. 根据触发异常的权限级和CPU状态,EL2异常向量表定义了四组:
  1. EL2t,当CPU在EL2且堆栈寄存器为SP_EL0时,触发了异常。
  2. EL2h,当CPU在EL2且堆栈寄存器为SP_EL2时,触发了异常。
  3. AArch64_EL1,当CPU在EL0/1且为AArch64模式,触发了异常。
  4. AArch32_EL1,当CPU在EL0/1且为AArch32模式,触发了异常。
    说明:

1~2,CPU权限级没有发生变化。
3~4,CPU权限级升高了。
这四组是按照位置顺序来定义的。

  1. 根据异常的类型,每一组又分为四个入口点
  1. 同步指令类型,例如系统调用指令、访存的缺页故障等
  2. IRQ中断类型,中断控制器GIC控制的一组外部设备中断
  3. FIQ中断类型,中断控制器GIC控制的另一组外部设备中断
  4. 系统错误类型,片上网络等相关部件故障中断(一般由具体CPU厂商定义的)
    说明:
    这四种类型是按照位置顺序来定义的。
  1. 根据上述EL2异常向量表,当前Hyp模式下虚拟机监控器VMM仅仅支持AArch64模式的虚拟机的同步指令触发的异常。

1.ventry el1_sync 2.*_invalid

  1. ventry代码分析,这是汇编宏。

.macro ventry label.align 7/*必须按照0x80长度对齐*/b \label/*虽然可以容纳0x80/4 =0x20条指令,但软件实现为直接跳转*/.endm

  1. 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:...

  1. 第一个参数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

  1. 如果调用号为HVC_SET_VECTORS(0), 设置新的异常向量表调用

msr vbar_el2, x1 设置寄存器vbar_el2
mov x0, xzr 正常返回零
eret

  1. 如果调用号为HVC_RESET_VECTORS(2),恢复异常向量表(其实什么也没有做,直接返回)

mov x0, xzreret

  1. 如果调用号为HVC_SOFT_RESTART,CPU软件启动
    执行下列代码之前,
    x1是CPU软件启动函数的首地址;
    x2/x3/x4是CPU软件启动函数的第一、二、三个参数

mov x0, x2mov x2, x4mov x4, x1mov x1, x3br x4 调用CPU软件启动函数之前,x0/x1/x2已经是第一、二、三个参数了。
mov x0, xzreret

  1. 出错返回

ldr x0, =HVC_STUB_ERReret

  1. 我们再看看虚拟机的内核如何实现HVC_SET/RESET_VECTORS
  1. ENTRY(__hyp_set_vectors)

mov x1, x0mov x0, #HVC_SET_VECTORShvc #0retENDPROC(__hyp_set_vectors)

  1. ENTRY(__hyp_reset_vectors)

mov x0, #HVC_RESET_VECTORShvc #0retENDPROC(__hyp_reset_vectors)

  1. 最后我们看看虚拟机的内核如何实现HVC_SOFT_RESTART
  1. 主要是调用__cpu_soft_restart(el2_switch, entry, arg0, arg1, arg2)
  1. el2_switch表示CPU是否需要进入EL2权限级
  2. entry表示软件启动CPU的代码地址
  3. arg0/1/2软件启动CPU函数的参数
  1. __cpu_soft_restart代码分析
    ENTRY(__cpu_soft_restart)mrs x12, sctlr_el1ldr x13, =SCTLR_ELx_FLAGSbic x12, x12, x13pre_disable_mmu_workaroundmsr sctlr_el1, x12isb 上面代码主要是将寄存器sctlr_el1的SCTLR_ELx_FLAGS对应的位清零
    cbz x0, 1f // el2_switch?mov x0, #HVC_SOFT_RESTARThvc #0 // no return1:下面代码是因为不希望CPU陷入EL2来处理。
    mov x18, x1 // entrymov x0, x2 // arg0mov x1, x3 // arg1mov x2, x4 // arg2br x18ENDPROC(__cpu_soft_restart)