diff --git a/1038-doc-update-AMD-SEV-to-include-Live-migration-flow.patch b/1038-doc-update-AMD-SEV-to-include-Live-migration-flow.patch new file mode 100644 index 0000000000000000000000000000000000000000..6672e3ff24320b3337acbfc49922cb263fb1d385 --- /dev/null +++ b/1038-doc-update-AMD-SEV-to-include-Live-migration-flow.patch @@ -0,0 +1,69 @@ +From 5152d49cc4aead7dc06e9f788e9c078701c5a160 Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Thu, 7 May 2020 22:26:17 +0000 +Subject: [PATCH 01/29] doc: update AMD SEV to include Live migration flow + +cherry-picked from https://github.com/AMDESE/qemu/commit/0e2b3d80e3. + +Reviewed-by: Dr. David Alan Gilbert +Signed-off-by: Brijesh Singh +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + docs/amd-memory-encryption.txt | 40 +++++++++++++++++++++++++++++++++- + 1 file changed, 39 insertions(+), 1 deletion(-) + +diff --git a/docs/amd-memory-encryption.txt b/docs/amd-memory-encryption.txt +index ffca382b5..17e2714d9 100644 +--- a/docs/amd-memory-encryption.txt ++++ b/docs/amd-memory-encryption.txt +@@ -126,7 +126,45 @@ TODO + + Live Migration + ---------------- +-TODO ++AMD SEV encrypts the memory of VMs and because a different key is used ++in each VM, the hypervisor will be unable to simply copy the ++ciphertext from one VM to another to migrate the VM. Instead the AMD SEV Key ++Management API provides sets of function which the hypervisor can use ++to package a guest page for migration, while maintaining the confidentiality ++provided by AMD SEV. ++ ++SEV guest VMs have the concept of private and shared memory. The private ++memory is encrypted with the guest-specific key, while shared memory may ++be encrypted with the hypervisor key. The migration APIs provided by the ++SEV API spec should be used for migrating the private pages. The ++KVM_GET_PAGE_ENC_BITMAP ioctl can be used to get the guest page encryption ++bitmap. The bitmap can be used to check if the given guest page is ++private or shared. ++ ++Before initiating the migration, we need to know the targets machine's public ++Diffie-Hellman key (PDH) and certificate chain. It can be retrieved ++with the 'query-sev-capabilities' QMP command or using the sev-tool. The ++migrate-set-parameter can be used to pass the target machine's PDH and ++certificate chain. ++ ++During the migration flow, the SEND_START is called on the source hypervisor ++to create an outgoing encryption context. The SEV guest policy dictates whether ++the certificate passed through the migrate-sev-set-info command will be ++validated. SEND_UPDATE_DATA is called to encrypt the guest private pages. ++After migration is completed, SEND_FINISH is called to destroy the encryption ++context and make the VM non-runnable to protect it against cloning. ++ ++On the target machine, RECEIVE_START is called first to create an ++incoming encryption context. The RECEIVE_UPDATE_DATA is called to copy ++the received encrypted page into guest memory. After migration has ++completed, RECEIVE_FINISH is called to make the VM runnable. ++ ++For more information about the migration see SEV API Appendix A ++Usage flow (Live migration section). ++ ++NOTE: ++To protect against the memory clone SEV APIs are designed to make the VM ++unrunnable in case of the migration failure. + + References + ----------------- +-- +2.31.1 + diff --git a/1039-migration.json-add-AMD-SEV-specific-migration-parame.patch b/1039-migration.json-add-AMD-SEV-specific-migration-parame.patch new file mode 100644 index 0000000000000000000000000000000000000000..bd46ba75d2d137171ea2cdc6ace9ec8274192816 --- /dev/null +++ b/1039-migration.json-add-AMD-SEV-specific-migration-parame.patch @@ -0,0 +1,253 @@ +From 7dc7f0659bfe9c8be64f65873df9595b3791dce5 Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Tue, 27 Jul 2021 11:27:00 +0000 +Subject: [PATCH 02/29] migration.json: add AMD SEV specific migration + parameters + +cherry-picked from https://github.com/AMDESE/qemu/commit/d6a23bde6b6e. + +AMD SEV migration flow requires that target machine's public Diffie-Hellman +key (PDH) and certificate chain must be passed before initiating the guest +migration. User can use QMP 'migrate-set-parameters' to pass the certificate +chain. The certificate chain will be used while creating the outgoing +encryption context. + +Signed-off-by: Brijesh Singh +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + migration/migration.c | 66 +++++++++++++++++++++++++++++++++++++++++++ + monitor/hmp-cmds.c | 18 ++++++++++++ + qapi/migration.json | 40 ++++++++++++++++++++++++-- + 3 files changed, 121 insertions(+), 3 deletions(-) + +diff --git a/migration/migration.c b/migration/migration.c +index 0885549de..6810bcefc 100644 +--- a/migration/migration.c ++++ b/migration/migration.c +@@ -932,6 +932,12 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp) + params->announce_rounds = s->parameters.announce_rounds; + params->has_announce_step = true; + params->announce_step = s->parameters.announce_step; ++ params->has_sev_pdh = true; ++ params->sev_pdh = g_strdup(s->parameters.sev_pdh); ++ params->has_sev_plat_cert = true; ++ params->sev_plat_cert = g_strdup(s->parameters.sev_plat_cert); ++ params->has_sev_amd_cert = true; ++ params->sev_amd_cert = g_strdup(s->parameters.sev_amd_cert); + + if (s->parameters.has_block_bitmap_mapping) { + params->has_block_bitmap_mapping = true; +@@ -1633,6 +1639,19 @@ static void migrate_params_test_apply(MigrateSetParameters *params, + dest->has_block_bitmap_mapping = true; + dest->block_bitmap_mapping = params->block_bitmap_mapping; + } ++ ++ if (params->has_sev_pdh) { ++ assert(params->sev_pdh->type == QTYPE_QSTRING); ++ dest->sev_pdh = g_strdup(params->sev_pdh->u.s); ++ } ++ if (params->has_sev_plat_cert) { ++ assert(params->sev_plat_cert->type == QTYPE_QSTRING); ++ dest->sev_plat_cert = g_strdup(params->sev_plat_cert->u.s); ++ } ++ if (params->has_sev_amd_cert) { ++ assert(params->sev_amd_cert->type == QTYPE_QSTRING); ++ dest->sev_amd_cert = g_strdup(params->sev_amd_cert->u.s); ++ } + } + + static void migrate_params_apply(MigrateSetParameters *params, Error **errp) +@@ -1755,6 +1774,22 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp) + QAPI_CLONE(BitmapMigrationNodeAliasList, + params->block_bitmap_mapping); + } ++ ++ if (params->has_sev_pdh) { ++ g_free(s->parameters.sev_pdh); ++ assert(params->sev_pdh->type == QTYPE_QSTRING); ++ s->parameters.sev_pdh = g_strdup(params->sev_pdh->u.s); ++ } ++ if (params->has_sev_plat_cert) { ++ g_free(s->parameters.sev_plat_cert); ++ assert(params->sev_plat_cert->type == QTYPE_QSTRING); ++ s->parameters.sev_plat_cert = g_strdup(params->sev_plat_cert->u.s); ++ } ++ if (params->has_sev_amd_cert) { ++ g_free(s->parameters.sev_amd_cert); ++ assert(params->sev_amd_cert->type == QTYPE_QSTRING); ++ s->parameters.sev_amd_cert = g_strdup(params->sev_amd_cert->u.s); ++ } + } + + void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp) +@@ -1775,6 +1810,27 @@ void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp) + params->tls_hostname->type = QTYPE_QSTRING; + params->tls_hostname->u.s = strdup(""); + } ++ /* TODO Rewrite "" to null instead */ ++ if (params->has_sev_pdh ++ && params->sev_pdh->type == QTYPE_QNULL) { ++ qobject_unref(params->sev_pdh->u.n); ++ params->sev_pdh->type = QTYPE_QSTRING; ++ params->sev_pdh->u.s = strdup(""); ++ } ++ /* TODO Rewrite "" to null instead */ ++ if (params->has_sev_plat_cert ++ && params->sev_plat_cert->type == QTYPE_QNULL) { ++ qobject_unref(params->sev_plat_cert->u.n); ++ params->sev_plat_cert->type = QTYPE_QSTRING; ++ params->sev_plat_cert->u.s = strdup(""); ++ } ++ /* TODO Rewrite "" to null instead */ ++ if (params->has_sev_amd_cert ++ && params->sev_amd_cert->type == QTYPE_QNULL) { ++ qobject_unref(params->sev_amd_cert->u.n); ++ params->sev_amd_cert->type = QTYPE_QSTRING; ++ params->sev_amd_cert->u.s = strdup(""); ++ } + + migrate_params_test_apply(params, &tmp); + +@@ -4332,6 +4388,9 @@ static void migration_instance_finalize(Object *obj) + qemu_mutex_destroy(&ms->qemu_file_lock); + g_free(params->tls_hostname); + g_free(params->tls_creds); ++ g_free(params->sev_pdh); ++ g_free(params->sev_plat_cert); ++ g_free(params->sev_amd_cert); + qemu_sem_destroy(&ms->wait_unplug_sem); + qemu_sem_destroy(&ms->rate_limit_sem); + qemu_sem_destroy(&ms->pause_sem); +@@ -4383,6 +4442,13 @@ static void migration_instance_init(Object *obj) + params->has_tls_hostname = true; + params->has_tls_authz = true; + ++ params->sev_pdh = g_strdup(""); ++ params->sev_plat_cert = g_strdup(""); ++ params->sev_amd_cert = g_strdup(""); ++ params->has_sev_pdh = true; ++ params->has_sev_plat_cert = true; ++ params->has_sev_amd_cert = true; ++ + qemu_sem_init(&ms->postcopy_pause_sem, 0); + qemu_sem_init(&ms->postcopy_pause_rp_sem, 0); + qemu_sem_init(&ms->rp_state.rp_sem, 0); +diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c +index f7216ab5d..409bd15a6 100644 +--- a/monitor/hmp-cmds.c ++++ b/monitor/hmp-cmds.c +@@ -1349,6 +1349,24 @@ void hmp_migrate_set_parameter(Monitor *mon, const QDict *qdict) + error_setg(&err, "The block-bitmap-mapping parameter can only be set " + "through QMP"); + break; ++ case MIGRATION_PARAMETER_SEV_PDH: ++ p->has_sev_pdh = true; ++ p->sev_pdh = g_new0(StrOrNull, 1); ++ p->sev_pdh->type = QTYPE_QSTRING; ++ visit_type_str(v, param, &p->sev_pdh->u.s, &err); ++ break; ++ case MIGRATION_PARAMETER_SEV_PLAT_CERT: ++ p->has_sev_plat_cert = true; ++ p->sev_plat_cert = g_new0(StrOrNull, 1); ++ p->sev_plat_cert->type = QTYPE_QSTRING; ++ visit_type_str(v, param, &p->sev_plat_cert->u.s, &err); ++ break; ++ case MIGRATION_PARAMETER_SEV_AMD_CERT: ++ p->has_sev_amd_cert = true; ++ p->sev_amd_cert = g_new0(StrOrNull, 1); ++ p->sev_amd_cert->type = QTYPE_QSTRING; ++ visit_type_str(v, param, &p->sev_amd_cert->u.s, &err); ++ break; + default: + assert(0); + } +diff --git a/qapi/migration.json b/qapi/migration.json +index 94bc5c69d..bad53a2f8 100644 +--- a/qapi/migration.json ++++ b/qapi/migration.json +@@ -774,6 +774,15 @@ + # block device name if there is one, and to their node name + # otherwise. (Since 5.2) + # ++# @sev-pdh: The target host platform diffie-hellman key encoded in base64 ++# (Since 4.2) ++# ++# @sev-plat-cert: The target host platform certificate chain encoded in base64 ++# (Since 4.2) ++# ++# @sev-amd-cert: AMD certificate chain which include ASK and OCA encoded in ++# base64 (Since 4.2) ++# + # Features: + # @unstable: Member @x-checkpoint-delay is experimental. + # +@@ -794,7 +803,8 @@ + 'xbzrle-cache-size', 'max-postcopy-bandwidth', + 'max-cpu-throttle', 'multifd-compression', + 'multifd-zlib-level' ,'multifd-zstd-level', +- 'block-bitmap-mapping' ] } ++ 'block-bitmap-mapping', ++ 'sev-pdh', 'sev-plat-cert', 'sev-amd-cert' ] } + + ## + # @MigrateSetParameters: +@@ -939,6 +949,15 @@ + # block device name if there is one, and to their node name + # otherwise. (Since 5.2) + # ++# @sev-pdh: The target host platform diffie-hellman key encoded in base64 ++# (Since 4.2) ++# ++# @sev-plat-cert: The target host platform certificate chain encoded in base64 ++# (Since 4.2) ++# ++# @sev-amd-cert: AMD certificate chain which include ASK and OCA encoded in ++# base64 (Since 4.2) ++# + # Features: + # @unstable: Member @x-checkpoint-delay is experimental. + # +@@ -974,7 +993,10 @@ + '*multifd-compression': 'MultiFDCompression', + '*multifd-zlib-level': 'uint8', + '*multifd-zstd-level': 'uint8', +- '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ] } } ++ '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ], ++ '*sev-pdh':'StrOrNull', ++ '*sev-plat-cert': 'StrOrNull', ++ '*sev-amd-cert' : 'StrOrNull' } } + + ## + # @migrate-set-parameters: +@@ -1139,6 +1161,15 @@ + # block device name if there is one, and to their node name + # otherwise. (Since 5.2) + # ++# @sev-pdh: The target host platform diffie-hellman key encoded in base64 ++# (Since 4.2) ++# ++# @sev-plat-cert: The target host platform certificate chain encoded in base64 ++# (Since 4.2) ++# ++# @sev-amd-cert: AMD certificate chain which include ASK and OCA encoded in ++# base64 (Since 4.2) ++# + # Features: + # @unstable: Member @x-checkpoint-delay is experimental. + # +@@ -1172,7 +1203,10 @@ + '*multifd-compression': 'MultiFDCompression', + '*multifd-zlib-level': 'uint8', + '*multifd-zstd-level': 'uint8', +- '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ] } } ++ '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ], ++ '*sev-pdh':'str', ++ '*sev-plat-cert': 'str', ++ '*sev-amd-cert' : 'str'} } + + ## + # @query-migrate-parameters: +-- +2.31.1 + diff --git a/1040-confidential-guest-support-introduce-ConfidentialGue.patch b/1040-confidential-guest-support-introduce-ConfidentialGue.patch new file mode 100644 index 0000000000000000000000000000000000000000..c76a7dcdbf7a2f1f78f9a7e0ab70068707d406cf --- /dev/null +++ b/1040-confidential-guest-support-introduce-ConfidentialGue.patch @@ -0,0 +1,67 @@ +From 7b50e920f4faacb529a43230565ae9e022e61649 Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Tue, 27 Jul 2021 11:41:37 +0000 +Subject: [PATCH 03/29] confidential guest support: introduce + ConfidentialGuestMemoryEncryptionOps for encrypted VMs + +cherry-picked from https://github.com/AMDESE/qemu/commit/74fce7be9bd. + +When memory encryption is enabled in VM, the guest RAM will be encrypted +with the guest-specific key, to protect the confidentiality of data while +in transit we need to platform specific hooks to save or migrate the +guest RAM. + +Introduce the new ConfidentialGuestMemoryEncryptionOps in this patch +which will be later used by the encrypted guest for migration. + +Signed-off-by: Brijesh Singh +Co-developed-by: Ashish Kalra +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + include/exec/confidential-guest-support.h | 27 +++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h +index ba2dd4b5d..343f686fc 100644 +--- a/include/exec/confidential-guest-support.h ++++ b/include/exec/confidential-guest-support.h +@@ -53,8 +53,35 @@ struct ConfidentialGuestSupport { + bool ready; + }; + ++/** ++ * The functions registers with ConfidentialGuestMemoryEncryptionOps will be ++ * used during the encrypted guest migration. ++ */ ++struct ConfidentialGuestMemoryEncryptionOps { ++ /* Initialize the platform specific state before starting the migration */ ++ int (*save_setup)(const char *pdh, const char *plat_cert, ++ const char *amd_cert); ++ ++ /* Write the encrypted page and metadata associated with it */ ++ int (*save_outgoing_page)(QEMUFile *f, uint8_t *ptr, uint32_t size, ++ uint64_t *bytes_sent); ++ ++ /* Load the incoming encrypted page into guest memory */ ++ int (*load_incoming_page)(QEMUFile *f, uint8_t *ptr); ++ ++ /* Check if gfn is in shared/unencrypted region */ ++ bool (*is_gfn_in_unshared_region)(unsigned long gfn); ++ ++ /* Write the shared regions list */ ++ int (*save_outgoing_shared_regions_list)(QEMUFile *f); ++ ++ /* Load the shared regions list */ ++ int (*load_incoming_shared_regions_list)(QEMUFile *f); ++}; ++ + typedef struct ConfidentialGuestSupportClass { + ObjectClass parent; ++ struct ConfidentialGuestMemoryEncryptionOps *memory_encryption_ops; + } ConfidentialGuestSupportClass; + + #endif /* !CONFIG_USER_ONLY */ +-- +2.31.1 + diff --git a/1041-target-i386-sev-provide-callback-to-setup-outgoing-c.patch b/1041-target-i386-sev-provide-callback-to-setup-outgoing-c.patch new file mode 100644 index 0000000000000000000000000000000000000000..f7c60e27f27c6ee986796d38fafb182e683be2b7 --- /dev/null +++ b/1041-target-i386-sev-provide-callback-to-setup-outgoing-c.patch @@ -0,0 +1,135 @@ +From 4e400056b6e3d895814d091da819ba742602581b Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Tue, 27 Jul 2021 12:10:23 +0000 +Subject: [PATCH 04/29] target/i386: sev: provide callback to setup outgoing + context + +cherry-picked from https://github.com/AMDESE/qemu/commit/7521883afc0. + +The user provides the target machine's Platform Diffie-Hellman key (PDH) +and certificate chain before starting the SEV guest migration. Cache the +certificate chain as we need them while creating the outgoing context. + +Signed-off-by: Brijesh Singh +Co-developed-by: Ashish Kalra +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + target/i386/sev.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++ + target/i386/sev.h | 2 ++ + 2 files changed, 61 insertions(+) + +diff --git a/target/i386/sev.c b/target/i386/sev.c +index 36669bbdf..5dd6c0a3f 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -76,6 +76,12 @@ struct SevGuestState { + int sev_fd; + SevState state; + gchar *measurement; ++ guchar *remote_pdh; ++ size_t remote_pdh_len; ++ guchar *remote_plat_cert; ++ size_t remote_plat_cert_len; ++ guchar *amd_cert; ++ size_t amd_cert_len; + + uint32_t reset_cs; + uint32_t reset_ip; +@@ -160,6 +166,12 @@ static const char *const sev_fw_errlist[] = { + + #define SEV_FW_MAX_ERROR ARRAY_SIZE(sev_fw_errlist) + ++#define SEV_FW_BLOB_MAX_SIZE 0x4000 /* 16KB */ ++ ++static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { ++ .save_setup = sev_save_setup, ++}; ++ + static int + sev_ioctl(int fd, int cmd, void *data, int *error) + { +@@ -872,6 +884,48 @@ sev_vm_state_change(void *opaque, bool running, RunState state) + } + } + ++static inline bool check_blob_length(size_t value) ++{ ++ if (value > SEV_FW_BLOB_MAX_SIZE) { ++ error_report("invalid length max=%d got=%ld", ++ SEV_FW_BLOB_MAX_SIZE, value); ++ return false; ++ } ++ ++ return true; ++} ++ ++int sev_save_setup(const char *pdh, const char *plat_cert, ++ const char *amd_cert) ++{ ++ SevGuestState *s = sev_guest; ++ ++ s->remote_pdh = g_base64_decode(pdh, &s->remote_pdh_len); ++ if (!check_blob_length(s->remote_pdh_len)) { ++ goto error; ++ } ++ ++ s->remote_plat_cert = g_base64_decode(plat_cert, ++ &s->remote_plat_cert_len); ++ if (!check_blob_length(s->remote_plat_cert_len)) { ++ goto error; ++ } ++ ++ s->amd_cert = g_base64_decode(amd_cert, &s->amd_cert_len); ++ if (!check_blob_length(s->amd_cert_len)) { ++ goto error; ++ } ++ ++ return 0; ++ ++error: ++ g_free(s->remote_pdh); ++ g_free(s->remote_plat_cert); ++ g_free(s->amd_cert); ++ ++ return 1; ++} ++ + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) + { + SevGuestState *sev +@@ -886,6 +940,9 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) + return 0; + } + ++ ConfidentialGuestSupportClass *cgs_class = ++ (ConfidentialGuestSupportClass *) object_get_class(OBJECT(cgs)); ++ + ret = ram_block_discard_disable(true); + if (ret) { + error_report("%s: cannot disable RAM discard", __func__); +@@ -980,6 +1037,8 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) + qemu_add_machine_init_done_notifier(&sev_machine_done_notify); + qemu_add_vm_change_state_handler(sev_vm_state_change, sev); + ++ cgs_class->memory_encryption_ops = &sev_memory_encryption_ops; ++ + cgs->ready = true; + + return 0; +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 1c97bff99..14801ec44 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -51,6 +51,8 @@ extern uint32_t sev_get_reduced_phys_bits(void); + extern bool sev_add_kernel_loader_hashes(SevKernelLoaderContext *ctx, Error **errp); + + int sev_encrypt_flash(uint8_t *ptr, uint64_t len, Error **errp); ++int sev_save_setup(const char *pdh, const char *plat_cert, ++ const char *amd_cert); + int sev_inject_launch_secret(const char *hdr, const char *secret, + uint64_t gpa, Error **errp); + +-- +2.31.1 + diff --git a/1042-target-i386-sev-do-not-create-launch-context-for-an-.patch b/1042-target-i386-sev-do-not-create-launch-context-for-an-.patch new file mode 100644 index 0000000000000000000000000000000000000000..da664a3f08bc7b89cc27f3e6f4f59b0921b81b64 --- /dev/null +++ b/1042-target-i386-sev-do-not-create-launch-context-for-an-.patch @@ -0,0 +1,48 @@ +From 36b08d0392f2baec061e6fb90416db42dfad20f7 Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Tue, 27 Jul 2021 12:16:09 +0000 +Subject: [PATCH 05/29] target/i386: sev: do not create launch context for an + incoming guest + +cherry-picked from https://github.com/AMDESE/qemu/commit/b85694233495. + +The LAUNCH_START is used for creating an encryption context to encrypt +newly created guest, for an incoming guest the RECEIVE_START should be +used. + +Reviewed-by: Dr. David Alan Gilbert +Signed-off-by: Brijesh Singh +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + target/i386/sev.c | 14 ++++++++++---- + 1 file changed, 10 insertions(+), 4 deletions(-) + +diff --git a/target/i386/sev.c b/target/i386/sev.c +index 5dd6c0a3f..fc8a2bb61 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -1024,10 +1024,16 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) + } + } + +- ret = sev_launch_start(sev); +- if (ret) { +- error_setg(errp, "%s: failed to create encryption context", __func__); +- goto err; ++ /* ++ * The LAUNCH context is used for new guest, if its an incoming guest ++ * then RECEIVE context will be created after the connection is established. ++ */ ++ if (!runstate_check(RUN_STATE_INMIGRATE)) { ++ ret = sev_launch_start(sev); ++ if (ret) { ++ error_setg(errp, "%s: failed to create encryption context", __func__); ++ goto err; ++ } + } + + /* CSV guest needs no notifier to reg/unreg memory */ +-- +2.31.1 + diff --git a/1043-target-i386-sev-add-support-to-encrypt-the-outgoing-.patch b/1043-target-i386-sev-add-support-to-encrypt-the-outgoing-.patch new file mode 100644 index 0000000000000000000000000000000000000000..f0a412a1bedbe3248da3954bdac6be6327853a01 --- /dev/null +++ b/1043-target-i386-sev-add-support-to-encrypt-the-outgoing-.patch @@ -0,0 +1,324 @@ +From 196380db46216f207e57b00e4bbc95178683d0cf Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Tue, 27 Jul 2021 12:55:25 +0000 +Subject: [PATCH 06/29] target/i386: sev: add support to encrypt the outgoing + page + +cherry-picked from https://github.com/AMDESE/qemu/commit/5187c6f86bd. + +The sev_save_outgoing_page() provide the implementation to encrypt the +guest private pages during the transit. The routines uses the SEND_START +command to create the outgoing encryption context on the first call then +uses the SEND_UPDATE_DATA command to encrypt the data before writing it +to the socket. While encrypting the data SEND_UPDATE_DATA produces some +metadata (e.g MAC, IV). The metadata is also sent to the target machine. +After migration is completed, we issue the SEND_FINISH command to transition +the SEV guest state from sending to unrunnable state. + +Signed-off-by: Brijesh Singh +Co-developed-by: Ashish Kalra +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + target/i386/sev.c | 221 +++++++++++++++++++++++++++++++++++++++ + target/i386/sev.h | 2 + + target/i386/trace-events | 3 + + 3 files changed, 226 insertions(+) + +diff --git a/target/i386/sev.c b/target/i386/sev.c +index fc8a2bb61..622150106 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -31,6 +31,8 @@ + #include "sysemu/runstate.h" + #include "trace.h" + #include "migration/blocker.h" ++#include "migration/qemu-file.h" ++#include "migration/misc.h" + #include "qom/object.h" + #include "monitor/monitor.h" + #include "monitor/hmp-target.h" +@@ -82,6 +84,8 @@ struct SevGuestState { + size_t remote_plat_cert_len; + guchar *amd_cert; + size_t amd_cert_len; ++ gchar *send_packet_hdr; ++ size_t send_packet_hdr_len; + + uint32_t reset_cs; + uint32_t reset_ip; +@@ -170,6 +174,7 @@ static const char *const sev_fw_errlist[] = { + + static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .save_setup = sev_save_setup, ++ .save_outgoing_page = sev_save_outgoing_page, + }; + + static int +@@ -926,6 +931,40 @@ error: + return 1; + } + ++static void ++sev_send_finish(void) ++{ ++ int ret, error; ++ ++ trace_kvm_sev_send_finish(); ++ ret = sev_ioctl(sev_guest->sev_fd, KVM_SEV_SEND_FINISH, 0, &error); ++ if (ret) { ++ error_report("%s: SEND_FINISH ret=%d fw_error=%d '%s'", ++ __func__, ret, error, fw_error_to_str(error)); ++ } ++ ++ g_free(sev_guest->send_packet_hdr); ++ sev_set_guest_state(sev_guest, SEV_STATE_RUNNING); ++} ++ ++static void ++sev_migration_state_notifier(Notifier *notifier, void *data) ++{ ++ MigrationState *s = data; ++ ++ if (migration_has_finished(s) || ++ migration_in_postcopy_after_devices(s) || ++ migration_has_failed(s)) { ++ if (sev_check_state(sev_guest, SEV_STATE_SEND_UPDATE)) { ++ sev_send_finish(); ++ } ++ } ++} ++ ++static Notifier sev_migration_state_notify = { ++ .notify = sev_migration_state_notifier, ++}; ++ + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) + { + SevGuestState *sev +@@ -1042,6 +1081,7 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) + } + qemu_add_machine_init_done_notifier(&sev_machine_done_notify); + qemu_add_vm_change_state_handler(sev_vm_state_change, sev); ++ add_migration_state_change_notifier(&sev_migration_state_notify); + + cgs_class->memory_encryption_ops = &sev_memory_encryption_ops; + +@@ -1283,6 +1323,187 @@ int sev_es_save_reset_vector(void *flash_ptr, uint64_t flash_size) + return 0; + } + ++static int ++sev_get_send_session_length(void) ++{ ++ int ret, fw_err = 0; ++ struct kvm_sev_send_start start = {}; ++ ++ ret = sev_ioctl(sev_guest->sev_fd, KVM_SEV_SEND_START, &start, &fw_err); ++ if (fw_err != SEV_RET_INVALID_LEN) { ++ ret = -1; ++ error_report("%s: failed to get session length ret=%d fw_error=%d '%s'", ++ __func__, ret, fw_err, fw_error_to_str(fw_err)); ++ goto err; ++ } ++ ++ ret = start.session_len; ++err: ++ return ret; ++} ++ ++static int ++sev_send_start(SevGuestState *s, QEMUFile *f, uint64_t *bytes_sent) ++{ ++ gsize pdh_len = 0, plat_cert_len; ++ int session_len, ret, fw_error; ++ struct kvm_sev_send_start start = { }; ++ guchar *pdh = NULL, *plat_cert = NULL, *session = NULL; ++ Error *local_err = NULL; ++ ++ if (!s->remote_pdh || !s->remote_plat_cert || !s->amd_cert_len) { ++ error_report("%s: missing remote PDH or PLAT_CERT", __func__); ++ return 1; ++ } ++ ++ start.pdh_cert_uaddr = (uintptr_t) s->remote_pdh; ++ start.pdh_cert_len = s->remote_pdh_len; ++ ++ start.plat_certs_uaddr = (uintptr_t)s->remote_plat_cert; ++ start.plat_certs_len = s->remote_plat_cert_len; ++ ++ start.amd_certs_uaddr = (uintptr_t)s->amd_cert; ++ start.amd_certs_len = s->amd_cert_len; ++ ++ /* get the session length */ ++ session_len = sev_get_send_session_length(); ++ if (session_len < 0) { ++ ret = 1; ++ goto err; ++ } ++ ++ session = g_new0(guchar, session_len); ++ start.session_uaddr = (unsigned long)session; ++ start.session_len = session_len; ++ ++ /* Get our PDH certificate */ ++ ret = sev_get_pdh_info(s->sev_fd, &pdh, &pdh_len, ++ &plat_cert, &plat_cert_len, &local_err); ++ if (ret) { ++ error_report("Failed to get our PDH cert"); ++ goto err; ++ } ++ ++ trace_kvm_sev_send_start(start.pdh_cert_uaddr, start.pdh_cert_len, ++ start.plat_certs_uaddr, start.plat_certs_len, ++ start.amd_certs_uaddr, start.amd_certs_len); ++ ++ ret = sev_ioctl(s->sev_fd, KVM_SEV_SEND_START, &start, &fw_error); ++ if (ret < 0) { ++ error_report("%s: SEND_START ret=%d fw_error=%d '%s'", ++ __func__, ret, fw_error, fw_error_to_str(fw_error)); ++ goto err; ++ } ++ ++ qemu_put_be32(f, start.policy); ++ qemu_put_be32(f, pdh_len); ++ qemu_put_buffer(f, (uint8_t *)pdh, pdh_len); ++ qemu_put_be32(f, start.session_len); ++ qemu_put_buffer(f, (uint8_t *)start.session_uaddr, start.session_len); ++ *bytes_sent = 12 + pdh_len + start.session_len; ++ ++ sev_set_guest_state(s, SEV_STATE_SEND_UPDATE); ++ ++err: ++ g_free(pdh); ++ g_free(plat_cert); ++ return ret; ++} ++ ++static int ++sev_send_get_packet_len(int *fw_err) ++{ ++ int ret; ++ struct kvm_sev_send_update_data update = { 0, }; ++ ++ ret = sev_ioctl(sev_guest->sev_fd, KVM_SEV_SEND_UPDATE_DATA, ++ &update, fw_err); ++ if (*fw_err != SEV_RET_INVALID_LEN) { ++ ret = -1; ++ error_report("%s: failed to get session length ret=%d fw_error=%d '%s'", ++ __func__, ret, *fw_err, fw_error_to_str(*fw_err)); ++ goto err; ++ } ++ ++ ret = update.hdr_len; ++ ++err: ++ return ret; ++} ++ ++static int ++sev_send_update_data(SevGuestState *s, QEMUFile *f, uint8_t *ptr, uint32_t size, ++ uint64_t *bytes_sent) ++{ ++ int ret, fw_error; ++ guchar *trans; ++ struct kvm_sev_send_update_data update = { }; ++ ++ /* ++ * If this is first call then query the packet header bytes and allocate ++ * the packet buffer. ++ */ ++ if (!s->send_packet_hdr) { ++ s->send_packet_hdr_len = sev_send_get_packet_len(&fw_error); ++ if (s->send_packet_hdr_len < 1) { ++ error_report("%s: SEND_UPDATE fw_error=%d '%s'", ++ __func__, fw_error, fw_error_to_str(fw_error)); ++ return 1; ++ } ++ ++ s->send_packet_hdr = g_new(gchar, s->send_packet_hdr_len); ++ } ++ ++ /* allocate transport buffer */ ++ trans = g_new(guchar, size); ++ ++ update.hdr_uaddr = (uintptr_t)s->send_packet_hdr; ++ update.hdr_len = s->send_packet_hdr_len; ++ update.guest_uaddr = (uintptr_t)ptr; ++ update.guest_len = size; ++ update.trans_uaddr = (uintptr_t)trans; ++ update.trans_len = size; ++ ++ trace_kvm_sev_send_update_data(ptr, trans, size); ++ ++ ret = sev_ioctl(s->sev_fd, KVM_SEV_SEND_UPDATE_DATA, &update, &fw_error); ++ if (ret) { ++ error_report("%s: SEND_UPDATE_DATA ret=%d fw_error=%d '%s'", ++ __func__, ret, fw_error, fw_error_to_str(fw_error)); ++ goto err; ++ } ++ ++ qemu_put_be32(f, update.hdr_len); ++ qemu_put_buffer(f, (uint8_t *)update.hdr_uaddr, update.hdr_len); ++ *bytes_sent = 4 + update.hdr_len; ++ ++ qemu_put_be32(f, update.trans_len); ++ qemu_put_buffer(f, (uint8_t *)update.trans_uaddr, update.trans_len); ++ *bytes_sent += (4 + update.trans_len); ++ ++err: ++ g_free(trans); ++ return ret; ++} ++ ++int sev_save_outgoing_page(QEMUFile *f, uint8_t *ptr, ++ uint32_t sz, uint64_t *bytes_sent) ++{ ++ SevGuestState *s = sev_guest; ++ ++ /* ++ * If this is a first buffer then create outgoing encryption context ++ * and write our PDH, policy and session data. ++ */ ++ if (!sev_check_state(s, SEV_STATE_SEND_UPDATE) && ++ sev_send_start(s, f, bytes_sent)) { ++ error_report("Failed to create outgoing context"); ++ return 1; ++ } ++ ++ return sev_send_update_data(s, f, ptr, sz, bytes_sent); ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 14801ec44..19d3136dd 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -53,6 +53,8 @@ extern bool sev_add_kernel_loader_hashes(SevKernelLoaderContext *ctx, Error **er + int sev_encrypt_flash(uint8_t *ptr, uint64_t len, Error **errp); + int sev_save_setup(const char *pdh, const char *plat_cert, + const char *amd_cert); ++int sev_save_outgoing_page(QEMUFile *f, uint8_t *ptr, ++ uint32_t size, uint64_t *bytes_sent); + int sev_inject_launch_secret(const char *hdr, const char *secret, + uint64_t gpa, Error **errp); + +diff --git a/target/i386/trace-events b/target/i386/trace-events +index b7da9bd74..c165e3737 100644 +--- a/target/i386/trace-events ++++ b/target/i386/trace-events +@@ -11,6 +11,9 @@ kvm_sev_launch_measurement(const char *value) "data %s" + kvm_sev_launch_finish(void) "" + kvm_sev_launch_secret(uint64_t hpa, uint64_t hva, uint64_t secret, int len) "hpa 0x%" PRIx64 " hva 0x%" PRIx64 " data 0x%" PRIx64 " len %d" + kvm_sev_attestation_report(const char *mnonce, const char *data) "mnonce %s data %s" ++kvm_sev_send_start(uint64_t pdh, int l1, uint64_t plat, int l2, uint64_t amd, int l3) "pdh 0x%" PRIx64 " len %d plat 0x%" PRIx64 " len %d amd 0x%" PRIx64 " len %d" ++kvm_sev_send_update_data(void *src, void *dst, int len) "guest %p trans %p len %d" ++kvm_sev_send_finish(void) "" + + # csv.c + kvm_csv_launch_encrypt_data(uint64_t gpa, void *addr, uint64_t len) "gpa 0x%" PRIx64 "addr %p len 0x%" PRIu64 +-- +2.31.1 + diff --git a/1044-target-i386-sev-add-support-to-load-incoming-encrypt.patch b/1044-target-i386-sev-add-support-to-load-incoming-encrypt.patch new file mode 100644 index 0000000000000000000000000000000000000000..cf10880f4f496086547e9a234f48925431b51e3a --- /dev/null +++ b/1044-target-i386-sev-add-support-to-load-incoming-encrypt.patch @@ -0,0 +1,224 @@ +From 10e03772a0a14f65602c496d65a4392b5b70da51 Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Tue, 27 Jul 2021 13:00:50 +0000 +Subject: [PATCH 07/29] target/i386: sev: add support to load incoming + encrypted page + +cherry-picked from https://github.com/AMDESE/qemu/commit/e86e5dccb045. + +The sev_load_incoming_page() provide the implementation to read the +incoming guest private pages from the socket and load it into the guest +memory. The routines uses the RECEIVE_START command to create the +incoming encryption context on the first call then uses the +RECEIEVE_UPDATE_DATA command to load the encrypted pages into the guest +memory. After migration is completed, we issue the RECEIVE_FINISH command +to transition the SEV guest to the runnable state so that it can be +executed. + +Signed-off-by: Brijesh Singh +Co-developed-by: Ashish Kalra +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + target/i386/sev.c | 137 ++++++++++++++++++++++++++++++++++++++- + target/i386/sev.h | 1 + + target/i386/trace-events | 3 + + 3 files changed, 140 insertions(+), 1 deletion(-) + +diff --git a/target/i386/sev.c b/target/i386/sev.c +index 622150106..bdd77fd0e 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -175,6 +175,7 @@ static const char *const sev_fw_errlist[] = { + static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .save_setup = sev_save_setup, + .save_outgoing_page = sev_save_outgoing_page, ++ .load_incoming_page = sev_load_incoming_page, + }; + + static int +@@ -877,13 +878,33 @@ sev_launch_finish(SevGuestState *sev) + migrate_add_blocker(sev_mig_blocker, &error_fatal); + } + ++static int ++sev_receive_finish(SevGuestState *s) ++{ ++ int error, ret = 1; ++ ++ trace_kvm_sev_receive_finish(); ++ ret = sev_ioctl(s->sev_fd, KVM_SEV_RECEIVE_FINISH, 0, &error); ++ if (ret) { ++ error_report("%s: RECEIVE_FINISH ret=%d fw_error=%d '%s'", ++ __func__, ret, error, fw_error_to_str(error)); ++ goto err; ++ } ++ ++ sev_set_guest_state(s, SEV_STATE_RUNNING); ++err: ++ return ret; ++} ++ + static void + sev_vm_state_change(void *opaque, bool running, RunState state) + { + SevGuestState *sev = opaque; + + if (running) { +- if (!sev_check_state(sev, SEV_STATE_RUNNING)) { ++ if (sev_check_state(sev, SEV_STATE_RECEIVE_UPDATE)) { ++ sev_receive_finish(sev); ++ } else if (!sev_check_state(sev, SEV_STATE_RUNNING)) { + sev_launch_finish(sev); + } + } +@@ -1504,6 +1525,120 @@ int sev_save_outgoing_page(QEMUFile *f, uint8_t *ptr, + return sev_send_update_data(s, f, ptr, sz, bytes_sent); + } + ++static int ++sev_receive_start(SevGuestState *sev, QEMUFile *f) ++{ ++ int ret = 1; ++ int fw_error; ++ struct kvm_sev_receive_start start = { }; ++ gchar *session = NULL, *pdh_cert = NULL; ++ ++ /* get SEV guest handle */ ++ start.handle = object_property_get_int(OBJECT(sev), "handle", ++ &error_abort); ++ ++ /* get the source policy */ ++ start.policy = qemu_get_be32(f); ++ ++ /* get source PDH key */ ++ start.pdh_len = qemu_get_be32(f); ++ if (!check_blob_length(start.pdh_len)) { ++ return 1; ++ } ++ ++ pdh_cert = g_new(gchar, start.pdh_len); ++ qemu_get_buffer(f, (uint8_t *)pdh_cert, start.pdh_len); ++ start.pdh_uaddr = (uintptr_t)pdh_cert; ++ ++ /* get source session data */ ++ start.session_len = qemu_get_be32(f); ++ if (!check_blob_length(start.session_len)) { ++ return 1; ++ } ++ session = g_new(gchar, start.session_len); ++ qemu_get_buffer(f, (uint8_t *)session, start.session_len); ++ start.session_uaddr = (uintptr_t)session; ++ ++ trace_kvm_sev_receive_start(start.policy, session, pdh_cert); ++ ++ ret = sev_ioctl(sev_guest->sev_fd, KVM_SEV_RECEIVE_START, ++ &start, &fw_error); ++ if (ret < 0) { ++ error_report("Error RECEIVE_START ret=%d fw_error=%d '%s'", ++ ret, fw_error, fw_error_to_str(fw_error)); ++ goto err; ++ } ++ ++ object_property_set_int(OBJECT(sev), "handle", start.handle, &error_abort); ++ sev_set_guest_state(sev, SEV_STATE_RECEIVE_UPDATE); ++err: ++ g_free(session); ++ g_free(pdh_cert); ++ ++ return ret; ++} ++ ++static int sev_receive_update_data(QEMUFile *f, uint8_t *ptr) ++{ ++ int ret = 1, fw_error = 0; ++ gchar *hdr = NULL, *trans = NULL; ++ struct kvm_sev_receive_update_data update = {}; ++ ++ /* get packet header */ ++ update.hdr_len = qemu_get_be32(f); ++ if (!check_blob_length(update.hdr_len)) { ++ return 1; ++ } ++ ++ hdr = g_new(gchar, update.hdr_len); ++ qemu_get_buffer(f, (uint8_t *)hdr, update.hdr_len); ++ update.hdr_uaddr = (uintptr_t)hdr; ++ ++ /* get transport buffer */ ++ update.trans_len = qemu_get_be32(f); ++ if (!check_blob_length(update.trans_len)) { ++ goto err; ++ } ++ ++ trans = g_new(gchar, update.trans_len); ++ update.trans_uaddr = (uintptr_t)trans; ++ qemu_get_buffer(f, (uint8_t *)update.trans_uaddr, update.trans_len); ++ ++ update.guest_uaddr = (uintptr_t) ptr; ++ update.guest_len = update.trans_len; ++ ++ trace_kvm_sev_receive_update_data(trans, ptr, update.guest_len, ++ hdr, update.hdr_len); ++ ++ ret = sev_ioctl(sev_guest->sev_fd, KVM_SEV_RECEIVE_UPDATE_DATA, ++ &update, &fw_error); ++ if (ret) { ++ error_report("Error RECEIVE_UPDATE_DATA ret=%d fw_error=%d '%s'", ++ ret, fw_error, fw_error_to_str(fw_error)); ++ goto err; ++ } ++err: ++ g_free(trans); ++ g_free(hdr); ++ return ret; ++} ++ ++int sev_load_incoming_page(QEMUFile *f, uint8_t *ptr) ++{ ++ SevGuestState *s = sev_guest; ++ ++ /* ++ * If this is first buffer and SEV is not in recieiving state then ++ * use RECEIVE_START command to create a encryption context. ++ */ ++ if (!sev_check_state(s, SEV_STATE_RECEIVE_UPDATE) && ++ sev_receive_start(s, f)) { ++ return 1; ++ } ++ ++ return sev_receive_update_data(f, ptr); ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 19d3136dd..1040b0cb2 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -55,6 +55,7 @@ int sev_save_setup(const char *pdh, const char *plat_cert, + const char *amd_cert); + int sev_save_outgoing_page(QEMUFile *f, uint8_t *ptr, + uint32_t size, uint64_t *bytes_sent); ++int sev_load_incoming_page(QEMUFile *f, uint8_t *ptr); + int sev_inject_launch_secret(const char *hdr, const char *secret, + uint64_t gpa, Error **errp); + +diff --git a/target/i386/trace-events b/target/i386/trace-events +index c165e3737..e32b0319b 100644 +--- a/target/i386/trace-events ++++ b/target/i386/trace-events +@@ -14,6 +14,9 @@ kvm_sev_attestation_report(const char *mnonce, const char *data) "mnonce %s data + kvm_sev_send_start(uint64_t pdh, int l1, uint64_t plat, int l2, uint64_t amd, int l3) "pdh 0x%" PRIx64 " len %d plat 0x%" PRIx64 " len %d amd 0x%" PRIx64 " len %d" + kvm_sev_send_update_data(void *src, void *dst, int len) "guest %p trans %p len %d" + kvm_sev_send_finish(void) "" ++kvm_sev_receive_start(int policy, void *session, void *pdh) "policy 0x%x session %p pdh %p" ++kvm_sev_receive_update_data(void *src, void *dst, int len, void *hdr, int hdr_len) "guest %p trans %p len %d hdr %p hdr_len %d" ++kvm_sev_receive_finish(void) "" + + # csv.c + kvm_csv_launch_encrypt_data(uint64_t gpa, void *addr, uint64_t len) "gpa 0x%" PRIx64 "addr %p len 0x%" PRIu64 +-- +2.31.1 + diff --git a/1045-kvm-Add-support-for-SEV-shared-regions-list-and-KVM_.patch b/1045-kvm-Add-support-for-SEV-shared-regions-list-and-KVM_.patch new file mode 100644 index 0000000000000000000000000000000000000000..27daa6b4a3e53ba382064110007e3099d34e11cf --- /dev/null +++ b/1045-kvm-Add-support-for-SEV-shared-regions-list-and-KVM_.patch @@ -0,0 +1,293 @@ +From da756577d513a92fdfd4c1bdda3961dc704ccf12 Mon Sep 17 00:00:00 2001 +From: Ashish Kalra +Date: Tue, 27 Jul 2021 15:05:49 +0000 +Subject: [PATCH 08/29] kvm: Add support for SEV shared regions list and + KVM_EXIT_HYPERCALL. + +cherry-picked from https://github.com/AMDESE/qemu/commit/fcbbd9b19ac. + +KVM_HC_MAP_GPA_RANGE hypercall is used by the SEV guest to notify a +change in the page encryption status to the hypervisor. The hypercall +should be invoked only when the encryption attribute is changed from +encrypted -> decrypted and vice versa. By default all guest pages are +considered encrypted. + +The hypercall exits to userspace with KVM_EXIT_HYPERCALL exit code, +currently this is used only by SEV guests for guest page encryptiion +status tracking. Add support to handle this exit and invoke SEV +shared regions list handlers. + +Add support for SEV guest shared regions and implementation of the +SEV shared regions list. + +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + linux-headers/linux/kvm.h | 3 ++ + target/i386/kvm/kvm.c | 48 +++++++++++++++++ + target/i386/sev.c | 105 ++++++++++++++++++++++++++++++++++++++ + target/i386/sev.h | 3 ++ + 4 files changed, 159 insertions(+) + +diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h +index 3bc062a18..b640e72c8 100644 +--- a/linux-headers/linux/kvm.h ++++ b/linux-headers/linux/kvm.h +@@ -344,6 +344,7 @@ struct kvm_run { + } mmio; + /* KVM_EXIT_HYPERCALL */ + struct { ++#define KVM_HC_MAP_GPA_RANGE 12 + __u64 nr; + __u64 args[6]; + __u64 ret; +@@ -1154,6 +1155,8 @@ struct kvm_ppc_resize_hpt { + #define KVM_CAP_S390_ZPCI_OP 221 + #define KVM_CAP_S390_CPU_TOPOLOGY 222 + ++#define KVM_EXIT_HYPERCALL_VALID_MASK (1 << KVM_HC_MAP_GPA_RANGE) ++ + #ifdef KVM_CAP_IRQ_ROUTING + + struct kvm_irq_routing_irqchip { +diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c +index a06221d3e..5262806c4 100644 +--- a/target/i386/kvm/kvm.c ++++ b/target/i386/kvm/kvm.c +@@ -127,6 +127,7 @@ static int has_xsave2; + static int has_xcrs; + static int has_pit_state2; + static int has_exception_payload; ++static int has_map_gpa_range; + + static bool has_msr_mcg_ext_ctl; + +@@ -2052,6 +2053,17 @@ int kvm_arch_init_vcpu(CPUState *cs) + c->eax = MAX(c->eax, KVM_CPUID_SIGNATURE | 0x10); + } + ++ if (sev_enabled()) { ++ c = cpuid_find_entry(&cpuid_data.cpuid, ++ KVM_CPUID_FEATURES | kvm_base, 0); ++ if (c) { ++ c->eax |= (1 << KVM_FEATURE_MIGRATION_CONTROL); ++ if (has_map_gpa_range) { ++ c->eax |= (1 << KVM_FEATURE_HC_MAP_GPA_RANGE); ++ } ++ } ++ } ++ + cpuid_data.cpuid.nent = cpuid_i; + + cpuid_data.cpuid.padding = 0; +@@ -2397,6 +2409,17 @@ int kvm_arch_init(MachineState *ms, KVMState *s) + } + } + ++ has_map_gpa_range = kvm_check_extension(s, KVM_CAP_EXIT_HYPERCALL); ++ if (has_map_gpa_range) { ++ ret = kvm_vm_enable_cap(s, KVM_CAP_EXIT_HYPERCALL, 0, ++ KVM_EXIT_HYPERCALL_VALID_MASK); ++ if (ret < 0) { ++ error_report("kvm: Failed to enable MAP_GPA_RANGE cap: %s", ++ strerror(-ret)); ++ return ret; ++ } ++ } ++ + ret = kvm_get_supported_msrs(s); + if (ret < 0) { + return ret; +@@ -4612,6 +4635,28 @@ static int kvm_handle_tpr_access(X86CPU *cpu) + return 1; + } + ++static int kvm_handle_exit_hypercall(X86CPU *cpu, struct kvm_run *run) ++{ ++ /* ++ * Currently this exit is only used by SEV guests for ++ * guest page encryption status tracking. ++ */ ++ if (run->hypercall.nr == KVM_HC_MAP_GPA_RANGE) { ++ unsigned long enc = run->hypercall.args[2]; ++ unsigned long gpa = run->hypercall.args[0]; ++ unsigned long npages = run->hypercall.args[1]; ++ unsigned long gfn_start = gpa >> TARGET_PAGE_BITS; ++ unsigned long gfn_end = gfn_start + npages; ++ ++ if (enc) { ++ sev_remove_shared_regions_list(gfn_start, gfn_end); ++ } else { ++ sev_add_shared_regions_list(gfn_start, gfn_end); ++ } ++ } ++ return 0; ++} ++ + int kvm_arch_insert_sw_breakpoint(CPUState *cs, struct kvm_sw_breakpoint *bp) + { + static const uint8_t int3 = 0xcc; +@@ -4902,6 +4947,9 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) + /* already handled in kvm_arch_post_run */ + ret = 0; + break; ++ case KVM_EXIT_HYPERCALL: ++ ret = kvm_handle_exit_hypercall(cpu, run); ++ break; + default: + fprintf(stderr, "KVM: unknown exit reason %d\n", run->exit_reason); + ret = -1; +diff --git a/target/i386/sev.c b/target/i386/sev.c +index bdd77fd0e..de57b0a27 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -45,6 +45,10 @@ + #define TYPE_SEV_GUEST "sev-guest" + OBJECT_DECLARE_SIMPLE_TYPE(SevGuestState, SEV_GUEST) + ++struct shared_region { ++ unsigned long gfn_start, gfn_end; ++ QTAILQ_ENTRY(shared_region) list; ++}; + + extern struct sev_ops sev_ops; + +@@ -90,6 +94,8 @@ struct SevGuestState { + uint32_t reset_cs; + uint32_t reset_ip; + bool reset_data_valid; ++ ++ QTAILQ_HEAD(, shared_region) shared_regions_list; + }; + + #define DEFAULT_GUEST_POLICY 0x1 /* disable debug */ +@@ -1105,6 +1111,7 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) + add_migration_state_change_notifier(&sev_migration_state_notify); + + cgs_class->memory_encryption_ops = &sev_memory_encryption_ops; ++ QTAILQ_INIT(&sev->shared_regions_list); + + cgs->ready = true; + +@@ -1639,6 +1646,104 @@ int sev_load_incoming_page(QEMUFile *f, uint8_t *ptr) + return sev_receive_update_data(f, ptr); + } + ++int sev_remove_shared_regions_list(unsigned long start, unsigned long end) ++{ ++ SevGuestState *s = sev_guest; ++ struct shared_region *pos; ++ ++ QTAILQ_FOREACH(pos, &s->shared_regions_list, list) { ++ unsigned long l, r; ++ unsigned long curr_gfn_end = pos->gfn_end; ++ ++ /* ++ * Find if any intersection exists ? ++ * left bound for intersecting segment ++ */ ++ l = MAX(start, pos->gfn_start); ++ /* right bound for intersecting segment */ ++ r = MIN(end, pos->gfn_end); ++ if (l <= r) { ++ if (pos->gfn_start == l && pos->gfn_end == r) { ++ QTAILQ_REMOVE(&s->shared_regions_list, pos, list); ++ } else if (l == pos->gfn_start) { ++ pos->gfn_start = r; ++ } else if (r == pos->gfn_end) { ++ pos->gfn_end = l; ++ } else { ++ /* Do a de-merge -- split linked list nodes */ ++ struct shared_region *shrd_region; ++ ++ pos->gfn_end = l; ++ shrd_region = g_malloc0(sizeof(*shrd_region)); ++ if (!shrd_region) { ++ return 0; ++ } ++ shrd_region->gfn_start = r; ++ shrd_region->gfn_end = curr_gfn_end; ++ QTAILQ_INSERT_AFTER(&s->shared_regions_list, pos, ++ shrd_region, list); ++ } ++ } ++ if (end <= curr_gfn_end) { ++ break; ++ } ++ } ++ return 0; ++} ++ ++int sev_add_shared_regions_list(unsigned long start, unsigned long end) ++{ ++ struct shared_region *shrd_region; ++ struct shared_region *pos; ++ SevGuestState *s = sev_guest; ++ ++ if (QTAILQ_EMPTY(&s->shared_regions_list)) { ++ shrd_region = g_malloc0(sizeof(*shrd_region)); ++ if (!shrd_region) { ++ return -1; ++ } ++ shrd_region->gfn_start = start; ++ shrd_region->gfn_end = end; ++ QTAILQ_INSERT_TAIL(&s->shared_regions_list, shrd_region, list); ++ return 0; ++ } ++ ++ /* ++ * shared regions list is a sorted list in ascending order ++ * of guest PA's and also merges consecutive range of guest PA's ++ */ ++ QTAILQ_FOREACH(pos, &s->shared_regions_list, list) { ++ /* handle duplicate overlapping regions */ ++ if (start >= pos->gfn_start && end <= pos->gfn_end) { ++ return 0; ++ } ++ if (pos->gfn_end < start) { ++ continue; ++ } ++ /* merge consecutive guest PA(s) -- forward merge */ ++ if (pos->gfn_start <= start && pos->gfn_end >= start) { ++ pos->gfn_end = end; ++ return 0; ++ } ++ break; ++ } ++ /* ++ * Add a new node ++ */ ++ shrd_region = g_malloc0(sizeof(*shrd_region)); ++ if (!shrd_region) { ++ return -1; ++ } ++ shrd_region->gfn_start = start; ++ shrd_region->gfn_end = end; ++ if (pos) { ++ QTAILQ_INSERT_BEFORE(pos, shrd_region, list); ++ } else { ++ QTAILQ_INSERT_TAIL(&s->shared_regions_list, shrd_region, list); ++ } ++ return 1; ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 1040b0cb2..8423b5522 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -61,6 +61,9 @@ int sev_inject_launch_secret(const char *hdr, const char *secret, + + int sev_es_save_reset_vector(void *flash_ptr, uint64_t flash_size); + void sev_es_set_reset_vector(CPUState *cpu); ++int sev_remove_shared_regions_list(unsigned long gfn_start, ++ unsigned long gfn_end); ++int sev_add_shared_regions_list(unsigned long gfn_start, unsigned long gfn_end); + + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); + +-- +2.31.1 + diff --git a/1046-migration-add-support-to-migrate-shared-regions-list.patch b/1046-migration-add-support-to-migrate-shared-regions-list.patch new file mode 100644 index 0000000000000000000000000000000000000000..72e8cbe7d0a7e92ca524e40c7b0b31bb10aa55be --- /dev/null +++ b/1046-migration-add-support-to-migrate-shared-regions-list.patch @@ -0,0 +1,103 @@ +From 9f9b6fbfc4cf9f73a001686954d34c40b29e3538 Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Tue, 27 Jul 2021 16:31:36 +0000 +Subject: [PATCH 09/29] migration: add support to migrate shared regions list + +cherry-picked from https://github.com/AMDESE/qemu/commit/9236f522e48b6. + +When memory encryption is enabled, the hypervisor maintains a shared +regions list which is referred by hypervisor during migration to check +if page is private or shared. This list is built during the VM bootup and +must be migrated to the target host so that hypervisor on target host can +use it for future migration. + +Signed-off-by: Brijesh Singh +Co-developed-by: Ashish Kalra +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + target/i386/sev.c | 43 +++++++++++++++++++++++++++++++++++++++++++ + target/i386/sev.h | 2 ++ + 2 files changed, 45 insertions(+) + +diff --git a/target/i386/sev.c b/target/i386/sev.c +index de57b0a27..30cd436ab 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -178,10 +178,15 @@ static const char *const sev_fw_errlist[] = { + + #define SEV_FW_BLOB_MAX_SIZE 0x4000 /* 16KB */ + ++#define SHARED_REGION_LIST_CONT 0x1 ++#define SHARED_REGION_LIST_END 0x2 ++ + static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .save_setup = sev_save_setup, + .save_outgoing_page = sev_save_outgoing_page, + .load_incoming_page = sev_load_incoming_page, ++ .save_outgoing_shared_regions_list = sev_save_outgoing_shared_regions_list, ++ .load_incoming_shared_regions_list = sev_load_incoming_shared_regions_list, + }; + + static int +@@ -1744,6 +1749,44 @@ int sev_add_shared_regions_list(unsigned long start, unsigned long end) + return 1; + } + ++int sev_save_outgoing_shared_regions_list(QEMUFile *f) ++{ ++ SevGuestState *s = sev_guest; ++ struct shared_region *pos; ++ ++ QTAILQ_FOREACH(pos, &s->shared_regions_list, list) { ++ qemu_put_be32(f, SHARED_REGION_LIST_CONT); ++ qemu_put_be32(f, pos->gfn_start); ++ qemu_put_be32(f, pos->gfn_end); ++ } ++ ++ qemu_put_be32(f, SHARED_REGION_LIST_END); ++ return 0; ++} ++ ++int sev_load_incoming_shared_regions_list(QEMUFile *f) ++{ ++ SevGuestState *s = sev_guest; ++ struct shared_region *shrd_region; ++ int status; ++ ++ status = qemu_get_be32(f); ++ while (status == SHARED_REGION_LIST_CONT) { ++ ++ shrd_region = g_malloc0(sizeof(*shrd_region)); ++ if (!shrd_region) { ++ return 0; ++ } ++ shrd_region->gfn_start = qemu_get_be32(f); ++ shrd_region->gfn_end = qemu_get_be32(f); ++ ++ QTAILQ_INSERT_TAIL(&s->shared_regions_list, shrd_region, list); ++ ++ status = qemu_get_be32(f); ++ } ++ return 0; ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 8423b5522..34814b9f2 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -64,6 +64,8 @@ void sev_es_set_reset_vector(CPUState *cpu); + int sev_remove_shared_regions_list(unsigned long gfn_start, + unsigned long gfn_end); + int sev_add_shared_regions_list(unsigned long gfn_start, unsigned long gfn_end); ++int sev_save_outgoing_shared_regions_list(QEMUFile *f); ++int sev_load_incoming_shared_regions_list(QEMUFile *f); + + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); + +-- +2.31.1 + diff --git a/1047-migration-ram-add-support-to-send-encrypted-pages.patch b/1047-migration-ram-add-support-to-send-encrypted-pages.patch new file mode 100644 index 0000000000000000000000000000000000000000..42223d6b7d6fe6c08de040212718b9579dc7ec14 --- /dev/null +++ b/1047-migration-ram-add-support-to-send-encrypted-pages.patch @@ -0,0 +1,338 @@ +From a973e7ee1489c9ee089f8c502f9b10f5ca4a4772 Mon Sep 17 00:00:00 2001 +From: Brijesh Singh +Date: Tue, 27 Jul 2021 16:53:19 +0000 +Subject: [PATCH 10/29] migration/ram: add support to send encrypted pages + +cherry-picked from https://github.com/AMDESE/qemu/commit/2d6bda0d4cf. + +When memory encryption is enabled, the guest memory will be encrypted with +the guest specific key. The patch introduces RAM_SAVE_FLAG_ENCRYPTED_PAGE +flag to distinguish the encrypted data from plaintext. Encrypted pages +may need special handling. The sev_save_outgoing_page() is used +by the sender to write the encrypted pages onto the socket, similarly the +sev_load_incoming_page() is used by the target to read the +encrypted pages from the socket and load into the guest memory. + +Signed-off-by: Brijesh Singh +Co-developed-by: Ashish Kalra +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + migration/migration.h | 2 + + migration/ram.c | 163 +++++++++++++++++++++++++++++++++++++++++- + target/i386/sev.c | 14 ++++ + target/i386/sev.h | 4 ++ + 4 files changed, 182 insertions(+), 1 deletion(-) + +diff --git a/migration/migration.h b/migration/migration.h +index 0ae213332..596668d80 100644 +--- a/migration/migration.h ++++ b/migration/migration.h +@@ -403,4 +403,6 @@ void migration_cancel(const Error *error); + + void populate_vfio_info(MigrationInfo *info); + ++bool memcrypt_enabled(void); ++ + #endif +diff --git a/migration/ram.c b/migration/ram.c +index 93cdb456a..57f2c01bf 100644 +--- a/migration/ram.c ++++ b/migration/ram.c +@@ -55,6 +55,10 @@ + #include "qemu/iov.h" + #include "multifd.h" + #include "sysemu/runstate.h" ++#include "exec/confidential-guest-support.h" ++ ++/* Defines RAM_SAVE_ENCRYPTED_PAGE and RAM_SAVE_SHARED_REGION_LIST */ ++#include "target/i386/sev.h" + + #include "hw/boards.h" /* for machine_dump_guest_core() */ + +@@ -80,6 +84,16 @@ + #define RAM_SAVE_FLAG_XBZRLE 0x40 + /* 0x80 is reserved in migration.h start with 0x100 next */ + #define RAM_SAVE_FLAG_COMPRESS_PAGE 0x100 ++#define RAM_SAVE_FLAG_ENCRYPTED_DATA 0x200 ++ ++bool memcrypt_enabled(void) ++{ ++ MachineState *ms = MACHINE(qdev_get_machine()); ++ if(ms->cgs) ++ return ms->cgs->ready; ++ else ++ return false; ++} + + static inline bool is_zero_range(uint8_t *p, uint64_t size) + { +@@ -468,6 +482,8 @@ static QemuCond decomp_done_cond; + + static bool do_compress_ram_page(QEMUFile *f, z_stream *stream, RAMBlock *block, + ram_addr_t offset, uint8_t *source_buf); ++static int ram_save_encrypted_page(RAMState *rs, PageSearchStatus *pss, ++ bool last_stage); + + static void *do_data_compress(void *opaque) + { +@@ -1301,6 +1317,80 @@ static int save_normal_page(RAMState *rs, RAMBlock *block, ram_addr_t offset, + return 1; + } + ++/** ++ * ram_save_encrypted_page - send the given encrypted page to the stream ++ */ ++static int ram_save_encrypted_page(RAMState *rs, PageSearchStatus *pss, ++ bool last_stage) ++{ ++ int ret; ++ uint8_t *p; ++ RAMBlock *block = pss->block; ++ ram_addr_t offset = pss->page << TARGET_PAGE_BITS; ++ uint64_t bytes_xmit; ++ MachineState *ms = MACHINE(qdev_get_machine()); ++ ConfidentialGuestSupportClass *cgs_class = ++ (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); ++ struct ConfidentialGuestMemoryEncryptionOps *ops = ++ cgs_class->memory_encryption_ops; ++ ++ p = block->host + offset; ++ ++ ram_counters.transferred += ++ save_page_header(rs, rs->f, block, ++ offset | RAM_SAVE_FLAG_ENCRYPTED_DATA); ++ ++ qemu_put_be32(rs->f, RAM_SAVE_ENCRYPTED_PAGE); ++ ret = ops->save_outgoing_page(rs->f, p, TARGET_PAGE_SIZE, &bytes_xmit); ++ if (ret) { ++ return -1; ++ } ++ ++ ram_counters.transferred += bytes_xmit; ++ ram_counters.normal++; ++ ++ return 1; ++} ++ ++/** ++ * ram_save_shared_region_list: send the shared region list ++ */ ++static int ram_save_shared_region_list(RAMState *rs, QEMUFile *f) ++{ ++ MachineState *ms = MACHINE(qdev_get_machine()); ++ ConfidentialGuestSupportClass *cgs_class = ++ (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); ++ struct ConfidentialGuestMemoryEncryptionOps *ops = ++ cgs_class->memory_encryption_ops; ++ ++ save_page_header(rs, rs->f, rs->last_seen_block, ++ RAM_SAVE_FLAG_ENCRYPTED_DATA); ++ qemu_put_be32(rs->f, RAM_SAVE_SHARED_REGIONS_LIST); ++ return ops->save_outgoing_shared_regions_list(rs->f); ++} ++ ++static int load_encrypted_data(QEMUFile *f, uint8_t *ptr) ++{ ++ MachineState *ms = MACHINE(qdev_get_machine()); ++ ConfidentialGuestSupportClass *cgs_class = ++ (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); ++ struct ConfidentialGuestMemoryEncryptionOps *ops = ++ cgs_class->memory_encryption_ops; ++ ++ int flag; ++ ++ flag = qemu_get_be32(f); ++ ++ if (flag == RAM_SAVE_ENCRYPTED_PAGE) { ++ return ops->load_incoming_page(f, ptr); ++ } else if (flag == RAM_SAVE_SHARED_REGIONS_LIST) { ++ return ops->load_incoming_shared_regions_list(f); ++ } else { ++ error_report("unknown encrypted flag %x", flag); ++ return 1; ++ } ++} ++ + /** + * ram_save_page: send the given page to the stream + * +@@ -2144,6 +2234,35 @@ static bool save_compress_page(RAMState *rs, RAMBlock *block, ram_addr_t offset) + return false; + } + ++/** ++ * encrypted_test_list: check if the page is encrypted ++ * ++ * Returns a bool indicating whether the page is encrypted. ++ */ ++static bool encrypted_test_list(RAMState *rs, RAMBlock *block, ++ unsigned long page) ++{ ++ MachineState *ms = MACHINE(qdev_get_machine()); ++ ConfidentialGuestSupportClass *cgs_class = ++ (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); ++ struct ConfidentialGuestMemoryEncryptionOps *ops = ++ cgs_class->memory_encryption_ops; ++ unsigned long gfn; ++ ++ /* ROM devices contains the unencrypted data */ ++ if (memory_region_is_rom(block->mr)) { ++ return false; ++ } ++ ++ /* ++ * Translate page in ram_addr_t address space to GPA address ++ * space using memory region. ++ */ ++ gfn = page + (block->mr->addr >> TARGET_PAGE_BITS); ++ ++ return ops->is_gfn_in_unshared_region(gfn); ++} ++ + /** + * ram_save_target_page: save one target page + * +@@ -2164,6 +2283,17 @@ static int ram_save_target_page(RAMState *rs, PageSearchStatus *pss, + return res; + } + ++ /* ++ * If memory encryption is enabled then use memory encryption APIs ++ * to write the outgoing buffer to the wire. The encryption APIs ++ * will take care of accessing the guest memory and re-encrypt it ++ * for the transport purposes. ++ */ ++ if (memcrypt_enabled() && ++ encrypted_test_list(rs, pss->block, pss->page)) { ++ return ram_save_encrypted_page(rs, pss, last_stage); ++ } ++ + if (save_compress_page(rs, block, offset)) { + return 1; + } +@@ -2990,6 +3120,18 @@ void qemu_guest_free_page_hint(void *addr, size_t len) + } + } + ++static int ram_encrypted_save_setup(void) ++{ ++ MachineState *ms = MACHINE(qdev_get_machine()); ++ ConfidentialGuestSupportClass *cgs_class = ++ (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); ++ struct ConfidentialGuestMemoryEncryptionOps *ops = ++ cgs_class->memory_encryption_ops; ++ MigrationParameters *p = &migrate_get_current()->parameters; ++ ++ return ops->save_setup(p->sev_pdh, p->sev_plat_cert, p->sev_amd_cert); ++} ++ + /* + * Each of ram_save_setup, ram_save_iterate and ram_save_complete has + * long-running RCU critical section. When rcu-reclaims in the code +@@ -3025,6 +3167,13 @@ static int ram_save_setup(QEMUFile *f, void *opaque) + (*rsp)->f = f; + + WITH_RCU_READ_LOCK_GUARD() { ++ ++ if (memcrypt_enabled()) { ++ if (ram_encrypted_save_setup()) { ++ return -1; ++ } ++ } ++ + qemu_put_be64(f, ram_bytes_total_common(true) | RAM_SAVE_FLAG_MEM_SIZE); + + RAMBLOCK_FOREACH_MIGRATABLE(block) { +@@ -3217,6 +3366,11 @@ static int ram_save_complete(QEMUFile *f, void *opaque) + + flush_compressed_data(rs); + ram_control_after_iterate(f, RAM_CONTROL_FINISH); ++ ++ /* send the shared regions list */ ++ if (memcrypt_enabled()) { ++ ret = ram_save_shared_region_list(rs, f); ++ } + } + + if (ret < 0) { +@@ -4038,7 +4192,8 @@ static int ram_load_precopy(QEMUFile *f) + } + + if (flags & (RAM_SAVE_FLAG_ZERO | RAM_SAVE_FLAG_PAGE | +- RAM_SAVE_FLAG_COMPRESS_PAGE | RAM_SAVE_FLAG_XBZRLE)) { ++ RAM_SAVE_FLAG_COMPRESS_PAGE | RAM_SAVE_FLAG_XBZRLE | ++ RAM_SAVE_FLAG_ENCRYPTED_DATA)) { + RAMBlock *block = ram_block_from_stream(f, flags); + + host = host_from_ram_block_offset(block, addr); +@@ -4167,6 +4322,12 @@ static int ram_load_precopy(QEMUFile *f) + break; + } + break; ++ case RAM_SAVE_FLAG_ENCRYPTED_DATA: ++ if (load_encrypted_data(f, host)) { ++ error_report("Failed to load encrypted data"); ++ ret = -EINVAL; ++ } ++ break; + case RAM_SAVE_FLAG_EOS: + /* normal exit */ + multifd_recv_sync_main(); +diff --git a/target/i386/sev.c b/target/i386/sev.c +index 30cd436ab..cc63a1aef 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -185,6 +185,7 @@ static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .save_setup = sev_save_setup, + .save_outgoing_page = sev_save_outgoing_page, + .load_incoming_page = sev_load_incoming_page, ++ .is_gfn_in_unshared_region = sev_is_gfn_in_unshared_region, + .save_outgoing_shared_regions_list = sev_save_outgoing_shared_regions_list, + .load_incoming_shared_regions_list = sev_load_incoming_shared_regions_list, + }; +@@ -1787,6 +1788,19 @@ int sev_load_incoming_shared_regions_list(QEMUFile *f) + return 0; + } + ++bool sev_is_gfn_in_unshared_region(unsigned long gfn) ++{ ++ SevGuestState *s = sev_guest; ++ struct shared_region *pos; ++ ++ QTAILQ_FOREACH(pos, &s->shared_regions_list, list) { ++ if (gfn >= pos->gfn_start && gfn < pos->gfn_end) { ++ return false; ++ } ++ } ++ return true; ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 34814b9f2..b8ad84014 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -38,6 +38,9 @@ typedef struct SevKernelLoaderContext { + size_t cmdline_size; + } SevKernelLoaderContext; + ++#define RAM_SAVE_ENCRYPTED_PAGE 0x1 ++#define RAM_SAVE_SHARED_REGIONS_LIST 0x2 ++ + #ifdef CONFIG_SEV + bool sev_enabled(void); + bool sev_es_enabled(void); +@@ -66,6 +69,7 @@ int sev_remove_shared_regions_list(unsigned long gfn_start, + int sev_add_shared_regions_list(unsigned long gfn_start, unsigned long gfn_end); + int sev_save_outgoing_shared_regions_list(QEMUFile *f); + int sev_load_incoming_shared_regions_list(QEMUFile *f); ++bool sev_is_gfn_in_unshared_region(unsigned long gfn); + + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); + +-- +2.31.1 + diff --git a/1048-migration-ram-Force-encrypted-status-for-flash0-flas.patch b/1048-migration-ram-Force-encrypted-status-for-flash0-flas.patch new file mode 100644 index 0000000000000000000000000000000000000000..8202afc3cf26d55da3f0e4570327b34be7cd8571 --- /dev/null +++ b/1048-migration-ram-Force-encrypted-status-for-flash0-flas.patch @@ -0,0 +1,44 @@ +From aae5a6ae741a985ff74b0ae7540d3c6ce3ef2fd0 Mon Sep 17 00:00:00 2001 +From: Ashish Kalra +Date: Tue, 27 Jul 2021 18:05:25 +0000 +Subject: [PATCH 11/29] migration/ram: Force encrypted status for flash0 & + flash1 devices. + +cherry-picked from https://github.com/AMDESE/qemu/commit/803d6a4c8d. + +Currently OVMF clears the C-bit and marks NonExistent memory space +as decrypted in the page encryption bitmap. By marking the +NonExistent memory space as decrypted it gurantees any future MMIO adds +will work correctly, but this marks flash0 device space as decrypted. +At reset the SEV core will be in forced encrypted state, so this +decrypted marking of flash0 device space will cause VCPU reset to fail +as flash0 device pages will be migrated incorrectly. + +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + migration/ram.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/migration/ram.c b/migration/ram.c +index 57f2c01bf..da7a80994 100644 +--- a/migration/ram.c ++++ b/migration/ram.c +@@ -2254,6 +2254,14 @@ static bool encrypted_test_list(RAMState *rs, RAMBlock *block, + return false; + } + ++ if (!strcmp(memory_region_name(block->mr), "system.flash0")) { ++ return true; ++ } ++ ++ if (!strcmp(memory_region_name(block->mr), "system.flash1")) { ++ return false; ++ } ++ + /* + * Translate page in ram_addr_t address space to GPA address + * space using memory region. +-- +2.31.1 + diff --git a/1049-migration-for-SEV-live-migration-bump-downtime-limit.patch b/1049-migration-for-SEV-live-migration-bump-downtime-limit.patch new file mode 100644 index 0000000000000000000000000000000000000000..70e9213e42c607b79906296778f6a28e666dff31 --- /dev/null +++ b/1049-migration-for-SEV-live-migration-bump-downtime-limit.patch @@ -0,0 +1,64 @@ +From a102d449e9601125c226cf40bc57b705bd1e3c28 Mon Sep 17 00:00:00 2001 +From: Ashish Kalra +Date: Tue, 3 Aug 2021 16:06:27 +0000 +Subject: [PATCH 12/29] migration: for SEV live migration bump downtime limit + to 1s. + +cherry-picked from https://github.com/AMDESE/qemu/commit/b1468803a2200. + +Now, qemu has a default expected downtime of 300 ms and +SEV Live migration has a page-per-second bandwidth of 350-450 pages +( SEV Live migration being generally slow due to guest RAM pages +being migrated after encryption using the security processor ). +With this expected downtime of 300ms and 350-450 pps bandwith, +the threshold size = <1/3 of the PPS bandwidth = ~100 pages. + +Now, this threshold size is the maximum pages/bytes that can be +sent in the final completion phase of Live migration +(where the source VM is stopped) with the expected downtime. +Therefore, with the threshold size computed above, +the migration completion phase which halts the source VM +and then transfers the leftover dirty pages, +is only reached in SEV live migration case when # of dirty pages are ~100. + +The dirty-pages-rate with larger guest RAM configuration like 4G, 8G, etc. +is much higher, typically in the range of 300-400+ pages, hence, +we always remain in the "dirty-sync" phase of migration and never +reach the migration completion phase with above guest RAM configs. + +To summarize, with larger guest RAM configs, +the dirty-pages-rate > threshold_size (with the default qemu expected downtime of 300ms). + +So, the fix is to increase qemu's expected downtime. + +This is a tweakable parameter which can be set using "migrate_set_downtime". + +With a downtime of 1 second, we get a threshold size of ~350-450 pages, +which will handle the "dirty-pages-rate" of 300+ pages and complete +the migration process, so we bump the default downtime to 1s in case +of SEV live migration being active. + +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + migration/migration.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/migration/migration.c b/migration/migration.c +index 6810bcefc..27c875c6c 100644 +--- a/migration/migration.c ++++ b/migration/migration.c +@@ -3663,6 +3663,10 @@ static void migration_update_counters(MigrationState *s, + transferred = current_bytes - s->iteration_initial_bytes; + time_spent = current_time - s->iteration_start_time; + bandwidth = (double)transferred / time_spent; ++ if (memcrypt_enabled() && ++ s->parameters.downtime_limit < 1000) { ++ s->parameters.downtime_limit = 1000; ++ } + s->threshold_size = bandwidth * s->parameters.downtime_limit; + + s->mbps = (((double) transferred * 8.0) / +-- +2.31.1 + diff --git a/1050-i386-kvm-Add-support-for-MSR-filtering.patch b/1050-i386-kvm-Add-support-for-MSR-filtering.patch new file mode 100644 index 0000000000000000000000000000000000000000..c967cf53f26f3e1b6d59f4350bc459bc90ca85bc --- /dev/null +++ b/1050-i386-kvm-Add-support-for-MSR-filtering.patch @@ -0,0 +1,201 @@ +From b45eabe2febd210065e964b6425f73f1d53af43e Mon Sep 17 00:00:00 2001 +From: Alexander Graf +Date: Wed, 5 Oct 2022 00:56:42 +0200 +Subject: [PATCH 13/29] i386: kvm: Add support for MSR filtering + +commit 860054d8ce4067ef2bc3deb2a98cf93350fc03e4 upstream. + +KVM has grown support to deflect arbitrary MSRs to user space since +Linux 5.10. For now we don't expect to make a lot of use of this +feature, so let's expose it the easiest way possible: With up to 16 +individually maskable MSRs. + +This patch adds a kvm_filter_msr() function that other code can call +to install a hook on KVM MSR reads or writes. + +Signed-off-by: Alexander Graf +Message-Id: <20221004225643.65036-3-agraf@csgraf.de> +Signed-off-by: Paolo Bonzini +--- + target/i386/kvm/kvm.c | 123 +++++++++++++++++++++++++++++++++++++ + target/i386/kvm/kvm_i386.h | 11 ++++ + 2 files changed, 134 insertions(+) + +diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c +index 5262806c4..607469fb3 100644 +--- a/target/i386/kvm/kvm.c ++++ b/target/i386/kvm/kvm.c +@@ -135,6 +135,8 @@ static struct kvm_cpuid2 *cpuid_cache; + static struct kvm_cpuid2 *hv_cpuid_cache; + static struct kvm_msr_list *kvm_feature_msrs; + ++static KVMMSRHandlers msr_handlers[KVM_MSR_FILTER_MAX_RANGES]; ++ + #define BUS_LOCK_SLICE_TIME 1000000000ULL /* ns */ + static RateLimit bus_lock_ratelimit_ctrl; + +@@ -2524,6 +2526,15 @@ int kvm_arch_init(MachineState *ms, KVMState *s) + x86ms->bus_lock_ratelimit, BUS_LOCK_SLICE_TIME); + } + } ++ if (kvm_vm_check_extension(s, KVM_CAP_X86_USER_SPACE_MSR)) { ++ ret = kvm_vm_enable_cap(s, KVM_CAP_X86_USER_SPACE_MSR, 0, ++ KVM_MSR_EXIT_REASON_FILTER); ++ if (ret) { ++ error_report("Could not enable user space MSRs: %s", ++ strerror(-ret)); ++ exit(1); ++ } ++ } + + return 0; + } +@@ -4848,6 +4859,108 @@ void kvm_arch_update_guest_debug(CPUState *cpu, struct kvm_guest_debug *dbg) + } + } + ++static bool kvm_install_msr_filters(KVMState *s) ++{ ++ uint64_t zero = 0; ++ struct kvm_msr_filter filter = { ++ .flags = KVM_MSR_FILTER_DEFAULT_ALLOW, ++ }; ++ int r, i, j = 0; ++ ++ for (i = 0; i < KVM_MSR_FILTER_MAX_RANGES; i++) { ++ KVMMSRHandlers *handler = &msr_handlers[i]; ++ if (handler->msr) { ++ struct kvm_msr_filter_range *range = &filter.ranges[j++]; ++ ++ *range = (struct kvm_msr_filter_range) { ++ .flags = 0, ++ .nmsrs = 1, ++ .base = handler->msr, ++ .bitmap = (__u8 *)&zero, ++ }; ++ ++ if (handler->rdmsr) { ++ range->flags |= KVM_MSR_FILTER_READ; ++ } ++ ++ if (handler->wrmsr) { ++ range->flags |= KVM_MSR_FILTER_WRITE; ++ } ++ } ++ } ++ ++ r = kvm_vm_ioctl(s, KVM_X86_SET_MSR_FILTER, &filter); ++ if (r) { ++ return false; ++ } ++ ++ return true; ++} ++ ++bool kvm_filter_msr(KVMState *s, uint32_t msr, QEMURDMSRHandler *rdmsr, ++ QEMUWRMSRHandler *wrmsr) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(msr_handlers); i++) { ++ if (!msr_handlers[i].msr) { ++ msr_handlers[i] = (KVMMSRHandlers) { ++ .msr = msr, ++ .rdmsr = rdmsr, ++ .wrmsr = wrmsr, ++ }; ++ ++ if (!kvm_install_msr_filters(s)) { ++ msr_handlers[i] = (KVMMSRHandlers) { }; ++ return false; ++ } ++ ++ return true; ++ } ++ } ++ ++ return false; ++} ++ ++static int kvm_handle_rdmsr(X86CPU *cpu, struct kvm_run *run) ++{ ++ int i; ++ bool r; ++ ++ for (i = 0; i < ARRAY_SIZE(msr_handlers); i++) { ++ KVMMSRHandlers *handler = &msr_handlers[i]; ++ if (run->msr.index == handler->msr) { ++ if (handler->rdmsr) { ++ r = handler->rdmsr(cpu, handler->msr, ++ (uint64_t *)&run->msr.data); ++ run->msr.error = r ? 0 : 1; ++ return 0; ++ } ++ } ++ } ++ ++ assert(false); ++} ++ ++static int kvm_handle_wrmsr(X86CPU *cpu, struct kvm_run *run) ++{ ++ int i; ++ bool r; ++ ++ for (i = 0; i < ARRAY_SIZE(msr_handlers); i++) { ++ KVMMSRHandlers *handler = &msr_handlers[i]; ++ if (run->msr.index == handler->msr) { ++ if (handler->wrmsr) { ++ r = handler->wrmsr(cpu, handler->msr, run->msr.data); ++ run->msr.error = r ? 0 : 1; ++ return 0; ++ } ++ } ++ } ++ ++ assert(false); ++} ++ + static bool has_sgx_provisioning; + + static bool __kvm_enable_sgx_provisioning(KVMState *s) +@@ -4947,6 +5060,16 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) + /* already handled in kvm_arch_post_run */ + ret = 0; + break; ++ case KVM_EXIT_X86_RDMSR: ++ /* We only enable MSR filtering, any other exit is bogus */ ++ assert(run->msr.reason == KVM_MSR_EXIT_REASON_FILTER); ++ ret = kvm_handle_rdmsr(cpu, run); ++ break; ++ case KVM_EXIT_X86_WRMSR: ++ /* We only enable MSR filtering, any other exit is bogus */ ++ assert(run->msr.reason == KVM_MSR_EXIT_REASON_FILTER); ++ ret = kvm_handle_wrmsr(cpu, run); ++ break; + case KVM_EXIT_HYPERCALL: + ret = kvm_handle_exit_hypercall(cpu, run); + break; +diff --git a/target/i386/kvm/kvm_i386.h b/target/i386/kvm/kvm_i386.h +index 4124912c2..2ed586c11 100644 +--- a/target/i386/kvm/kvm_i386.h ++++ b/target/i386/kvm/kvm_i386.h +@@ -54,4 +54,15 @@ uint64_t kvm_swizzle_msi_ext_dest_id(uint64_t address); + bool kvm_enable_sgx_provisioning(KVMState *s); + void kvm_request_xsave_components(X86CPU *cpu, uint64_t mask); + ++typedef bool QEMURDMSRHandler(X86CPU *cpu, uint32_t msr, uint64_t *val); ++typedef bool QEMUWRMSRHandler(X86CPU *cpu, uint32_t msr, uint64_t val); ++typedef struct kvm_msr_handlers { ++ uint32_t msr; ++ QEMURDMSRHandler *rdmsr; ++ QEMUWRMSRHandler *wrmsr; ++} KVMMSRHandlers; ++ ++bool kvm_filter_msr(KVMState *s, uint32_t msr, QEMURDMSRHandler *rdmsr, ++ QEMUWRMSRHandler *wrmsr); ++ + #endif +-- +2.31.1 + diff --git a/1051-kvm-Add-support-for-userspace-MSR-filtering-and-hand.patch b/1051-kvm-Add-support-for-userspace-MSR-filtering-and-hand.patch new file mode 100644 index 0000000000000000000000000000000000000000..998be40766f3c5ede01fa12f90c399e8000797a5 --- /dev/null +++ b/1051-kvm-Add-support-for-userspace-MSR-filtering-and-hand.patch @@ -0,0 +1,118 @@ +From f2f9d5860a3a32431b397f4a643de16423b5e4b3 Mon Sep 17 00:00:00 2001 +From: Ashish Kalra +Date: Tue, 27 Jul 2021 17:59:33 +0000 +Subject: [PATCH 14/29] kvm: Add support for userspace MSR filtering and + handling of MSR_KVM_MIGRATION_CONTROL. + +cherry-picked from https://github.com/AMDESE/qemu/commit/67935c3fd5f. + +Add support for userspace MSR filtering using KVM_X86_SET_MSR_FILTER +ioctl and handling of MSRs in userspace. Currently this is only used +for SEV guests which use MSR_KVM_MIGRATION_CONTROL to indicate if the +guest is enabled and ready for migration. + +KVM arch code calls into SEV guest specific code to delete the +SEV migrate blocker which has been setup at SEV_LAUNCH_FINISH. + +Signed-off-by: Ashish Kalra +Signed-off-by: hanliyang +--- + target/i386/kvm/kvm.c | 37 +++++++++++++++++++++++++++++++++++++ + target/i386/sev.c | 6 ++++++ + target/i386/sev.h | 1 + + 3 files changed, 44 insertions(+) + +diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c +index 607469fb3..a833219d0 100644 +--- a/target/i386/kvm/kvm.c ++++ b/target/i386/kvm/kvm.c +@@ -2330,6 +2330,32 @@ static int kvm_get_supported_msrs(KVMState *s) + return ret; + } + ++/* ++ * Currently this exit is only used by SEV guests for ++ * MSR_KVM_MIGRATION_CONTROL to indicate if the guest ++ * is ready for migration. ++ */ ++static uint64_t msr_kvm_migration_control; ++ ++static bool kvm_rdmsr_kvm_migration_control(X86CPU *cpu, uint32_t msr, ++ uint64_t *val) ++{ ++ *val = msr_kvm_migration_control; ++ ++ return true; ++} ++ ++static bool kvm_wrmsr_kvm_migration_control(X86CPU *cpu, uint32_t msr, ++ uint64_t val) ++{ ++ msr_kvm_migration_control = val; ++ ++ if (val == KVM_MIGRATION_READY) ++ sev_del_migrate_blocker(); ++ ++ return true; ++} ++ + static Notifier smram_machine_done; + static KVMMemoryListener smram_listener; + static AddressSpace smram_address_space; +@@ -2527,6 +2553,8 @@ int kvm_arch_init(MachineState *ms, KVMState *s) + } + } + if (kvm_vm_check_extension(s, KVM_CAP_X86_USER_SPACE_MSR)) { ++ bool r; ++ + ret = kvm_vm_enable_cap(s, KVM_CAP_X86_USER_SPACE_MSR, 0, + KVM_MSR_EXIT_REASON_FILTER); + if (ret) { +@@ -2534,6 +2562,15 @@ int kvm_arch_init(MachineState *ms, KVMState *s) + strerror(-ret)); + exit(1); + } ++ ++ r = kvm_filter_msr(s, MSR_KVM_MIGRATION_CONTROL, ++ kvm_rdmsr_kvm_migration_control, ++ kvm_wrmsr_kvm_migration_control); ++ if (!r) { ++ error_report("Could not install MSR_KVM_MIGRATION_CONTROL handler: %s", ++ strerror(-ret)); ++ exit(1); ++ } + } + + return 0; +diff --git a/target/i386/sev.c b/target/i386/sev.c +index cc63a1aef..b66665021 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -890,6 +890,12 @@ sev_launch_finish(SevGuestState *sev) + migrate_add_blocker(sev_mig_blocker, &error_fatal); + } + ++void ++sev_del_migrate_blocker(void) ++{ ++ migrate_del_blocker(sev_mig_blocker); ++} ++ + static int + sev_receive_finish(SevGuestState *s) + { +diff --git a/target/i386/sev.h b/target/i386/sev.h +index b8ad84014..7e53f8461 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -70,6 +70,7 @@ int sev_add_shared_regions_list(unsigned long gfn_start, unsigned long gfn_end); + int sev_save_outgoing_shared_regions_list(QEMUFile *f); + int sev_load_incoming_shared_regions_list(QEMUFile *f); + bool sev_is_gfn_in_unshared_region(unsigned long gfn); ++void sev_del_migrate_blocker(void); + + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); + +-- +2.31.1 + diff --git a/1052-anolis-migration-ram-Force-encrypted-status-for-VGA-.patch b/1052-anolis-migration-ram-Force-encrypted-status-for-VGA-.patch new file mode 100644 index 0000000000000000000000000000000000000000..b9cfa6e482d6cffe5ae88e926ad899db5c1a4337 --- /dev/null +++ b/1052-anolis-migration-ram-Force-encrypted-status-for-VGA-.patch @@ -0,0 +1,33 @@ +From 1e332429c9aa4c996191ec703e3ec10a6bd459b3 Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Tue, 8 Dec 2020 22:57:46 -0500 +Subject: [PATCH 15/29] anolis: migration/ram: Force encrypted status for VGA + vram + +The VGA vram memory region act as frame buffer of VM. This memory +is decrypted in the QEMU process. For CSV VM live migration, we +should avoid memory encryption status check on VGA vram. + +Signed-off-by: hanliyang +--- + migration/ram.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/migration/ram.c b/migration/ram.c +index da7a80994..fb05ff44e 100644 +--- a/migration/ram.c ++++ b/migration/ram.c +@@ -2262,6 +2262,10 @@ static bool encrypted_test_list(RAMState *rs, RAMBlock *block, + return false; + } + ++ if (!strcmp(memory_region_name(block->mr), "vga.vram")) { ++ return false; ++ } ++ + /* + * Translate page in ram_addr_t address space to GPA address + * space using memory region. +-- +2.31.1 + diff --git a/1053-anolis-target-i386-sev-Clear-shared_regions_list-whe.patch b/1053-anolis-target-i386-sev-Clear-shared_regions_list-whe.patch new file mode 100644 index 0000000000000000000000000000000000000000..9e00e5478a02a482daa4036f21aa281cb670fbf6 --- /dev/null +++ b/1053-anolis-target-i386-sev-Clear-shared_regions_list-whe.patch @@ -0,0 +1,58 @@ +From 6ff9e3e73b0a756c9a42f2df43178ef52cf5aa26 Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Sun, 16 Jan 2022 19:57:58 -0500 +Subject: [PATCH 16/29] anolis: target/i386: sev: Clear shared_regions_list + when reboot CSV Guest + +Also fix memory leak in sev_remove_shared_regions_list(). + +Signed-off-by: hanliyang +--- + target/i386/kvm/kvm.c | 6 ++++++ + target/i386/sev.c | 5 +++-- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c +index a833219d0..f0b61ec3c 100644 +--- a/target/i386/kvm/kvm.c ++++ b/target/i386/kvm/kvm.c +@@ -2143,6 +2143,12 @@ void kvm_arch_reset_vcpu(X86CPU *cpu) + + hyperv_x86_synic_reset(cpu); + } ++ ++ if (cpu_is_bsp(cpu) && ++ sev_enabled() && has_map_gpa_range) { ++ sev_remove_shared_regions_list(0, -1); ++ } ++ + /* enabled by default */ + env->poll_control_msr = 1; + +diff --git a/target/i386/sev.c b/target/i386/sev.c +index b66665021..e3eab4119 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -1661,9 +1661,9 @@ int sev_load_incoming_page(QEMUFile *f, uint8_t *ptr) + int sev_remove_shared_regions_list(unsigned long start, unsigned long end) + { + SevGuestState *s = sev_guest; +- struct shared_region *pos; ++ struct shared_region *pos, *next_pos; + +- QTAILQ_FOREACH(pos, &s->shared_regions_list, list) { ++ QTAILQ_FOREACH_SAFE(pos, &s->shared_regions_list, list, next_pos) { + unsigned long l, r; + unsigned long curr_gfn_end = pos->gfn_end; + +@@ -1677,6 +1677,7 @@ int sev_remove_shared_regions_list(unsigned long start, unsigned long end) + if (l <= r) { + if (pos->gfn_start == l && pos->gfn_end == r) { + QTAILQ_REMOVE(&s->shared_regions_list, pos, list); ++ g_free(pos); + } else if (l == pos->gfn_start) { + pos->gfn_start = r; + } else if (r == pos->gfn_end) { +-- +2.31.1 + diff --git a/1054-anolis-migration-ram-Fix-calculation-of-gfn-correpon.patch b/1054-anolis-migration-ram-Fix-calculation-of-gfn-correpon.patch new file mode 100644 index 0000000000000000000000000000000000000000..d385706e40ad6a607d1a03e9f63fcc9b911ab945 --- /dev/null +++ b/1054-anolis-migration-ram-Fix-calculation-of-gfn-correpon.patch @@ -0,0 +1,57 @@ +From 7a8e357130982817732272c89a53adbb13408832 Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Sun, 16 Jan 2022 20:05:02 -0500 +Subject: [PATCH 17/29] anolis: migration/ram: Fix calculation of gfn correpond + to a page in ramblock + +A RAMBlock contains a host memory region which may consist of many +discontiguous MemoryRegion in AddressSpace of a Guest, so we cannot +get gpa by MemoryRegion.addr. Since KVM memslot records the relationship +between gpa and hva, so we can pass the hva of page in RAMBlock to +kvm_phisical_memory_addr_from_host() to get the expected gpa. + +Signed-off-by: hanliyang +--- + migration/ram.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/migration/ram.c b/migration/ram.c +index fb05ff44e..33e2c9d5f 100644 +--- a/migration/ram.c ++++ b/migration/ram.c +@@ -59,6 +59,7 @@ + + /* Defines RAM_SAVE_ENCRYPTED_PAGE and RAM_SAVE_SHARED_REGION_LIST */ + #include "target/i386/sev.h" ++#include "sysemu/kvm.h" + + #include "hw/boards.h" /* for machine_dump_guest_core() */ + +@@ -2248,6 +2249,8 @@ static bool encrypted_test_list(RAMState *rs, RAMBlock *block, + struct ConfidentialGuestMemoryEncryptionOps *ops = + cgs_class->memory_encryption_ops; + unsigned long gfn; ++ hwaddr paddr = 0; ++ int ret; + + /* ROM devices contains the unencrypted data */ + if (memory_region_is_rom(block->mr)) { +@@ -2270,7 +2273,14 @@ static bool encrypted_test_list(RAMState *rs, RAMBlock *block, + * Translate page in ram_addr_t address space to GPA address + * space using memory region. + */ +- gfn = page + (block->mr->addr >> TARGET_PAGE_BITS); ++ if (kvm_enabled()) { ++ ret = kvm_physical_memory_addr_from_host(kvm_state, ++ block->host + (page << TARGET_PAGE_BITS), &paddr); ++ if (ret == 0) { ++ return false; ++ } ++ } ++ gfn = paddr >> TARGET_PAGE_BITS; + + return ops->is_gfn_in_unshared_region(gfn); + } +-- +2.31.1 + diff --git a/1055-anolis-target-i386-csv-Move-is_hygon_cpu-to-header-f.patch b/1055-anolis-target-i386-csv-Move-is_hygon_cpu-to-header-f.patch new file mode 100644 index 0000000000000000000000000000000000000000..41c62695afc73330f7081d283a548a5671b6f729 --- /dev/null +++ b/1055-anolis-target-i386-csv-Move-is_hygon_cpu-to-header-f.patch @@ -0,0 +1,85 @@ +From 00e7b439f3c514d8ae20c20d5f18d24d71351c7d Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Wed, 1 Nov 2023 06:00:11 +0000 +Subject: [PATCH 18/29] anolis: target/i386: csv: Move is_hygon_cpu() to header + file csv.h + +The helper function is_hygon_cpu() will be called by functions out of +target/i386/csv.c. Move it to header file csv.h so that it can be a +common helper function. + +Signed-off-by: hanliyang +--- + target/i386/csv.c | 20 -------------------- + target/i386/csv.h | 22 ++++++++++++++++++++++ + 2 files changed, 22 insertions(+), 20 deletions(-) + +diff --git a/target/i386/csv.c b/target/i386/csv.c +index d166d3775..161cad39a 100644 +--- a/target/i386/csv.c ++++ b/target/i386/csv.c +@@ -27,28 +27,8 @@ + + CsvGuestState csv_guest = { 0 }; + +-#define CPUID_VENDOR_HYGON_EBX 0x6f677948 /* "Hygo" */ +-#define CPUID_VENDOR_HYGON_ECX 0x656e6975 /* "uine" */ +-#define CPUID_VENDOR_HYGON_EDX 0x6e65476e /* "nGen" */ +- + #define GUEST_POLICY_CSV_BIT (1 << 6) + +-static bool is_hygon_cpu(void) +-{ +- uint32_t ebx = 0; +- uint32_t ecx = 0; +- uint32_t edx = 0; +- +- host_cpuid(0, 0, NULL, &ebx, &ecx, &edx); +- +- if (ebx == CPUID_VENDOR_HYGON_EBX && +- ecx == CPUID_VENDOR_HYGON_ECX && +- edx == CPUID_VENDOR_HYGON_EDX) +- return true; +- else +- return false; +-} +- + int + csv_init(uint32_t policy, int fd, void *state, struct sev_ops *ops) + { +diff --git a/target/i386/csv.h b/target/i386/csv.h +index 7dc5c7536..4f5b2773c 100644 +--- a/target/i386/csv.h ++++ b/target/i386/csv.h +@@ -16,6 +16,28 @@ + + #include "qapi/qapi-commands-misc-target.h" + ++#include "cpu.h" ++ ++#define CPUID_VENDOR_HYGON_EBX 0x6f677948 /* "Hygo" */ ++#define CPUID_VENDOR_HYGON_ECX 0x656e6975 /* "uine" */ ++#define CPUID_VENDOR_HYGON_EDX 0x6e65476e /* "nGen" */ ++ ++static __attribute__((unused)) bool is_hygon_cpu(void) ++{ ++ uint32_t ebx = 0; ++ uint32_t ecx = 0; ++ uint32_t edx = 0; ++ ++ host_cpuid(0, 0, NULL, &ebx, &ecx, &edx); ++ ++ if (ebx == CPUID_VENDOR_HYGON_EBX && ++ ecx == CPUID_VENDOR_HYGON_ECX && ++ edx == CPUID_VENDOR_HYGON_EDX) ++ return true; ++ else ++ return false; ++} ++ + #ifdef CONFIG_CSV + bool csv_enabled(void); + #else +-- +2.31.1 + diff --git a/1056-anolis-target-i386-csv-Read-cert-chain-from-file-whe.patch b/1056-anolis-target-i386-csv-Read-cert-chain-from-file-whe.patch new file mode 100644 index 0000000000000000000000000000000000000000..23a528c638742d4939f3a5dd07d63c2554f8709d --- /dev/null +++ b/1056-anolis-target-i386-csv-Read-cert-chain-from-file-whe.patch @@ -0,0 +1,131 @@ +From 980dff011e266d60f8ce669a1dc14cc23b192c8b Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Mon, 13 Nov 2023 21:55:33 +0000 +Subject: [PATCH 19/29] anolis: target/i386: csv: Read cert chain from file + when prepared for CSV live migration + +The cert chain is too long when encoded with base64, use the filename +of cert chain instead of the encoded string when prepared for CSV live +migration. + +Signed-off-by: hanliyang +--- + qapi/migration.json | 24 +++++++++++++++--------- + target/i386/sev.c | 29 +++++++++++++++++++++++++---- + 2 files changed, 40 insertions(+), 13 deletions(-) + +diff --git a/qapi/migration.json b/qapi/migration.json +index bad53a2f8..8632abb20 100644 +--- a/qapi/migration.json ++++ b/qapi/migration.json +@@ -774,14 +774,16 @@ + # block device name if there is one, and to their node name + # otherwise. (Since 5.2) + # +-# @sev-pdh: The target host platform diffie-hellman key encoded in base64 ++# @sev-pdh: The target host platform diffie-hellman key encoded in base64, or ++# pdh filename for hygon + # (Since 4.2) + # +-# @sev-plat-cert: The target host platform certificate chain encoded in base64 ++# @sev-plat-cert: The target host platform certificate chain encoded in base64, ++# or plat cert filename for hygon + # (Since 4.2) + # + # @sev-amd-cert: AMD certificate chain which include ASK and OCA encoded in +-# base64 (Since 4.2) ++# base64, or vendor cert filename for hygon (Since 4.2) + # + # Features: + # @unstable: Member @x-checkpoint-delay is experimental. +@@ -949,14 +951,16 @@ + # block device name if there is one, and to their node name + # otherwise. (Since 5.2) + # +-# @sev-pdh: The target host platform diffie-hellman key encoded in base64 ++# @sev-pdh: The target host platform diffie-hellman key encoded in base64, or ++# pdh filename for hygon + # (Since 4.2) + # +-# @sev-plat-cert: The target host platform certificate chain encoded in base64 ++# @sev-plat-cert: The target host platform certificate chain encoded in base64, ++# or plat cert filename for hygon + # (Since 4.2) + # + # @sev-amd-cert: AMD certificate chain which include ASK and OCA encoded in +-# base64 (Since 4.2) ++# base64, or vendor cert filename for hygon (Since 4.2) + # + # Features: + # @unstable: Member @x-checkpoint-delay is experimental. +@@ -1161,14 +1165,16 @@ + # block device name if there is one, and to their node name + # otherwise. (Since 5.2) + # +-# @sev-pdh: The target host platform diffie-hellman key encoded in base64 ++# @sev-pdh: The target host platform diffie-hellman key encoded in base64, or ++# pdh filename for hygon + # (Since 4.2) + # +-# @sev-plat-cert: The target host platform certificate chain encoded in base64 ++# @sev-plat-cert: The target host platform certificate chain encoded in base64, ++# or plat cert filename for hygon + # (Since 4.2) + # + # @sev-amd-cert: AMD certificate chain which include ASK and OCA encoded in +-# base64 (Since 4.2) ++# base64, or vendor cert filename for hygon (Since 4.2) + # + # Features: + # @unstable: Member @x-checkpoint-delay is experimental. +diff --git a/target/i386/sev.c b/target/i386/sev.c +index e3eab4119..ebe97799c 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -944,18 +944,39 @@ int sev_save_setup(const char *pdh, const char *plat_cert, + { + SevGuestState *s = sev_guest; + +- s->remote_pdh = g_base64_decode(pdh, &s->remote_pdh_len); ++ if (is_hygon_cpu()) { ++ if (sev_read_file_base64(pdh, &s->remote_pdh, ++ &s->remote_pdh_len) < 0) { ++ goto error; ++ } ++ } else { ++ s->remote_pdh = g_base64_decode(pdh, &s->remote_pdh_len); ++ } + if (!check_blob_length(s->remote_pdh_len)) { + goto error; + } + +- s->remote_plat_cert = g_base64_decode(plat_cert, +- &s->remote_plat_cert_len); ++ if (is_hygon_cpu()) { ++ if (sev_read_file_base64(plat_cert, &s->remote_plat_cert, ++ &s->remote_plat_cert_len) < 0) { ++ goto error; ++ } ++ } else { ++ s->remote_plat_cert = g_base64_decode(plat_cert, ++ &s->remote_plat_cert_len); ++ } + if (!check_blob_length(s->remote_plat_cert_len)) { + goto error; + } + +- s->amd_cert = g_base64_decode(amd_cert, &s->amd_cert_len); ++ if (is_hygon_cpu()) { ++ if (sev_read_file_base64(amd_cert, &s->amd_cert, ++ &s->amd_cert_len) < 0) { ++ goto error; ++ } ++ } else { ++ s->amd_cert = g_base64_decode(amd_cert, &s->amd_cert_len); ++ } + if (!check_blob_length(s->amd_cert_len)) { + goto error; + } +-- +2.31.1 + diff --git a/1057-anolis-target-i386-csv-add-support-to-queue-the-outg.patch b/1057-anolis-target-i386-csv-add-support-to-queue-the-outg.patch new file mode 100644 index 0000000000000000000000000000000000000000..f6a3b3b0b771850989aeac27cc9d1e8647dfa2be --- /dev/null +++ b/1057-anolis-target-i386-csv-add-support-to-queue-the-outg.patch @@ -0,0 +1,270 @@ +From 19941d177378b596a52a7905925525d68d152dea Mon Sep 17 00:00:00 2001 +From: fangbaoshun +Date: Mon, 2 Aug 2021 11:00:07 +0800 +Subject: [PATCH 20/29] anolis: target/i386: csv: add support to queue the + outgoing page into a list + +The csv_queue_outgoing_page() provide the implementation to queue the +guest private pages during transmission. The routines queues the outgoing +pages into a listi, and then issues the KVM_CSV_COMMAND_BATCH command to +encrypt the pages togather before writing them to the socket. + +Signed-off-by: hanliyang +--- + include/exec/confidential-guest-support.h | 3 + + linux-headers/linux/kvm.h | 6 + + target/i386/sev.c | 172 ++++++++++++++++++++++ + target/i386/sev.h | 2 + + 4 files changed, 183 insertions(+) + +diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h +index 343f686fc..1f32519d4 100644 +--- a/include/exec/confidential-guest-support.h ++++ b/include/exec/confidential-guest-support.h +@@ -77,6 +77,9 @@ struct ConfidentialGuestMemoryEncryptionOps { + + /* Load the shared regions list */ + int (*load_incoming_shared_regions_list)(QEMUFile *f); ++ ++ /* Queue the encrypted page and metadata associated with it into a list */ ++ int (*queue_outgoing_page)(uint8_t *ptr, uint32_t size, uint64_t addr); + }; + + typedef struct ConfidentialGuestSupportClass { +diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h +index b640e72c8..8dd8c5e6a 100644 +--- a/linux-headers/linux/kvm.h ++++ b/linux-headers/linux/kvm.h +@@ -1938,6 +1938,12 @@ struct kvm_csv_init_data { + __u64 nodemask; + }; + ++struct kvm_csv_batch_list_node { ++ __u64 cmd_data_addr; ++ __u64 addr; ++ __u64 next_cmd_addr; ++}; ++ + #define KVM_DEV_ASSIGN_ENABLE_IOMMU (1 << 0) + #define KVM_DEV_ASSIGN_PCI_2_3 (1 << 1) + #define KVM_DEV_ASSIGN_MASK_INTX (1 << 2) +diff --git a/target/i386/sev.c b/target/i386/sev.c +index ebe97799c..e7ff3f4dd 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -50,6 +50,15 @@ struct shared_region { + QTAILQ_ENTRY(shared_region) list; + }; + ++typedef struct CsvBatchCmdList CsvBatchCmdList; ++typedef void (*CsvDestroyCmdNodeFn) (void *data); ++ ++struct CsvBatchCmdList { ++ struct kvm_csv_batch_list_node *head; ++ struct kvm_csv_batch_list_node *tail; ++ CsvDestroyCmdNodeFn destroy_fn; ++}; ++ + extern struct sev_ops sev_ops; + + /** +@@ -96,6 +105,9 @@ struct SevGuestState { + bool reset_data_valid; + + QTAILQ_HEAD(, shared_region) shared_regions_list; ++ ++ /* link list used for HYGON CSV */ ++ CsvBatchCmdList *csv_batch_cmd_list; + }; + + #define DEFAULT_GUEST_POLICY 0x1 /* disable debug */ +@@ -188,6 +200,7 @@ static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .is_gfn_in_unshared_region = sev_is_gfn_in_unshared_region, + .save_outgoing_shared_regions_list = sev_save_outgoing_shared_regions_list, + .load_incoming_shared_regions_list = sev_load_incoming_shared_regions_list, ++ .queue_outgoing_page = csv_queue_outgoing_page, + }; + + static int +@@ -1829,6 +1842,165 @@ bool sev_is_gfn_in_unshared_region(unsigned long gfn) + return true; + } + ++#include "csv.h" ++ ++static CsvBatchCmdList * ++csv_batch_cmd_list_create(struct kvm_csv_batch_list_node *head, ++ CsvDestroyCmdNodeFn func) ++{ ++ CsvBatchCmdList *csv_batch_cmd_list = ++ g_malloc0(sizeof(*csv_batch_cmd_list)); ++ ++ if (!csv_batch_cmd_list) { ++ return NULL; ++ } ++ ++ csv_batch_cmd_list->head = head; ++ csv_batch_cmd_list->tail = head; ++ csv_batch_cmd_list->destroy_fn = func; ++ ++ return csv_batch_cmd_list; ++} ++ ++static int ++csv_batch_cmd_list_add_after(CsvBatchCmdList *list, ++ struct kvm_csv_batch_list_node *new_node) ++{ ++ list->tail->next_cmd_addr = (__u64)new_node; ++ list->tail = new_node; ++ ++ return 0; ++} ++ ++static struct kvm_csv_batch_list_node * ++csv_batch_cmd_list_node_create(uint64_t cmd_data_addr, uint64_t addr) ++{ ++ struct kvm_csv_batch_list_node *new_node = ++ g_malloc0(sizeof(struct kvm_csv_batch_list_node)); ++ ++ if (!new_node) { ++ return NULL; ++ } ++ ++ new_node->cmd_data_addr = cmd_data_addr; ++ new_node->addr = addr; ++ new_node->next_cmd_addr = 0; ++ ++ return new_node; ++} ++ ++static int csv_batch_cmd_list_destroy(CsvBatchCmdList *list) ++{ ++ struct kvm_csv_batch_list_node *node = list->head; ++ ++ while (node != NULL) { ++ if (list->destroy_fn != NULL) ++ list->destroy_fn((void *)node->cmd_data_addr); ++ ++ list->head = (struct kvm_csv_batch_list_node *)node->next_cmd_addr; ++ g_free(node); ++ node = list->head; ++ } ++ ++ g_free(list); ++ return 0; ++} ++ ++static void send_update_data_free(void *data) ++{ ++ struct kvm_sev_send_update_data *update = ++ (struct kvm_sev_send_update_data *)data; ++ g_free((guchar *)update->hdr_uaddr); ++ g_free((guchar *)update->trans_uaddr); ++ g_free(update); ++} ++ ++static int ++csv_send_queue_data(SevGuestState *s, uint8_t *ptr, ++ uint32_t size, uint64_t addr) ++{ ++ int ret = 0; ++ int fw_error; ++ guchar *trans; ++ guchar *packet_hdr; ++ struct kvm_sev_send_update_data *update; ++ struct kvm_csv_batch_list_node *new_node = NULL; ++ ++ /* If this is first call then query the packet header bytes and allocate ++ * the packet buffer. ++ */ ++ if (s->send_packet_hdr_len < 1) { ++ s->send_packet_hdr_len = sev_send_get_packet_len(&fw_error); ++ if (s->send_packet_hdr_len < 1) { ++ error_report("%s: SEND_UPDATE fw_error=%d '%s'", ++ __func__, fw_error, fw_error_to_str(fw_error)); ++ return 1; ++ } ++ } ++ ++ packet_hdr = g_new(guchar, s->send_packet_hdr_len); ++ memset(packet_hdr, 0, s->send_packet_hdr_len); ++ ++ update = g_new0(struct kvm_sev_send_update_data, 1); ++ ++ /* allocate transport buffer */ ++ trans = g_new(guchar, size); ++ ++ update->hdr_uaddr = (unsigned long)packet_hdr; ++ update->hdr_len = s->send_packet_hdr_len; ++ update->guest_uaddr = (unsigned long)ptr; ++ update->guest_len = size; ++ update->trans_uaddr = (unsigned long)trans; ++ update->trans_len = size; ++ ++ new_node = csv_batch_cmd_list_node_create((uint64_t)update, addr); ++ if (!new_node) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ ++ if (s->csv_batch_cmd_list == NULL) { ++ s->csv_batch_cmd_list = csv_batch_cmd_list_create(new_node, ++ send_update_data_free); ++ if (s->csv_batch_cmd_list == NULL) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } else { ++ /* Add new_node's command address to the last_node */ ++ csv_batch_cmd_list_add_after(s->csv_batch_cmd_list, new_node); ++ } ++ ++ trace_kvm_sev_send_update_data(ptr, trans, size); ++ ++ return ret; ++ ++err: ++ g_free(trans); ++ g_free(update); ++ g_free(packet_hdr); ++ g_free(new_node); ++ if (s->csv_batch_cmd_list) { ++ csv_batch_cmd_list_destroy(s->csv_batch_cmd_list); ++ s->csv_batch_cmd_list = NULL; ++ } ++ return ret; ++} ++ ++int ++csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr) ++{ ++ SevGuestState *s = sev_guest; ++ ++ /* Only support for HYGON CSV */ ++ if (!is_hygon_cpu()) { ++ error_report("Only support enqueue pages for HYGON CSV"); ++ return -EINVAL; ++ } ++ ++ return csv_send_queue_data(s, ptr, sz, addr); ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 7e53f8461..1eba67bcf 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -74,6 +74,8 @@ void sev_del_migrate_blocker(void); + + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); + ++int csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); ++ + struct sev_ops { + int (*sev_ioctl)(int fd, int cmd, void *data, int *error); + const char *(*fw_error_to_str)(int code); +-- +2.31.1 + diff --git a/1058-anolis-target-i386-csv-add-support-to-encrypt-the-ou.patch b/1058-anolis-target-i386-csv-add-support-to-encrypt-the-ou.patch new file mode 100644 index 0000000000000000000000000000000000000000..393b47475269bf0c55f0be42250e6181669bffc1 --- /dev/null +++ b/1058-anolis-target-i386-csv-add-support-to-encrypt-the-ou.patch @@ -0,0 +1,204 @@ +From 500646168eb4dc297d01dbc35759e38d0206a3b6 Mon Sep 17 00:00:00 2001 +From: fangbaoshun +Date: Mon, 2 Aug 2021 11:41:58 +0800 +Subject: [PATCH 21/29] anolis: target/i386: csv: add support to encrypt the + outgoing pages in the list queued before. + +The csv_save_queued_outgoing_pages() provide the implementation to encrypt +the guest private pages during transmission. The routines uses SEND_START +command to create the outgoing encryption context on the first call then +uses COMMAND_BATCH command to send the SEND_UPDATE_DATA commands queued +in the list to encrypt the data before writing it to the socket. While +encrypting the data SEND_UPDATE_DATA produces some metadata (e.g MAC, IV). +The metadata is also sent to the target machine. After migration is completed, +we issue the SEND_FINISH command to transition the SEV guest state from sending +to unrunnable state. + +Signed-off-by: hanliyang +--- + include/exec/confidential-guest-support.h | 4 + + linux-headers/linux/kvm.h | 8 ++ + target/i386/sev.c | 89 +++++++++++++++++++++++ + target/i386/sev.h | 4 + + 4 files changed, 105 insertions(+) + +diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h +index 1f32519d4..1ff4823b6 100644 +--- a/include/exec/confidential-guest-support.h ++++ b/include/exec/confidential-guest-support.h +@@ -80,6 +80,10 @@ struct ConfidentialGuestMemoryEncryptionOps { + + /* Queue the encrypted page and metadata associated with it into a list */ + int (*queue_outgoing_page)(uint8_t *ptr, uint32_t size, uint64_t addr); ++ ++ /* Write the list queued with encrypted pages and metadata associated ++ * with them */ ++ int (*save_queued_outgoing_pages)(QEMUFile *f, uint64_t *bytes_sent); + }; + + typedef struct ConfidentialGuestSupportClass { +diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h +index 8dd8c5e6a..951afc8b2 100644 +--- a/linux-headers/linux/kvm.h ++++ b/linux-headers/linux/kvm.h +@@ -1823,6 +1823,9 @@ enum sev_cmd_id { + /* Guest Migration Extension */ + KVM_SEV_SEND_CANCEL, + ++ /* Hygon CSV batch command */ ++ KVM_CSV_COMMAND_BATCH = 0x18, ++ + KVM_SEV_NR_MAX, + }; + +@@ -1944,6 +1947,11 @@ struct kvm_csv_batch_list_node { + __u64 next_cmd_addr; + }; + ++struct kvm_csv_command_batch { ++ __u32 command_id; ++ __u64 csv_batch_list_uaddr; ++}; ++ + #define KVM_DEV_ASSIGN_ENABLE_IOMMU (1 << 0) + #define KVM_DEV_ASSIGN_PCI_2_3 (1 << 1) + #define KVM_DEV_ASSIGN_MASK_INTX (1 << 2) +diff --git a/target/i386/sev.c b/target/i386/sev.c +index e7ff3f4dd..6ff547a23 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -201,6 +201,7 @@ static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .save_outgoing_shared_regions_list = sev_save_outgoing_shared_regions_list, + .load_incoming_shared_regions_list = sev_load_incoming_shared_regions_list, + .queue_outgoing_page = csv_queue_outgoing_page, ++ .save_queued_outgoing_pages = csv_save_queued_outgoing_pages, + }; + + static int +@@ -1987,6 +1988,70 @@ err: + return ret; + } + ++static int ++csv_command_batch(uint32_t cmd_id, uint64_t head_uaddr, int *fw_err) ++{ ++ int ret; ++ struct kvm_csv_command_batch command_batch = { }; ++ ++ command_batch.command_id = cmd_id; ++ command_batch.csv_batch_list_uaddr = head_uaddr; ++ ++ ret = sev_ioctl(sev_guest->sev_fd, KVM_CSV_COMMAND_BATCH, ++ &command_batch, fw_err); ++ if (ret) { ++ error_report("%s: COMMAND_BATCH ret=%d fw_err=%d '%s'", ++ __func__, ret, *fw_err, fw_error_to_str(*fw_err)); ++ } ++ ++ return ret; ++} ++ ++static int ++csv_send_update_data_batch(SevGuestState *s, QEMUFile *f, uint64_t *bytes_sent) ++{ ++ int ret, fw_error = 0; ++ struct kvm_sev_send_update_data *update; ++ struct kvm_csv_batch_list_node *node; ++ ++ ret = csv_command_batch(KVM_SEV_SEND_UPDATE_DATA, ++ (uint64_t)s->csv_batch_cmd_list->head, &fw_error); ++ if (ret) { ++ error_report("%s: csv_command_batch ret=%d fw_error=%d '%s'", ++ __func__, ret, fw_error, fw_error_to_str(fw_error)); ++ goto err; ++ } ++ ++ *bytes_sent = 0; ++ for (node = s->csv_batch_cmd_list->head; ++ node != NULL; ++ node = (struct kvm_csv_batch_list_node *)node->next_cmd_addr) { ++ if (node != s->csv_batch_cmd_list->head) { ++ /* head's page header is saved before send_update_data */ ++ qemu_put_be64(f, node->addr); ++ *bytes_sent += 8; ++ if (node->next_cmd_addr != 0) ++ qemu_put_be32(f, RAM_SAVE_ENCRYPTED_PAGE_BATCH); ++ else ++ qemu_put_be32(f, RAM_SAVE_ENCRYPTED_PAGE_BATCH_END); ++ *bytes_sent += 4; ++ } ++ update = (struct kvm_sev_send_update_data *)node->cmd_data_addr; ++ qemu_put_be32(f, update->hdr_len); ++ qemu_put_buffer(f, (uint8_t *)update->hdr_uaddr, update->hdr_len); ++ *bytes_sent += (4 + update->hdr_len); ++ ++ qemu_put_be32(f, update->trans_len); ++ qemu_put_buffer(f, (uint8_t *)update->trans_uaddr, update->trans_len); ++ *bytes_sent += (4 + update->trans_len); ++ } ++ ++err: ++ csv_batch_cmd_list_destroy(s->csv_batch_cmd_list); ++ s->csv_batch_cmd_list = NULL; ++ return ret; ++} ++ + int + csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr) + { +@@ -2001,6 +2066,30 @@ csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr) + return csv_send_queue_data(s, ptr, sz, addr); + } + ++int ++csv_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent) ++{ ++ SevGuestState *s = sev_guest; ++ ++ /* Only support for HYGON CSV */ ++ if (!is_hygon_cpu()) { ++ error_report("Only support transfer queued pages for HYGON CSV"); ++ return -EINVAL; ++ } ++ ++ /* ++ * If this is a first buffer then create outgoing encryption context ++ * and write our PDH, policy and session data. ++ */ ++ if (!sev_check_state(s, SEV_STATE_SEND_UPDATE) && ++ sev_send_start(s, f, bytes_sent)) { ++ error_report("Failed to create outgoing context"); ++ return 1; ++ } ++ ++ return csv_send_update_data_batch(s, f, bytes_sent); ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 1eba67bcf..37ed17f32 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -41,6 +41,9 @@ typedef struct SevKernelLoaderContext { + #define RAM_SAVE_ENCRYPTED_PAGE 0x1 + #define RAM_SAVE_SHARED_REGIONS_LIST 0x2 + ++#define RAM_SAVE_ENCRYPTED_PAGE_BATCH 0x4 ++#define RAM_SAVE_ENCRYPTED_PAGE_BATCH_END 0x5 ++ + #ifdef CONFIG_SEV + bool sev_enabled(void); + bool sev_es_enabled(void); +@@ -75,6 +78,7 @@ void sev_del_migrate_blocker(void); + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); + + int csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); ++int csv_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent); + + struct sev_ops { + int (*sev_ioctl)(int fd, int cmd, void *data, int *error); +-- +2.31.1 + diff --git a/1059-anolis-target-i386-csv-add-support-to-queue-the-inco.patch b/1059-anolis-target-i386-csv-add-support-to-queue-the-inco.patch new file mode 100644 index 0000000000000000000000000000000000000000..cc70aac8f9f954197ad85df733242d11b66990cb --- /dev/null +++ b/1059-anolis-target-i386-csv-add-support-to-queue-the-inco.patch @@ -0,0 +1,171 @@ +From 8af625f3b5f4d2eb768b1eee2dc2e7938800d47e Mon Sep 17 00:00:00 2001 +From: fangbaoshun +Date: Mon, 2 Aug 2021 13:49:48 +0800 +Subject: [PATCH 22/29] anolis: target/i386: csv: add support to queue the + incoming page into a list + +The csv_queue_incoming_page() provide the implementation to queue the +guest private pages during transmission. The routines queues the incoming +socket which contains the guest private pages into a list then uses the +COMMAND_BATCH command to load the encrypted pages into the guest memory. + +Signed-off-by: hanliyang +--- + include/exec/confidential-guest-support.h | 3 + + target/i386/sev.c | 92 +++++++++++++++++++++++ + target/i386/sev.h | 1 + + 3 files changed, 96 insertions(+) + +diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h +index 1ff4823b6..530b5057b 100644 +--- a/include/exec/confidential-guest-support.h ++++ b/include/exec/confidential-guest-support.h +@@ -84,6 +84,9 @@ struct ConfidentialGuestMemoryEncryptionOps { + /* Write the list queued with encrypted pages and metadata associated + * with them */ + int (*save_queued_outgoing_pages)(QEMUFile *f, uint64_t *bytes_sent); ++ ++ /* Queue the incoming encrypted page into a list */ ++ int (*queue_incoming_page)(QEMUFile *f, uint8_t *ptr); + }; + + typedef struct ConfidentialGuestSupportClass { +diff --git a/target/i386/sev.c b/target/i386/sev.c +index 6ff547a23..e235fb207 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -202,6 +202,7 @@ static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .load_incoming_shared_regions_list = sev_load_incoming_shared_regions_list, + .queue_outgoing_page = csv_queue_outgoing_page, + .save_queued_outgoing_pages = csv_save_queued_outgoing_pages, ++ .queue_incoming_page = csv_queue_incoming_page, + }; + + static int +@@ -1916,6 +1917,15 @@ static void send_update_data_free(void *data) + g_free(update); + } + ++static void receive_update_data_free(void *data) ++{ ++ struct kvm_sev_receive_update_data *update = ++ (struct kvm_sev_receive_update_data *)data; ++ g_free((guchar *)update->hdr_uaddr); ++ g_free((guchar *)update->trans_uaddr); ++ g_free(update); ++} ++ + static int + csv_send_queue_data(SevGuestState *s, uint8_t *ptr, + uint32_t size, uint64_t addr) +@@ -1988,6 +1998,66 @@ err: + return ret; + } + ++static int ++csv_receive_queue_data(SevGuestState *s, QEMUFile *f, uint8_t *ptr) ++{ ++ int ret = 0; ++ gchar *hdr = NULL, *trans = NULL; ++ struct kvm_sev_receive_update_data *update; ++ struct kvm_csv_batch_list_node *new_node = NULL; ++ ++ update = g_new0(struct kvm_sev_receive_update_data, 1); ++ /* get packet header */ ++ update->hdr_len = qemu_get_be32(f); ++ hdr = g_new(gchar, update->hdr_len); ++ qemu_get_buffer(f, (uint8_t *)hdr, update->hdr_len); ++ update->hdr_uaddr = (unsigned long)hdr; ++ ++ /* get transport buffer */ ++ update->trans_len = qemu_get_be32(f); ++ trans = g_new(gchar, update->trans_len); ++ update->trans_uaddr = (unsigned long)trans; ++ qemu_get_buffer(f, (uint8_t *)update->trans_uaddr, update->trans_len); ++ ++ /* set guest address,guest len is page_size */ ++ update->guest_uaddr = (uint64_t)ptr; ++ update->guest_len = TARGET_PAGE_SIZE; ++ ++ new_node = csv_batch_cmd_list_node_create((uint64_t)update, 0); ++ if (!new_node) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ ++ if (s->csv_batch_cmd_list == NULL) { ++ s->csv_batch_cmd_list = csv_batch_cmd_list_create(new_node, ++ receive_update_data_free); ++ if (s->csv_batch_cmd_list == NULL) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } else { ++ /* Add new_node's command address to the last_node */ ++ csv_batch_cmd_list_add_after(s->csv_batch_cmd_list, new_node); ++ } ++ ++ trace_kvm_sev_receive_update_data(trans, (void *)ptr, update->guest_len, ++ (void *)hdr, update->hdr_len); ++ ++ return ret; ++ ++err: ++ g_free(trans); ++ g_free(update); ++ g_free(hdr); ++ g_free(new_node); ++ if (s->csv_batch_cmd_list) { ++ csv_batch_cmd_list_destroy(s->csv_batch_cmd_list); ++ s->csv_batch_cmd_list = NULL; ++ } ++ return ret; ++} ++ + static int + csv_command_batch(uint32_t cmd_id, uint64_t head_uaddr, int *fw_err) + { +@@ -2066,6 +2136,28 @@ csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr) + return csv_send_queue_data(s, ptr, sz, addr); + } + ++int csv_queue_incoming_page(QEMUFile *f, uint8_t *ptr) ++{ ++ SevGuestState *s = sev_guest; ++ ++ /* Only support for HYGON CSV */ ++ if (!is_hygon_cpu()) { ++ error_report("Only support enqueue received pages for HYGON CSV"); ++ return -EINVAL; ++ } ++ ++ /* ++ * If this is first buffer and SEV is not in recieiving state then ++ * use RECEIVE_START command to create a encryption context. ++ */ ++ if (!sev_check_state(s, SEV_STATE_RECEIVE_UPDATE) && ++ sev_receive_start(s, f)) { ++ return 1; ++ } ++ ++ return csv_receive_queue_data(s, f, ptr); ++} ++ + int + csv_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent) + { +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 37ed17f32..6166be420 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -79,6 +79,7 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); + + int csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); + int csv_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent); ++int csv_queue_incoming_page(QEMUFile *f, uint8_t *ptr); + + struct sev_ops { + int (*sev_ioctl)(int fd, int cmd, void *data, int *error); +-- +2.31.1 + diff --git a/1060-anolis-target-i386-csv-add-support-to-load-incoming-.patch b/1060-anolis-target-i386-csv-add-support-to-load-incoming-.patch new file mode 100644 index 0000000000000000000000000000000000000000..f54f872658b2f2cbd9eca3c4ebd22e4cf9e13c71 --- /dev/null +++ b/1060-anolis-target-i386-csv-add-support-to-load-incoming-.patch @@ -0,0 +1,108 @@ +From 8949b2ac9cc6d9a59a482ca76f81a417c9c38e6d Mon Sep 17 00:00:00 2001 +From: fangbaoshun +Date: Mon, 2 Aug 2021 14:11:43 +0800 +Subject: [PATCH 23/29] anolis: target/i386: csv: add support to load incoming + encrypted pages queued in the CMD list + +The csv_load_queued_incoming_pages() provide the implementation to read the +incoming guest private pages from the socket queued in the CMD list and load +them into the guest memory. The routines uses the RECEIVE_START command to +create the incoming encryption context on the first call then uses the +COMMAND_BATCH carried with RECEIEVE_UPDATE_DATA commands to load the encrypted +pages into the guest memory. After migration is completed, we issue the +RECEIVE_FINISH command to transition the SEV guest to the runnable state +so that it can be executed. + +Signed-off-by: hanliyang +--- + include/exec/confidential-guest-support.h | 3 +++ + target/i386/sev.c | 32 +++++++++++++++++++++++ + target/i386/sev.h | 1 + + 3 files changed, 36 insertions(+) + +diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h +index 530b5057b..f6a30fab3 100644 +--- a/include/exec/confidential-guest-support.h ++++ b/include/exec/confidential-guest-support.h +@@ -87,6 +87,9 @@ struct ConfidentialGuestMemoryEncryptionOps { + + /* Queue the incoming encrypted page into a list */ + int (*queue_incoming_page)(QEMUFile *f, uint8_t *ptr); ++ ++ /* Load the incoming encrypted pages queued in list into guest memory */ ++ int (*load_queued_incoming_pages)(QEMUFile *f); + }; + + typedef struct ConfidentialGuestSupportClass { +diff --git a/target/i386/sev.c b/target/i386/sev.c +index e235fb207..2f7d98be6 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -203,6 +203,7 @@ static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .queue_outgoing_page = csv_queue_outgoing_page, + .save_queued_outgoing_pages = csv_save_queued_outgoing_pages, + .queue_incoming_page = csv_queue_incoming_page, ++ .load_queued_incoming_pages = csv_load_queued_incoming_pages, + }; + + static int +@@ -2122,6 +2123,24 @@ err: + return ret; + } + ++static int ++csv_receive_update_data_batch(SevGuestState *s) ++{ ++ int ret; ++ int fw_error; ++ ++ ret = csv_command_batch(KVM_SEV_RECEIVE_UPDATE_DATA, ++ (uint64_t)s->csv_batch_cmd_list->head, &fw_error); ++ if (ret) { ++ error_report("%s: csv_command_batch ret=%d fw_error=%d '%s'", ++ __func__, ret, fw_error, fw_error_to_str(fw_error)); ++ } ++ ++ csv_batch_cmd_list_destroy(s->csv_batch_cmd_list); ++ s->csv_batch_cmd_list = NULL; ++ return ret; ++} ++ + int + csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr) + { +@@ -2182,6 +2201,19 @@ csv_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent) + return csv_send_update_data_batch(s, f, bytes_sent); + } + ++int csv_load_queued_incoming_pages(QEMUFile *f) ++{ ++ SevGuestState *s = sev_guest; ++ ++ /* Only support for HYGON CSV */ ++ if (!is_hygon_cpu()) { ++ error_report("Only support load queued pages for HYGON CSV"); ++ return -EINVAL; ++ } ++ ++ return csv_receive_update_data_batch(s); ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 6166be420..c53f96b04 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -80,6 +80,7 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); + int csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); + int csv_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent); + int csv_queue_incoming_page(QEMUFile *f, uint8_t *ptr); ++int csv_load_queued_incoming_pages(QEMUFile *f); + + struct sev_ops { + int (*sev_ioctl)(int fd, int cmd, void *data, int *error); +-- +2.31.1 + diff --git a/1061-anolis-migration-ram-Accelerate-the-transmission-of-.patch b/1061-anolis-migration-ram-Accelerate-the-transmission-of-.patch new file mode 100644 index 0000000000000000000000000000000000000000..a065a0e4ef9ea439eff3cf5e50053daaf6dc06be --- /dev/null +++ b/1061-anolis-migration-ram-Accelerate-the-transmission-of-.patch @@ -0,0 +1,229 @@ +From 379463820571f9ca239e68f2f5f588634b96bbff Mon Sep 17 00:00:00 2001 +From: fangbaoshun +Date: Mon, 2 Aug 2021 14:35:51 +0800 +Subject: [PATCH 24/29] anolis: migration/ram: Accelerate the transmission of + CSV guest's encrypted pages + +When memory encryption is enabled, the guest memory will be encrypted with +the guest specific key. The patch introduces an accelerate solution which +queued the pages into list and send them togather by COMMAND_BATCH. + +Signed-off-by: hanliyang +--- + configs/devices/i386-softmmu/default.mak | 1 + + hw/i386/Kconfig | 5 ++ + migration/ram.c | 106 +++++++++++++++++++++++ + target/i386/csv.h | 11 +++ + target/i386/sev.h | 1 + + 5 files changed, 124 insertions(+) + +diff --git a/configs/devices/i386-softmmu/default.mak b/configs/devices/i386-softmmu/default.mak +index db83ffcab..e948e54e4 100644 +--- a/configs/devices/i386-softmmu/default.mak ++++ b/configs/devices/i386-softmmu/default.mak +@@ -24,6 +24,7 @@ + #CONFIG_VTD=n + #CONFIG_SGX=n + #CONFIG_CSV=n ++#CONFIG_HYGON_CSV_MIG_ACCEL=n + + # Boards: + # +diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig +index ed35d762b..8876b360b 100644 +--- a/hw/i386/Kconfig ++++ b/hw/i386/Kconfig +@@ -4,6 +4,7 @@ config X86_FW_OVMF + config SEV + bool + select X86_FW_OVMF ++ select HYGON_CSV_MIG_ACCEL + depends on KVM + + config SGX +@@ -14,6 +15,10 @@ config CSV + bool + depends on SEV + ++config HYGON_CSV_MIG_ACCEL ++ bool ++ depends on SEV ++ + config PC + bool + imply APPLESMC +diff --git a/migration/ram.c b/migration/ram.c +index 33e2c9d5f..7e2d4e7df 100644 +--- a/migration/ram.c ++++ b/migration/ram.c +@@ -59,6 +59,7 @@ + + /* Defines RAM_SAVE_ENCRYPTED_PAGE and RAM_SAVE_SHARED_REGION_LIST */ + #include "target/i386/sev.h" ++#include "target/i386/csv.h" + #include "sysemu/kvm.h" + + #include "hw/boards.h" /* for machine_dump_guest_core() */ +@@ -2348,6 +2349,99 @@ static int ram_save_target_page(RAMState *rs, PageSearchStatus *pss, + return ram_save_page(rs, pss, last_stage); + } + ++#ifdef CONFIG_HYGON_CSV_MIG_ACCEL ++/** ++ * ram_save_encrypted_pages_in_batch - send the given encrypted pages to ++ * the stream. ++ */ ++static int ++ram_save_encrypted_pages_in_batch(RAMState *rs, PageSearchStatus *pss, bool last_stage) ++{ ++ int ret; ++ int tmppages = 0, pages = 0; ++ RAMBlock *block = pss->block; ++ ram_addr_t offset = pss->page << TARGET_PAGE_BITS; ++ ram_addr_t start_offset = 0; ++ uint32_t host_len = 0; ++ uint8_t *p; ++ uint64_t bytes_xmit; ++ MachineState *ms = MACHINE(qdev_get_machine()); ++ ConfidentialGuestSupportClass *cgs_class = ++ (ConfidentialGuestSupportClass *)object_get_class(OBJECT(ms->cgs)); ++ struct ConfidentialGuestMemoryEncryptionOps *ops = ++ cgs_class->memory_encryption_ops; ++ ++ do { ++ /* Check the pages is dirty and if it is send it */ ++ if (!migration_bitmap_clear_dirty(rs, block, pss->page)) { ++ pss->page++; ++ continue; ++ } ++ ++ /* Process the unencrypted page */ ++ if (!encrypted_test_list(rs, block, pss->page)) { ++ tmppages = ram_save_target_page(rs, pss, last_stage); ++ } else { ++ /* Caculate the offset and host virtual address of the page */ ++ offset = pss->page << TARGET_PAGE_BITS; ++ p = block->host + offset; ++ ++ /* Record the offset and host virtual address of the first ++ * page in this loop which will be used below. ++ */ ++ if (host_len == 0) { ++ start_offset = offset | RAM_SAVE_FLAG_ENCRYPTED_DATA; ++ } else { ++ offset |= (RAM_SAVE_FLAG_ENCRYPTED_DATA | RAM_SAVE_FLAG_CONTINUE); ++ } ++ ++ /* Queue the outgoing page if the page is not zero page. ++ * If the queued pages are up to the outgoing page window size, ++ * process them below. ++ */ ++ if (ops->queue_outgoing_page(p, TARGET_PAGE_SIZE, offset)) ++ return -1; ++ ++ tmppages = 1; ++ host_len += TARGET_PAGE_SIZE; ++ ram_counters.normal++; ++ } ++ ++ if (tmppages < 0) { ++ return tmppages; ++ } ++ ++ pages += tmppages; ++ ++ pss->page++; ++ } while (offset_in_ramblock(block, pss->page << TARGET_PAGE_BITS) && ++ host_len < CSV_OUTGOING_PAGE_WINDOW_SIZE); ++ ++ /* Check if there are any queued pages */ ++ if (host_len != 0) { ++ ram_counters.transferred += ++ save_page_header(rs, rs->f, block, start_offset); ++ /* if only one page queued, flag is BATCH_END, else flag is BATCH */ ++ if (host_len > TARGET_PAGE_SIZE) ++ qemu_put_be32(rs->f, RAM_SAVE_ENCRYPTED_PAGE_BATCH); ++ else ++ qemu_put_be32(rs->f, RAM_SAVE_ENCRYPTED_PAGE_BATCH_END); ++ ram_counters.transferred += 4; ++ /* Process the queued pages in batch */ ++ ret = ops->save_queued_outgoing_pages(rs->f, &bytes_xmit); ++ if (ret) { ++ return -1; ++ } ++ ram_counters.transferred += bytes_xmit; ++ } ++ ++ /* The offset we leave with is the last one we looked at */ ++ pss->page--; ++ ++ return pages; ++} ++#endif ++ + /** + * ram_save_host_page: save a whole host page + * +@@ -2382,6 +2476,18 @@ static int ram_save_host_page(RAMState *rs, PageSearchStatus *pss, + return 0; + } + ++#ifdef CONFIG_HYGON_CSV_MIG_ACCEL ++ /* ++ * If command_batch function is enabled and memory encryption is enabled ++ * then use command batch APIs to accelerate the sending process ++ * to write the outgoing buffer to the wire. The encryption APIs ++ * will re-encrypt the data with transport key so that data is prototect ++ * on the wire. ++ */ ++ if (memcrypt_enabled() && is_hygon_cpu() && !migration_in_postcopy()) ++ return ram_save_encrypted_pages_in_batch(rs, pss, last_stage); ++#endif ++ + do { + /* Check the pages is dirty and if it is send it */ + if (migration_bitmap_clear_dirty(rs, pss->block, pss->page)) { +diff --git a/target/i386/csv.h b/target/i386/csv.h +index 4f5b2773c..e51cc8302 100644 +--- a/target/i386/csv.h ++++ b/target/i386/csv.h +@@ -16,6 +16,8 @@ + + #include "qapi/qapi-commands-misc-target.h" + ++#ifdef CONFIG_SEV ++ + #include "cpu.h" + + #define CPUID_VENDOR_HYGON_EBX 0x6f677948 /* "Hygo" */ +@@ -38,6 +40,15 @@ static __attribute__((unused)) bool is_hygon_cpu(void) + return false; + } + ++#else ++ ++static __attribute__((unused)) bool is_hygon_cpu(void) ++{ ++ return false; ++} ++ ++#endif ++ + #ifdef CONFIG_CSV + bool csv_enabled(void); + #else +diff --git a/target/i386/sev.h b/target/i386/sev.h +index c53f96b04..345dffac5 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -43,6 +43,7 @@ typedef struct SevKernelLoaderContext { + + #define RAM_SAVE_ENCRYPTED_PAGE_BATCH 0x4 + #define RAM_SAVE_ENCRYPTED_PAGE_BATCH_END 0x5 ++#define CSV_OUTGOING_PAGE_WINDOW_SIZE (4094 * TARGET_PAGE_SIZE) + + #ifdef CONFIG_SEV + bool sev_enabled(void); +-- +2.31.1 + diff --git a/1062-anolis-migration-ram-Accelerate-the-loading-of-CSV-g.patch b/1062-anolis-migration-ram-Accelerate-the-loading-of-CSV-g.patch new file mode 100644 index 0000000000000000000000000000000000000000..56c11eb31074d00363896ad069af86f42ff2735d --- /dev/null +++ b/1062-anolis-migration-ram-Accelerate-the-loading-of-CSV-g.patch @@ -0,0 +1,37 @@ +From e9b70bcaf165cb163304ba68a1b1db078489d9e0 Mon Sep 17 00:00:00 2001 +From: fangbaoshun +Date: Mon, 2 Aug 2021 14:49:45 +0800 +Subject: [PATCH 25/29] anolis: migration/ram: Accelerate the loading of CSV + guest's encrypted pages + +When memory encryption is enabled, the guest memory will be encrypted with +the guest specific key. The patch introduces an accelerate solution which +queued the pages into list and load them togather by COMMAND_BATCH. + +Signed-off-by: hanliyang +--- + migration/ram.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/migration/ram.c b/migration/ram.c +index 7e2d4e7df..90f1bda83 100644 +--- a/migration/ram.c ++++ b/migration/ram.c +@@ -1387,6 +1387,14 @@ static int load_encrypted_data(QEMUFile *f, uint8_t *ptr) + return ops->load_incoming_page(f, ptr); + } else if (flag == RAM_SAVE_SHARED_REGIONS_LIST) { + return ops->load_incoming_shared_regions_list(f); ++ } else if (flag == RAM_SAVE_ENCRYPTED_PAGE_BATCH) { ++ return ops->queue_incoming_page(f, ptr); ++ } else if (flag == RAM_SAVE_ENCRYPTED_PAGE_BATCH_END) { ++ if (ops->queue_incoming_page(f, ptr)) { ++ error_report("Failed to queue incoming data"); ++ return -EINVAL; ++ } ++ return ops->load_queued_incoming_pages(f); + } else { + error_report("unknown encrypted flag %x", flag); + return 1; +-- +2.31.1 + diff --git a/1063-anolis-target-i386-csv-Add-support-for-migrate-VMSA-.patch b/1063-anolis-target-i386-csv-Add-support-for-migrate-VMSA-.patch new file mode 100644 index 0000000000000000000000000000000000000000..166fb5835da60f6f4135cd382716dc6425d6f569 --- /dev/null +++ b/1063-anolis-target-i386-csv-Add-support-for-migrate-VMSA-.patch @@ -0,0 +1,416 @@ +From 133fe2e6a250623ccc9305fdacc1e8d990878fb4 Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Tue, 7 Jun 2022 15:19:32 +0800 +Subject: [PATCH 26/29] anolis: target/i386/csv: Add support for migrate VMSA + for CSV2 guest + +CSV2 can protect guest's cpu state through memory encryption. Each +vcpu has its corresponding memory, which is also called VMSA, and +is encrypted by guest's specific encrytion key. + +When CSV2 guest exit to host, the vcpu's state will be encrypted +and saved to VMSA, and the VMSA will be decrypted and loaded to cpu +when the guest's vcpu running at next time. + +If user wants to migrate one CSV2 guest to target machine, the VMSA +of the vcpus also should be migrated to target. CSV firmware provides +SEND_UPDATE_VMSA/RECEIVE_UPDATE_VMSA API through which VMSA can be +converted into secure data and transmitted to the remote end (for +example, network transmission). + +The migration of cpu state is identified by CPUState.cpu_index which +may not equals to vcpu id from KVM's perspective. + +When migrate the VMSA, the source QEMU will invoke SEND_UPDATE_VMSA to +generate data correspond to VMSA, after target QEMU received the data, +it will calc target vcpu id in the KVM by CPUState.cpu_index, and then +invoke RECEIVE_UPDATE_VMSA to restore VMSA correspond to vcpu. + +Signed-off-by: hanliyang +--- + include/exec/confidential-guest-support.h | 6 + + linux-headers/linux/kvm.h | 16 ++ + migration/ram.c | 30 ++++ + target/i386/sev.c | 196 ++++++++++++++++++++++ + target/i386/sev.h | 3 + + target/i386/trace-events | 2 + + 6 files changed, 253 insertions(+) + +diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h +index f6a30fab3..a069bad9d 100644 +--- a/include/exec/confidential-guest-support.h ++++ b/include/exec/confidential-guest-support.h +@@ -90,6 +90,12 @@ struct ConfidentialGuestMemoryEncryptionOps { + + /* Load the incoming encrypted pages queued in list into guest memory */ + int (*load_queued_incoming_pages)(QEMUFile *f); ++ ++ /* Write the encrypted cpu state */ ++ int (*save_outgoing_cpu_state)(QEMUFile *f); ++ ++ /* Load the encrypted cpu state */ ++ int (*load_incoming_cpu_state)(QEMUFile *f); + }; + + typedef struct ConfidentialGuestSupportClass { +diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h +index 951afc8b2..e5d282c75 100644 +--- a/linux-headers/linux/kvm.h ++++ b/linux-headers/linux/kvm.h +@@ -1904,6 +1904,14 @@ struct kvm_sev_send_update_data { + __u32 trans_len; + }; + ++struct kvm_sev_send_update_vmsa { ++ __u32 vcpu_id; ++ __u64 hdr_uaddr; ++ __u32 hdr_len; ++ __u64 trans_uaddr; ++ __u32 trans_len; ++}; ++ + struct kvm_sev_receive_start { + __u32 handle; + __u32 policy; +@@ -1922,6 +1930,14 @@ struct kvm_sev_receive_update_data { + __u32 trans_len; + }; + ++struct kvm_sev_receive_update_vmsa { ++ __u32 vcpu_id; ++ __u64 hdr_uaddr; ++ __u32 hdr_len; ++ __u64 trans_uaddr; ++ __u32 trans_len; ++}; ++ + /* CSV command */ + enum csv_cmd_id { + KVM_CSV_NR_MIN = 0xc0, +diff --git a/migration/ram.c b/migration/ram.c +index 90f1bda83..8c61eecb9 100644 +--- a/migration/ram.c ++++ b/migration/ram.c +@@ -1371,6 +1371,23 @@ static int ram_save_shared_region_list(RAMState *rs, QEMUFile *f) + return ops->save_outgoing_shared_regions_list(rs->f); + } + ++/** ++ * ram_save_encrypted_cpu_state: send the encrypted cpu state ++ */ ++static int ram_save_encrypted_cpu_state(RAMState *rs, QEMUFile *f) ++{ ++ MachineState *ms = MACHINE(qdev_get_machine()); ++ ConfidentialGuestSupportClass *cgs_class = ++ (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); ++ struct ConfidentialGuestMemoryEncryptionOps *ops = ++ cgs_class->memory_encryption_ops; ++ ++ save_page_header(rs, rs->f, rs->last_seen_block, ++ RAM_SAVE_FLAG_ENCRYPTED_DATA); ++ qemu_put_be32(rs->f, RAM_SAVE_ENCRYPTED_CPU_STATE); ++ return ops->save_outgoing_cpu_state(rs->f); ++} ++ + static int load_encrypted_data(QEMUFile *f, uint8_t *ptr) + { + MachineState *ms = MACHINE(qdev_get_machine()); +@@ -1395,6 +1412,8 @@ static int load_encrypted_data(QEMUFile *f, uint8_t *ptr) + return -EINVAL; + } + return ops->load_queued_incoming_pages(f); ++ } else if (flag == RAM_SAVE_ENCRYPTED_CPU_STATE) { ++ return ops->load_incoming_cpu_state(f); + } else { + error_report("unknown encrypted flag %x", flag); + return 1; +@@ -3507,6 +3526,17 @@ static int ram_save_complete(QEMUFile *f, void *opaque) + if (memcrypt_enabled()) { + ret = ram_save_shared_region_list(rs, f); + } ++ ++ /* ++ * send the encrypted cpu state, for example, CSV2 guest's ++ * vmsa for each vcpu. ++ */ ++ if (!ret && memcrypt_enabled() && is_hygon_cpu()) { ++ ret = ram_save_encrypted_cpu_state(rs, f); ++ if (ret) { ++ error_report("Failed to save encrypted cpu state"); ++ } ++ } + } + + if (ret < 0) { +diff --git a/target/i386/sev.c b/target/i386/sev.c +index 2f7d98be6..9cdf3e6bf 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -100,6 +100,10 @@ struct SevGuestState { + gchar *send_packet_hdr; + size_t send_packet_hdr_len; + ++ /* needed by live migration of HYGON CSV2 guest */ ++ gchar *send_vmsa_packet_hdr; ++ size_t send_vmsa_packet_hdr_len; ++ + uint32_t reset_cs; + uint32_t reset_ip; + bool reset_data_valid; +@@ -193,6 +197,9 @@ static const char *const sev_fw_errlist[] = { + #define SHARED_REGION_LIST_CONT 0x1 + #define SHARED_REGION_LIST_END 0x2 + ++#define ENCRYPTED_CPU_STATE_CONT 0x1 ++#define ENCRYPTED_CPU_STATE_END 0x2 ++ + static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .save_setup = sev_save_setup, + .save_outgoing_page = sev_save_outgoing_page, +@@ -204,6 +211,8 @@ static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .save_queued_outgoing_pages = csv_save_queued_outgoing_pages, + .queue_incoming_page = csv_queue_incoming_page, + .load_queued_incoming_pages = csv_load_queued_incoming_pages, ++ .save_outgoing_cpu_state = csv_save_outgoing_cpu_state, ++ .load_incoming_cpu_state = csv_load_incoming_cpu_state, + }; + + static int +@@ -1020,6 +1029,9 @@ sev_send_finish(void) + } + + g_free(sev_guest->send_packet_hdr); ++ if (sev_es_enabled() && is_hygon_cpu()) { ++ g_free(sev_guest->send_vmsa_packet_hdr); ++ } + sev_set_guest_state(sev_guest, SEV_STATE_RUNNING); + } + +@@ -2214,6 +2226,190 @@ int csv_load_queued_incoming_pages(QEMUFile *f) + return csv_receive_update_data_batch(s); + } + ++static int ++sev_send_vmsa_get_packet_len(int *fw_err) ++{ ++ int ret; ++ struct kvm_sev_send_update_vmsa update = { 0, }; ++ ++ ret = sev_ioctl(sev_guest->sev_fd, KVM_SEV_SEND_UPDATE_VMSA, ++ &update, fw_err); ++ if (*fw_err != SEV_RET_INVALID_LEN) { ++ ret = -1; ++ error_report("%s: failed to get session length ret=%d fw_error=%d '%s'", ++ __func__, ret, *fw_err, fw_error_to_str(*fw_err)); ++ goto err; ++ } ++ ++ ret = update.hdr_len; ++ ++err: ++ return ret; ++} ++ ++static int ++sev_send_update_vmsa(SevGuestState *s, QEMUFile *f, uint32_t cpu_id, ++ uint32_t cpu_index, uint32_t size) ++{ ++ int ret, fw_error; ++ guchar *trans = NULL; ++ struct kvm_sev_send_update_vmsa update = {}; ++ ++ /* ++ * If this is first call then query the packet header bytes and allocate ++ * the packet buffer. ++ */ ++ if (!s->send_vmsa_packet_hdr) { ++ s->send_vmsa_packet_hdr_len = sev_send_vmsa_get_packet_len(&fw_error); ++ if (s->send_vmsa_packet_hdr_len < 1) { ++ error_report("%s: SEND_UPDATE_VMSA fw_error=%d '%s'", ++ __func__, fw_error, fw_error_to_str(fw_error)); ++ return 1; ++ } ++ ++ s->send_vmsa_packet_hdr = g_new(gchar, s->send_vmsa_packet_hdr_len); ++ } ++ ++ /* allocate transport buffer */ ++ trans = g_new(guchar, size); ++ ++ update.vcpu_id = cpu_id; ++ update.hdr_uaddr = (uintptr_t)s->send_vmsa_packet_hdr; ++ update.hdr_len = s->send_vmsa_packet_hdr_len; ++ update.trans_uaddr = (uintptr_t)trans; ++ update.trans_len = size; ++ ++ trace_kvm_sev_send_update_vmsa(cpu_id, cpu_index, trans, size); ++ ++ ret = sev_ioctl(s->sev_fd, KVM_SEV_SEND_UPDATE_VMSA, &update, &fw_error); ++ if (ret) { ++ error_report("%s: SEND_UPDATE_VMSA ret=%d fw_error=%d '%s'", ++ __func__, ret, fw_error, fw_error_to_str(fw_error)); ++ goto err; ++ } ++ ++ /* ++ * Migration of vCPU's VMState according to the instance_id ++ * (i.e. CPUState.cpu_index) ++ */ ++ qemu_put_be32(f, sizeof(uint32_t)); ++ qemu_put_buffer(f, (uint8_t *)&cpu_index, sizeof(uint32_t)); ++ ++ qemu_put_be32(f, update.hdr_len); ++ qemu_put_buffer(f, (uint8_t *)update.hdr_uaddr, update.hdr_len); ++ ++ qemu_put_be32(f, update.trans_len); ++ qemu_put_buffer(f, (uint8_t *)update.trans_uaddr, update.trans_len); ++ ++err: ++ g_free(trans); ++ return ret; ++} ++ ++int csv_save_outgoing_cpu_state(QEMUFile *f) ++{ ++ SevGuestState *s = sev_guest; ++ CPUState *cpu; ++ int ret = 0; ++ ++ /* Only support migrate VMSAs for HYGON CSV2 guest */ ++ if (!sev_es_enabled() || !is_hygon_cpu()) { ++ return 0; ++ } ++ ++ CPU_FOREACH(cpu) { ++ qemu_put_be32(f, ENCRYPTED_CPU_STATE_CONT); ++ ret = sev_send_update_vmsa(s, f, kvm_arch_vcpu_id(cpu), ++ cpu->cpu_index, TARGET_PAGE_SIZE); ++ if (ret) { ++ goto err; ++ } ++ } ++ ++ qemu_put_be32(f, ENCRYPTED_CPU_STATE_END); ++ ++err: ++ return ret; ++} ++ ++static int sev_receive_update_vmsa(QEMUFile *f) ++{ ++ int ret = 1, fw_error = 0; ++ CPUState *cpu; ++ uint32_t cpu_index, cpu_id = 0; ++ gchar *hdr = NULL, *trans = NULL; ++ struct kvm_sev_receive_update_vmsa update = {}; ++ ++ /* get cpu index buffer */ ++ assert(qemu_get_be32(f) == sizeof(uint32_t)); ++ qemu_get_buffer(f, (uint8_t *)&cpu_index, sizeof(uint32_t)); ++ ++ CPU_FOREACH(cpu) { ++ if (cpu->cpu_index == cpu_index) { ++ cpu_id = kvm_arch_vcpu_id(cpu); ++ break; ++ } ++ } ++ update.vcpu_id = cpu_id; ++ ++ /* get packet header */ ++ update.hdr_len = qemu_get_be32(f); ++ if (!check_blob_length(update.hdr_len)) { ++ return 1; ++ } ++ ++ hdr = g_new(gchar, update.hdr_len); ++ qemu_get_buffer(f, (uint8_t *)hdr, update.hdr_len); ++ update.hdr_uaddr = (uintptr_t)hdr; ++ ++ /* get transport buffer */ ++ update.trans_len = qemu_get_be32(f); ++ if (!check_blob_length(update.trans_len)) { ++ goto err; ++ } ++ ++ trans = g_new(gchar, update.trans_len); ++ update.trans_uaddr = (uintptr_t)trans; ++ qemu_get_buffer(f, (uint8_t *)update.trans_uaddr, update.trans_len); ++ ++ trace_kvm_sev_receive_update_vmsa(cpu_id, cpu_index, ++ trans, update.trans_len, hdr, update.hdr_len); ++ ++ ret = sev_ioctl(sev_guest->sev_fd, KVM_SEV_RECEIVE_UPDATE_VMSA, ++ &update, &fw_error); ++ if (ret) { ++ error_report("Error RECEIVE_UPDATE_VMSA ret=%d fw_error=%d '%s'", ++ ret, fw_error, fw_error_to_str(fw_error)); ++ } ++ ++err: ++ g_free(trans); ++ g_free(hdr); ++ return ret; ++} ++ ++int csv_load_incoming_cpu_state(QEMUFile *f) ++{ ++ int status, ret = 0; ++ ++ /* Only support migrate VMSAs for HYGON CSV2 guest */ ++ if (!sev_es_enabled() || !is_hygon_cpu()) { ++ return 0; ++ } ++ ++ status = qemu_get_be32(f); ++ while (status == ENCRYPTED_CPU_STATE_CONT) { ++ ret = sev_receive_update_vmsa(f); ++ if (ret) { ++ break; ++ } ++ ++ status = qemu_get_be32(f); ++ } ++ ++ return ret; ++} ++ + static const QemuUUID sev_hash_table_header_guid = { + .data = UUID_LE(0x9438d606, 0x4f22, 0x4cc9, 0xb4, 0x79, 0xa7, 0x93, + 0xd4, 0x11, 0xfd, 0x21) +diff --git a/target/i386/sev.h b/target/i386/sev.h +index 345dffac5..8b38567c3 100644 +--- a/target/i386/sev.h ++++ b/target/i386/sev.h +@@ -44,6 +44,7 @@ typedef struct SevKernelLoaderContext { + #define RAM_SAVE_ENCRYPTED_PAGE_BATCH 0x4 + #define RAM_SAVE_ENCRYPTED_PAGE_BATCH_END 0x5 + #define CSV_OUTGOING_PAGE_WINDOW_SIZE (4094 * TARGET_PAGE_SIZE) ++#define RAM_SAVE_ENCRYPTED_CPU_STATE 0x6 + + #ifdef CONFIG_SEV + bool sev_enabled(void); +@@ -82,6 +83,8 @@ int csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); + int csv_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent); + int csv_queue_incoming_page(QEMUFile *f, uint8_t *ptr); + int csv_load_queued_incoming_pages(QEMUFile *f); ++int csv_save_outgoing_cpu_state(QEMUFile *f); ++int csv_load_incoming_cpu_state(QEMUFile *f); + + struct sev_ops { + int (*sev_ioctl)(int fd, int cmd, void *data, int *error); +diff --git a/target/i386/trace-events b/target/i386/trace-events +index e32b0319b..60a4609c0 100644 +--- a/target/i386/trace-events ++++ b/target/i386/trace-events +@@ -17,6 +17,8 @@ kvm_sev_send_finish(void) "" + kvm_sev_receive_start(int policy, void *session, void *pdh) "policy 0x%x session %p pdh %p" + kvm_sev_receive_update_data(void *src, void *dst, int len, void *hdr, int hdr_len) "guest %p trans %p len %d hdr %p hdr_len %d" + kvm_sev_receive_finish(void) "" ++kvm_sev_send_update_vmsa(uint32_t cpu_id, uint32_t cpu_index, void *dst, int len) "cpu_id %d cpu_index %d trans %p len %d" ++kvm_sev_receive_update_vmsa(uint32_t cpu_id, uint32_t cpu_index, void *src, int len, void *hdr, int hdr_len) "cpu_id %d cpu_index %d trans %p len %d hdr %p hdr_len %d" + + # csv.c + kvm_csv_launch_encrypt_data(uint64_t gpa, void *addr, uint64_t len) "gpa 0x%" PRIx64 "addr %p len 0x%" PRIu64 +-- +2.31.1 + diff --git a/1064-anolis-target-i386-get-set-migrate-GHCB-state.patch b/1064-anolis-target-i386-get-set-migrate-GHCB-state.patch new file mode 100644 index 0000000000000000000000000000000000000000..a58e394630041ecb581b24c3fce00bcb4a203bc2 --- /dev/null +++ b/1064-anolis-target-i386-get-set-migrate-GHCB-state.patch @@ -0,0 +1,175 @@ +From a95faafa015e094f7252ef35afb74933d07a9334 Mon Sep 17 00:00:00 2001 +From: panpingsheng +Date: Sat, 12 Jun 2021 15:15:29 +0800 +Subject: [PATCH 27/29] anolis: target/i386: get/set/migrate GHCB state + +GHCB state is necessary to CSV2 guest when migrating to target. + +Add GHCB related definition, it also adds corresponding part +to kvm_get/put, and vmstate. + +Signed-off-by: hanliyang +--- + include/sysemu/kvm.h | 1 + + linux-headers/linux/kvm.h | 1 + + target/i386/cpu.h | 4 ++++ + target/i386/kvm/kvm.c | 11 +++++++++++ + target/i386/machine.c | 24 ++++++++++++++++++++++++ + target/i386/sev.c | 10 ++++++++++ + 6 files changed, 51 insertions(+) + +diff --git a/include/sysemu/kvm.h b/include/sysemu/kvm.h +index 7b22aeb6a..9f8099f48 100644 +--- a/include/sysemu/kvm.h ++++ b/include/sysemu/kvm.h +@@ -46,6 +46,7 @@ extern bool kvm_readonly_mem_allowed; + extern bool kvm_direct_msi_allowed; + extern bool kvm_ioeventfd_any_length_allowed; + extern bool kvm_msi_use_devid; ++extern bool kvm_has_msr_ghcb; + + #define kvm_enabled() (kvm_allowed) + /** +diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h +index e5d282c75..4a177f81d 100644 +--- a/linux-headers/linux/kvm.h ++++ b/linux-headers/linux/kvm.h +@@ -1154,6 +1154,7 @@ struct kvm_ppc_resize_hpt { + #define KVM_CAP_S390_PROTECTED_DUMP 217 + #define KVM_CAP_S390_ZPCI_OP 221 + #define KVM_CAP_S390_CPU_TOPOLOGY 222 ++#define KVM_CAP_SEV_ES_GHCB 500 + + #define KVM_EXIT_HYPERCALL_VALID_MASK (1 << KVM_HC_MAP_GPA_RANGE) + +diff --git a/target/i386/cpu.h b/target/i386/cpu.h +index 5d2ddd81b..5c476a029 100644 +--- a/target/i386/cpu.h ++++ b/target/i386/cpu.h +@@ -505,6 +505,8 @@ typedef enum X86Seg { + + #define MSR_VM_HSAVE_PA 0xc0010117 + ++#define MSR_AMD64_SEV_ES_GHCB 0xc0010130 ++ + #define MSR_IA32_XFD 0x000001c4 + #define MSR_IA32_XFD_ERR 0x000001c5 + +@@ -1732,6 +1734,8 @@ typedef struct CPUX86State { + TPRAccess tpr_access_type; + + unsigned nr_dies; ++ ++ uint64_t ghcb_gpa; + } CPUX86State; + + struct kvm_msrs; +diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c +index f0b61ec3c..fb85cc698 100644 +--- a/target/i386/kvm/kvm.c ++++ b/target/i386/kvm/kvm.c +@@ -3346,6 +3346,10 @@ static int kvm_put_msrs(X86CPU *cpu, int level) + } + } + ++ if (kvm_has_msr_ghcb) { ++ kvm_msr_entry_add(cpu, MSR_AMD64_SEV_ES_GHCB, env->ghcb_gpa); ++ } ++ + return kvm_buf_set_msrs(cpu); + } + +@@ -3686,6 +3690,10 @@ static int kvm_get_msrs(X86CPU *cpu) + kvm_msr_entry_add(cpu, MSR_IA32_XFD_ERR, 0); + } + ++ if (kvm_has_msr_ghcb) { ++ kvm_msr_entry_add(cpu, MSR_AMD64_SEV_ES_GHCB, 0); ++ } ++ + ret = kvm_vcpu_ioctl(CPU(cpu), KVM_GET_MSRS, cpu->kvm_msr_buf); + if (ret < 0) { + return ret; +@@ -3991,6 +3999,9 @@ static int kvm_get_msrs(X86CPU *cpu) + case MSR_IA32_XFD_ERR: + env->msr_xfd_err = msrs[i].data; + break; ++ case MSR_AMD64_SEV_ES_GHCB: ++ env->ghcb_gpa = msrs[i].data; ++ break; + } + } + +diff --git a/target/i386/machine.c b/target/i386/machine.c +index 3977e9d8f..8aa54432d 100644 +--- a/target/i386/machine.c ++++ b/target/i386/machine.c +@@ -1497,6 +1497,27 @@ static const VMStateDescription vmstate_amx_xtile = { + }; + #endif + ++#if defined(CONFIG_KVM) && defined(TARGET_X86_64) ++static bool msr_ghcb_gpa_needed(void *opaque) ++{ ++ X86CPU *cpu = opaque; ++ CPUX86State *env = &cpu->env; ++ ++ return env->ghcb_gpa != 0; ++} ++ ++static const VMStateDescription vmstate_msr_ghcb_gpa = { ++ .name = "cpu/svm_msr_ghcb_gpa", ++ .version_id = 1, ++ .minimum_version_id = 1, ++ .needed = msr_ghcb_gpa_needed, ++ .fields = (VMStateField[]) { ++ VMSTATE_UINT64(env.ghcb_gpa, X86CPU), ++ VMSTATE_END_OF_LIST() ++ } ++}; ++#endif ++ + const VMStateDescription vmstate_x86_cpu = { + .name = "cpu", + .version_id = 12, +@@ -1638,6 +1659,9 @@ const VMStateDescription vmstate_x86_cpu = { + &vmstate_msr_xfd, + #ifdef TARGET_X86_64 + &vmstate_amx_xtile, ++#endif ++#if defined(CONFIG_KVM) && defined(TARGET_X86_64) ++ &vmstate_msr_ghcb_gpa, + #endif + NULL + } +diff --git a/target/i386/sev.c b/target/i386/sev.c +index 9cdf3e6bf..26b6e84d3 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -215,6 +215,8 @@ static struct ConfidentialGuestMemoryEncryptionOps sev_memory_encryption_ops = { + .load_incoming_cpu_state = csv_load_incoming_cpu_state, + }; + ++bool kvm_has_msr_ghcb; ++ + static int + sev_ioctl(int fd, int cmd, void *data, int *error) + { +@@ -1174,6 +1176,14 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) + cgs_class->memory_encryption_ops = &sev_memory_encryption_ops; + QTAILQ_INIT(&sev->shared_regions_list); + ++ /* Determine whether support MSR_AMD64_SEV_ES_GHCB */ ++ if (sev_es_enabled()) { ++ kvm_has_msr_ghcb = ++ kvm_vm_check_extension(kvm_state, KVM_CAP_SEV_ES_GHCB); ++ } else { ++ kvm_has_msr_ghcb = false; ++ } ++ + cgs->ready = true; + + return 0; +-- +2.31.1 + diff --git a/1065-anolis-target-i386-kvm-Return-resettable-when-emulat.patch b/1065-anolis-target-i386-kvm-Return-resettable-when-emulat.patch new file mode 100644 index 0000000000000000000000000000000000000000..966d6ffef29179b3d6fde37cd7b0a74812ace5c0 --- /dev/null +++ b/1065-anolis-target-i386-kvm-Return-resettable-when-emulat.patch @@ -0,0 +1,39 @@ +From 84ea1a98ae7cb888eba8f9be9f51955a4402355c Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Sun, 19 Jun 2022 16:49:45 +0800 +Subject: [PATCH 28/29] anolis: target/i386/kvm: Return resettable when emulate + HYGON CSV2 guest + +SEV-ES guest will be terminated by QEMU when receive reboot request. +In order to support reboot for CSV2 guest, report resettable in +kvm_arch_cpu_check_are_resettable(). + +Signed-off-by: hanliyang +--- + target/i386/kvm/kvm.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c +index fb85cc698..19c622bb0 100644 +--- a/target/i386/kvm/kvm.c ++++ b/target/i386/kvm/kvm.c +@@ -30,6 +30,7 @@ + #include "sysemu/runstate.h" + #include "kvm_i386.h" + #include "sev.h" ++#include "csv.h" + #include "hyperv.h" + #include "hyperv-proto.h" + +@@ -5363,7 +5364,7 @@ bool kvm_has_waitpkg(void) + + bool kvm_arch_cpu_check_are_resettable(void) + { +- return !sev_es_enabled(); ++ return !(sev_es_enabled() && !is_hygon_cpu()) && !csv_enabled(); + } + + #define ARCH_REQ_XCOMP_GUEST_PERM 0x1025 +-- +2.31.1 + diff --git a/1066-anolis-kvm-Add-support-for-CSV2-reboot.patch b/1066-anolis-kvm-Add-support-for-CSV2-reboot.patch new file mode 100644 index 0000000000000000000000000000000000000000..3c246b0133aabdd15cb4e3d4af358e4e56fef877 --- /dev/null +++ b/1066-anolis-kvm-Add-support-for-CSV2-reboot.patch @@ -0,0 +1,170 @@ +From a165dedbb9121f948738e5265198410487aa7acf Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Thu, 15 Apr 2021 08:32:24 -0400 +Subject: [PATCH 29/29] anolis: kvm: Add support for CSV2 reboot + +Linux will set vcpu.arch.guest_state_protected to true after execute +LAUNCH_UPDATE_VMSA successfully, and then KVM will prevent any changes +to VMCB State Save Area. + +In order to support CSV2 guest reboot, calls cpus_control_pre_system_reset() +to set vcpu.arch.guest_state_protected to false, and calls +cpus_control_post_system_reset() to restore VMSA of guest's vcpu with +data generated by LAUNCH_UPDATE_VMSA. + +In addition, for memory encrypted guest, additional works may be +required during system reset, such as flushing the cache. The function +cpus_control_post_system_reset() hints linux to flush caches of guest +memory. + +Signed-off-by: hanliyang +--- + accel/kvm/kvm-accel-ops.c | 3 +++ + accel/kvm/kvm-all.c | 10 ++++++++++ + accel/kvm/kvm-cpus.h | 3 +++ + include/sysemu/accel-ops.h | 3 +++ + include/sysemu/cpus.h | 2 ++ + linux-headers/linux/kvm.h | 4 ++++ + softmmu/cpus.c | 14 ++++++++++++++ + softmmu/runstate.c | 4 ++++ + 8 files changed, 43 insertions(+) + +diff --git a/accel/kvm/kvm-accel-ops.c b/accel/kvm/kvm-accel-ops.c +index 7516c67a3..9602f9609 100644 +--- a/accel/kvm/kvm-accel-ops.c ++++ b/accel/kvm/kvm-accel-ops.c +@@ -83,6 +83,9 @@ static void kvm_accel_ops_class_init(ObjectClass *oc, void *data) + ops->synchronize_post_init = kvm_cpu_synchronize_post_init; + ops->synchronize_state = kvm_cpu_synchronize_state; + ops->synchronize_pre_loadvm = kvm_cpu_synchronize_pre_loadvm; ++ ++ ops->control_pre_system_reset = kvm_cpus_control_pre_system_reset; ++ ops->control_post_system_reset = kvm_cpus_control_post_system_reset; + } + + static const TypeInfo kvm_accel_ops_type = { +diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c +index 6d63f0ab0..10af4170d 100644 +--- a/accel/kvm/kvm-all.c ++++ b/accel/kvm/kvm-all.c +@@ -2848,6 +2848,16 @@ void kvm_cpu_synchronize_pre_loadvm(CPUState *cpu) + run_on_cpu(cpu, do_kvm_cpu_synchronize_pre_loadvm, RUN_ON_CPU_NULL); + } + ++void kvm_cpus_control_pre_system_reset(void) ++{ ++ kvm_vm_ioctl(kvm_state, KVM_CONTROL_VCPU_PRE_SYSTEM_RESET, NULL); ++} ++ ++void kvm_cpus_control_post_system_reset(void) ++{ ++ kvm_vm_ioctl(kvm_state, KVM_CONTROL_VCPU_POST_SYSTEM_RESET, NULL); ++} ++ + #ifdef KVM_HAVE_MCE_INJECTION + static __thread void *pending_sigbus_addr; + static __thread int pending_sigbus_code; +diff --git a/accel/kvm/kvm-cpus.h b/accel/kvm/kvm-cpus.h +index bf0bd1bee..8ba363e0b 100644 +--- a/accel/kvm/kvm-cpus.h ++++ b/accel/kvm/kvm-cpus.h +@@ -19,4 +19,7 @@ void kvm_cpu_synchronize_post_reset(CPUState *cpu); + void kvm_cpu_synchronize_post_init(CPUState *cpu); + void kvm_cpu_synchronize_pre_loadvm(CPUState *cpu); + ++void kvm_cpus_control_pre_system_reset(void); ++void kvm_cpus_control_post_system_reset(void); ++ + #endif /* KVM_CPUS_H */ +diff --git a/include/sysemu/accel-ops.h b/include/sysemu/accel-ops.h +index 032f6979d..43893c1a4 100644 +--- a/include/sysemu/accel-ops.h ++++ b/include/sysemu/accel-ops.h +@@ -40,6 +40,9 @@ struct AccelOpsClass { + + int64_t (*get_virtual_clock)(void); + int64_t (*get_elapsed_ticks)(void); ++ ++ void (*control_pre_system_reset)(void); ++ void (*control_post_system_reset)(void); + }; + + #endif /* ACCEL_OPS_H */ +diff --git a/include/sysemu/cpus.h b/include/sysemu/cpus.h +index 868f1192d..70d6e8ad3 100644 +--- a/include/sysemu/cpus.h ++++ b/include/sysemu/cpus.h +@@ -42,6 +42,8 @@ extern int icount_align_option; + void qemu_cpu_kick_self(void); + + bool cpus_are_resettable(void); ++void cpus_control_pre_system_reset(void); ++void cpus_control_post_system_reset(void); + + void cpu_synchronize_all_states(void); + void cpu_synchronize_all_post_reset(void); +diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h +index 4a177f81d..d5278df1c 100644 +--- a/linux-headers/linux/kvm.h ++++ b/linux-headers/linux/kvm.h +@@ -1528,6 +1528,10 @@ struct kvm_s390_ucas_mapping { + #define KVM_GET_DEVICE_ATTR _IOW(KVMIO, 0xe2, struct kvm_device_attr) + #define KVM_HAS_DEVICE_ATTR _IOW(KVMIO, 0xe3, struct kvm_device_attr) + ++/* ioctls for control vcpu setup during system reset */ ++#define KVM_CONTROL_VCPU_PRE_SYSTEM_RESET _IO(KVMIO, 0xe8) ++#define KVM_CONTROL_VCPU_POST_SYSTEM_RESET _IO(KVMIO, 0xe9) ++ + /* + * ioctls for vcpu fds + */ +diff --git a/softmmu/cpus.c b/softmmu/cpus.c +index 071085f84..319a72d92 100644 +--- a/softmmu/cpus.c ++++ b/softmmu/cpus.c +@@ -200,6 +200,20 @@ bool cpus_are_resettable(void) + return cpu_check_are_resettable(); + } + ++void cpus_control_pre_system_reset(void) ++{ ++ if (cpus_accel->control_pre_system_reset) { ++ cpus_accel->control_pre_system_reset(); ++ } ++} ++ ++void cpus_control_post_system_reset(void) ++{ ++ if (cpus_accel->control_post_system_reset) { ++ cpus_accel->control_post_system_reset(); ++ } ++} ++ + int64_t cpus_get_virtual_clock(void) + { + /* +diff --git a/softmmu/runstate.c b/softmmu/runstate.c +index 10d9b7365..4b9c9f9e0 100644 +--- a/softmmu/runstate.c ++++ b/softmmu/runstate.c +@@ -437,6 +437,8 @@ void qemu_system_reset(ShutdownCause reason) + + mc = current_machine ? MACHINE_GET_CLASS(current_machine) : NULL; + ++ cpus_control_pre_system_reset(); ++ + cpu_synchronize_all_states(); + + if (mc && mc->reset) { +@@ -448,6 +450,8 @@ void qemu_system_reset(ShutdownCause reason) + qapi_event_send_reset(shutdown_caused_by_guest(reason), reason); + } + cpu_synchronize_all_post_reset(); ++ ++ cpus_control_post_system_reset(); + } + + /* +-- +2.31.1 + diff --git a/qemu-kvm.spec b/qemu-kvm.spec index 072824c7985da2d46a5cfc9dd04b1efd04350b7d..c8130c112c89c0b1f451d724a3a30a4ac5e3e5c6 100644 --- a/qemu-kvm.spec +++ b/qemu-kvm.spec @@ -1,4 +1,4 @@ -%define anolis_release .0.3 +%define anolis_release .0.4 %global SLOF_gittagdate 20191022 %global SLOF_gittagcommit 899d9883 @@ -706,6 +706,35 @@ Patch1034: 0005-anolis-cpu-i386-populate-CPUID-0x8000_001F-when-CSV-.patch Patch1035: 0006-anolis-csv-i386-CSV-guest-do-not-need-register-unreg.patch Patch1036: 0007-anolis-target-i386-csv-load-initial-image-to-private.patch Patch1037: 0008-anolis-vga-force-full-update-for-CSV-guest.patch +Patch1038: 1038-doc-update-AMD-SEV-to-include-Live-migration-flow.patch +Patch1039: 1039-migration.json-add-AMD-SEV-specific-migration-parame.patch +Patch1040: 1040-confidential-guest-support-introduce-ConfidentialGue.patch +Patch1041: 1041-target-i386-sev-provide-callback-to-setup-outgoing-c.patch +Patch1042: 1042-target-i386-sev-do-not-create-launch-context-for-an-.patch +Patch1043: 1043-target-i386-sev-add-support-to-encrypt-the-outgoing-.patch +Patch1044: 1044-target-i386-sev-add-support-to-load-incoming-encrypt.patch +Patch1045: 1045-kvm-Add-support-for-SEV-shared-regions-list-and-KVM_.patch +Patch1046: 1046-migration-add-support-to-migrate-shared-regions-list.patch +Patch1047: 1047-migration-ram-add-support-to-send-encrypted-pages.patch +Patch1048: 1048-migration-ram-Force-encrypted-status-for-flash0-flas.patch +Patch1049: 1049-migration-for-SEV-live-migration-bump-downtime-limit.patch +Patch1050: 1050-i386-kvm-Add-support-for-MSR-filtering.patch +Patch1051: 1051-kvm-Add-support-for-userspace-MSR-filtering-and-hand.patch +Patch1052: 1052-anolis-migration-ram-Force-encrypted-status-for-VGA-.patch +Patch1053: 1053-anolis-target-i386-sev-Clear-shared_regions_list-whe.patch +Patch1054: 1054-anolis-migration-ram-Fix-calculation-of-gfn-correpon.patch +Patch1055: 1055-anolis-target-i386-csv-Move-is_hygon_cpu-to-header-f.patch +Patch1056: 1056-anolis-target-i386-csv-Read-cert-chain-from-file-whe.patch +Patch1057: 1057-anolis-target-i386-csv-add-support-to-queue-the-outg.patch +Patch1058: 1058-anolis-target-i386-csv-add-support-to-encrypt-the-ou.patch +Patch1059: 1059-anolis-target-i386-csv-add-support-to-queue-the-inco.patch +Patch1060: 1060-anolis-target-i386-csv-add-support-to-load-incoming-.patch +Patch1061: 1061-anolis-migration-ram-Accelerate-the-transmission-of-.patch +Patch1062: 1062-anolis-migration-ram-Accelerate-the-loading-of-CSV-g.patch +Patch1063: 1063-anolis-target-i386-csv-Add-support-for-migrate-VMSA-.patch +Patch1064: 1064-anolis-target-i386-get-set-migrate-GHCB-state.patch +Patch1065: 1065-anolis-target-i386-kvm-Return-resettable-when-emulat.patch +Patch1066: 1066-anolis-kvm-Add-support-for-CSV2-reboot.patch BuildRequires: wget BuildRequires: rpm-build @@ -1944,6 +1973,9 @@ sh %{_sysconfdir}/sysconfig/modules/kvm.modules &> /dev/null || : %endif %changelog +* Thu Dec 1 2023 Liyang Han - 6.2.0-33.0.4 +- Support Hygon CSV/CSV2 live migration, CSV2 reboot + * Thu Sep 14 2023 Xin Jiang - 6.2.0-33.0.3 - 0001-anolis-csv-i386-add-CSV-context.patch - 0002-anolis-csv-i386-add-command-to-initialize-CSV-contex.patch