飞腾CPU虚拟化相关代码分析(一)—— 函数el2_setup

函数el2_setup是ARM64体系结构下Linux内核运行的第一个和虚拟化相关的函数。

相关概念

ARM64支持两种虚拟方式:Hyp和VHE两种方式。

  1. 传统分裂模式Hyp:

宿主OS内核处于EL1状态,客户OS内核也处于EL1状态,CPU需要两次陷入和四次上下文切换才能完成一次对客户OS的服务。

  1. 虚拟主机扩展模式VHE(当前飞腾CPU还没有支持VHE模式):

宿主OS内核处于EL2状态,客户OS内核处于EL1状态,CPU只需要一次陷入和两次上下文切换就可以完成一次对客户OS的服务。

函数输入输出描述

  1. 输入
  1. MMU关闭,数据cache关闭。
  2. x21保存了设备树起始地址,在启动阶段不能被占用。
  3. u64 __cacheline_aligned boot_args[4]保存了寄存器x0/1/2/3
    1)boot_args[0]保存了设备树起始地址。
    2)boot_args[1]-[3]如果非零,表示固件不是符合标准的UEFI,很可能就是uboot。
  1. 输出
  1. 寄存器w0返回,刚刚进入内核时,CPU的权限级
    1) BOOT_CPU_MODE_EL1:表示当前CPU跳入内核时处于权限级EL1;函数正常返回,无权限级切换。
    2)BOOT_CPU_MODE_EL2:表示当前CPU跳入内核时处于权限级EL2。此次内核启动可以支持KVM虚拟机,Hyp模式下函数异常返回,需要将CPU切换回EL1权限级;如果VHE模式,函数正常返回,CPU保持EL2权限级。

函数分析

  1. 堆栈寄存器的选择

msr SPsel, #1

  1. 判断当前CPU权限级

mrs x0, CurrentELcmp x0, #CurrentEL_EL2b.eq 1f/*不跳转,说明当前为EL1,内核后续不能支持虚拟化*/1: /*跳转,说明当前为EL2,el2_setup函数为后续虚拟化做好铺垫*/

  1. 如果当前CPU权限级已经是EL1,通过设置sctlr_el1,将CPU的EL0/1设置为小端模式,就直接调用ret指令返回,返回地址为lr寄存器所包含的地址(因此el2_setup函数需要用bl调用)。

mov_q x0, (SCTLR_EL1_RES1 | ENDIAN_SET_EL1)msr sctlr_el1, x0mov w0, #BOOT_CPU_MODE_EL1isbret

  1. 我们现在在权限级EL2级。
  1. 通过设置sctlr_el2,将CPU的EL2设置为小端模式。
  2. 通过读取id_aa64mmfr1_el1,判断CPU是支持虚拟主机扩展VHE模式,还是传统的分离Hyp模式。
    mrs x2, id_aa64mmfr1_el1ubfx x2, x2, #8, #4
  1. 设置Hypervisor配置寄存器hcr_el2。
  1. Hyp模式
    #define HCR_HOST_NVHE_FLAGS (HCR_RW | HCR_API | HCR_APK)mov_q x0, HCR_HOST_NVHE_FLAGSmsr hcr_el2, x0
  2. VHE模式
    #define HCR_HOST_VHE_FLAGS (HCR_RW | HCR_TGE | HCR_E2H)mov_q x0, HCR_HOST_VHE_FLAGSmsr hcr_el2, x0

这以后,寄存器x2,零表示Hyp模式;非零表示VHE模式

  1. 设置定时器相关寄存器

将定时器虚拟偏移量清零

  1. Hyp模式
    /*启动计数器事件流*/mrs x0, cnthctl_el2orr x0, x0, #3msr cnthctl_el2, x0/*虚拟偏移量清零*/msr cntvoff_el2, xzr
  2. VHE模式
    msr cntvoff_el2, xzr
  1. GICv3中断控制器相关设置
  1. 中断控制器类型判断
    mrs x1, id_aa64pfr0_el1ubfx x0, x0, #24, #4cbz x0, 3f/*当中断控制是GICv3时,不发生跳转*/......3: /*如果直接跳转到这里,说明CPU不支持GICv3*/
  2. 如果是GICv3,设置SYS_ICC_SRE_EL2、SYS_ICH_HCR_EL2寄存器,这两个寄存器是GICv3的CPU接口寄存器。
    /*不允许内核访问ICC_SRE_EL1;设置采用MSR/MRS方式访问ICC_*相关寄存器*/mrs_s x0, SYS_ICC_SRE_EL2orr x0, x0, #ICC_SRE_EL2_SREorr x0, x0, #ICC_SRE_EL2_ENABLEmsr_s SYS_ICC_SRE_EL2, x0isbmrs_s x0, SYS_ICC_SRE_EL2/*写之后再读,确认是否真正设置成功*/tbz x0, #0, 3f/*如果SYS_ICC_SRE_EL2寄存器的SRE位不为0,表示设置成功*/msr_s SYS_ICH_HCR_EL2, xzr3: /*如果设置不成功,就跳转到此处运行*/msr_s SYS_ICH_HCR_EL2, xzr /*关闭所有虚拟中断和维护中断*/
  1. 根据物理CPU的ID寄存器和亲合属性寄存器,来设置虚拟CPU对应的寄存器。

