hyperv: only add SynIC in compatible configurations
authorRoman Kagan <rkagan@virtuozzo.com>
Fri, 21 Sep 2018 08:22:10 +0000 (11:22 +0300)
committerPaolo Bonzini <pbonzini@redhat.com>
Fri, 19 Oct 2018 11:44:14 +0000 (13:44 +0200)
Certain configurations do not allow SynIC to be used in QEMU.  In
particular,

- when hyperv_vpindex is off, SINT routes can't be used as they refer to
  the destination vCPU by vp_index

- older KVM (which doesn't expose KVM_CAP_HYPERV_SYNIC2) zeroes out
  SynIC message and event pages on every msr load, breaking migration

OTOH in-KVM users of SynIC -- SynIC timers -- do work in those
configurations, and we shouldn't stop the guest from using them.

To cover both scenarios, introduce an X86CPU property that makes CPU
init code to skip creation of the SynIC object (and thus disables any
SynIC use in QEMU) but keeps the KVM part of the SynIC working.
The property is clear by default but is set via compat logic for older
machine types.

As a result, when hv_synic and a modern machine type are specified, QEMU
will refuse to run unless vp_index is on and the kernel is recent
enough.  OTOH with an older machine type QEMU will run fine with
hv_synic=on against an older kernel and/or without vp_index enabled but
will disallow the in-QEMU uses of SynIC (in e.g. VMBus).

Signed-off-by: Roman Kagan <rkagan@virtuozzo.com>
Message-Id: <20180921082217.29481-4-rkagan@virtuozzo.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
include/hw/i386/pc.h
target/i386/cpu.c
target/i386/cpu.h
target/i386/kvm.c

index 6894f37df1d29e7b947fc5a4c0dd391512d76a8b..dfe67466928bd11e8e4d52eda32b402b4e56fb42 100644 (file)
@@ -294,6 +294,14 @@ int e820_add_entry(uint64_t, uint64_t, uint32_t);
 int e820_get_num_entries(void);
 bool e820_get_entry(int, uint32_t, uint64_t *, uint64_t *);
 
+#define PC_COMPAT_3_0 \
+    HW_COMPAT_3_0 \
+    {\
+        .driver   = TYPE_X86_CPU,\
+        .property = "x-hv-synic-kvm-only",\
+        .value    = "on",\
+    }
+
 #define PC_COMPAT_2_12 \
     HW_COMPAT_2_12 \
     {\
index 32ea041c063c0d7b71df505df578e561a64e3d0c..f0d9f7cf49db599c49d78bcb9c0d1dde248589b6 100644 (file)
@@ -5607,6 +5607,8 @@ static Property x86_cpu_properties[] = {
      * to the specific Windows version being used."
      */
     DEFINE_PROP_INT32("x-hv-max-vps", X86CPU, hv_max_vps, -1),
+    DEFINE_PROP_BOOL("x-hv-synic-kvm-only", X86CPU, hyperv_synic_kvm_only,
+                     false),
     DEFINE_PROP_END_OF_LIST()
 };
 
index caa1544b2e64da441781a785a1b5788ef26cfd44..663f3a5e67319f4edcd7dc2b1ea8624e68b4be98 100644 (file)
@@ -1378,6 +1378,7 @@ struct X86CPU {
     bool hyperv_vpindex;
     bool hyperv_runtime;
     bool hyperv_synic;
+    bool hyperv_synic_kvm_only;
     bool hyperv_stimer;
     bool hyperv_frequencies;
     bool hyperv_reenlightenment;
index cf6270ae396e481145819247848ab2803f40b708..7b7a56593e88f48d0f5fac644f06b2fc8d4672a9 100644 (file)
@@ -734,8 +734,18 @@ static int hyperv_handle_properties(CPUState *cs)
         env->features[FEAT_HYPERV_EAX] |= HV_VP_RUNTIME_AVAILABLE;
     }
     if (cpu->hyperv_synic) {
-        if (!has_msr_hv_synic ||
-            !kvm_check_extension(cs->kvm_state, KVM_CAP_HYPERV_SYNIC)) {
+        unsigned int cap = KVM_CAP_HYPERV_SYNIC;
+        if (!cpu->hyperv_synic_kvm_only) {
+            if (!cpu->hyperv_vpindex) {
+                fprintf(stderr, "Hyper-V SynIC "
+                        "(requested by 'hv-synic' cpu flag) "
+                        "requires Hyper-V VP_INDEX ('hv-vpindex')\n");
+            return -ENOSYS;
+            }
+            cap = KVM_CAP_HYPERV_SYNIC2;
+        }
+
+        if (!has_msr_hv_synic || !kvm_check_extension(cs->kvm_state, cap)) {
             fprintf(stderr, "Hyper-V SynIC (requested by 'hv-synic' cpu flag) "
                     "is not supported by kernel\n");
             return -ENOSYS;
@@ -784,18 +794,22 @@ static int hyperv_init_vcpu(X86CPU *cpu)
     }
 
     if (cpu->hyperv_synic) {
-        ret = kvm_vcpu_enable_cap(cs, KVM_CAP_HYPERV_SYNIC, 0);
+        uint32_t synic_cap = cpu->hyperv_synic_kvm_only ?
+            KVM_CAP_HYPERV_SYNIC : KVM_CAP_HYPERV_SYNIC2;
+        ret = kvm_vcpu_enable_cap(cs, synic_cap, 0);
         if (ret < 0) {
             error_report("failed to turn on HyperV SynIC in KVM: %s",
                          strerror(-ret));
             return ret;
         }
 
-        ret = hyperv_x86_synic_add(cpu);
-        if (ret < 0) {
-            error_report("failed to create HyperV SynIC: %s",
-                         strerror(-ret));
-            return ret;
+        if (!cpu->hyperv_synic_kvm_only) {
+            ret = hyperv_x86_synic_add(cpu);
+            if (ret < 0) {
+                error_report("failed to create HyperV SynIC: %s",
+                             strerror(-ret));
+                return ret;
+            }
         }
     }