qemuDomainMakeCPUMigratable() strips the features that a CPU model marks
as added (in src/cpu_map/x86_*.xml) from the migratable definition unless
the user requested them explicitly. This keeps a custom CPU migratable to
an older destination libvirt whose copy of the model does not know those
features yet.

A host-model CPU gains nothing from this and is actively harmed by it. By
the time it reaches this function host-model has already been expanded
into an explicit custom model that is exactly what we ask QEMU for on the
source, and host-model is not guaranteed to migrate to an older libvirt
in the first place. Stripping the added features only narrows the guest
CPU silently on the destination.

Every Intel model from Westmere through Sapphire Rapids marks
vmx-exit-load-perf-global-ctrl and vmx-entry-load-perf-global-ctrl as
added. These control the LOAD_IA32_PERF_GLOBAL_CTRL allowed-1 bits of the
MSR_IA32_VMX_{EXIT,ENTRY}_CTLS MSRs, which modern QEMU only advertises
when the features are present on the -cpu command line. After a host-model
live migration the destination QEMU is started without them, so a nested
guest observes different VMX capability MSRs than it did before the
migration. A guest that snapshots those MSRs at kvm_intel module load and
validates every newly onlined CPU against the snapshot -- Linux does
exactly that -- then refuses to bring up a vCPU hot-plugged after the
migration:

  kvm_intel: Inconsistent VMCS config on CPU N
  kvm: enabling virtualization on CPUN failed
  smpboot: CPU N is now offline

and the guest agent's online attempt returns -EIO.

Treat a host-model CPU's features as explicitly requested by building the
keep list from the already expanded definition rather than from origCPU.
Custom CPUs are unaffected, so their migration compatibility with older
destinations is preserved.

Signed-off-by: Denis V. Lunev <[email protected]>
---

v1 -> v2: instead of dropping the added-feature stripping altogether (v1),
    keep it and only stop applying it to a host-model CPU. Custom
    CPUs still rely on the stripping to stay migratable to an older
    destination libvirt, which v1 regressed.

 src/qemu/qemu_domain.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index a43a5c0e4f..e1b805d906 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -5373,12 +5373,18 @@ qemuDomainMakeCPUMigratable(virArch arch,
         if (virCPUx86GetAddedFeatures(cpu->model, &data.added) < 0)
             return -1;
 
-        /* Drop features marked as added in a cpu model, but only
-         * when they are not mentioned in origCPU, i.e., when they were not
-         * explicitly mentioned by the user.
-         */
+        /* Drop features marked as added in a CPU model unless they were
+         * explicitly requested, so the migratable definition stays
+         * compatible with destinations that do not know them. For host-model
+         * the expanded definition is itself the requested CPU, so keep all of
+         * its features; for a custom CPU keep only those listed in origCPU. */
         if (data.added) {
-            g_auto(GStrv) keep = virCPUDefListExplicitFeatures(origCPU);
+            g_auto(GStrv) keep = NULL;
+
+            if (origCPU->mode == VIR_CPU_MODE_HOST_MODEL)
+                keep = virCPUDefListExplicitFeatures(cpu);
+            else
+                keep = virCPUDefListExplicitFeatures(origCPU);
             data.keep = keep;
 
             virCPUDefFilterFeatures(cpu, qemuDomainDropAddedCPUFeatures, 
&data);
-- 
2.53.0

Reply via email to