読者です 読者をやめる 読者になる 読者になる

φ(・・*)ゞ ウーン jailhouseのコードを読んでみるの2

virtualization linux kernel

前回の「φ(・・*)ゞ ウーン jailhouseのコードを読んでみるの1」からの続きでjailhouse_enable()から。

この関数は以下のようにjailhouseコマンドを実行した時に呼ばれる。

jailhouse enable /path/to/qemu-vm.cell

引数に渡しているファイル名はconfigs/の下に有ってフロッピーディスクイメージになってますね。

masami@saga:~/codes/jailhouse/config (master)$ file qemu-vm.cell
qemu-vm.cell: floppy with old FAT filesystem 320k, Media descriptor 0xff

この.cellファイルは.cファイルより作られてます。

そしてjailhouse_enable()に目を向けるとこの間数の主な処理は2点でjailhouseのファームウェアを読み込み関連の処理、cpuの初期化等が行われる。
上から処理を追っていくと。

static int jailhouse_enable(struct jailhouse_system __user *arg)
{
        unsigned long hv_core_size, percpu_size, config_size;
        const struct firmware *hypervisor;
        struct jailhouse_system config_header;
        struct jailhouse_memory *hv_mem = &config_header.hypervisor_memory;
        struct jailhouse_header *header;
        int err;

        if (copy_from_user(&config_header, arg, sizeof(config_header)))
                return -EFAULT;

        if (mutex_lock_interruptible(&lock) != 0)
                return -EINTR;

引数のargはjailhouseコマンドで指定した.cellファイルのファイルのデータで、jailhouseコマンドはこのファイルを読んでその中身をioctlで渡してます。
このデータはhypervisor/include/jailhouse/cell-config.hにてこのように定義されています。

struct jailhouse_system {
        struct jailhouse_memory hypervisor_memory;
        struct jailhouse_memory config_memory;
        struct jailhouse_cell_desc system;
};

既にjailhouseがenableになっていたら終了。

        err = -EBUSY;
        if (enabled || !try_module_get(THIS_MODULE))
                goto error_unlock;

ファームウェアを読み込んで、

        err = request_firmware(&hypervisor, JAILHOUSE_FW_NAME, jailhouse_dev);
        if (err)
                goto error_put_module;

読み込んだデータのシグネチャチェック

        header = (struct jailhouse_header *)hypervisor->data;

        err = -EINVAL;
        if (memcmp(header->signature, JAILHOUSE_SIGNATURE,
                   sizeof(header->signature)) != 0)
                goto error_release_fw;

シグネチャはこんな風になっています。

masami@saga:~/codes/jailhouse/hypervisor (master)$ hexdump -C jailhouse.bin | head -n 1
00000000  4a 41 49 4c 48 4f 55 53  00 90 00 00 00 00 00 00  |JAILHOUS........|

ここからは各データのサイズ取得とチェックがあり

        hv_core_size = PAGE_ALIGN(header->bss_end);
        percpu_size = num_possible_cpus() * header->percpu_size;
        config_size = jailhouse_system_config_size(&config_header);
        if (hv_mem->size <= hv_core_size + percpu_size + config_size)
                goto error_release_fw;

ioremapでハイパーバイザをメモリにマップしてからデータの設定。

        /* CMA would be better... */
        hypervisor_mem = jailhouse_ioremap(hv_mem->phys_start, hv_mem->size);
        if (!hypervisor_mem)
                goto error_release_fw;

        memcpy(hypervisor_mem, hypervisor->data, hypervisor->size);
        memset(hypervisor_mem + hypervisor->size, 0,
               hv_mem->size - hypervisor->size);

        header = (struct jailhouse_header *)hypervisor_mem;
        header->size = hv_mem->size;
        header->page_offset =
                (unsigned long)hypervisor_mem - hv_mem->phys_start;
        header->possible_cpus = num_possible_cpus();

        if (copy_from_user(hypervisor_mem + hv_core_size + percpu_size, arg,
                           config_size)) {
                err = -EFAULT;
                goto error_unmap;
        }

ここからcpuの初期処理。やることはホスト上にある利用可能な全cpuでenter_hypervisor()を実行する。ここで使っているon_each_cpu()を見ていくのも面白そうなんだけど本題から外れるので飛ばす><

        error_code = 0;

        preempt_disable();

        header->online_cpus = num_online_cpus();

        atomic_set(&call_done, 0);
        on_each_cpu(enter_hypervisor, header, 0);
        while (atomic_read(&call_done) != num_online_cpus())
                cpu_relax();

        preempt_enable();

        if (error_code) {
                err = error_code;
                goto error_unmap;
        }

ここまでの処理ですべて成功していれば読み込んだファームウェアのリリース等、この関数の終了処理を実行。

        release_firmware(hypervisor);

        enabled = true;

        mutex_unlock(&lock);

        printk("The Jailhouse is opening.\n");

        return 0;

残りはエラー時の処理なので省略。

次にenter_hypervisor()を見ていきます。

static void enter_hypervisor(void *info)
{
        struct jailhouse_header *header = info;
        entry_func entry;
        int err;

        entry = (entry_func)(hypervisor_mem + header->entry);

        /* either returns 0 or the same error code across all CPUs */
        err = entry(smp_processor_id());
        if (err)
                error_code = err;

        atomic_inc(&call_done);
}

まずは呼び出すべき関数のアドレスをoremapした領域から読み出してentry()を実行。このentry()はアーキテクチャ依存部分なのでhypervisor/arch/x86/entry.Sが読むべきファイルに。
ここで呼んでいる関数のプロトタイプはhypervisor/include/jailhouse/entry.hで定義されている↓になります。

int arch_entry(int cpu_id);

という訳でarch_entry()を探すとhypervisor/arch/x86/entry.Sがヒットするということです。

/* Entry point for Linux loader module on JAILHOUSE_ENABLE */
        .text
        .globl arch_entry
arch_entry:
        cli

        push %rbp
        push %rbx
        push %r12
        push %r13
        push %r14
        push %r15

        mov %rdi,%rdx
        shl $PERCPU_SIZE_SHIFT,%rdi
        lea __page_pool(%rip),%rax
        add %rax,%rdi

        mov %rsp,PERCPU_LINUX_SP(%rdi)
        mov %edx,PERCPU_CPU_ID(%rdi)

        lea PERCPU_STACK_END-8(%rdi),%rsp

        push %rdi

        call entry

        pop %rdi

        mov PERCPU_LINUX_SP(%rdi),%rsp

        pop %r15
        pop %r14
        pop %r13
        pop %r12
        pop %rbx
        pop %rbp

        ret

この関数の細かいことは無視するとcall entryでentry()を呼ぶための引数の設定をしてからentry()を呼んでいるだけなのでentry()を読みましょう。ここで呼んでいるentry()はhypervisor/setup.cにあります。

引数に使われているper_cpu構造体はhypervisor/arch/x86/include/asm/percpu.hにあるjailhouseで定義している構造体です。では、またまた上から見ていくと。

master_cpu_idはこのファイルで定義されているグローバル変数で初期値は-1です。なので、最初はif文の中を通ります。

int entry(struct per_cpu *cpu_data)
{
        bool master = false;

        spin_lock(&init_lock);

        if (master_cpu_id == -1) {
                master = true;
                init_early(cpu_data->cpu_id);
        }

init_early()には今のcpuの番号を渡しているのでこのcpuがjailhouseで仮想環境を管理する上でのメインcpuという風にしているっぽいです。init_early()のほうで"master_cpu_id == cpu_data->cpu_id"という感じに設定されます。

cpu_init()もhypervisor/setup.cにあり、アーキテクチャ固有の処理が入ります。cpu_init() -> arch_cpu_init()という流れ。

        if (!error) {
                cpu_init(cpu_data);

                if (master && !error)
                        init_late();
        }

ここは全部のcpuで処理が終わるのを待つのみ。

        spin_unlock(&init_lock);

        while (!error && initialized_cpus < hypervisor_header.online_cpus)
                cpu_relax();

エラーが有ったらcpuの設定は元に戻しましょう。

        if (error) {
                arch_cpu_restore(cpu_data);
                return error;
        }

v(‾Д‾)v イエイ

        if (master)
                printk("Activating hypervisor\n");

VMMを有効にして終了。

        /* point of no return */
        arch_cpu_activate_vmm(cpu_data);
}

(´-`).。oO(次はinit_early()を読んでいこう