LoongArch 处理器虚拟化

LoongArch指令集是中国龙芯中科公司于2020年发布的自主RISC指令集,包括基础指令集、二进制翻译拓展(LBT)、向量拓展(LSX)、高级向量扩展(LASX)和虚拟化拓展(LVZ)五个模块。

本文将主要对LoongArch指令集的CPU虚拟化设计进行简要介绍。

LoongArch寄存器简介

通用寄存器使用约定[1]

名称别名用途在调用中是否保留
$r0$zero常数 0(常数)
$r1$ra返回地址
$r2$tp线程指针(不可分配)
$r3$sp栈指针
$r4 - $r5$a0 - $a1传参寄存器、返回值寄存器
$r6 - $r11$a2 - $a7传参寄存器
$r12 - $r20$t0 - $t8临时寄存器
$r21保留(不可分配)
$r22$fp / $s9栈帧指针 / 静态寄存器
$r23 - $r31$s0 - $s8静态寄存器

浮点寄存器使用约定[1]

名称别名用途在调用中是否保留
$f0 - $f1$fa0 - $fa1传参寄存器、返回值寄存器
$f2 - $f7$fa2 - $fa7传参寄存器
$f8 - $f23$ft0 - $ft15临时寄存器
$f24 - $f31$fs0 - $fs7静态寄存器

临时寄存器也被称为调用者保存寄存器。 静态寄存器也被称为被调用者保存寄存器。

CSR寄存器

控制状态寄存器(Control and Status Register, CSR) 是LoongArch架构中一类特殊的寄存器,用于控制处理器的运行状态。 控制状态寄存器一览表(不包括LVZ虚拟化拓展中新的CSR):

编号名称编号名称编号名称
0x0当前模式信息 CRMD0x1例外前模式信息 PRMD0x2扩展部件使能 EUEN
0x3杂项控制 MISC0x4例外配置 ECFG0x5例外状态 ESTAT
0x6例外返回地址 ERA0x7出错虚地址 BADV0x8出错指令 BADI
0xc例外入口地址 EENTRY0x10TLB 索引 TLBIDX0x11TLB 表项高位 TLBEHI
0x12TLB 表项低位 0 TLBELO00x13TLB 表项低位 1 TLBELO10x18地址空间标识符 ASID
0x19低半地址空间全局目录基址 PGDL0x1A高半地址空间全局目录基址 PGDH0x1B全局目录基址 PGD
0x1C页表遍历控制低半部分 PWCL0x1D页表遍历控制高半部分 PWCH0x1ESTLB 页大小 STLBPS
0x1F缩减虚地址配置 RVACFG0x20处理器编号 CPUID0x21特权资源配置信息 1 PRCFG1
0x22特权资源配置信息 2 PRCFG20x23特权资源配置信息 3 PRCFG30x30+n (0≤n≤15)数据保存 SAVEn
0x40定时器编号 TID0x41定时器配置 TCFG0x42定时器值 TVAL
0x43计时器补偿 CNTC0x44定时中断清除 TICLR0x60LLBit 控制 LLBCTL
0x80实现相关控制 1 IMPCTL10x81实现相关控制 2 IMPCTL20x88TLB 重填例外入口地址 TLBRENTRY
0x89TLB 重填例外出错虚地址 TLBRBADV0x8ATLB 重填例外返回地址 TLBRERA0x8BTLB 重填例外数据保存 TLBRSAVE
0x8CTLB 重填例外表项低位 0 TLBRELO00x8DTLB 重填例外表项低位 1 TLBRELO10x8ETLB 重填例外表项高位 TLBREHI
0x8FTLB 重填例外前模式信息 TLBRPRMD0x90机器错误控制 MERRCTL0x91机器错误信息 1 MERRINFO1
0x92机器错误信息 2 MERRINFO20x93机器错误例外入口地址 MERRENTRY0x94机器错误例外返回地址 MERRERA
0x95机器错误例外数据保存 MERRSAVE0x98高速缓存标签 CTAG0x180+n (0≤n≤3)直接映射配置窗口 n DMWn
0x200+2n (0≤n≤31)性能监测配置 n PMCFGn0x201+2n (0≤n≤31)性能监测计数器 n PMCNTn0x300load/store 监视点整体控制 MWPC
0x301load/store 监视点整体状态 MWPS0x310+8n (0≤n≤7)load/store 监视点 n 配置 1 MWPnCFG10x311+8n (0≤n≤7)load/store 监视点 n 配置 2 MWPnCFG2
0x312+8n (0≤n≤7)load/store 监视点 n 配置 3 MWPnCFG30x313+8n (0≤n≤7)load/store 监视点 n 配置 4 MWPnCFG40x380取指监视点整体控制 FWPC
0x381取指监视点整体状态 FWPS0x390+8n (0≤n≤7)取指监视点 n 配置 1 FWPnCFG10x391+8n (0≤n≤7)取指监视点 n 配置 2 FWPnCFG2
0x392+8n (0≤n≤7)取指监视点 n 配置 3 FWPnCFG30x393+8n (0≤n≤7)取指监视点 n 配置 4 FWPnCFG40x500调试寄存器 DBG
0x501调试例外返回地址 DERA0x502调试数据保存 DSAVE

对于实现了LVZ虚拟化拓展的处理器,还有一组用于控制虚拟化的CSR寄存器[3]。

编号名称
0x15客户机TLB控制 GTLBC
0x16TLBRD读Guest项 TRGP
0x50客户机状态 GSTAT
0x51客户机控制 GCTL
0x52客户机中断控制 GINTC
0x53客户机计数器补偿 GCNTC

