ひまわり

はやく人間になりたい

RISC-V KVMの初期化

RISC-V ISA manual Vol.2 Privileged Architectureを片手に、kvmtoolとkvm.koのコードを追っていく

ちょっとその前に、現状で把握している情報を
あっちこっち見ている間に, kvmのpatchがv7まで来ている

lkml.org

あと、昨日から開催されている、 www.linuxplumbersconf.orgRISC-V Microconference で, パッチを投げているWD達が以下の内容で発表を行うので、資料公開されたら見ていきたい公開されていたのでリンクを追加した

本題にもどります

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が起動するまでの流れ

  1. kvm.koがloadされる
  2. kvmtoolによって、kvmと連携してguest用のregister, メモリ空間の初期化をおこなう
  3. 実行

上から、順にコードを追っていく

kvm.koによる、HWの初期化

大体読むところは、アーキテクチャ依存に関する
arch/riscv/kvm/*

main.cのriscv_kvm_init関数から一度、アーキテクチャ共通ルーチンのkvm_init(virt/kvm/kvm_main.c) へ飛ぶ
プロセッサの初期化に関してはここらへん

  1. kvm_arch_init
  2. kvm_arch_hardware_setup
  3. check_processor_compat
  4. kvm_starting_cpu, kvm_dyning_cpu
  5. 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;
}

f:id:himaaaatti:20190910225059j:plain [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が呼ばれた後なので、
次の記事にまわす


参考文献