/*ID寄存器*/mrs x0, midr_el1msr vpidr_el1, x0/*亲合属性寄存器*/mrs x1, mpidr_el1msr vmpidr_el2, x1

  1. 将Hypervisor系统陷入寄存器HSTR_EL2清零。一般情况下,当客户虚拟机是AArch32位,会有Thumb和协处理器方式,不希望在访问相关寄存器陷入到EL2中。

msr hstr_el2, xzr

  1. 获取事件计数器数量。通过AArch64调试特征寄存器0,id_aa64dfr0_el1,判断CPU是否可以访问PMU寄存器,如果可以就获取事件计数器数量,否则直接为零。

mrs x1, id_aa64dfr0_el1sbfx x0, x1, #8, #4cmp x0, #1b.lt 4f/*id_aa64dfr0_el1的11:8位小于0b0001,不跳转表示,支持性能监控扩展系统寄存器*//*当支持PMU时,这里会获取事件计数器数量*/mrs x0, pmcr_el0ubfx x0, x0, #11, #54:/*跳转,表示支持*/csel x3, xze, x0, lt /*这条指令的直接含义是:当前面cmp x0, #1和b.lt 跳转成功时,x3直接赋值为零,否则赋值为x0 */

此时,寄存器x3的低5位记录了事件计数器的数量,如果该数量为零,表示不支持PMU

  1. 也是通过AArch64调试特征寄存器0,id_aa64dfr0_el1,判断CPU是否可以访问SPE寄存器。

ubfx x0, x1, #32, #4cbz x0, 7f/*不跳转,表示支持PMS*//*飞腾还没有支持,这一段我们先不分析(?)*/7: /*直接跳转到这里,表示不支持SPE*/

  1. 根据前面的第10和第11步,已经完成对寄存器x3的初始化,然后直接赋值给寄存器mdcr_el2。

msr mdcr_el2, x3

  1. 内存模式特征寄存器id_aa64mmfr1_el1,这个寄存器手册上还没有说明。

mrs x1, id_aa64mmfr1_el1ubfx x0, x1, #ID_AA64MMFR1_LOR_SHIFT, 4cbz x0, 1fmsr_s SYS_LORC_EL1, zxr1:

  1. 将非安全态EL0/EL1的stage2阶段的虚拟页表基址寄存器清零,即将寄存器vttbr_el2清零。

msr vttbr_el2, xzr

  1. 从下面代码开始,VHE模式和Hyp模式走向不同的分支。

cbz x2, install_el2_stub/*VHE模式返回代码*/install_el2_stub:/*Hyp模式设置和返回代码*/

  1. 如果是VHE模式,直接返回w0为BOOT_CPU_MODE_EL2。由于进入内核时,CPU处于EL2,所以直接调用ret指令返回,CPU仍然是EL2。

mov w0, #BOOT_CPU_MODE_EL2isbret

  1. install_el2_stub是Hyp模式相关设置和返回代码。
  2. 通过设置sctlr_el1,将CPU的EL0/1设置为小端模式。

mov_q x0, (SCTLR_EL1_RES1 | ENDIAN_SET_EL1)msr sctlr_el1, x0

  1. 设置寄存器cptr_el2,浮点和ASIMD相关寄存器的访问不陷入EL2

mov x0, #0x33ffmsr cptr_el2, x0

  1. 先判断是否支持SVE指令,如果支持设置相关寄存器访问不陷入EL2,而且设置在EL1可以进行全向量长度支持。
  1. SVE指令支持判断

mrs x1, id_aa64pfr0_el1ubfx x1, x1, #ID_AA64PFR0_SVE_SHIFT, #4cbz x1, 7f/*支持SVE指令*/7: /**/ 2.支持设置相关寄存器访问不陷入EL2
bic x0, x0, #CPTR_EL2_TZmsr cptr_el2, x0isb

  1. 设置全向量长度支持

mov x1, #ZCR_ELx_LEN_MASKmsr_s SYS_ZCR_EL2, x1

  1. 设置EL2异常向量表基地址寄存器(参见《飞腾CPU虚拟化相关代码分析(二)之EL2异常向量表》)

adr_l x0, __hyp_stub_vectors /*装载64位地址*/msr vbar_el2, x0 __hyp_stub_vectors是EL2异常向量表,寄存器vbar_el2是EL2级异常向量表基地址寄存器。

  1. Hyp模式的el2_setup函数返回

mov x0, #(PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT | PSR_MODE_EL1h)msr spsr_el2, x0msr elr_el2, lrmov w0, #BOOT_CPU_MODE_EL2eret

  1. 设置寄存器spsr_el2是为了返回设置CPU返回后的EL1状态,el1h说明了返回EL1状态,EL1堆栈寄存器采用sp_el1,F/I/A/D表明各种异常都屏蔽。
  2. elr_el2异常链接地址寄存器,也设置为当前链接寄存器lr地址,即即使从EL2返回到EL1,返回地址不变。
  3. eret指令为异常返回指令。