RISC-V KVMの初期化
RISC-V ISA manual Vol.2 Privileged Architectureを片手に、kvmtoolとkvm.koのコードを追っていく
ちょっとその前に、現状で把握している情報を
あっちこっち見ている間に, kvmのpatchがv7まで来ている
あと、昨日から開催されている、
www.linuxplumbersconf.org
のRISC-V Microconference で, パッチを投げているWD達が以下の内容で発表を行うので、資料公開されたら見ていきたい公開されていたのでリンクを追加した
- RISC-V Hypervisor ISA Emulation
QEMUに実装した話とこれから, pdf - RISC-V hypervisor implementation
Type-1 hypervisorとして実装した、xvisorとType-2 hypervisorとして実装した, KVMについてとこれから, pdf
本題にもどります
RISC-Vでの、HWの仮想化支援について
上の発表資料を見るに、AArch64よりは、ARM-VHEの仮想化支援機構と似た形になっているとのこと。
M, S, U modeに加えて、HS, VS, VU modeをhypervisor extensionでは追加している
HS mode
Hypervisor extended supervisor modeの略称で、既存のOSレイヤー相当がうごくS(supervisor) modeの互換をもち、guest physical memoryとsupervisor physical memoryへの変換や、MMIOのメモリマップの仮想化の機能を持つVS, US mode
virtual supervisor(user) modeの略称で、guest環境でのS mode, U modeである
これらを実現するために、新たに, hart(Hardware thread)の仮想化についての内部状態であるVirtualization Mode (Vで資料では略される)と、いくつかのCSR (hstatus, hedeleg, hideleg, bsstatus, bsip, etc)が追加されている.
background CSR(bから始まるbsstatus等)については, kvmの実装ではどうも、vから始まる名前(vsstatus等)を使っているっぽい、なぜ差異があるのかはわからない、策定途中だから、まぁこういうこともあるとは思うが
これらを用いて、kvmは仮想化を提供している [3]
guestが起動するまでの流れ
上から、順にコードを追っていく
kvm.koによる、HWの初期化
大体読むところは、アーキテクチャ依存に関する
arch/riscv/kvm/*
main.cのriscv_kvm_init関数から一度、アーキテクチャ共通ルーチンのkvm_init(virt/kvm/kvm_main.c) へ飛ぶ
プロセッサの初期化に関してはここらへん
- kvm_arch_init
- kvm_arch_hardware_setup
- check_processor_compat
- kvm_starting_cpu, kvm_dyning_cpu
- register_syscore_ops
3と4に関しては、関数ポインタを渡した関数の中で呼ばれている
int kvm_init(void *opaque, unsigned vcpu_size, unsigned vcpu_align, struct module *module) { int r; int cpu; r = kvm_arch_init(opaque); // (1) ... r = kvm_arch_hardware_setup(); // (2) if (r < 0) goto out_free_0a; for_each_online_cpu(cpu) { smp_call_function_single(cpu, check_processor_compat, &r, 1); // (3) if (r < 0) goto out_free_1; } r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_STARTING, "kvm/cpu:starting", kvm_starting_cpu, kvm_dying_cpu); // (4) register_reboot_notifier(&kvm_reboot_notifier); // (5) ... r = misc_register(&kvm_dev); // /dev/kvmの準備 if (r) { pr_err("kvm: misc device register failed\n"); goto out_unreg; } register_syscore_ops(&kvm_syscore_ops); // (6) ... }
1. kvm_arch_init
- (1.1) hardwareがhypervisor拡張に対応しているかどうかのチェック
- (1.2) プロセッサ毎のvsipを保持する領域の確保
guest用のinterrupt pending register. 割り込みの通知に使われる. - (1.3) a virtual machine identifier (VMID)の取得
fence命令に用いられる, fence命令はよくあるメモリバリア用の命令
Hypervisor Guest Address Translation and Protection Register (hgatp)からとってくる
int kvm_arch_init(void *opaque) { int ret; if (!riscv_isa_extension_available(NULL, h)) { // (1.1) kvm_info("hypervisor extension not available\n"); return -ENODEV; } ret = kvm_riscv_setup_vsip(); // (1.2) if (ret) return ret; kvm_riscv_stage2_vmid_detect(); // (1.3) kvm_info("hypervisor extension available\n"); // log kvm_info("host has %ld VMID bits\n", kvm_riscv_stage2_vmid_bits()); return 0; }
2. kvm_arch_hardware_setup
ここでは、hypervisor trap delegation registers (hedeleg, hideleg)の初期化を行っている. hedelegは、exceptionが起きたときに, そのexceptionを下位のモードへ”委譲”するために使われ、それぞれのビットはexception code番号に対応していて、委譲したいexceptionのbitを立てて使う. exception のリストはISA manual Vol. 2, Table 3.6にあり、下に乗せる
hidelegには、interruptに関する委譲を扱い、番号は同じTableのinterrupt bitが立っている場合のところを参照するとわかる.
わかりやすい記事を, msyksphinzさんが上げているので、リンク[1]を乗せる
今回は, guest でOSを動かすので、instruction address misaligned, breakpoint, syscall, page faultが設定してある.
割り込みは、supervisor modeでの委譲に関する話なので、supervisor {software, timer, external} interruptが設定でき、ここではすべて設定している.
それぞれの略称は、
- SSIE (supervisor software interrupt enable) bit
- STIE (supervisor timer interrupt enable) bit
- SEIE (supervisor external interrupt enable) bit
最後のVSIPは、推定だが、v1.11のdraftでのbackground supervisor interrupt pending registers同等だと思われ、割り込みの通知を知らせるレジスタであり、割り込みがあると対応したbitがたつ。その初期値で0だと思われる
int kvm_arch_hardware_enable(void) { unsigned long hideleg, hedeleg; // hypervisor trap delegation registers (hedeleg, hideleg)の初期化 hedeleg = 0; hedeleg |= (1UL << EXC_INST_MISALIGNED); hedeleg |= (1UL << EXC_BREAKPOINT); hedeleg |= (1UL << EXC_SYSCALL); hedeleg |= (1UL << EXC_INST_PAGE_FAULT); hedeleg |= (1UL << EXC_LOAD_PAGE_FAULT); hedeleg |= (1UL << EXC_STORE_PAGE_FAULT); csr_write(CSR_HEDELEG, hedeleg); // exception delegation hideleg = 0; hideleg |= SIE_SSIE; hideleg |= SIE_STIE; hideleg |= SIE_SEIE; csr_write(CSR_HIDELEG, hideleg); // interrupt delegation csr_write(CSR_VSIP, 0); // interrupt pending return 0; }
[2]
3. check_processor_compat
プロセッサ毎に, 機能があるかどうかの判定に使うはずだが、現状はまだ実装されておらず空
int kvm_arch_check_processor_compat(void) { return 0; }
4. kvm_starting_cpu, kvm_dyning_cpu
hotplugでの、始まりと終わりに呼び出される関数の登録で、 アーキテクチャ依存で呼び出されるのは、 それぞれ、kvm_arch_hardware_enableとkvm_arch_hardware_disable.
kvm_arch_hardware_enableは、一つ上の項目で説明したとおりで、もう一つは 以下で、exceptionとinterruptの委譲レジスタの初期化である j
void kvm_arch_hardware_disable(void) { csr_write(CSR_HEDELEG, 0); csr_write(CSR_HIDELEG, 0); }
5. register_reboot_notifier
ここは、rebootが起きる直前に呼ばれる関数の登録で、実際に呼ばれる関数は、 kvm_rebootで、この中でも、hardware_disable_nolockの先で、kvm_arch_hardware_disableが呼ばれる
static int kvm_reboot(struct notifier_block *notifier, unsigned long val, void *v) { /* * Some (well, at least mine) BIOSes hang on reboot if * in vmx root mode. * * And Intel TXT required VMX off for all cpu when system shutdown. */ pr_info("kvm: exiting hardware virtualization\n"); kvm_rebooting = true; on_each_cpu(hardware_disable_nolock, NULL, 1); return NOTIFY_OK; }
6. register_syscore_ops
systemがsuspend, resumeするときに、呼ばれる関数を登録している。 実際に、suspend時に、呼ばれる関数は、kvm_suspend、resume時はkvm_resumeで以下の通り
static int kvm_suspend(void) { if (kvm_usage_count) hardware_disable_nolock(NULL); return 0; } static void kvm_resume(void) { if (kvm_usage_count) { #ifdef CONFIG_LOCKDEP WARN_ON(lockdep_is_held(&kvm_count_lock)); #endif hardware_enable_nolock(NULL); } }
これらも、kvm_arch_hardware_enable, kvm_arch_hardware_disableが呼ばれる
初期化にまつわる話は以上
実際に、HS-modeからVS-modeへ、行くには、hsstatusのSPVとSPPを立ててsretをしているのだが、それは, vcpuが始まるタイミングで、KVM_VCPU_RUNが呼ばれた後なので、
次の記事にまわす