FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RISC-V ハイパーバイザー拡張の勉強 (LinuxとOpenSBIのハイパーバイザー対応を確認する1)

ハイパーバイザーについて、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_availablelinux/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;
}