前回の「φ(・・*)ゞ ウーン 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()を読んでいこう