[PATCH] qemu: let incoming AMD guests drop spec-ctrl instead of failing

Denis V. Lunev via Devel posted 1 patch 2 weeks, 2 days ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/libvirt tags/patchew/20261026190654.791689-1-den@openvz.org
src/cpu/cpu_x86.c                             | 55 ++++++++++++
src/cpu/cpu_x86.h                             |  4 +
src/libvirt_private.syms                      |  1 +
src/qemu/qemu_process.c                       | 15 +++-
tests/cputest.c                               | 87 +++++++++++++++++++
...PYC-7713-64-Core-no-spec-ctrl-disabled.xml |  4 +
...EPYC-7713-64-Core-no-spec-ctrl-enabled.xml | 11 +++
...3-64-Core-no-spec-ctrl-no-ibpb-enabled.xml | 11 +++
8 files changed, 186 insertions(+), 2 deletions(-)
create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml
[PATCH] qemu: let incoming AMD guests drop spec-ctrl instead of failing
Posted by Denis V. Lunev via Devel 2 weeks, 2 days ago
From: Denis V. Lunev <den@openvz.org>

Since Linux kernel commit de572491a975 ("KVM: x86: AMD's IBPB is not
equivalent to Intel's IBPB") KVM offers spec-ctrl on AMD hosts only
when the CPU enumerates IBPB_RET, which Zen 1-3 do not. A host-model
guest started on such a CPU under an older kernel has spec-ctrl in its
live definition. On a destination with the newer kernel QEMU filters
the feature out and the incoming migration fails:

  guest CPU doesn't match specification: missing features: spec-ctrl

Restoring a save image or reverting a snapshot fails the same way, so
the guest cannot be moved to the new kernel without a restart.

Dropping the bit in libvirt costs the guest nothing beyond what KVM
already withholds. KVM grants PRED_CMD for spec-ctrl or ibpb and
SPEC_CTRL for spec-ctrl or any of ibrs, amd-stibp and amd-ssbd (kernel
commit 39485ed95d6b), and checks the values written to SPEC_CTRL
against the host, not the guest CPUID (kernel commit 841c2be09fe4).
Both predate de572491a975. Since kernel commit e592ec657d84 KVM also
ignores guest CPUID bits it does not support, so a host without
IBPB_RET treats the guest as lacking spec-ctrl even if the bit stayed.
QEMU drops the bit either way; libvirt only decides whether the guest
survives.

When an existing domain is started (incoming migration, restore,
snapshot revert) and the hypervisor dropped a required spec-ctrl while
providing ibpb and one of ibrs, amd-stibp and amd-ssbd, disable
spec-ctrl in the live definition and log a warning. spec-ctrl is also
removed from the features QEMU reports as filtered, because
check='full' fails on any filtered feature whatever the definition
says. Fresh starts and any other missing or extra feature still fail
with check='full'.

Signed-off-by: Denis V. Lunev <den@openvz.org>
---
RHEL-149512 reports the same failure.

Linux commit: https://git.kernel.org/torvalds/c/de572491a975
KVM thread:   https://lore.kernel.org/all/20241011214353.1625057-5-jmattson@google.com/
RHEL bug:     https://redhat.atlassian.net/browse/RHEL-149512

 src/cpu/cpu_x86.c                             | 55 ++++++++++++
 src/cpu/cpu_x86.h                             |  4 +
 src/libvirt_private.syms                      |  1 +
 src/qemu/qemu_process.c                       | 15 +++-
 tests/cputest.c                               | 87 +++++++++++++++++++
 ...PYC-7713-64-Core-no-spec-ctrl-disabled.xml |  4 +
 ...EPYC-7713-64-Core-no-spec-ctrl-enabled.xml | 11 +++
 ...3-64-Core-no-spec-ctrl-no-ibpb-enabled.xml | 11 +++
 8 files changed, 186 insertions(+), 2 deletions(-)
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml

diff --git a/src/cpu/cpu_x86.c b/src/cpu/cpu_x86.c
index 18c5756ef9..b986720224 100644
--- a/src/cpu/cpu_x86.c
+++ b/src/cpu/cpu_x86.c
@@ -3269,6 +3269,61 @@ virCPUx86UpdateLive(virCPUDef *cpu,
 }
 
 
+/*
+ * Since kernel commit de572491a975 KVM no longer offers spec-ctrl on AMD
+ * hosts without IBPB_RET, while a guest started before still requires it.
+ *
+ * Returns 1 if spec-ctrl was disabled in @cpu, 0 if not, -1 on error.
+ */
+int
+virCPUx86UpdateLiveSpecCtrl(virCPUDef *cpu,
+                            const virCPUData *dataEnabled,
+                            virCPUData *dataDisabled)
+{
+    const char *specCtrlMSR[] = { "ibrs", "amd-stibp", "amd-ssbd" };
+    virCPUx86Map *map;
+    virCPUx86Feature *specCtrl;
+    virCPUFeatureDef *feature;
+    const virCPUx86Data *enabled;
+    size_t i;
+    int rc;
+
+    if (!dataEnabled)
+        return 0;
+
+    if (!(feature = virCPUDefFindFeature(cpu, "spec-ctrl")) ||
+        feature->policy != VIR_CPU_FEATURE_REQUIRE)
+        return 0;
+
+    if (!(map = virCPUx86GetMap()))
+        return -1;
+
+    enabled = &dataEnabled->data.x86;
+
+    if ((rc = x86FeatureInData("spec-ctrl", enabled, map)) != 0)
+        return rc < 0 ? -1 : 0;
+
+    if ((rc = x86FeatureInData("ibpb", enabled, map)) <= 0)
+        return rc;
+
+    for (i = 0; i < G_N_ELEMENTS(specCtrlMSR); i++) {
+        if ((rc = x86FeatureInData(specCtrlMSR[i], enabled, map)) < 0)
+            return -1;
+        if (rc == 1)
+            break;
+    }
+
+    if (i == G_N_ELEMENTS(specCtrlMSR))
+        return 0;
+
+    virCPUDefUpdateFeature(cpu, "spec-ctrl", VIR_CPU_FEATURE_DISABLE);
+    if (dataDisabled && (specCtrl = x86FeatureFind(map, "spec-ctrl")))
+        x86DataSubtract(&dataDisabled->data.x86, &specCtrl->data);
+
+    return 1;
+}
+
+
 static int
 virCPUx86GetModels(char ***models)
 {
diff --git a/src/cpu/cpu_x86.h b/src/cpu/cpu_x86.h
index bbc2a16447..0b05e5c695 100644
--- a/src/cpu/cpu_x86.h
+++ b/src/cpu/cpu_x86.h
@@ -55,3 +55,7 @@ bool virCPUx86FeatureFilterDropMSR(const char *name,
 
 int virCPUx86GetAddedFeatures(const char *modelName,
                               const char * const **features);
+
+int virCPUx86UpdateLiveSpecCtrl(virCPUDef *cpu,
+                                const virCPUData *dataEnabled,
+                                virCPUData *dataDisabled);
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 798d2d28c8..6b2eb3eef4 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1608,6 +1608,7 @@ virCPUx86DataSetVendor;
 virCPUx86FeatureFilterDropMSR;
 virCPUx86FeatureFilterSelectMSR;
 virCPUx86GetAddedFeatures;
+virCPUx86UpdateLiveSpecCtrl;
 
 # datatypes.h
 virConnectClass;
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 3f1960f314..9eee738334 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -4799,10 +4799,12 @@ qemuProcessUpdateLiveGuestCPU(virDomainObj *vm,
 
 static int
 qemuProcessUpdateAndVerifyCPU(virDomainObj *vm,
-                              virDomainAsyncJob asyncJob)
+                              virDomainAsyncJob asyncJob,
+                              unsigned int flags)
 {
     g_autoptr(virCPUData) cpu = NULL;
     g_autoptr(virCPUData) disabled = NULL;
+    int rc;
 
     if (qemuProcessFetchGuestCPU(vm, asyncJob, &cpu, &disabled) < 0)
         return -1;
@@ -4810,6 +4812,15 @@ qemuProcessUpdateAndVerifyCPU(virDomainObj *vm,
     if (qemuProcessVerifyCPU(vm, cpu) < 0)
         return -1;
 
+    if (!(flags & VIR_QEMU_PROCESS_START_NEW) &&
+        ARCH_IS_X86(vm->def->os.arch) && vm->def->cpu) {
+        if ((rc = virCPUx86UpdateLiveSpecCtrl(vm->def->cpu, cpu, disabled)) < 0)
+            return -1;
+        if (rc > 0)
+            VIR_WARN("Hypervisor no longer provides spec-ctrl, disabling it in the live CPU of domain %s",
+                     vm->def->name);
+    }
+
     if (qemuProcessUpdateLiveGuestCPU(vm, cpu, disabled) < 0)
         return -1;
 
@@ -8639,7 +8650,7 @@ qemuProcessLaunch(virConnectPtr conn,
     }
 
     VIR_DEBUG("Verifying and updating provided guest CPU");
-    if (qemuProcessUpdateAndVerifyCPU(vm, asyncJob) < 0)
+    if (qemuProcessUpdateAndVerifyCPU(vm, asyncJob, flags) < 0)
         goto cleanup;
 
     VIR_DEBUG("Detecting IOThread PIDs");
diff --git a/tests/cputest.c b/tests/cputest.c
index e29c81cb8e..38ed45cfdb 100644
--- a/tests/cputest.c
+++ b/tests/cputest.c
@@ -905,6 +905,91 @@ cpuTestUpdateLive(const void *arg)
 }
 
 
+static virCPUData *
+cpuTestLoadCPUData(const struct data *data,
+                   const char *suffix)
+{
+    g_autofree char *file = NULL;
+    g_autofree char *xml = NULL;
+
+    file = g_strdup_printf("%s/cputestdata/%s-cpuid-%s-%s.xml",
+                           abs_srcdir, virArchToString(data->arch),
+                           data->host, suffix);
+    virTestCaseMarkUsed(data->existingTestCases, file);
+
+    if (virTestLoadFile(file, &xml) < 0)
+        return NULL;
+
+    return virCPUDataParse(xml);
+}
+
+
+static int
+cpuTestUpdateLiveSpecCtrl(const void *arg)
+{
+    const struct data *data = arg;
+    g_autofree char *cpuFile = NULL;
+    g_autoptr(virCPUDef) cpu = NULL;
+    g_autoptr(virCPUDef) fresh = NULL;
+    g_autoptr(virCPUData) srcEnabled = NULL;
+    g_autoptr(virCPUData) srcDisabled = NULL;
+    g_autoptr(virCPUData) enabled = NULL;
+    g_autoptr(virCPUData) disabled = NULL;
+    g_autoptr(virCPUData) noIbpb = NULL;
+    virCPUFeatureDef *feature;
+
+    cpuFile = g_strdup_printf("cpuid-%s-json", data->host);
+    if (!(cpu = cpuTestLoadXML(data->arch, cpuFile, data->existingTestCases)))
+        return -1;
+
+    if (!(srcEnabled = cpuTestLoadCPUData(data, "enabled")) ||
+        !(srcDisabled = cpuTestLoadCPUData(data, "disabled")) ||
+        !(enabled = cpuTestLoadCPUData(data, "no-spec-ctrl-enabled")) ||
+        !(disabled = cpuTestLoadCPUData(data, "no-spec-ctrl-disabled")) ||
+        !(noIbpb = cpuTestLoadCPUData(data, "no-spec-ctrl-no-ibpb-enabled")))
+        return -1;
+
+    /* the live CPU of a guest started where KVM still offered spec-ctrl */
+    if (virCPUUpdateLive(data->arch, cpu, srcEnabled, srcDisabled) < 0)
+        return -1;
+    cpu->check = VIR_CPU_CHECK_FULL;
+
+    fresh = virCPUDefCopy(cpu);
+
+    if (virCPUx86UpdateLiveSpecCtrl(fresh, srcEnabled, srcDisabled) != 0) {
+        VIR_TEST_VERBOSE("spec-ctrl touched although the hypervisor provides it");
+        return -1;
+    }
+
+    if (virCPUx86UpdateLiveSpecCtrl(fresh, noIbpb, disabled) != 0) {
+        VIR_TEST_VERBOSE("spec-ctrl disabled although ibpb is missing");
+        return -1;
+    }
+
+    if (virCPUUpdateLive(data->arch, fresh, enabled, disabled) == 0) {
+        VIR_TEST_VERBOSE("missing spec-ctrl passed check='full'");
+        return -1;
+    }
+    virResetLastError();
+
+    if (virCPUx86UpdateLiveSpecCtrl(cpu, enabled, disabled) != 1) {
+        VIR_TEST_VERBOSE("spec-ctrl was not disabled");
+        return -1;
+    }
+
+    if (virCPUUpdateLive(data->arch, cpu, enabled, disabled) < 0)
+        return -1;
+
+    if (!(feature = virCPUDefFindFeature(cpu, "spec-ctrl")) ||
+        feature->policy != VIR_CPU_FEATURE_DISABLE) {
+        VIR_TEST_VERBOSE("spec-ctrl is not disabled in the live CPU");
+        return -1;
+    }
+
+    return 0;
+}
+
+
 #if WITH_QEMU
 static int
 cpuTestJSONCPUID(const void *arg)
@@ -1270,6 +1355,8 @@ mymain(void)
     DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7601-32-Core-ibpb", JSON_MODELS_REQUIRED);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7502-32-Core", JSON_MODELS);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7713-64-Core", JSON_MODELS);
+    DO_TEST(VIR_ARCH_X86_64, cpuTestUpdateLiveSpecCtrl, "EPYC-7713-64-Core",
+            "EPYC-7713-64-Core", NULL, NULL, 0, NULL, 0, 0);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-9334-32-Core", JSON_MODELS);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "FX-8150", JSON_NONE);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Opteron-1352", JSON_NONE);
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
new file mode 100644
index 0000000000..d29d62bcad
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
@@ -0,0 +1,4 @@
+<!-- Features disabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x04000000'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
new file mode 100644
index 0000000000..6c7b14cd6f
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
@@ -0,0 +1,11 @@
+<!-- Features enabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0xf7fa3203' edx='0x078bfbff'/>
+  <cpuid eax_in='0x00000006' ecx_in='0x00' eax='0x00000004' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x219c07ab' ecx='0x0040060c' edx='0xa8000010'/>
+  <cpuid eax_in='0x0000000d' ecx_in='0x01' eax='0x0000000f' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x80000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x008003f7' edx='0x2e500800'/>
+  <cpuid eax_in='0x80000008' ecx_in='0x00' eax='0x00000000' ebx='0x03009205' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x8000000a' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x00000009'/>
+  <msr index='0x10a' edx='0x00000000' eax='0x00000069'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml
new file mode 100644
index 0000000000..0662433a08
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml
@@ -0,0 +1,11 @@
+<!-- Features enabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0xf7fa3203' edx='0x078bfbff'/>
+  <cpuid eax_in='0x00000006' ecx_in='0x00' eax='0x00000004' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x219c07ab' ecx='0x0040060c' edx='0xa8000010'/>
+  <cpuid eax_in='0x0000000d' ecx_in='0x01' eax='0x0000000f' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x80000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x008003f7' edx='0x2e500800'/>
+  <cpuid eax_in='0x80000008' ecx_in='0x00' eax='0x00000000' ebx='0x03008205' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x8000000a' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x00000009'/>
+  <msr index='0x10a' edx='0x00000000' eax='0x00000069'/>
+</cpudata>

base-commit: 742c1555b7d439ca8edca6297093c74e2c20a913
-- 
2.53.0
[PATCH v2 0/2] cpu_x86: let AMD guests drop features newer KVM withholds
Posted by Denis V. Lunev via Devel 6 days ago
Newer kernels stop offering some CPUID bits on AMD hosts that older
kernels gave to guests: spec-ctrl on Zen 1-3 since kernel commit
de572491a975 and bus-lock-detect on Zen 5 since 54950bfe2b69. A guest
started under the older kernel keeps the bit in its live definition
and cannot be migrated, restored or reverted onto the newer kernel:

  guest CPU doesn't match specification: missing features: spec-ctrl

These bits are safe to ignore in this comparison.

A fresh start of a domain with the default check='partial' still fails
earlier, when the guest CPU is compared with the host CPU. Covering it
would need the same exception in virCPUx86Compare(); I left it out
unless someone wants it.

Tested on an EPYC 7443P host whose kernel withholds spec-ctrl and
bus-lock-detect. Without the series a check='full' domain requiring
either feature fails to start, and a save image whose CPU requires
either fails to restore. With it both succeed with the feature disabled
in the live definition. Without ibpb, spec-ctrl still fails.

Changes since v1:
- moved into virCPUx86UpdateLive(), which also covers fresh starts
  (Jiri)
- the cpu code logs the change itself, at VIR_INFO (Jiri)
- simplified the checks (Jiri)
- cputest gets data-driven cases instead of a dedicated function (Jiri)
- table of droppable features, bus-lock-detect added in patch 2

v1: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/EE7DTIUI3USA7SVAOXOGLUDOI5IWG2TJ/

Denis V. Lunev (2):
  cpu_x86: let AMD guests drop spec-ctrl instead of failing
  cpu_x86: let AMD guests drop bus-lock-detect instead of failing

 src/cpu/cpu_x86.c                             | 114 +++++++++++++++++-
 tests/cputest.c                               |  17 +++
 ...e-i7-1365U-no-bus-lock-detect-disabled.xml |   4 +
 ...re-i7-1365U-no-bus-lock-detect-enabled.xml |  21 ++++
 ...Core-i7-1365U-no-bus-lock-detect-guest.xml |  61 ++++++++++
 ...PYC-7713-64-Core-no-spec-ctrl-disabled.xml |   4 +
 ...EPYC-7713-64-Core-no-spec-ctrl-enabled.xml |  11 ++
 ...d-EPYC-7713-64-Core-no-spec-ctrl-guest.xml |  56 +++++++++
 ...id-EPYC-7713-64-Core-no-spec-ctrl-json.xml |  56 +++++++++
 ...-64-Core-no-spec-ctrl-no-ibpb-disabled.xml |   4 +
 ...3-64-Core-no-spec-ctrl-no-ibpb-enabled.xml |  11 ++
 ...713-64-Core-no-spec-ctrl-no-ibpb-guest.xml |  56 +++++++++
 ...0X-16-Core-no-bus-lock-detect-disabled.xml |   4 +
 ...50X-16-Core-no-bus-lock-detect-enabled.xml |  14 +++
 ...9950X-16-Core-no-bus-lock-detect-guest.xml |  45 +++++++
 ...-9950X-16-Core-no-bus-lock-detect-json.xml |  45 +++++++
 16 files changed, 519 insertions(+), 4 deletions(-)
 create mode 100644 tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-guest.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-guest.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-json.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-guest.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-guest.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-json.xml


base-commit: d6c09bf592632a242e3475e832187610aba71763
-- 
2.53.0
[PATCH v2 1/2] cpu_x86: let AMD guests drop spec-ctrl instead of failing
Posted by Denis V. Lunev via Devel 6 days ago
From: Denis V. Lunev <den@openvz.org>

Since Linux kernel commit de572491a975 ("KVM: x86: AMD's IBPB is not
equivalent to Intel's IBPB") KVM offers spec-ctrl on AMD hosts only
when the CPU enumerates IBPB_RET, which Zen 1-3 do not. A host-model
guest started on such a CPU under an older kernel has spec-ctrl in its
live definition. On a destination with the newer kernel QEMU filters
the feature out and the incoming migration fails:

  guest CPU doesn't match specification: missing features: spec-ctrl

Restoring a save image or reverting a snapshot fails the same way, so
the guest cannot be moved to the new kernel without a restart.

Dropping the bit in libvirt costs the guest nothing beyond what KVM
already withholds. KVM grants PRED_CMD for spec-ctrl or ibpb and
SPEC_CTRL for spec-ctrl or any of ibrs, amd-stibp and amd-ssbd (kernel
commit 39485ed95d6b), and checks the values written to SPEC_CTRL
against the host, not the guest CPUID (kernel commit 841c2be09fe4).
Both predate de572491a975. Since kernel commit e592ec657d84 KVM also
ignores guest CPUID bits it does not support, so a host without
IBPB_RET treats the guest as lacking spec-ctrl even if the bit stayed.
QEMU drops the bit either way; libvirt only decides whether the guest
survives.

When a domain is started (fresh start, incoming migration, restore,
snapshot revert) and the hypervisor dropped a required spec-ctrl while
providing ibpb and one of ibrs, amd-stibp and amd-ssbd,
virCPUUpdateLive() disables spec-ctrl in the live definition and logs it
at VIR_INFO. spec-ctrl is also removed from the features QEMU reports as
filtered, because check='full' fails on any filtered feature whatever
the definition says. Any other missing or extra feature still fails with
check='full'.

Signed-off-by: Denis V. Lunev <den@openvz.org>
---
 src/cpu/cpu_x86.c                             | 93 ++++++++++++++++++-
 tests/cputest.c                               | 15 +++
 ...PYC-7713-64-Core-no-spec-ctrl-disabled.xml |  4 +
 ...EPYC-7713-64-Core-no-spec-ctrl-enabled.xml | 11 +++
 ...d-EPYC-7713-64-Core-no-spec-ctrl-guest.xml | 56 +++++++++++
 ...id-EPYC-7713-64-Core-no-spec-ctrl-json.xml | 56 +++++++++++
 ...-64-Core-no-spec-ctrl-no-ibpb-disabled.xml |  4 +
 ...3-64-Core-no-spec-ctrl-no-ibpb-enabled.xml | 11 +++
 ...713-64-Core-no-spec-ctrl-no-ibpb-guest.xml | 56 +++++++++++
 9 files changed, 302 insertions(+), 4 deletions(-)
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-guest.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-json.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-guest.xml

diff --git a/src/cpu/cpu_x86.c b/src/cpu/cpu_x86.c
index 18c5756ef9..3e89d5ef0a 100644
--- a/src/cpu/cpu_x86.c
+++ b/src/cpu/cpu_x86.c
@@ -3144,6 +3144,88 @@ virCPUx86Update(virCPUDef *guest,
 }
 
 
+/*
+ * Since kernel commit de572491a975 KVM no longer offers spec-ctrl on AMD
+ * hosts without IBPB_RET. The MSRs it stands for are still available to
+ * the guest through ibpb and any of ibrs, amd-stibp and amd-ssbd.
+ */
+static int
+x86SpecCtrlDroppable(const virCPUDef *cpu G_GNUC_UNUSED,
+                     virCPUx86Map *map,
+                     const virCPUx86Data *enabled)
+{
+    const char *specCtrlMSR[] = { "ibrs", "amd-stibp", "amd-ssbd" };
+    size_t i;
+    int rc;
+
+    if ((rc = x86FeatureInData("ibpb", enabled, map)) <= 0)
+        return rc;
+
+    for (i = 0; i < G_N_ELEMENTS(specCtrlMSR); i++) {
+        if ((rc = x86FeatureInData(specCtrlMSR[i], enabled, map)) != 0)
+            return rc;
+    }
+
+    return 0;
+}
+
+
+static const struct {
+    const char *name;
+    int (*droppable)(const virCPUDef *cpu,
+                     virCPUx86Map *map,
+                     const virCPUx86Data *enabled);
+} x86DroppableFeatures[] = {
+    { "spec-ctrl", x86SpecCtrlDroppable },
+};
+
+
+/*
+ * Newer KVM may stop offering a feature a guest started elsewhere already
+ * requires, although the guest loses nothing without it. Disable such
+ * features rather than failing the check.
+ */
+static int
+x86UpdateLiveDroppable(virCPUDef *cpu,
+                       virCPUx86Map *map,
+                       const virCPUx86Data *enabled,
+                       virCPUx86Data *disabled)
+{
+    size_t i;
+
+    for (i = 0; i < G_N_ELEMENTS(x86DroppableFeatures); i++) {
+        const char *name = x86DroppableFeatures[i].name;
+        virCPUFeatureDef *feature;
+        virCPUx86Feature *x86Feature;
+        int rc;
+
+        if (!(feature = virCPUDefFindFeature(cpu, name)) ||
+            feature->policy != VIR_CPU_FEATURE_REQUIRE)
+            continue;
+
+        if ((rc = x86FeatureInData(name, enabled, map)) < 0)
+            return -1;
+
+        if (rc == 1)
+            continue;
+
+        if ((rc = x86DroppableFeatures[i].droppable(cpu, map, enabled)) < 0)
+            return -1;
+
+        if (rc == 0)
+            continue;
+
+        VIR_INFO("Hypervisor does not provide '%s', disabling it in the guest CPU",
+                 name);
+        virCPUDefUpdateFeature(cpu, name, VIR_CPU_FEATURE_DISABLE);
+        if ((x86Feature = x86FeatureFind(map, name)))
+            x86DataSubtract(disabled, &x86Feature->data);
+    }
+
+    return 0;
+}
+
+
 static int
 virCPUx86UpdateLive(virCPUDef *cpu,
                     virCPUData *dataEnabled,
@@ -3165,16 +3247,19 @@ virCPUx86UpdateLive(virCPUDef *cpu,
     if (!(map = virCPUx86GetMap()))
         return -1;
 
-    if (!(model = x86ModelFromCPU(cpu, map, -1)) ||
-        !(modelDisabled = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_DISABLE)))
-        return -1;
-
     if (dataEnabled)
         x86DataCopy(&enabled, &dataEnabled->data.x86);
 
     if (dataDisabled)
         x86DataCopy(&disabled, &dataDisabled->data.x86);
 
+    if (x86UpdateLiveDroppable(cpu, map, &enabled, &disabled) < 0)
+        return -1;
+
+    if (!(model = x86ModelFromCPU(cpu, map, -1)) ||
+        !(modelDisabled = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_DISABLE)))
+        return -1;
+
     for (i = 0; i < map->nfeatures; i++) {
         virCPUx86Feature *feature = map->features[i];
         virCPUFeaturePolicy expected = VIR_CPU_FEATURE_LAST;
diff --git a/tests/cputest.c b/tests/cputest.c
index e29c81cb8e..35b426155e 100644
--- a/tests/cputest.c
+++ b/tests/cputest.c
@@ -857,6 +857,15 @@ cpuTestUpdateLive(const void *arg)
         !(disabledData = virCPUDataParse(disabled)))
         return -1;
 
+    if (data->result == -1) {
+        if (virCPUUpdateLive(data->arch, cpu, enabledData, disabledData) == 0) {
+            VIR_TEST_VERBOSE("virCPUUpdateLive succeeded unexpectedly");
+            return -1;
+        }
+        virResetLastError();
+        return 0;
+    }
+
     expectedFile = g_strdup_printf("cpuid-%s-json", data->host);
     if (!(expected = cpuTestLoadXML(data->arch, expectedFile,
                                     data->existingTestCases))) {
@@ -1110,6 +1119,10 @@ mymain(void)
         } \
     } while (0)
 
+#define DO_TEST_UPDATE_LIVE(arch, host, result) \
+    DO_TEST(arch, cpuTestUpdateLive, host, host, \
+            NULL, NULL, 0, NULL, 0, result)
+
 #define DO_TEST_CPUID_BASELINE(arch, label, cpu1, cpu2) \
     do { \
         const char *cpus[] = {cpu1, cpu2}; \
@@ -1270,6 +1283,8 @@ mymain(void)
     DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7601-32-Core-ibpb", JSON_MODELS_REQUIRED);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7502-32-Core", JSON_MODELS);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-7713-64-Core", JSON_MODELS);
+    DO_TEST_UPDATE_LIVE(VIR_ARCH_X86_64, "EPYC-7713-64-Core-no-spec-ctrl", 0);
+    DO_TEST_UPDATE_LIVE(VIR_ARCH_X86_64, "EPYC-7713-64-Core-no-spec-ctrl-no-ibpb", -1);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "EPYC-9334-32-Core", JSON_MODELS);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "FX-8150", JSON_NONE);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Opteron-1352", JSON_NONE);
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
new file mode 100644
index 0000000000..d29d62bcad
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
@@ -0,0 +1,4 @@
+<!-- Features disabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x04000000'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
new file mode 100644
index 0000000000..6c7b14cd6f
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
@@ -0,0 +1,11 @@
+<!-- Features enabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0xf7fa3203' edx='0x078bfbff'/>
+  <cpuid eax_in='0x00000006' ecx_in='0x00' eax='0x00000004' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x219c07ab' ecx='0x0040060c' edx='0xa8000010'/>
+  <cpuid eax_in='0x0000000d' ecx_in='0x01' eax='0x0000000f' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x80000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x008003f7' edx='0x2e500800'/>
+  <cpuid eax_in='0x80000008' ecx_in='0x00' eax='0x00000000' ebx='0x03009205' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x8000000a' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x00000009'/>
+  <msr index='0x10a' edx='0x00000000' eax='0x00000069'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-guest.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-guest.xml
new file mode 100644
index 0000000000..546896260f
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-guest.xml
@@ -0,0 +1,56 @@
+<cpu mode='custom' match='exact' check='full'>
+  <model fallback='forbid'>EPYC-Milan</model>
+  <vendor>AMD</vendor>
+  <feature policy='require' name='x2apic'/>
+  <feature policy='require' name='tsc-deadline'/>
+  <feature policy='require' name='hypervisor'/>
+  <feature policy='require' name='tsc_adjust'/>
+  <feature policy='require' name='vaes'/>
+  <feature policy='require' name='vpclmulqdq'/>
+  <feature policy='require' name='spec-ctrl'/>
+  <feature policy='require' name='stibp'/>
+  <feature policy='require' name='arch-capabilities'/>
+  <feature policy='require' name='ssbd'/>
+  <feature policy='require' name='cmp_legacy'/>
+  <feature policy='require' name='virt-ssbd'/>
+  <feature policy='require' name='rdctl-no'/>
+  <feature policy='require' name='skip-l1dfl-vmentry'/>
+  <feature policy='require' name='mds-no'/>
+  <feature policy='require' name='pschange-mc-no'/>
+  <feature policy='disable' name='ibrs'/>
+  <feature policy='disable' name='svme-addr-chk'/>
+  <feature policy='disable' name='monitor'/>
+  <feature policy='disable' name='osxsave'/>
+  <feature policy='disable' name='ht'/>
+  <feature policy='disable' name='cmt'/>
+  <feature policy='disable' name='ospke'/>
+  <feature policy='disable' name='cet-ss'/>
+  <feature policy='disable' name='mbm_total'/>
+  <feature policy='disable' name='mbm_local'/>
+  <feature policy='disable' name='extapic'/>
+  <feature policy='disable' name='ibs'/>
+  <feature policy='disable' name='skinit'/>
+  <feature policy='disable' name='wdt'/>
+  <feature policy='disable' name='tce'/>
+  <feature policy='disable' name='topoext'/>
+  <feature policy='disable' name='perfctr_nb'/>
+  <feature policy='disable' name='overflow-recov'/>
+  <feature policy='disable' name='succor'/>
+  <feature policy='disable' name='invtsc'/>
+  <feature policy='disable' name='stibp-always-on'/>
+  <feature policy='disable' name='amd-psfd'/>
+  <feature policy='disable' name='lbrv'/>
+  <feature policy='disable' name='svm-lock'/>
+  <feature policy='disable' name='tsc-scale'/>
+  <feature policy='disable' name='vmcb-clean'/>
+  <feature policy='disable' name='flushbyasid'/>
+  <feature policy='disable' name='decodeassists'/>
+  <feature policy='disable' name='pause-filter'/>
+  <feature policy='disable' name='pfthreshold'/>
+  <feature policy='disable' name='v-vmsave-vmload'/>
+  <feature policy='disable' name='vgif'/>
+  <feature policy='disable' name='gmet'/>
+  <feature policy='disable' name='no-nested-data-bp'/>
+  <feature policy='disable' name='lfence-always-serializing'/>
+  <feature policy='disable' name='null-sel-clr-base'/>
+</cpu>
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-json.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-json.xml
new file mode 100644
index 0000000000..d68763d61b
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-json.xml
@@ -0,0 +1,56 @@
+<cpu mode='custom' match='exact'>
+  <model fallback='forbid'>EPYC-Milan</model>
+  <vendor>AMD</vendor>
+  <feature policy='require' name='x2apic'/>
+  <feature policy='require' name='tsc-deadline'/>
+  <feature policy='require' name='hypervisor'/>
+  <feature policy='require' name='tsc_adjust'/>
+  <feature policy='require' name='vaes'/>
+  <feature policy='require' name='vpclmulqdq'/>
+  <feature policy='disable' name='spec-ctrl'/>
+  <feature policy='require' name='stibp'/>
+  <feature policy='require' name='arch-capabilities'/>
+  <feature policy='require' name='ssbd'/>
+  <feature policy='require' name='cmp_legacy'/>
+  <feature policy='require' name='virt-ssbd'/>
+  <feature policy='require' name='rdctl-no'/>
+  <feature policy='require' name='skip-l1dfl-vmentry'/>
+  <feature policy='require' name='mds-no'/>
+  <feature policy='require' name='pschange-mc-no'/>
+  <feature policy='disable' name='ibrs'/>
+  <feature policy='disable' name='svme-addr-chk'/>
+  <feature policy='disable' name='monitor'/>
+  <feature policy='disable' name='osxsave'/>
+  <feature policy='disable' name='ht'/>
+  <feature policy='disable' name='cmt'/>
+  <feature policy='disable' name='ospke'/>
+  <feature policy='disable' name='cet-ss'/>
+  <feature policy='disable' name='mbm_total'/>
+  <feature policy='disable' name='mbm_local'/>
+  <feature policy='disable' name='extapic'/>
+  <feature policy='disable' name='ibs'/>
+  <feature policy='disable' name='skinit'/>
+  <feature policy='disable' name='wdt'/>
+  <feature policy='disable' name='tce'/>
+  <feature policy='disable' name='topoext'/>
+  <feature policy='disable' name='perfctr_nb'/>
+  <feature policy='disable' name='overflow-recov'/>
+  <feature policy='disable' name='succor'/>
+  <feature policy='disable' name='invtsc'/>
+  <feature policy='disable' name='stibp-always-on'/>
+  <feature policy='disable' name='amd-psfd'/>
+  <feature policy='disable' name='lbrv'/>
+  <feature policy='disable' name='svm-lock'/>
+  <feature policy='disable' name='tsc-scale'/>
+  <feature policy='disable' name='vmcb-clean'/>
+  <feature policy='disable' name='flushbyasid'/>
+  <feature policy='disable' name='decodeassists'/>
+  <feature policy='disable' name='pause-filter'/>
+  <feature policy='disable' name='pfthreshold'/>
+  <feature policy='disable' name='v-vmsave-vmload'/>
+  <feature policy='disable' name='vgif'/>
+  <feature policy='disable' name='gmet'/>
+  <feature policy='disable' name='no-nested-data-bp'/>
+  <feature policy='disable' name='lfence-always-serializing'/>
+  <feature policy='disable' name='null-sel-clr-base'/>
+</cpu>
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-disabled.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-disabled.xml
new file mode 100644
index 0000000000..d29d62bcad
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-disabled.xml
@@ -0,0 +1,4 @@
+<!-- Features disabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x04000000'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml
new file mode 100644
index 0000000000..0662433a08
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml
@@ -0,0 +1,11 @@
+<!-- Features enabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0xf7fa3203' edx='0x078bfbff'/>
+  <cpuid eax_in='0x00000006' ecx_in='0x00' eax='0x00000004' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x219c07ab' ecx='0x0040060c' edx='0xa8000010'/>
+  <cpuid eax_in='0x0000000d' ecx_in='0x01' eax='0x0000000f' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x80000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x008003f7' edx='0x2e500800'/>
+  <cpuid eax_in='0x80000008' ecx_in='0x00' eax='0x00000000' ebx='0x03008205' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x8000000a' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x00000009'/>
+  <msr index='0x10a' edx='0x00000000' eax='0x00000069'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-guest.xml b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-guest.xml
new file mode 100644
index 0000000000..546896260f
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-guest.xml
@@ -0,0 +1,56 @@
+<cpu mode='custom' match='exact' check='full'>
+  <model fallback='forbid'>EPYC-Milan</model>
+  <vendor>AMD</vendor>
+  <feature policy='require' name='x2apic'/>
+  <feature policy='require' name='tsc-deadline'/>
+  <feature policy='require' name='hypervisor'/>
+  <feature policy='require' name='tsc_adjust'/>
+  <feature policy='require' name='vaes'/>
+  <feature policy='require' name='vpclmulqdq'/>
+  <feature policy='require' name='spec-ctrl'/>
+  <feature policy='require' name='stibp'/>
+  <feature policy='require' name='arch-capabilities'/>
+  <feature policy='require' name='ssbd'/>
+  <feature policy='require' name='cmp_legacy'/>
+  <feature policy='require' name='virt-ssbd'/>
+  <feature policy='require' name='rdctl-no'/>
+  <feature policy='require' name='skip-l1dfl-vmentry'/>
+  <feature policy='require' name='mds-no'/>
+  <feature policy='require' name='pschange-mc-no'/>
+  <feature policy='disable' name='ibrs'/>
+  <feature policy='disable' name='svme-addr-chk'/>
+  <feature policy='disable' name='monitor'/>
+  <feature policy='disable' name='osxsave'/>
+  <feature policy='disable' name='ht'/>
+  <feature policy='disable' name='cmt'/>
+  <feature policy='disable' name='ospke'/>
+  <feature policy='disable' name='cet-ss'/>
+  <feature policy='disable' name='mbm_total'/>
+  <feature policy='disable' name='mbm_local'/>
+  <feature policy='disable' name='extapic'/>
+  <feature policy='disable' name='ibs'/>
+  <feature policy='disable' name='skinit'/>
+  <feature policy='disable' name='wdt'/>
+  <feature policy='disable' name='tce'/>
+  <feature policy='disable' name='topoext'/>
+  <feature policy='disable' name='perfctr_nb'/>
+  <feature policy='disable' name='overflow-recov'/>
+  <feature policy='disable' name='succor'/>
+  <feature policy='disable' name='invtsc'/>
+  <feature policy='disable' name='stibp-always-on'/>
+  <feature policy='disable' name='amd-psfd'/>
+  <feature policy='disable' name='lbrv'/>
+  <feature policy='disable' name='svm-lock'/>
+  <feature policy='disable' name='tsc-scale'/>
+  <feature policy='disable' name='vmcb-clean'/>
+  <feature policy='disable' name='flushbyasid'/>
+  <feature policy='disable' name='decodeassists'/>
+  <feature policy='disable' name='pause-filter'/>
+  <feature policy='disable' name='pfthreshold'/>
+  <feature policy='disable' name='v-vmsave-vmload'/>
+  <feature policy='disable' name='vgif'/>
+  <feature policy='disable' name='gmet'/>
+  <feature policy='disable' name='no-nested-data-bp'/>
+  <feature policy='disable' name='lfence-always-serializing'/>
+  <feature policy='disable' name='null-sel-clr-base'/>
+</cpu>
-- 
2.53.0
[PATCH v2 2/2] cpu_x86: let AMD guests drop bus-lock-detect instead of failing
Posted by Denis V. Lunev via Devel 6 days ago
From: Denis V. Lunev <den@openvz.org>

