QEMU/KVM上のゲストのハイパーコールをQEMU側に渡す方法
前回説明したように,KVMでは,ゲストのハイパーコール(Intel CPUの場合はVMCALL命令,AMDの場合はVMMCALL命令)はKVM側で処理され,ioctl側に戻ることなくゲストに戻ります.
QEMU/KVMにおいて,独自にハイパーコールを追加してQEMU側で処理するには修正が必要です.
対象環境
方法
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index fbda5a917c5b..fba797631a3f 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -78,6 +78,7 @@ #define KVM_REQ_HV_STIMER KVM_ARCH_REQ(22) #define KVM_REQ_LOAD_EOI_EXITMAP KVM_ARCH_REQ(23) #define KVM_REQ_GET_VMCS12_PAGES KVM_ARCH_REQ(24) +#define KVM_REQ_HYPERCALL KVM_ARCH_REQ(25) #define CR0_RESERVED_BITS \ (~(unsigned long)(X86_CR0_PE | X86_CR0_MP | X86_CR0_EM | X86_CR0_TS \ diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index f049ecfac7bb..2accaba0e75a 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -7002,6 +7002,10 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) ret = kvm_pv_send_ipi(vcpu->kvm, a0, a1, a2, a3, op_64_bit); break; #endif + case KVM_HC_MY_HYPERCALL: + kvm_make_request(KVM_REQ_HYPERCALL, vcpu); + ret = 0; + break; default: ret = -KVM_ENOSYS; break; @@ -7624,6 +7628,11 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) r = 0; goto out; } + if (kvm_check_request(KVM_REQ_HYPERCALL, vcpu)) { + vcpu->run->exit_reason = KVM_EXIT_MY_HYPERCALL; + r = 0; + goto out; + } /* * KVM_REQ_HV_STIMER has to be processed after diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 2b7a652c9fa4..1c2707d5b70c 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -235,6 +235,7 @@ struct kvm_hyperv_exit { #define KVM_EXIT_S390_STSI 25 #define KVM_EXIT_IOAPIC_EOI 26 #define KVM_EXIT_HYPERV 27 +#define KVM_EXIT_MY_HYPERCALL 28 /* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h index 6c0ce49931e5..a6a0417ee029 100644 --- a/include/uapi/linux/kvm_para.h +++ b/include/uapi/linux/kvm_para.h @@ -28,6 +28,7 @@ #define KVM_HC_MIPS_CONSOLE_OUTPUT 8 #define KVM_HC_CLOCK_PAIRING 9 #define KVM_HC_SEND_IPI 10 +#define KVM_HC_MY_HYPERCALL 11 /* * hypercalls use architecture specific
diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index f11a7eb49c..0b44980eda 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -235,6 +235,7 @@ struct kvm_hyperv_exit { #define KVM_EXIT_S390_STSI 25 #define KVM_EXIT_IOAPIC_EOI 26 #define KVM_EXIT_HYPERV 27 +#define KVM_EXIT_MY_HYPERCALL 28 /* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ diff --git a/target/i386/kvm.c b/target/i386/kvm.c index b2401d13ea..685c3ac58e 100644 --- a/target/i386/kvm.c +++ b/target/i386/kvm.c @@ -3622,6 +3622,21 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) ioapic_eoi_broadcast(run->eoi.vector); ret = 0; break; + case KVM_EXIT_MY_HYPERCALL: + ret = kvm_getput_regs(cpu, 0); + if (ret < 0) { + return ret; + } + fprintf(stderr, "KVM: KVM_EXIT_MY_HYPERCALL %d, %ld, %ld, %ld, %ld, %ld\n", + run->exit_reason, + cpu->env.regs[R_EAX], + cpu->env.regs[R_EBX], + cpu->env.regs[R_ECX], + cpu->env.regs[R_EDX], + cpu->env.regs[R_ESI]); + cpu->env.regs[R_EAX] = 0; + ret = kvm_getput_regs(cpu, 1); + break; default: fprintf(stderr, "KVM: unknown exit reason %d\n", run->exit_reason); ret = -1;
解説
QEMU側からkvm_vcpu_ioctl(cpu, KVM_RUN, 0)
すると,いろいろあってKVMのvcpu_enter_guest()
にきます.
この中のkvm_x86_ops->run(vcpu)
でVMENTRY, kvm_x86_ops->handle_exit(vcpu)
でVMEIXT後の処理がおこなわれます.
ちなみにVMENTRYやVMEXITが関数ポインタになっているのはIntelとAMD双方に対応するためです.
VMCALL命令でVMEXITした場合は,handle_vmcall()
からkvm_emulate_hypercall()
が呼ばれ,ここでハイパーコールが処理されます.
vcpu_enter_guest()
のコメントに書いてある通り,kvm_emulate_hypercall()
の戻り値で1以外の値を返せばioctl側に制御が戻ります.そうしてもいいのですが,するとQEMU側で命令エミュレーション(RIPを進める処理)をおこなう必要があります.
今回はその代わりにKVMのVCPU request
という機能を使っています.
これはもともとはカーネルのスレッドがvCPU側に何かリクエストをするために使用する機能のようです.
ハイパーコール処理の中で,自分が定義したハイパーコール番号だった場合,kvm_make_request()
でリクエストの登録し,あとはこれまで通り命令のエミュレーション自体はKVMにまかせます.このままだとまたKVMはゲストにVMENTRYしますが,vcpu_enter_guest()
の先頭の方でkvm_check_request()
し,もし対応するリクエストが存在したらexit_reasonをその旨に書き換えてユーザランド側に戻るようにしています.
QEMUはioctlから戻ってきたら,kvm_arch_handle_exit()
(accel/kvm/kvm-all.c => target/i386/kvm.c) でVMEXIT後の処理をするので,ここにハイパーコール用の処理を追加しています.
QEMUからはkvm_getput_regs()
を使ってゲストのレジスタを取得することが可能です.