CPU模式

实现了LVZ虚拟化拓展的CPU(即龙芯3系处理器)支持两个运行模式—— Host模式和Guest模式 [3],其中每个模式各自有四个特权级(PLV0-PLV3)。处理器核当前处于哪个特权等级由CSR.CRMDPLV域的值唯一确定[2]。 对于无虚拟化的场景如在Host模式下启动一个Linux内核,其Linux内核态位于PLV0,用户态通常位于PLV3。 对于虚拟化场景,Host模式由Hypervisor使用,Guest模式则是Hypervisor所启动的虚拟机的运行模式。Guest模式在诸多方面受到Host模式下Hypervisor的控制,并且Guest模式下可以通过 Hypercall超级调用指令(hvcl)强制陷入Host模式下的Hypervisor。

下图是在loongarch架构下Hypervisor处理guest模式的异常的一种流程:

LoongArch-Exception-Guest

GCSR寄存器组

在实现虚拟化的LoongArch处理器中会额外有一组 GCSR(Guest Control and Status Register) 寄存器,供Guest模式下的虚拟机内操作系统使用,这里需要和Host模式下的CSR寄存器区分开。 通过这一套GCSR寄存器可以让虚拟机有自己的特权资源和对应管理,同时避免和Hypervisor的特权资源冲突并减少虚拟机陷入Hypervisor的次数。需要注意的是虚拟机对GCSR寄存器的操作、对特权指令(cpucfgcacop等)的执行等仍然可以被Hypervisor监控和控制,LVZ拓展允许Hypervisor自由选择是否对这些操作进行拦截。

进入Guest模式的流程(KVM)[4]

  1. switch_to_guest】:

  2. 清空CSR.ECFG.VS字段(设置为0,即所有异常共用一个入口地址)

  3. 读取Hypervisor中保存的guest eentry(客户OS中断向量地址)-> GEENTRY

    1. 然后将GEENTRY写入CSR.EENTRY
  4. 读取Hypervisor中保存的guest era(客户OS异常返回地址)-> GPC

    1. 然后将GPC写入CSR.ERA
  5. 读取CSR.PGDL全局页表地址,存到Hypervisor中

  6. 从Hypervisor中加载guest pgdl到CSR.PGDL

  7. 读出CSR.GSTAT.GIDCSR.GTLBC.TGID,写入CSR.GTLBC

  8. CSR.PRMD.PIE置1,打开Hypervisor级的全局中断

  9. CSR.GSTAT.PGM置1,其目的是使ertn指令进入guest mode

  10. Hypervisor将自己保存的该guest的通用寄存器(GPRS)恢复到硬件寄存器上(恢复现场)

  11. 执行ertn指令,进入guest模式

虚拟化相关的异常[2][3]

codesubcode缩写介绍
22-GSPR客户机敏感特权资源异常,由cpucfgidlecacop指令触发,以及在虚拟机访问了不存在的GCSR和IOCSR时触发,强制陷入Hypervisor进行处理(如软件模拟)
23-HVChvcl超级调用指令触发的异常
240GCM客户机GCSR软件修改异常
241GCHC客户机GCSR硬件修改异常

处理Guest模式下异常的流程(KVM)[4]

  1. kvm_exc_entry】:

  2. Hypervisor首先保存好guest的通用寄存器(GPRS),保护现场。

  3. Hypervisor保存CSR.ESTAT -> host ESTAT

  4. Hypervisor保存CSR.ERA -> GPC

  5. Hypervisor保存CSR.BADV -> host BADV,即触发地址错误例外时,记录出错的虚拟地址

  6. Hypervisor保存CSR.BADI -> host BADI,该寄存器用于记录触发同步类例外的指令的指令码,所谓同步类例外是指除了中断(INT)、客户机CSR硬件修改例外(GCHC)、机器错误例外(MERR)之外的所有例外。

  7. 读取Hypervisor保存好的host ECFG,写入CSR.ECFG(即切换到host下的异常配置)

  8. 读取Hypervisor保存好的host EENTRY,写入CSR.EENTRY

  9. 读取Hypervisor保存好的host PGD,写入CSR.PGDL(恢复host页表全局目录基址,低半空间)

  10. 设置CSR.GSTAT.PGM关闭

  11. 清空GTLBC.TGID

  12. 恢复kvm per cpu寄存器

    1. kvm汇编里涉及到KVM_ARCH_HTP, KVM_ARCH_HSP, KVM_ARCH_HPERCPU
  13. 跳转到KVM_ARCH_HANDLE_EXIT位置处理异常

  14. 判断刚才的函数ret是否<=0

    1. 若<=0,则继续运行host
    2. 否则继续运行guest,保存percpu寄存器,因为可能会切换到不同的CPU继续运行guest。保存host percpu寄存器到CSR.KSAVE寄存器
  15. 跳转到switch_to_guest

vCPU上下文需要保存的寄存器

由LoongArch函数调用规范可知如果需要手动切换CPU函数运行上下文,需要保存的寄存器如下(不考虑浮点寄存器):$s0-$s9$sp$ra

参考资料

[1] 龙芯中科技术股份有限公司.龙芯架构ELF psABI规范.Version 2.01.

[2] 龙芯中科技术股份有限公司.龙芯架构参考手册.卷一:基础架构.

[3] 龙芯中科技术股份有限公司.龙芯架构参考手册.卷三.

[4] https://github.com/torvalds/linux/blob/master/arch/loongarch/kvm/switch.S.