QEMU's AMD SEV support requires KVM on costly AMD EPYC processors,
limiting development and testing to users with specialized server
hardware. This makes it hard to validate SEV guest behavior, like
OVMF boots or SEV-aware software, on common dev machines.
A solution to this is the emulation of SEV from the guest's
perspective using TCG.

This change begins this process with the exposure of the SEV CPUID leaf.
In target/i386/cpu.c:cpu_x86_cpuid() case 0x8000001F:

case 0x8000001F:
    *eax = *ebx = *ecx = *edx = 0;
    if (sev_enabled()) {
        *eax = 0x2;
        *eax |= sev_es_enabled() ? 0x8 : 0;
        *eax |= sev_snp_enabled() ? 0x10 : 0;
        *ebx = sev_get_cbit_position() & 0x3f; /* EBX[5:0] */
        *ebx |= (sev_get_reduced_phys_bits() & 0x3f) << 6; /* EBX[11:6] */
    }
    break;

sev_enabled() verifies if the QOM object is TYPE_SEV_GUEST;
TYPE_SEV_EMULATED is derived from TYPE_SEV_GUEST with SevEmulatedState
to satisfy this check with minimal changes. In particular this allows
to bypass all the sev_enabled() checks for future features.

Since KVM hardware isn't available, override the QOM's kvm_init() and add
a conditional confidential_guest_kvm_init() call during machine_init() to
set up emulated confidential support using the ConfidentialGuestSupport
structure.

With this change it is possible to run a VM with the SEV CPUID active
adding:

    -accel tcg \
    -object sev-emulated,id=sev0,cbitpos=47,reduced-phys-bits=1 \
    -machine memory-encryption=sev0

To the QEMU start arguments.

Signed-off-by: Tommaso Califano <[email protected]>
---
 accel/tcg/tcg-all.c | 18 ++++++++++++-
 qapi/qom.json       | 15 +++++++++++
 target/i386/sev.c   | 65 +++++++++++++++++++++++++++++++++++++++++++++
 target/i386/sev.h   |  1 +
 4 files changed, 98 insertions(+), 1 deletion(-)

diff --git a/accel/tcg/tcg-all.c b/accel/tcg/tcg-all.c
index 8eb4a6b89e..d55827e214 100644
--- a/accel/tcg/tcg-all.c
+++ b/accel/tcg/tcg-all.c
@@ -45,7 +45,7 @@
 #include "accel/accel-cpu-ops.h"
 #include "accel/tcg/cpu-ops.h"
 #include "internal-common.h"
-
+#include "system/confidential-guest-support.h"
 
 struct TCGState {
     AccelState parent_obj;
@@ -107,6 +107,9 @@ static int tcg_init_machine(AccelState *as, MachineState 
*ms)
     unsigned max_threads = 1;
 
 #ifndef CONFIG_USER_ONLY
+    int ret;
+    Error *local_err = NULL;
+    const char *cgs_type = NULL;
     CPUClass *cc = CPU_CLASS(object_class_by_name(target_cpu_type()));
     bool mttcg_supported = cc->tcg_ops->mttcg_supported;
 
@@ -163,6 +166,19 @@ static int tcg_init_machine(AccelState *as, MachineState 
*ms)
 
 #ifdef CONFIG_USER_ONLY
     qdev_create_fake_machine();
+#else
+    /* TCG SEV/Confidential Guest Support init */
+    if (ms->cgs) {
+        cgs_type = object_get_typename(OBJECT(ms->cgs));
+
+        if (g_str_has_prefix(cgs_type, "sev-emulated")) {
+            ret = confidential_guest_kvm_init(ms->cgs, &local_err);
+            if (ret < 0) {
+                error_report_err(local_err);
+                return ret;
+            }
+        }
+    }
 #endif
 
     return 0;
diff --git a/qapi/qom.json b/qapi/qom.json
index c653248f85..35cda819ec 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -1057,6 +1057,19 @@
             '*handle': 'uint32',
             '*legacy-vm-type': 'OnOffAuto' } }
 
+##
+# @SevEmulatedProperties:
+#
+# Properties for sev-emulated objects.
+# This object functionally emulates AMD SEV hardware via TCG, so
+# it does not require real hardware to run.
+#
+# Since: 10.1.0
+##
+{ 'struct': 'SevEmulatedProperties',
+  'base': 'SevGuestProperties',
+  'data': {}}
+
 ##
 # @SevSnpGuestProperties:
 #
@@ -1241,6 +1254,7 @@
     { 'name': 'secret_keyring',
       'if': 'CONFIG_SECRET_KEYRING' },
     'sev-guest',
+    'sev-emulated',
     'sev-snp-guest',
     'thread-context',
     's390-pv-guest',
@@ -1318,6 +1332,7 @@
       'secret_keyring':             { 'type': 'SecretKeyringProperties',
                                       'if': 'CONFIG_SECRET_KEYRING' },
       'sev-guest':                  'SevGuestProperties',