Kernel commit 76ea438b4afc ("KVM: X86: Expose bus lock debug exception
to guest") made KVM offer bus-lock-detect on every host that enumerates
it, AMD Zen 5 included. Kernel commit 54950bfe2b69 ("KVM: SVM: Don't
advertise Bus Lock Detect to guest if SVM support is missing"), marked
for stable, stopped offering it on AMD. A host-model guest started on
Zen 5 under an older kernel has bus-lock-detect in its live definition.
On a destination with the newer kernel QEMU filters the feature out and
the incoming migration fails:

  guest CPU doesn't match specification: missing features: bus-lock-detect

Restoring a save image or reverting a snapshot fails the same way, so
the guest cannot be moved to the new kernel without a restart.

Dropping the bit in libvirt costs the guest nothing. Bus lock detection
is enabled through DEBUGCTL, which SVM loads for the guest only with
LBR virtualization active, and 54950bfe2b69 says the guest could not
use the feature despite the CPUID bit. QEMU does not migrate DEBUGCTL,
so the guest resumes with bus lock detection off. A Linux guest since
kernel commit 408eb7417a92 ("x86/bus_lock: Add support for AMD") sets
DEBUGCTL.BLD again only when it brings a CPU online; with LBR
virtualization enabled, the default, kernels since 5ecdb48dd918 ("KVM:
SVM: Treat DEBUGCTL[5:2] as reserved") answer that write with #GP,
which Linux reports as an "unchecked MSR access error" warning and
carries on, and older kernels ignore the bit. QEMU drops the bit either
way; libvirt only decides whether the guest survives.

Kernel commit 62ad461079e7 ("KVM: SVM: Add support for virtualizating
Bus Lock Detect") lets KVM offer the feature on AMD again with LBR
virtualization. A guest started there and moved to a host without it
loses the bus lock #DB, which Linux uses only to warn about or throttle
tasks that cause bus locks.

Add bus-lock-detect next to spec-ctrl in the table of droppable
features, for CPU models whose vendor is AMD: when the hypervisor
dropped it, virCPUUpdateLive() disables it in the live definition and
removes it from the features QEMU reports as filtered. Intel keeps
failing: VMX has virtualized bus lock detection since 76ea438b4afc, so
a missing bit there is a real loss.

Signed-off-by: Denis V. Lunev <den@openvz.org>
---
 src/cpu/cpu_x86.c                             | 21 +++++++
 tests/cputest.c                               |  2 +
 ...e-i7-1365U-no-bus-lock-detect-disabled.xml |  4 ++
 ...re-i7-1365U-no-bus-lock-detect-enabled.xml | 21 +++++++
 ...Core-i7-1365U-no-bus-lock-detect-guest.xml | 61 +++++++++++++++++++
 ...0X-16-Core-no-bus-lock-detect-disabled.xml |  4 ++
 ...50X-16-Core-no-bus-lock-detect-enabled.xml | 14 +++++
 ...9950X-16-Core-no-bus-lock-detect-guest.xml | 45 ++++++++++++++
 ...-9950X-16-Core-no-bus-lock-detect-json.xml | 45 ++++++++++++++
 9 files changed, 217 insertions(+)
 create mode 100644 tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-guest.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-disabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-enabled.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-guest.xml
 create mode 100644 tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-json.xml

diff --git a/src/cpu/cpu_x86.c b/src/cpu/cpu_x86.c
index 3e89d5ef0a..7f6d29b447 100644
--- a/src/cpu/cpu_x86.c
+++ b/src/cpu/cpu_x86.c
@@ -3170,6 +3170,26 @@ x86SpecCtrlDroppable(const virCPUDef *cpu G_GNUC_UNUSED,
 }
 
 
+/*
+ * KVM offered bus-lock-detect on AMD hosts until kernel commit
+ * 54950bfe2b69 although SVM never let the guest use it.
+ */
+static int
+x86BusLockDetectDroppable(const virCPUDef *cpu,
+                          virCPUx86Map *map,
+                          const virCPUx86Data *enabled G_GNUC_UNUSED)
+{
+    virCPUx86Model *model;
+
+    if (!cpu->model ||
+        !(model = x86ModelFind(map, cpu->model)) ||
+        !model->vendor)
+        return 0;
+
+    return STREQ(model->vendor->name, "AMD");
+}
+
+
 static const struct {
     const char *name;
     int (*droppable)(const virCPUDef *cpu,
@@ -3177,6 +3197,7 @@ static const struct {
                      const virCPUx86Data *enabled);
 } x86DroppableFeatures[] = {
     { "spec-ctrl", x86SpecCtrlDroppable },
+    { "bus-lock-detect", x86BusLockDetectDroppable },
 };
 
 
diff --git a/tests/cputest.c b/tests/cputest.c
index 35b426155e..6c15d71365 100644
--- a/tests/cputest.c
+++ b/tests/cputest.c
@@ -1262,6 +1262,7 @@ mymain(void)
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i5-6600", JSON_HOST);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-1270P", JSON_MODELS_REQUIRED);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-1365U", JSON_MODELS_REQUIRED);
+    DO_TEST_UPDATE_LIVE(VIR_ARCH_X86_64, "Core-i7-1365U-no-bus-lock-detect", -1);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-2600", JSON_HOST);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-2600-xsaveopt", JSON_MODELS_REQUIRED);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Core-i7-3520M", JSON_NONE);
@@ -1297,6 +1298,7 @@ mymain(void)
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Ryzen-7-1800X-Eight-Core", JSON_HOST);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Ryzen-9-3900X-12-Core", JSON_MODELS);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Ryzen-9-9950X-16-Core", JSON_MODELS);
+    DO_TEST_UPDATE_LIVE(VIR_ARCH_X86_64, "Ryzen-9-9950X-16-Core-no-bus-lock-detect", 0);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-5110", JSON_NONE);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-6731E", JSON_MODELS);
     DO_TEST_CPUID(VIR_ARCH_X86_64, "Xeon-6788P", JSON_MODELS_REQUIRED);
diff --git a/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-disabled.xml b/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-disabled.xml
new file mode 100644
index 0000000000..e97a5ab0c5
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-disabled.xml
@@ -0,0 +1,4 @@
+<!-- Features disabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x01000000' edx='0x00000000'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-enabled.xml b/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-enabled.xml
new file mode 100644
index 0000000000..1f6363183c
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-enabled.xml
@@ -0,0 +1,21 @@
+<!-- Features enabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0xf7fa3223' edx='0x0f8bfbff'/>
+  <cpuid eax_in='0x00000006' ecx_in='0x00' eax='0x00000004' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x219c27eb' ecx='0x1840072c' edx='0xbc004410'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x01' eax='0x00000810' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x02' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x0000001f'/>
+  <cpuid eax_in='0x0000000d' ecx_in='0x01' eax='0x0000000f' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x80000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000121' edx='0x2c100800'/>
+  <cpuid eax_in='0x80000008' ecx_in='0x00' eax='0x00000000' ebx='0x0100d000' ecx='0x00000000' edx='0x00000000'/>
+  <msr index='0x10a' edx='0x00000000' eax='0x1400e06b'/>
+  <msr index='0x480' edx='0x00c00000' eax='0x00000000'/>
+  <msr index='0x485' edx='0x00000000' eax='0x20000160'/>
+  <msr index='0x48b' edx='0x06137bff' eax='0x00000000'/>
+  <msr index='0x48c' edx='0x00000d01' eax='0x06330041'/>
+  <msr index='0x48d' edx='0x000000e9' eax='0x00000000'/>
+  <msr index='0x48e' edx='0xfbf99e8c' eax='0x00000000'/>
+  <msr index='0x48f' edx='0x007c9004' eax='0x00000000'/>
+  <msr index='0x490' edx='0x0000e204' eax='0x00000000'/>
+  <msr index='0x491' edx='0x00000000' eax='0x00000001'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-guest.xml b/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-guest.xml
new file mode 100644
index 0000000000..268d5484a4
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-Core-i7-1365U-no-bus-lock-detect-guest.xml
@@ -0,0 +1,61 @@
+<cpu mode='custom' match='exact' check='full'>
+  <model fallback='forbid'>SierraForest</model>
+  <vendor>Intel</vendor>
+  <feature policy='require' name='vmx'/>
+  <feature policy='require' name='hypervisor'/>
+  <feature policy='require' name='ss'/>
+  <feature policy='require' name='tsc_adjust'/>
+  <feature policy='require' name='fdp-excptn-only'/>
+  <feature policy='require' name='zero-fcs-fds'/>
+  <feature policy='require' name='waitpkg'/>
+  <feature policy='require' name='movdiri'/>
+  <feature policy='require' name='movdir64b'/>
+  <feature policy='require' name='md-clear'/>
+  <feature policy='require' name='stibp'/>
+  <feature policy='require' name='flush-l1d'/>
+  <feature policy='require' name='intel-psfd'/>
+  <feature policy='require' name='ipred-ctrl'/>
+  <feature policy='require' name='rrsba-ctrl'/>
+  <feature policy='require' name='ddpd-u'/>
+  <feature policy='require' name='bhi-ctrl'/>
+  <feature policy='require' name='ibpb'/>
+  <feature policy='require' name='ibrs'/>
+  <feature policy='require' name='amd-stibp'/>
+  <feature policy='require' name='amd-ssbd'/>
+  <feature policy='require' name='gds-no'/>
+  <feature policy='require' name='rfds-clear'/>
+  <feature policy='require' name='vmx-activity-wait-sipi'/>
+  <feature policy='require' name='vmx-tsc-scaling'/>
+  <feature policy='require' name='vmx-enable-user-wait-pause'/>
+  <feature policy='require' name='bus-lock-detect'/>
+  <feature policy='disable' name='cmpccxadd'/>
+  <feature policy='disable' name='avx-ifma'/>
+  <feature policy='disable' name='avx-vnni-int8'/>
+  <feature policy='disable' name='avx-ne-convert'/>
+  <feature policy='disable' name='mcdt-no'/>
+  <feature policy='disable' name='wbnoinvd'/>
+  <feature policy='disable' name='pbrsb-no'/>
+  <feature policy='disable' name='dtes64'/>
+  <feature policy='disable' name='monitor'/>
+  <feature policy='disable' name='ds_cpl'/>
+  <feature policy='disable' name='smx'/>
+  <feature policy='disable' name='est'/>
+  <feature policy='disable' name='tm2'/>
+  <feature policy='disable' name='xtpr'/>
+  <feature policy='disable' name='pdcm'/>
+  <feature policy='disable' name='osxsave'/>
+  <feature policy='disable' name='ds'/>
+  <feature policy='disable' name='acpi'/>
+  <feature policy='disable' name='ht'/>
+  <feature policy='disable' name='tm'/>
+  <feature policy='disable' name='pbe'/>
+  <feature policy='disable' name='intel-pt'/>
+  <feature policy='disable' name='ospke'/>
+  <feature policy='disable' name='cet-ss'/>
+  <feature policy='disable' name='pks'/>
+  <feature policy='disable' name='pconfig'/>
+  <feature policy='disable' name='arch-lbr'/>
+  <feature policy='disable' name='cet-ibt'/>
+  <feature policy='disable' name='core-capability'/>
+  <feature policy='disable' name='invtsc'/>
+</cpu>
diff --git a/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-disabled.xml b/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-disabled.xml
new file mode 100644
index 0000000000..e97a5ab0c5
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-disabled.xml
@@ -0,0 +1,4 @@
+<!-- Features disabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x01000000' edx='0x00000000'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-enabled.xml b/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-enabled.xml
new file mode 100644
index 0000000000..281971e99f
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-enabled.xml
@@ -0,0 +1,14 @@
+<!-- Features enabled by QEMU -->
+<cpudata arch='x86'>
+  <cpuid eax_in='0x00000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0xf7f83203' edx='0x078bfbff'/>
+  <cpuid eax_in='0x00000006' ecx_in='0x00' eax='0x00000004' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x00' eax='0x00000000' ebx='0xf1bf07ab' ecx='0x18405f4e' edx='0x9c000110'/>
+  <cpuid eax_in='0x00000007' ecx_in='0x01' eax='0x00000030' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x0000000d' ecx_in='0x01' eax='0x0000000f' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x80000001' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x008003f7' edx='0x2e500800'/>
+  <cpuid eax_in='0x80000007' ecx_in='0x00' eax='0x00000000' ebx='0x00000003' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x80000008' ecx_in='0x00' eax='0x00000000' ebx='0x1302d205' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x8000000a' ecx_in='0x00' eax='0x00000000' ebx='0x00000000' ecx='0x00000000' edx='0x1201947b'/>
+  <cpuid eax_in='0x80000021' ecx_in='0x00' eax='0x58100147' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+  <cpuid eax_in='0x80000022' ecx_in='0x00' eax='0x00000001' ebx='0x00000000' ecx='0x00000000' edx='0x00000000'/>
+</cpudata>
diff --git a/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-guest.xml b/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-guest.xml
new file mode 100644
index 0000000000..174ac2293f
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-guest.xml
@@ -0,0 +1,45 @@
+<cpu mode='custom' match='exact' check='full'>
+  <model fallback='forbid'>EPYC-Turin</model>
+  <vendor>AMD</vendor>
+  <feature policy='require' name='x2apic'/>
+  <feature policy='require' name='tsc-deadline'/>
+  <feature policy='require' name='hypervisor'/>
+  <feature policy='require' name='tsc_adjust'/>
+  <feature policy='require' name='spec-ctrl'/>
+  <feature policy='require' name='stibp'/>
+  <feature policy='require' name='flush-l1d'/>
+  <feature policy='require' name='ssbd'/>
+  <feature policy='require' name='cmp_legacy'/>
+  <feature policy='require' name='virt-ssbd'/>
+  <feature policy='disable' name='pcid'/>
+  <feature policy='disable' name='la57'/>
+  <feature policy='disable' name='monitor'/>
+  <feature policy='disable' name='osxsave'/>
+  <feature policy='disable' name='ht'/>
+  <feature policy='disable' name='cmt'/>
+  <feature policy='disable' name='ospke'/>
+  <feature policy='disable' name='cet-ss'/>
+  <feature policy='require' name='bus-lock-detect'/>
+  <feature policy='disable' name='mbm_total'/>
+  <feature policy='disable' name='mbm_local'/>
+  <feature policy='disable' name='extapic'/>
+  <feature policy='disable' name='ibs'/>
+  <feature policy='disable' name='skinit'/>
+  <feature policy='disable' name='wdt'/>
+  <feature policy='disable' name='tce'/>
+  <feature policy='disable' name='topoext'/>
+  <feature policy='disable' name='perfctr_nb'/>
+  <feature policy='disable' name='invtsc'/>
+  <feature policy='disable' name='svm-lock'/>
+  <feature policy='disable' name='decodeassists'/>
+  <feature policy='disable' name='avic'/>
+  <feature policy='disable' name='gmet'/>
+  <feature policy='disable' name='eraps'/>
+  <feature policy='disable' name='rdctl-no'/>
+  <feature policy='disable' name='skip-l1dfl-vmentry'/>
+  <feature policy='disable' name='mds-no'/>
+  <feature policy='disable' name='pschange-mc-no'/>
+  <feature policy='disable' name='gds-no'/>
+  <feature policy='disable' name='rfds-no'/>
+  <feature policy='disable' name='its-no'/>
+</cpu>
diff --git a/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-json.xml b/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-json.xml
new file mode 100644
index 0000000000..533b04903a
--- /dev/null
+++ b/tests/cputestdata/x86_64-cpuid-Ryzen-9-9950X-16-Core-no-bus-lock-detect-json.xml
@@ -0,0 +1,45 @@
+<cpu mode='custom' match='exact'>
+  <model fallback='forbid'>EPYC-Turin</model>
+  <vendor>AMD</vendor>
+  <feature policy='require' name='x2apic'/>
+  <feature policy='require' name='tsc-deadline'/>
+  <feature policy='require' name='hypervisor'/>
+  <feature policy='require' name='tsc_adjust'/>
+  <feature policy='require' name='spec-ctrl'/>
+  <feature policy='require' name='stibp'/>
+  <feature policy='require' name='flush-l1d'/>
+  <feature policy='require' name='ssbd'/>
+  <feature policy='require' name='cmp_legacy'/>
+  <feature policy='require' name='virt-ssbd'/>
+  <feature policy='disable' name='pcid'/>
+  <feature policy='disable' name='la57'/>
+  <feature policy='disable' name='monitor'/>
+  <feature policy='disable' name='osxsave'/>
+  <feature policy='disable' name='ht'/>
+  <feature policy='disable' name='cmt'/>
+  <feature policy='disable' name='ospke'/>
+  <feature policy='disable' name='cet-ss'/>
+  <feature policy='disable' name='bus-lock-detect'/>
+  <feature policy='disable' name='mbm_total'/>
+  <feature policy='disable' name='mbm_local'/>
+  <feature policy='disable' name='extapic'/>
+  <feature policy='disable' name='ibs'/>
+  <feature policy='disable' name='skinit'/>
+  <feature policy='disable' name='wdt'/>
+  <feature policy='disable' name='tce'/>
+  <feature policy='disable' name='topoext'/>
+  <feature policy='disable' name='perfctr_nb'/>
+  <feature policy='disable' name='invtsc'/>
+  <feature policy='disable' name='svm-lock'/>
+  <feature policy='disable' name='decodeassists'/>
+  <feature policy='disable' name='avic'/>
+  <feature policy='disable' name='gmet'/>
+  <feature policy='disable' name='eraps'/>
+  <feature policy='disable' name='rdctl-no'/>
+  <feature policy='disable' name='skip-l1dfl-vmentry'/>
+  <feature policy='disable' name='mds-no'/>
+  <feature policy='disable' name='pschange-mc-no'/>
+  <feature policy='disable' name='gds-no'/>
+  <feature policy='disable' name='rfds-no'/>
+  <feature policy='disable' name='its-no'/>
+</cpu>
-- 
2.53.0
Re: [PATCH] qemu: let incoming AMD guests drop spec-ctrl instead of failing
Posted by Jiri Denemark via Devel 1 week, 2 days ago
On Mon, Oct 26, 2026 at 20:06:54 +0100, Denis V. Lunev wrote:
> From: Denis V. Lunev <den@openvz.org>
> 
> Since Linux kernel commit de572491a975 ("KVM: x86: AMD's IBPB is not
> equivalent to Intel's IBPB") KVM offers spec-ctrl on AMD hosts only
> when the CPU enumerates IBPB_RET, which Zen 1-3 do not. A host-model
> guest started on such a CPU under an older kernel has spec-ctrl in its
> live definition. On a destination with the newer kernel QEMU filters
> the feature out and the incoming migration fails:
> 
>   guest CPU doesn't match specification: missing features: spec-ctrl
> 
> Restoring a save image or reverting a snapshot fails the same way, so
> the guest cannot be moved to the new kernel without a restart.
> 
> Dropping the bit in libvirt costs the guest nothing beyond what KVM
> already withholds. KVM grants PRED_CMD for spec-ctrl or ibpb and
> SPEC_CTRL for spec-ctrl or any of ibrs, amd-stibp and amd-ssbd (kernel
> commit 39485ed95d6b), and checks the values written to SPEC_CTRL
> against the host, not the guest CPUID (kernel commit 841c2be09fe4).
> Both predate de572491a975. Since kernel commit e592ec657d84 KVM also
> ignores guest CPUID bits it does not support, so a host without
> IBPB_RET treats the guest as lacking spec-ctrl even if the bit stayed.
> QEMU drops the bit either way; libvirt only decides whether the guest
> survives.
> 
> When an existing domain is started (incoming migration, restore,
> snapshot revert) and the hypervisor dropped a required spec-ctrl while
> providing ibpb and one of ibrs, amd-stibp and amd-ssbd, disable
> spec-ctrl in the live definition and log a warning. spec-ctrl is also
> removed from the features QEMU reports as filtered, because
> check='full' fails on any filtered feature whatever the definition
> says. Fresh starts and any other missing or extra feature still fail
> with check='full'.
> 
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> ---
> RHEL-149512 reports the same failure.
> 
> Linux commit: https://git.kernel.org/torvalds/c/de572491a975
> KVM thread:   https://lore.kernel.org/all/20241011214353.1625057-5-jmattson@google.com/
> RHEL bug:     https://redhat.atlassian.net/browse/RHEL-149512
> 
>  src/cpu/cpu_x86.c                             | 55 ++++++++++++
>  src/cpu/cpu_x86.h                             |  4 +
>  src/libvirt_private.syms                      |  1 +
>  src/qemu/qemu_process.c                       | 15 +++-
>  tests/cputest.c                               | 87 +++++++++++++++++++
>  ...PYC-7713-64-Core-no-spec-ctrl-disabled.xml |  4 +
>  ...EPYC-7713-64-Core-no-spec-ctrl-enabled.xml | 11 +++
>  ...3-64-Core-no-spec-ctrl-no-ibpb-enabled.xml | 11 +++
>  8 files changed, 186 insertions(+), 2 deletions(-)
>  create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-disabled.xml
>  create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-enabled.xml
>  create mode 100644 tests/cputestdata/x86_64-cpuid-EPYC-7713-64-Core-no-spec-ctrl-no-ibpb-enabled.xml
> 
> diff --git a/src/cpu/cpu_x86.c b/src/cpu/cpu_x86.c
> index 18c5756ef9..b986720224 100644
> --- a/src/cpu/cpu_x86.c
> +++ b/src/cpu/cpu_x86.c
> @@ -3269,6 +3269,61 @@ virCPUx86UpdateLive(virCPUDef *cpu,
>  }
>  
>  
> +/*
> + * Since kernel commit de572491a975 KVM no longer offers spec-ctrl on AMD
> + * hosts without IBPB_RET, while a guest started before still requires it.
> + *
> + * Returns 1 if spec-ctrl was disabled in @cpu, 0 if not, -1 on error.
> + */
> +int
> +virCPUx86UpdateLiveSpecCtrl(virCPUDef *cpu,
> +                            const virCPUData *dataEnabled,
> +                            virCPUData *dataDisabled)
> +{
> +    const char *specCtrlMSR[] = { "ibrs", "amd-stibp", "amd-ssbd" };
> +    virCPUx86Map *map;
> +    virCPUx86Feature *specCtrl;
> +    virCPUFeatureDef *feature;
> +    const virCPUx86Data *enabled;
> +    size_t i;
> +    int rc;
> +
> +    if (!dataEnabled)
> +        return 0;

This will become redundant if the function is called from a different
place (see below).

> +
> +    if (!(feature = virCPUDefFindFeature(cpu, "spec-ctrl")) ||
> +        feature->policy != VIR_CPU_FEATURE_REQUIRE)
> +        return 0;
> +
> +    if (!(map = virCPUx86GetMap()))
> +        return -1;
> +
> +    enabled = &dataEnabled->data.x86;
> +
> +    if ((rc = x86FeatureInData("spec-ctrl", enabled, map)) != 0)
> +        return rc < 0 ? -1 : 0;

I think it would be a bit more readable without the ternary operator,
e.g.:

        if ((rc = ...) < 0)
            return -1;

        if (rc == 1)
            return 0;

> +
> +    if ((rc = x86FeatureInData("ibpb", enabled, map)) <= 0)
> +        return rc;
> +
> +    for (i = 0; i < G_N_ELEMENTS(specCtrlMSR); i++) {
> +        if ((rc = x86FeatureInData(specCtrlMSR[i], enabled, map)) < 0)
> +            return -1;
> +        if (rc == 1)
> +            break;

Instead of breaking and than checking whether the for loop did ended
early we could just update the CPU here and return.

> +    }
> +
> +    if (i == G_N_ELEMENTS(specCtrlMSR))
> +        return 0;
> +

This code is making the changes so it should log a message about it
rather than leaving it up to the caller

> +    virCPUDefUpdateFeature(cpu, "spec-ctrl", VIR_CPU_FEATURE_DISABLE);
> +    if (dataDisabled && (specCtrl = x86FeatureFind(map, "spec-ctrl")))
> +        x86DataSubtract(&dataDisabled->data.x86, &specCtrl->data);
> +
> +    return 1;
> +}
> +
> +
>  static int
>  virCPUx86GetModels(char ***models)
>  {
> diff --git a/src/cpu/cpu_x86.h b/src/cpu/cpu_x86.h
> index bbc2a16447..0b05e5c695 100644
> --- a/src/cpu/cpu_x86.h
> +++ b/src/cpu/cpu_x86.h
> @@ -55,3 +55,7 @@ bool virCPUx86FeatureFilterDropMSR(const char *name,
>  
>  int virCPUx86GetAddedFeatures(const char *modelName,
>                                const char * const **features);
> +
> +int virCPUx86UpdateLiveSpecCtrl(virCPUDef *cpu,
> +                                const virCPUData *dataEnabled,
> +                                virCPUData *dataDisabled);
> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
> index 798d2d28c8..6b2eb3eef4 100644
> --- a/src/libvirt_private.syms
> +++ b/src/libvirt_private.syms
> @@ -1608,6 +1608,7 @@ virCPUx86DataSetVendor;
>  virCPUx86FeatureFilterDropMSR;
>  virCPUx86FeatureFilterSelectMSR;
>  virCPUx86GetAddedFeatures;
> +virCPUx86UpdateLiveSpecCtrl;
>  
>  # datatypes.h
>  virConnectClass;
> diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
> index 3f1960f314..9eee738334 100644
> --- a/src/qemu/qemu_process.c
> +++ b/src/qemu/qemu_process.c
> @@ -4799,10 +4799,12 @@ qemuProcessUpdateLiveGuestCPU(virDomainObj *vm,
>  
>  static int
>  qemuProcessUpdateAndVerifyCPU(virDomainObj *vm,
> -                              virDomainAsyncJob asyncJob)
> +                              virDomainAsyncJob asyncJob,
> +                              unsigned int flags)
>  {
>      g_autoptr(virCPUData) cpu = NULL;
>      g_autoptr(virCPUData) disabled = NULL;
> +    int rc;
>  
>      if (qemuProcessFetchGuestCPU(vm, asyncJob, &cpu, &disabled) < 0)
>          return -1;
> @@ -4810,6 +4812,15 @@ qemuProcessUpdateAndVerifyCPU(virDomainObj *vm,
>      if (qemuProcessVerifyCPU(vm, cpu) < 0)
>          return -1;
>  
> +    if (!(flags & VIR_QEMU_PROCESS_START_NEW) &&

The more I think about this the more I'm leaning towards doing this
update even for freshly started domains. Imagine you have defined
domains which require spec-ctrl and then update the host to the version
that drops spec-ctrl. You'd have to update all affected domain XMLs to
be able to start them for no real reason.

> +        ARCH_IS_X86(vm->def->os.arch) && vm->def->cpu) {
> +        if ((rc = virCPUx86UpdateLiveSpecCtrl(vm->def->cpu, cpu, disabled)) < 0)
> +            return -1;
> +        if (rc > 0)
> +            VIR_WARN("Hypervisor no longer provides spec-ctrl, disabling it in the live CPU of domain %s",
> +                     vm->def->name);

This VIR_WARN should be moved inside virCPUx86UpdateLiveSpecCtrl. And
since warnings are not really visible to end users, I think VIR_INFO
would be good enough.

> +    }
> +

Anyway, the call to virCPUx86UpdateLiveSpecCtrl should be moved inside
virCPUUpdateLive.

>      if (qemuProcessUpdateLiveGuestCPU(vm, cpu, disabled) < 0)
>          return -1;
>  
> @@ -8639,7 +8650,7 @@ qemuProcessLaunch(virConnectPtr conn,
>      }
>  
>      VIR_DEBUG("Verifying and updating provided guest CPU");
> -    if (qemuProcessUpdateAndVerifyCPU(vm, asyncJob) < 0)
> +    if (qemuProcessUpdateAndVerifyCPU(vm, asyncJob, flags) < 0)
>          goto cleanup;
>  
>      VIR_DEBUG("Detecting IOThread PIDs");
> diff --git a/tests/cputest.c b/tests/cputest.c
> index e29c81cb8e..38ed45cfdb 100644
> --- a/tests/cputest.c
> +++ b/tests/cputest.c

I believe that calling virCPUx86UpdateLiveSpecCtrl from virCPUUpdateLive
would limit the required cputest modifications to test cases.

Jirka
Re: [PATCH] qemu: let incoming AMD guests drop spec-ctrl instead of failing
Posted by Jiri Denemark via Devel 1 week, 5 days ago
On Mon, Oct 26, 2026 at 20:06:54 +0100, Denis V. Lunev wrote:
> From: Denis V. Lunev <den@openvz.org>
> 
> Since Linux kernel commit de572491a975 ("KVM: x86: AMD's IBPB is not
> equivalent to Intel's IBPB") KVM offers spec-ctrl on AMD hosts only
> when the CPU enumerates IBPB_RET, which Zen 1-3 do not. A host-model
> guest started on such a CPU under an older kernel has spec-ctrl in its
> live definition. On a destination with the newer kernel QEMU filters
> the feature out and the incoming migration fails:
> 
>   guest CPU doesn't match specification: missing features: spec-ctrl
> 
> Restoring a save image or reverting a snapshot fails the same way, so
> the guest cannot be moved to the new kernel without a restart.
> 
> Dropping the bit in libvirt costs the guest nothing beyond what KVM
> already withholds. KVM grants PRED_CMD for spec-ctrl or ibpb and
> SPEC_CTRL for spec-ctrl or any of ibrs, amd-stibp and amd-ssbd (kernel
> commit 39485ed95d6b), and checks the values written to SPEC_CTRL
> against the host, not the guest CPUID (kernel commit 841c2be09fe4).
> Both predate de572491a975. Since kernel commit e592ec657d84 KVM also
> ignores guest CPUID bits it does not support, so a host without
> IBPB_RET treats the guest as lacking spec-ctrl even if the bit stayed.
> QEMU drops the bit either way; libvirt only decides whether the guest
> survives.
> 
> When an existing domain is started (incoming migration, restore,
> snapshot revert) and the hypervisor dropped a required spec-ctrl while
> providing ibpb and one of ibrs, amd-stibp and amd-ssbd, disable
> spec-ctrl in the live definition and log a warning. spec-ctrl is also
> removed from the features QEMU reports as filtered, because
> check='full' fails on any filtered feature whatever the definition
> says. Fresh starts and any other missing or extra feature still fail
> with check='full'.
> 
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> ---
> RHEL-149512 reports the same failure.
> 
> Linux commit: https://git.kernel.org/torvalds/c/de572491a975
> KVM thread:   https://lore.kernel.org/all/20241011214353.1625057-5-jmattson@google.com/
> RHEL bug:     https://redhat.atlassian.net/browse/RHEL-149512

So in other words.. The spec-ctrl feature (bit 26 of the main sub leaf
of CPUID leaf 0x7) says whether the CPU supports both IA32_PRED_CMD and
IA32_SPEC_CTRL MSRs. So when migrating to a host which does not report
spec-ctrl, we must make sure the two MSRs are still available by
checking other features. The following is the KVM code for deciding when
these two MSRs are available

    static inline bool guest_has_spec_ctrl_msr(struct kvm_vcpu *vcpu)
    {
        return (guest_cpuid_has(vcpu, X86_FEATURE_SPEC_CTRL) ||
                guest_cpuid_has(vcpu, X86_FEATURE_AMD_STIBP) ||
                guest_cpuid_has(vcpu, X86_FEATURE_AMD_IBRS) ||
                guest_cpuid_has(vcpu, X86_FEATURE_AMD_SSBD));
    }

    static inline bool guest_has_pred_cmd_msr(struct kvm_vcpu *vcpu)
    {
        return (guest_cpuid_has(vcpu, X86_FEATURE_SPEC_CTRL) ||
                guest_cpuid_has(vcpu, X86_FEATURE_AMD_IBPB));
    }

Which confirms your claim that the CPU must provide ibpb and either
ibrs, amd-stibp, or amd-ssbd to support both MSRs. In which case we can
safely drop the requirement for spec-ctrl as a guest using these MSRs
based on previous spec-ctrl check will keep working.

That said I agree with the idea of this patch. I'll look at the actual
code with a fresh mind next week :-)

Jirka