ハイパーバイザーについて、OpenSBIとLinux側の実装を読んでみる。OpenSBI側のリポジトリにはLinuxカーネルのリポジトリが含まれており、RISC-VのKVM開発用のブランチを指しているようだった。Linux上でのKVMの立ち上げについては全くド素人だが、何となくソースコードを読んで雰囲気をつかんでいくことにする。
KVMの立ち上げについてはこの辺のソースコードが該当するのだろうか?
linux/virt/kvm/kvm_main.c
int kvm_init(void *opaque, unsigned vcpu_size, unsigned vcpu_align, struct module *module) { struct kvm_cpu_compat_check c; int r; int cpu; r = kvm_arch_init(opaque); if (r) goto out_fail; /* ... 中略 ... */
kvm_arch_init()
はアーキテクチャ毎に定義されており、RISC-Vのものも定義されているようだ。
linux/arch/riscv/kvm/main.c
int kvm_arch_init(void *opaque) { const char *str; if (!riscv_isa_extension_available(NULL, h)) { kvm_info("hypervisor extension not available\n"); return -ENODEV; } if (sbi_spec_is_0_1()) { kvm_info("require SBI v0.2 or higher\n"); return -ENODEV; } if (sbi_probe_extension(SBI_EXT_RFENCE) <= 0) { kvm_info("require SBI RFENCE extension\n"); return -ENODEV; } kvm_riscv_stage2_mode_detect(); kvm_riscv_stage2_vmid_detect(); kvm_info("hypervisor extension available\n"); switch (kvm_riscv_stage2_mode()) { case HGATP_MODE_SV32X4: str = "Sv32x4"; break; case HGATP_MODE_SV39X4: str = "Sv39x4"; break; case HGATP_MODE_SV48X4: str = "Sv48x4"; break; default: return -ENODEV; } kvm_info("using %s G-stage page table format\n", str); kvm_info("VMID %ld bits available\n", kvm_riscv_stage2_vmid_bits()); return 0; }
riscv_isa_extension_available
はlinux/arch/riscv/kernel/cpufeature.c
に定義されている。ここではHypervisor拡張が有効化されているかどうかをチェックする。
/** * __riscv_isa_extension_available() - Check whether given extension * is available or not * * @isa_bitmap: ISA bitmap to use * @bit: bit position of the desired extension * Return: true or false * * NOTE: If isa_bitmap is NULL then Host ISA bitmap will be used. */ bool __riscv_isa_extension_available(const unsigned long *isa_bitmap, int bit) { const unsigned long *bmap = (isa_bitmap) ? isa_bitmap : riscv_isa; if (bit >= RISCV_ISA_EXT_MAX) return false; return test_bit(bit, bmap) ? true : false; } EXPORT_SYMBOL_GPL(__riscv_isa_extension_available);
OpenSBIのバージョンが0.2以上でなければならない。
if (sbi_spec_is_0_1()) { kvm_info("require SBI v0.2 or higher\n"); return -ENODEV; }
linux/arch/riscv/include/asm/sbi.h
extern unsigned long sbi_spec_version; // これの実体はどこ? /* Check if current SBI specification version is 0.1 or not */ static inline int sbi_spec_is_0_1(void) { return (sbi_spec_version == SBI_SPEC_VERSION_DEFAULT) ? 1 : 0; }
opensbi/linux/arch/riscv/kernel/sbi.c
/* default SBI version is 0.1 */ unsigned long sbi_spec_version = SBI_SPEC_VERSION_DEFAULT; EXPORT_SYMBOL(sbi_spec_version);
次に2ステージアドレス変換の部分を確認する。
linux/arch/riscv/kvm/main.c
kvm_riscv_stage2_mode_detect(); kvm_riscv_stage2_vmid_detect(); kvm_info("hypervisor extension available\n"); switch (kvm_riscv_stage2_mode()) { case HGATP_MODE_SV32X4: str = "Sv32x4"; break; case HGATP_MODE_SV39X4: str = "Sv39x4"; break; case HGATP_MODE_SV48X4: str = "Sv48x4"; break; default: return -ENODEV; } kvm_info("using %s G-stage page table format\n", str); kvm_info("VMID %ld bits available\n", kvm_riscv_stage2_vmid_bits());
kvm_riscv_stage2_mode_detect()
の実装はこっちになっている。64ビットの場合は2ステージモードの設定はデフォルトでSv39x4になっていて、Sv48x4にアップグレードできるかどうかをテストしているようだ。CSRに一度書き込んで読み出すことでSv48x4に昇格できるかテストしている。HGATPレジスタはWARL属性なのでこのような操作が許される。
linux/arch/riscv/kvm/mmu.c
#ifdef CONFIG_64BIT static unsigned long stage2_mode = (HGATP_MODE_SV39X4 << HGATP_MODE_SHIFT); static unsigned long stage2_pgd_levels = 3; #define stage2_index_bits 9 #else static unsigned long stage2_mode = (HGATP_MODE_SV32X4 << HGATP_MODE_SHIFT); static unsigned long stage2_pgd_levels = 2; #define stage2_index_bits 10 #endif void kvm_riscv_stage2_mode_detect(void) { #ifdef CONFIG_64BIT /* Try Sv48x4 stage2 mode */ csr_write(CSR_HGATP, HGATP_MODE_SV48X4 << HGATP_MODE_SHIFT); if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV48X4) { stage2_mode = (HGATP_MODE_SV48X4 << HGATP_MODE_SHIFT); stage2_pgd_levels = 4; } csr_write(CSR_HGATP, 0); __kvm_riscv_hfence_gvma_all(); #endif }
kvm_riscv_stage2_vmid_detect()
はHGATPのVMIDフィールドを取得している。これもHGATPレジスタを取得してフィールドを抽出している。
void kvm_riscv_stage2_vmid_detect(void) { unsigned long old; /* Figure-out number of VMID bits in HW */ old = csr_read(CSR_HGATP); csr_write(CSR_HGATP, old | HGATP_VMID_MASK); vmid_bits = csr_read(CSR_HGATP); vmid_bits = (vmid_bits & HGATP_VMID_MASK) >> HGATP_VMID_SHIFT; vmid_bits = fls_long(vmid_bits); csr_write(CSR_HGATP, old); /* We polluted local TLB so flush all guest TLB */ __kvm_riscv_hfence_gvma_all(); /* We don't use VMID bits if they are not sufficient */ if ((1UL << vmid_bits) < num_possible_cpus()) vmid_bits = 0; }
そして取得したアドレス変換モードとVMIDを表示して終了となる。
kvm_info("hypervisor extension available\n"); switch (kvm_riscv_stage2_mode()) { case HGATP_MODE_SV32X4: str = "Sv32x4"; break; case HGATP_MODE_SV39X4: str = "Sv39x4"; break; case HGATP_MODE_SV48X4: str = "Sv48x4"; break; default: return -ENODEV; } kvm_info("using %s G-stage page table format\n", str); kvm_info("VMID %ld bits available\n", kvm_riscv_stage2_vmid_bits()); return 0; }