+      'sev-emulated':               'SevEmulatedProperties',
       'sev-snp-guest':              'SevSnpGuestProperties',
       'tdx-guest':                  'TdxGuestProperties',
       'thread-context':             'ThreadContextProperties',
diff --git a/target/i386/sev.c b/target/i386/sev.c
index 9dde972c11..2502e860e2 100644
--- a/target/i386/sev.c
+++ b/target/i386/sev.c
@@ -51,6 +51,7 @@
 
 OBJECT_DECLARE_TYPE(SevCommonState, SevCommonStateClass, SEV_COMMON)
 OBJECT_DECLARE_TYPE(SevGuestState, SevCommonStateClass, SEV_GUEST)
+OBJECT_DECLARE_TYPE(SevEmulatedState, SevCommonStateClass, SEV_EMULATED)
 OBJECT_DECLARE_TYPE(SevSnpGuestState, SevCommonStateClass, SEV_SNP_GUEST)
 
 /* hard code sha256 digest size */
@@ -177,6 +178,21 @@ struct SevGuestState {
     OnOffAuto legacy_vm_type;
 };
 
+/**
+ * SevEmulatedState:
+ *
+ * The SevEmulatedState object is used for creating and managing a SEV emulated
+ * guest.
+ *
+ * # $QEMU \
+ *         -object sev-emulated,id=sev0 \
+ *         -machine ...,memory-encryption=sev0
+ */
+
+typedef struct SevEmulatedState {
+    SevGuestState parent_obj;
+} SevEmulatedState;
+
 struct SevSnpGuestState {
     SevCommonState parent_obj;
 
@@ -2936,6 +2952,46 @@ sev_guest_instance_init(Object *obj)
     sev_guest->legacy_vm_type = ON_OFF_AUTO_AUTO;
 }
 
+static int sev_emulated_init(ConfidentialGuestSupport *cgs, Error **errp)
+{
+    SevCommonState *sev_common = SEV_COMMON(cgs);
+
+    /*
+     * The cbitpos value will be placed in bit positions 5:0 of the EBX
+     * register of CPUID 0x8000001F. We need to verify the range as the
+     * comparison with the host cbitpos is missing.
+     */
+    if (sev_common->cbitpos < 32 ||
+        sev_common->cbitpos > 63) {
+        error_setg(errp, "%s: cbitpos check failed, requested '%d',"
+                   "the firmware requires >=32",
+                   __func__, sev_common->cbitpos);
+        return -1;
+    }
+
+    /*
+     * The reduced-phys-bits value will be placed in bit positions 11:6 of
+     * the EBX register of CPUID 0x8000001F, so verify the supplied value
+     * is in the range of 1 to 63.
+     */
+    if (sev_common->reduced_phys_bits < 1 ||
+        sev_common->reduced_phys_bits > 63) {
+        error_setg(errp, "%s: reduced_phys_bits check failed,"
+                   " it should be in the range of 1 to 63, requested '%d'",
+                   __func__, sev_common->reduced_phys_bits);
+        return -1;
+    }
+    cgs->ready = true;
+    return 0;
+}
+
+static void sev_emulated_class_init(ObjectClass *oc, const void *data)
+{
+    ConfidentialGuestSupportClass *klass = 
CONFIDENTIAL_GUEST_SUPPORT_CLASS(oc);
+    /* Override the sev-common method that uses kvm */
+    klass->kvm_init = sev_emulated_init;
+}
+
 /* guest info specific sev/sev-es */
 static const TypeInfo sev_guest_info = {
     .parent = TYPE_SEV_COMMON,
@@ -2945,6 +3001,14 @@ static const TypeInfo sev_guest_info = {
     .class_init = sev_guest_class_init,
 };
 
+/* emulated sev */
+static const TypeInfo sev_emulated_info = {
+    .parent = TYPE_SEV_GUEST,
+    .name = TYPE_SEV_EMULATED,
+    .instance_size = sizeof(SevEmulatedState),
+    .class_init = sev_emulated_class_init
+};
+
 static void
 sev_snp_guest_get_policy(Object *obj, Visitor *v, const char *name,
                          void *opaque, Error **errp)
@@ -3207,6 +3271,7 @@ static void
 sev_register_types(void)
 {
     type_register_static(&sev_common_info);
+    type_register_static(&sev_emulated_info);
     type_register_static(&sev_guest_info);
     type_register_static(&sev_snp_guest_info);
 }
diff --git a/target/i386/sev.h b/target/i386/sev.h
index 4358df40e4..839656e2be 100644
--- a/target/i386/sev.h
+++ b/target/i386/sev.h
@@ -33,6 +33,7 @@ bool sev_snp_enabled(void);
 #if !defined(CONFIG_USER_ONLY)
 
 #define TYPE_SEV_COMMON "sev-common"
+#define TYPE_SEV_EMULATED "sev-emulated"
 #define TYPE_SEV_GUEST "sev-guest"
 #define TYPE_SEV_SNP_GUEST "sev-snp-guest"
 
-- 
2.53.0

Reply via email to