From 5c688679ed8afe0082e2770439a6e10fff04ee75 Mon Sep 17 00:00:00 2001 From: xiongmengbiao Date: Thu, 30 Nov 2023 13:47:21 +0800 Subject: [PATCH 01/51] support vpsp simulate a psp misc device for support tkm's key isolation Change-Id: I4d9fb5a8722e90a62c52eb97069c613834ced63f Signed-off-by: xiongmengbiao --- hw/misc/Kconfig | 4 ++ hw/misc/meson.build | 1 + hw/misc/psp.c | 141 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 hw/misc/psp.c diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index cc8a8c1418f..2ea5c68eb5d 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -200,4 +200,8 @@ config IOSB config XLNX_VERSAL_TRNG bool +config PSP_DEV + bool + default y + source macio/Kconfig diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 36c20d5637f..28cba0ac285 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -9,6 +9,7 @@ system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c')) system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c')) system_ss.add(when: 'CONFIG_LED', if_true: files('led.c')) system_ss.add(when: 'CONFIG_PVPANIC_COMMON', if_true: files('pvpanic.c')) +system_ss.add(when: 'CONFIG_PSP_DEV', if_true: files('psp.c')) # ARM devices system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c')) diff --git a/hw/misc/psp.c b/hw/misc/psp.c new file mode 100644 index 00000000000..da0a69efdb8 --- /dev/null +++ b/hw/misc/psp.c @@ -0,0 +1,141 @@ +/* + * hygon psp device emulation + * + * Copyright 2024 HYGON Corp. + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at + * your option) any later version. See the COPYING file in the top-level + * directory. + */ + +#include "qemu/osdep.h" +#include "qemu/compiler.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "sysemu/runstate.h" +#include + +#define TYPE_PSP_DEV "psp" +OBJECT_DECLARE_SIMPLE_TYPE(PSPDevState, PSP_DEV) + +struct PSPDevState { + /* Private */ + DeviceState pdev; + + /* Public */ + Notifier shutdown_notifier; + int dev_fd; + uint8_t enabled; + + /** + * vid is used to identify a virtual machine in qemu. + * When a virtual machine accesses a tkm key, + * the TKM module uses different key spaces based on different vids. + */ + uint32_t vid; +}; + +#define PSP_DEV_PATH "/dev/hygon_psp_config" +#define HYGON_PSP_IOC_TYPE 'H' +#define PSP_IOC_MUTEX_ENABLE _IOWR(HYGON_PSP_IOC_TYPE, 1, NULL) +#define PSP_IOC_MUTEX_DISABLE _IOWR(HYGON_PSP_IOC_TYPE, 2, NULL) +#define PSP_IOC_VPSP_OPT _IOWR(HYGON_PSP_IOC_TYPE, 3, NULL) + +enum VPSP_DEV_CTRL_OPCODE { + VPSP_OP_VID_ADD, + VPSP_OP_VID_DEL, +}; + +struct psp_dev_ctrl { + unsigned char op; + union { + unsigned int vid; + unsigned char reserved[128]; + } data; +}; + +static void psp_dev_destroy(PSPDevState *state) +{ + struct psp_dev_ctrl ctrl = { 0 }; + if (state && state->dev_fd >= 0) { + if (state->enabled) { + ctrl.op = VPSP_OP_VID_DEL; + if (ioctl(state->dev_fd, PSP_IOC_VPSP_OPT, &ctrl) < 0) { + error_report("VPSP_OP_VID_DEL: %d", -errno); + } else { + state->enabled = false; + } + } + qemu_close(state->dev_fd); + state->dev_fd = -1; + } +} + +/** + * Guest OS performs shut down operations through 'shutdown' and 'powerdown' event. + * The 'powerdown' event will also trigger 'shutdown' in the end, + * so only attention to the 'shutdown' event. + * + * When Guest OS trigger 'reboot' or 'reset' event, to do nothing. +*/ +static void psp_dev_shutdown_notify(Notifier *notifier, void *data) +{ + PSPDevState *state = container_of(notifier, PSPDevState, shutdown_notifier); + psp_dev_destroy(state); +} + +static void psp_dev_realize(DeviceState *dev, Error **errp) +{ + struct psp_dev_ctrl ctrl = { 0 }; + PSPDevState *state = PSP_DEV(dev); + + state->dev_fd = qemu_open_old(PSP_DEV_PATH, O_RDWR); + if (state->dev_fd < 0) { + error_setg(errp, "fail to open %s, errno %d.", PSP_DEV_PATH, errno); + goto end; + } + + ctrl.op = VPSP_OP_VID_ADD; + ctrl.data.vid = state->vid; + if (ioctl(state->dev_fd, PSP_IOC_VPSP_OPT, &ctrl) < 0) { + error_setg(errp, "psp_dev_realize VPSP_OP_VID_ADD vid %d, return %d", ctrl.data.vid, -errno); + goto end; + } + + state->enabled = true; + state->shutdown_notifier.notify = psp_dev_shutdown_notify; + qemu_register_shutdown_notifier(&state->shutdown_notifier); +end: + return; +} + +static struct Property psp_dev_properties[] = { + DEFINE_PROP_UINT32("vid", PSPDevState, vid, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void psp_dev_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "PSP Device"; + dc->realize = psp_dev_realize; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + device_class_set_props(dc, psp_dev_properties); +} + +static const TypeInfo psp_dev_info = { + .name = TYPE_PSP_DEV, + .parent = TYPE_DEVICE, + .instance_size = sizeof(PSPDevState), + .class_init = psp_dev_class_init, +}; + +static void psp_dev_register_types(void) +{ + type_register_static(&psp_dev_info); +} + +type_init(psp_dev_register_types) -- Gitee From ff30577f3ebef6b2e57c31909e0a87fa96f43a46 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Thu, 7 May 2020 22:26:17 +0000 Subject: [PATCH 02/51] 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/system/i386/amd-memory-encryption.rst | 40 +++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/system/i386/amd-memory-encryption.rst b/docs/system/i386/amd-memory-encryption.rst index e9bc142bc13..b7e3f46ff68 100644 --- a/docs/system/i386/amd-memory-encryption.rst +++ b/docs/system/i386/amd-memory-encryption.rst @@ -177,7 +177,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 ---------- -- Gitee From 0499615720dea35526bd7ef18449240f8cdfe630 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 27 Jul 2021 11:27:00 +0000 Subject: [PATCH 03/51] 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 [ Fix conflicts and qapi errors. ] Signed-off-by: hanliyang --- migration/migration-hmp-cmds.c | 28 ++++++++++++++++ migration/options.c | 60 ++++++++++++++++++++++++++++++++++ qapi/migration.json | 41 +++++++++++++++++++++-- 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c index 2faa5cad46c..58da696ff7a 100644 --- a/migration/migration-hmp-cmds.c +++ b/migration/migration-hmp-cmds.c @@ -392,6 +392,19 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict) monitor_printf(mon, "%s: %s\n", MigrationParameter_str(MIGRATION_PARAMETER_MODE), qapi_enum_lookup(&MigMode_lookup, params->mode)); + + assert(params->sev_pdh); + monitor_printf(mon, "%s: %s\n", + MigrationParameter_str(MIGRATION_PARAMETER_SEV_PDH), + params->sev_pdh); + assert(params->sev_plat_cert); + monitor_printf(mon, "%s: %s\n", + MigrationParameter_str(MIGRATION_PARAMETER_SEV_PLAT_CERT), + params->sev_plat_cert); + assert(params->sev_amd_cert); + monitor_printf(mon, "%s: %s\n", + MigrationParameter_str(MIGRATION_PARAMETER_SEV_AMD_CERT), + params->sev_amd_cert); } qapi_free_MigrationParameters(params); @@ -679,6 +692,21 @@ void hmp_migrate_set_parameter(Monitor *mon, const QDict *qdict) p->has_mode = true; visit_type_MigMode(v, param, &p->mode, &err); break; + case MIGRATION_PARAMETER_SEV_PDH: + 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->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->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/migration/options.c b/migration/options.c index 8d8ec73ad95..70f6beb83c0 100644 --- a/migration/options.c +++ b/migration/options.c @@ -179,6 +179,9 @@ Property migration_properties[] = { DEFINE_PROP_MIG_MODE("mode", MigrationState, parameters.mode, MIG_MODE_NORMAL), + DEFINE_PROP_STRING("sev-pdh", MigrationState, parameters.sev_pdh), + DEFINE_PROP_STRING("sev-plat-cert", MigrationState, parameters.sev_plat_cert), + DEFINE_PROP_STRING("sev-amd-cert", MigrationState, parameters.sev_amd_cert), /* Migration capabilities */ DEFINE_PROP_MIG_CAP("x-xbzrle", MIGRATION_CAPABILITY_XBZRLE), @@ -997,6 +1000,9 @@ 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->sev_pdh = g_strdup(s->parameters.sev_pdh); + params->sev_plat_cert = g_strdup(s->parameters.sev_plat_cert); + params->sev_amd_cert = g_strdup(s->parameters.sev_amd_cert); if (s->parameters.has_block_bitmap_mapping) { params->has_block_bitmap_mapping = true; @@ -1047,6 +1053,10 @@ void migrate_params_init(MigrationParameters *params) params->has_x_vcpu_dirty_limit_period = true; params->has_vcpu_dirty_limit = true; params->has_mode = true; + + params->sev_pdh = g_strdup(""); + params->sev_plat_cert = g_strdup(""); + params->sev_amd_cert = g_strdup(""); } /* @@ -1348,6 +1358,19 @@ static void migrate_params_test_apply(MigrateSetParameters *params, if (params->has_mode) { dest->mode = params->mode; } + + if (params->sev_pdh) { + assert(params->sev_pdh->type == QTYPE_QSTRING); + dest->sev_pdh = params->sev_pdh->u.s; + } + if (params->sev_plat_cert) { + assert(params->sev_plat_cert->type == QTYPE_QSTRING); + dest->sev_plat_cert = params->sev_plat_cert->u.s; + } + if (params->sev_amd_cert) { + assert(params->sev_amd_cert->type == QTYPE_QSTRING); + dest->sev_amd_cert = params->sev_amd_cert->u.s; + } } static void migrate_params_apply(MigrateSetParameters *params, Error **errp) @@ -1492,6 +1515,22 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp) if (params->has_mode) { s->parameters.mode = params->mode; } + + if (params->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->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->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) @@ -1517,6 +1556,27 @@ void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp) params->tls_authz->type = QTYPE_QSTRING; params->tls_authz->u.s = strdup(""); } + /* TODO Rewrite "" to null instead */ + if (params->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->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->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); diff --git a/qapi/migration.json b/qapi/migration.json index 197d3faa43f..d9b25efa9a6 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -874,6 +874,15 @@ # @mode: Migration mode. See description in @MigMode. Default is 'normal'. # (Since 8.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: # # @deprecated: Member @block-incremental is deprecated. Use @@ -907,7 +916,8 @@ 'block-bitmap-mapping', { 'name': 'x-vcpu-dirty-limit-period', 'features': ['unstable'] }, 'vcpu-dirty-limit', - 'mode'] } + 'mode', + 'sev-pdh', 'sev-plat-cert', 'sev-amd-cert'] } ## # @MigrateSetParameters: @@ -1062,6 +1072,15 @@ # @mode: Migration mode. See description in @MigMode. Default is 'normal'. # (Since 8.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: # # @deprecated: Member @block-incremental is deprecated. Use @@ -1115,7 +1134,11 @@ '*x-vcpu-dirty-limit-period': { 'type': 'uint64', 'features': [ 'unstable' ] }, '*vcpu-dirty-limit': 'uint64', - '*mode': 'MigMode'} } + '*mode': 'MigMode', + '*sev-pdh': 'StrOrNull', + '*sev-plat-cert': 'StrOrNull', + '*sev-amd-cert' : 'StrOrNull' } } + ## # @migrate-set-parameters: @@ -1290,6 +1313,15 @@ # @mode: Migration mode. See description in @MigMode. Default is 'normal'. # (Since 8.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: # # @deprecated: Member @block-incremental is deprecated. Use @@ -1340,7 +1372,10 @@ '*x-vcpu-dirty-limit-period': { 'type': 'uint64', 'features': [ 'unstable' ] }, '*vcpu-dirty-limit': 'uint64', - '*mode': 'MigMode'} } + '*mode': 'MigMode', + '*sev-pdh': 'str', + '*sev-plat-cert': 'str', + '*sev-amd-cert' : 'str'} } ## # @query-migrate-parameters: -- Gitee From dacd8c4764e41e0b8e6fe46fe29f9e65ca1f5625 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 27 Jul 2021 11:41:37 +0000 Subject: [PATCH 04/51] 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 ba2dd4b5dfc..343f686fc2b 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 */ -- Gitee From dbd192392ad43804584938db8179f5861940dcd4 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 27 Jul 2021 12:10:23 +0000 Subject: [PATCH 05/51] 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 [ Fix conflict. ] 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 9a712466825..3ab02f4c6b2 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -73,6 +73,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; @@ -157,6 +163,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) { @@ -906,6 +918,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 @@ -920,6 +974,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__); @@ -1013,6 +1070,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 e7499c95b1e..e96de021f5b 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -51,6 +51,8 @@ uint32_t sev_get_reduced_phys_bits(void); 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); -- Gitee From 85eccfb010be99335ab4465fff0504dd2f7acc09 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 27 Jul 2021 12:16:09 +0000 Subject: [PATCH 06/51] 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 [ Fix conflict. ] 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 3ab02f4c6b2..b56cbdb6bb4 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -1060,10 +1060,16 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) goto err; } - 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; + } } ram_block_notifier_add(&sev_ram_notifier); -- Gitee From 32124419dfb142e6c0d23b8a7917ce8566a4160b Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 27 Jul 2021 12:55:25 +0000 Subject: [PATCH 07/51] 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 [ Fix conflict. ] Signed-off-by: hanliyang --- target/i386/sev.c | 219 +++++++++++++++++++++++++++++++++++++++ target/i386/sev.h | 2 + target/i386/trace-events | 3 + 3 files changed, 224 insertions(+) diff --git a/target/i386/sev.c b/target/i386/sev.c index b56cbdb6bb4..617587c69ac 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" @@ -79,6 +81,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; @@ -167,6 +171,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 @@ -960,6 +965,38 @@ 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; + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) { SevGuestState *sev @@ -1075,6 +1112,7 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) ram_block_notifier_add(&sev_ram_notifier); qemu_add_machine_init_done_notifier(&sev_machine_done_notify); qemu_add_vm_change_state_handler(sev_vm_state_change, sev); + migration_add_notifier(&sev_migration_state, sev_migration_state_notifier); cgs_class->memory_encryption_ops = &sev_memory_encryption_ops; @@ -1316,6 +1354,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 e96de021f5b..463e94bb81f 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -53,6 +53,8 @@ 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_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 2cd8726eebb..e8d4aec1251 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -11,3 +11,6 @@ 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) "" -- Gitee From 814f0c308cf71dcd076f23e9fe554c2b5f56c5bf Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 27 Jul 2021 13:00:50 +0000 Subject: [PATCH 08/51] 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 [ Fix conflicts. ] 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 617587c69ac..ee42edf4e98 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -172,6 +172,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 @@ -911,13 +912,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); } } @@ -1535,6 +1556,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 463e94bb81f..d94da2956bf 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 e8d4aec1251..475de65ad4a 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -14,3 +14,6 @@ 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) "" -- Gitee From 493ec7bb50b5020429809cacdbcbad1c5b2f5b65 Mon Sep 17 00:00:00 2001 From: Ashish Kalra Date: Tue, 27 Jul 2021 15:05:49 +0000 Subject: [PATCH 09/51] 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 [ Fix conflicts. ] Signed-off-by: hanliyang --- linux-headers/linux/kvm.h | 3 ++ target/i386/kvm/kvm.c | 48 +++++++++++++++++ target/i386/kvm/sev-stub.c | 11 ++++ target/i386/sev.c | 106 +++++++++++++++++++++++++++++++++++++ target/i386/sev.h | 3 ++ 5 files changed, 171 insertions(+) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 549fea3a978..9758e8fecca 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -346,6 +346,7 @@ struct kvm_run { } iocsr_io; /* KVM_EXIT_HYPERCALL */ struct { +#define KVM_HC_MAP_GPA_RANGE 12 __u64 nr; __u64 args[6]; __u64 ret; @@ -1198,6 +1199,8 @@ struct kvm_ppc_resize_hpt { #define KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES 229 #define KVM_CAP_ARM_SUPPORTED_REG_MASK_RANGES 230 +#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 a0bc9ea7b19..82f6d3b0480 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -148,6 +148,7 @@ static int has_xcrs; static int has_sregs2; static int has_exception_payload; static int has_triple_fault_event; +static int has_map_gpa_range; static bool has_msr_mcg_ext_ctl; @@ -2191,6 +2192,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; @@ -2584,6 +2596,17 @@ int kvm_arch_init(MachineState *ms, KVMState *s) #endif } + 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; @@ -4936,6 +4959,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; @@ -5359,6 +5404,9 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) ret = kvm_xen_handle_exit(cpu, &run->xen); break; #endif + 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/kvm/sev-stub.c b/target/i386/kvm/sev-stub.c index 1be5341e8a6..1282d242a7e 100644 --- a/target/i386/kvm/sev-stub.c +++ b/target/i386/kvm/sev-stub.c @@ -19,3 +19,14 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) /* If we get here, cgs must be some non-SEV thing */ return 0; } + +int sev_remove_shared_regions_list(unsigned long gfn_start, + unsigned long gfn_end) +{ + return 0; +} + +int sev_add_shared_regions_list(unsigned long gfn_start, unsigned long gfn_end) +{ + return 0; +} diff --git a/target/i386/sev.c b/target/i386/sev.c index ee42edf4e98..bd00a28e9f0 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -44,6 +44,11 @@ #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; +}; + /** * SevGuestState: @@ -87,6 +92,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 */ @@ -1136,6 +1143,7 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) migration_add_notifier(&sev_migration_state, sev_migration_state_notifier); cgs_class->memory_encryption_ops = &sev_memory_encryption_ops; + QTAILQ_INIT(&sev->shared_regions_list); cgs->ready = true; @@ -1670,6 +1678,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 d94da2956bf..acf69d4e6f9 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); -- Gitee From 54a29f988c761e7da755da938b52f96d2662a99f Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 27 Jul 2021 16:31:36 +0000 Subject: [PATCH 10/51] 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 [ Fix conflicts. ] Signed-off-by: hanliyang --- include/exec/confidential-guest-support.h | 2 +- target/i386/sev.c | 45 +++++++++++++++++++++++ target/i386/sev.h | 2 + 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h index 343f686fc2b..dd4887f65ff 100644 --- a/include/exec/confidential-guest-support.h +++ b/include/exec/confidential-guest-support.h @@ -73,7 +73,7 @@ struct ConfidentialGuestMemoryEncryptionOps { bool (*is_gfn_in_unshared_region)(unsigned long gfn); /* Write the shared regions list */ - int (*save_outgoing_shared_regions_list)(QEMUFile *f); + int (*save_outgoing_shared_regions_list)(QEMUFile *f, uint64_t *bytes_sent); /* Load the shared regions list */ int (*load_incoming_shared_regions_list)(QEMUFile *f); diff --git a/target/i386/sev.c b/target/i386/sev.c index bd00a28e9f0..aeb3e323da5 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -176,10 +176,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 @@ -1776,6 +1781,46 @@ int sev_add_shared_regions_list(unsigned long start, unsigned long end) return 1; } +int sev_save_outgoing_shared_regions_list(QEMUFile *f, uint64_t *bytes_sent) +{ + 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); + *bytes_sent += 12; + } + + qemu_put_be32(f, SHARED_REGION_LIST_END); + *bytes_sent += 4; + 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 acf69d4e6f9..5b4231c8594 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, uint64_t *bytes_sent); +int sev_load_incoming_shared_regions_list(QEMUFile *f); int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); -- Gitee From 2b93c1ead8fdf9730e6d2ea5799bac409c87825c Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 27 Jul 2021 16:53:19 +0000 Subject: [PATCH 11/51] 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 [ Fix conflicts. ] Signed-off-by: hanliyang --- migration/migration.h | 2 + migration/ram.c | 174 +++++++++++++++++++++++++++++++++++++++++- target/i386/sev.c | 14 ++++ target/i386/sev.h | 4 + 4 files changed, 192 insertions(+), 2 deletions(-) diff --git a/migration/migration.h b/migration/migration.h index cf2c9c88e01..65f5599f450 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -550,4 +550,6 @@ void migration_rp_kick(MigrationState *s); int migration_stop_vm(RunState state); +bool memcrypt_enabled(void); + #endif diff --git a/migration/ram.c b/migration/ram.c index 8c7886ab797..317aea56351 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -63,6 +63,10 @@ #include "options.h" #include "sysemu/dirtylimit.h" #include "sysemu/kvm.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() */ @@ -92,7 +96,16 @@ /* 0x80 is reserved in rdma.h for RAM_SAVE_FLAG_HOOK */ #define RAM_SAVE_FLAG_COMPRESS_PAGE 0x100 #define RAM_SAVE_FLAG_MULTIFD_FLUSH 0x200 -/* We can't use any flag that is bigger than 0x200 */ +#define RAM_SAVE_FLAG_ENCRYPTED_DATA 0x400 + +bool memcrypt_enabled(void) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + if(ms->cgs) + return ms->cgs->ready; + else + return false; +} XBZRLECacheStats xbzrle_counters; @@ -1204,6 +1217,88 @@ static int save_normal_page(PageSearchStatus *pss, RAMBlock *block, return 1; } +/** + * ram_save_encrypted_page - send the given encrypted page to the stream + */ +static int ram_save_encrypted_page(RAMState *rs, PageSearchStatus *pss) +{ + QEMUFile *file = pss->pss_channel; + int ret; + uint8_t *p; + RAMBlock *block = pss->block; + ram_addr_t offset = ((ram_addr_t)pss->page) << TARGET_PAGE_BITS; + uint64_t bytes_xmit = 0; + 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; + trace_ram_save_page(block->idstr, (uint64_t)offset, p); + + ram_transferred_add(save_page_header(pss, file, block, + offset | RAM_SAVE_FLAG_ENCRYPTED_DATA)); + qemu_put_be32(file, RAM_SAVE_ENCRYPTED_PAGE); + ret = ops->save_outgoing_page(file, p, TARGET_PAGE_SIZE, &bytes_xmit); + if (ret) { + return -1; + } + ram_transferred_add(4 + bytes_xmit); + stat64_add(&mig_stats.normal_pages, 1); + + return 1; +} + +/** + * ram_save_shared_region_list: send the shared region list + */ +static int ram_save_shared_region_list(RAMState *rs, QEMUFile *f) +{ + int ret; + uint64_t bytes_xmit = 0; + PageSearchStatus *pss = &rs->pss[RAM_CHANNEL_PRECOPY]; + MachineState *ms = MACHINE(qdev_get_machine()); + ConfidentialGuestSupportClass *cgs_class = + (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); + struct ConfidentialGuestMemoryEncryptionOps *ops = + cgs_class->memory_encryption_ops; + + ram_transferred_add(save_page_header(pss, f, + pss->last_sent_block, + RAM_SAVE_FLAG_ENCRYPTED_DATA)); + qemu_put_be32(f, RAM_SAVE_SHARED_REGIONS_LIST); + ret = ops->save_outgoing_shared_regions_list(f, &bytes_xmit); + if (ret < 0) { + return ret; + } + ram_transferred_add(4 + bytes_xmit); + + return 0; +} + +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 * @@ -2034,6 +2129,35 @@ static bool save_compress_page(RAMState *rs, PageSearchStatus *pss, compress_send_queued_data); } +/** + * 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_legacy: save one target page * @@ -2052,6 +2176,17 @@ static int ram_save_target_page_legacy(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); + } + if (save_compress_page(rs, pss, offset)) { return 1; } @@ -2917,6 +3052,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 @@ -2952,6 +3099,13 @@ static int ram_save_setup(QEMUFile *f, void *opaque) (*rsp)->pss[RAM_CHANNEL_PRECOPY].pss_channel = f; WITH_RCU_READ_LOCK_GUARD() { + + if (memcrypt_enabled()) { + if (ram_encrypted_save_setup()) { + return -1; + } + } + qemu_put_be64(f, ram_bytes_total_with_ignored() | RAM_SAVE_FLAG_MEM_SIZE); @@ -3181,6 +3335,15 @@ static int ram_save_complete(QEMUFile *f, void *opaque) qemu_file_set_error(f, ret); return ret; } + + /* send the shared regions list */ + if (memcrypt_enabled()) { + ret = ram_save_shared_region_list(rs, f); + if (ret < 0) { + qemu_file_set_error(f, ret); + return ret; + } + } } ret = multifd_send_sync_main(rs->pss[RAM_CHANNEL_PRECOPY].pss_channel); @@ -3918,7 +4081,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(mis, f, flags, RAM_CHANNEL_PRECOPY); @@ -4011,6 +4175,12 @@ static int ram_load_precopy(QEMUFile *f) qemu_file_set_error(f, ret); } break; + case RAM_SAVE_FLAG_ENCRYPTED_DATA: + if (load_encrypted_data(f, host)) { + error_report("Failed to load encrypted data"); + ret = -EINVAL; + } + break; default: error_report("Unknown combination of migration flags: 0x%x", flags); ret = -EINVAL; diff --git a/target/i386/sev.c b/target/i386/sev.c index aeb3e323da5..8511756311a 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -183,6 +183,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, }; @@ -1821,6 +1822,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 5b4231c8594..b9c2afb799e 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, uint64_t *bytes_sent); 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); -- Gitee From 388bb911357c1cf7d2b03e31bebe9670e3139408 Mon Sep 17 00:00:00 2001 From: Ashish Kalra Date: Tue, 27 Jul 2021 18:05:25 +0000 Subject: [PATCH 12/51] 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 317aea56351..09faa8572d4 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -2149,6 +2149,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. -- Gitee From bfa2537c9ae8d8db06cb6fd585a556b2104b4c4d Mon Sep 17 00:00:00 2001 From: Ashish Kalra Date: Tue, 27 Jul 2021 17:59:33 +0000 Subject: [PATCH 13/51] 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 [ Fix conflicts. ] Signed-off-by: hanliyang --- target/i386/kvm/kvm.c | 35 +++++++++++++++++++++++++++++++++++ target/i386/kvm/sev-stub.c | 4 ++++ target/i386/sev.c | 6 ++++++ target/i386/sev.h | 1 + 4 files changed, 46 insertions(+) diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 82f6d3b0480..a5a755db014 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -2488,6 +2488,32 @@ static bool kvm_rdmsr_core_thread_count(X86CPU *cpu, uint32_t msr, return true; } +/* + * 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; @@ -2735,6 +2761,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/kvm/sev-stub.c b/target/i386/kvm/sev-stub.c index 1282d242a7e..99899688e4f 100644 --- a/target/i386/kvm/sev-stub.c +++ b/target/i386/kvm/sev-stub.c @@ -30,3 +30,7 @@ int sev_add_shared_regions_list(unsigned long gfn_start, unsigned long gfn_end) { return 0; } + +void sev_del_migrate_blocker(void) +{ +} diff --git a/target/i386/sev.c b/target/i386/sev.c index 8511756311a..aeeb479b599 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -925,6 +925,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 b9c2afb799e..84e3bdf2df5 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, uint64_t *bytes_sent); 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); -- Gitee From ddc1237fe7657246829340cca45f855030cb7159 Mon Sep 17 00:00:00 2001 From: hanliyang Date: Wed, 31 Jan 2024 07:26:57 +0800 Subject: [PATCH 14/51] target/i386: sev: Return 0 if sev_send_get_packet_len() fails The send_packet_hdr_len of struct SEVState is of type size_t which is an unsigned class type. If the send_packet_hdr_len is assigned as -1, then it will be a huge number and the QEMU process will crash when allocating packet buffer with the huge size. For example, the following code could cause crash described above. ``` static int sev_send_update_data(SEVState *s, QEMUFile *f, uint8_t *ptr, uint32_t size, uint64_t *bytes_sent) { ...... 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); } ...... } ``` Signed-off-by: hanliyang --- target/i386/sev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/i386/sev.c b/target/i386/sev.c index aeeb479b599..b1b26b8a2b2 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -1491,7 +1491,7 @@ sev_send_get_packet_len(int *fw_err) ret = sev_ioctl(sev_guest->sev_fd, KVM_SEV_SEND_UPDATE_DATA, &update, fw_err); if (*fw_err != SEV_RET_INVALID_LEN) { - ret = -1; + ret = 0; 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; -- Gitee From 478d0a924da5e32ba7b707c98798f25d66e80c42 Mon Sep 17 00:00:00 2001 From: hanliyang Date: Tue, 8 Dec 2020 22:57:46 -0500 Subject: [PATCH 15/51] 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 09faa8572d4..f711738553c 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -2157,6 +2157,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. -- Gitee From 1a43356669c8b2e2b8d0b7168d3f9cddc84cac9a Mon Sep 17 00:00:00 2001 From: hanliyang Date: Sun, 16 Jan 2022 19:57:58 -0500 Subject: [PATCH 16/51] 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 | 5 +++++ target/i386/sev.c | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index a5a755db014..5730d0e0c0d 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -2270,6 +2270,11 @@ void kvm_arch_reset_vcpu(X86CPU *cpu) env->mp_state = KVM_MP_STATE_RUNNABLE; } + 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 b1b26b8a2b2..594f034e887 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -1693,9 +1693,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; @@ -1709,6 +1709,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) { -- Gitee From 982df892d3b3896a8379447bc8b85ce84d733a1f Mon Sep 17 00:00:00 2001 From: hanliyang Date: Sun, 16 Jan 2022 20:05:02 -0500 Subject: [PATCH 17/51] 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 f711738553c..22f07a064aa 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -67,6 +67,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() */ @@ -2143,6 +2144,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)) { @@ -2165,7 +2168,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); } -- Gitee From 576385738a1c163c11acba84594178ee47383fdd Mon Sep 17 00:00:00 2001 From: hanliyang Date: Thu, 14 Mar 2024 19:21:11 +0800 Subject: [PATCH 18/51] target/i386: Introduce header file csv.h This header file is used to provide common helper functions and data structures for Hygon CSV. Signed-off-by: hanliyang --- configs/devices/i386-softmmu/default.mak | 1 + hw/i386/Kconfig | 5 +++ target/i386/csv.h | 47 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 target/i386/csv.h diff --git a/configs/devices/i386-softmmu/default.mak b/configs/devices/i386-softmmu/default.mak index 598c6646dfc..db83ffcab9d 100644 --- a/configs/devices/i386-softmmu/default.mak +++ b/configs/devices/i386-softmmu/default.mak @@ -23,6 +23,7 @@ #CONFIG_TPM_TIS_ISA=n #CONFIG_VTD=n #CONFIG_SGX=n +#CONFIG_CSV=n # Boards: # diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index 55850791df4..08f3ae43f80 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -10,6 +10,10 @@ config SGX bool depends on KVM +config CSV + bool + depends on SEV + config PC bool imply APPLESMC @@ -26,6 +30,7 @@ config PC imply QXL imply SEV imply SGX + imply CSV imply TEST_DEVICES imply TPM_CRB imply TPM_TIS_ISA diff --git a/target/i386/csv.h b/target/i386/csv.h new file mode 100644 index 00000000000..f935babe972 --- /dev/null +++ b/target/i386/csv.h @@ -0,0 +1,47 @@ +/* + * QEMU CSV support + * + * Copyright: Hygon Info Technologies Ltd. 2022 + * + * Author: + * Jiang Xin + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef I386_CSV_H +#define I386_CSV_H + +#ifdef CONFIG_CSV + +#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 bool __attribute__((unused)) 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; +} + +#else + +#define is_hygon_cpu() (false) + +#endif + +#endif -- Gitee From 6cd1b91414784f2651c279814663e30d9b028e57 Mon Sep 17 00:00:00 2001 From: hanliyang Date: Mon, 13 Nov 2023 21:55:33 +0000 Subject: [PATCH 19/51] 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. [ Fix conflicts. ] Signed-off-by: hanliyang --- qapi/migration.json | 24 +++++++++++++++--------- target/i386/sev.c | 30 ++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/qapi/migration.json b/qapi/migration.json index d9b25efa9a6..3c4724db1b2 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -874,14 +874,16 @@ # @mode: Migration mode. See description in @MigMode. Default is 'normal'. # (Since 8.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: # @@ -1072,14 +1074,16 @@ # @mode: Migration mode. See description in @MigMode. Default is 'normal'. # (Since 8.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: # @@ -1313,14 +1317,16 @@ # @mode: Migration mode. See description in @MigMode. Default is 'normal'. # (Since 8.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: # diff --git a/target/i386/sev.c b/target/i386/sev.c index 594f034e887..ab7893fcaeb 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -27,6 +27,7 @@ #include "crypto/hash.h" #include "sysemu/kvm.h" #include "sev.h" +#include "csv.h" #include "sysemu/sysemu.h" #include "sysemu/runstate.h" #include "trace.h" @@ -979,18 +980,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; } -- Gitee From 459ef550486fbf7211707bda276a348a9711e0da Mon Sep 17 00:00:00 2001 From: fangbaoshun Date: Mon, 2 Aug 2021 11:00:07 +0800 Subject: [PATCH 20/51] 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/csv.h | 11 ++ target/i386/sev.c | 161 ++++++++++++++++++++++ 4 files changed, 181 insertions(+) diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h index dd4887f65ff..8949568acce 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 9758e8fecca..a61be972c7b 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -2024,6 +2024,12 @@ struct kvm_sev_receive_update_data { __u32 trans_len; }; +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/csv.h b/target/i386/csv.h index f935babe972..4c1ef200298 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -44,4 +44,15 @@ static bool __attribute__((unused)) is_hygon_cpu(void) #endif +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; +}; + +int csv_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); + #endif diff --git a/target/i386/sev.c b/target/i386/sev.c index ab7893fcaeb..027249e443e 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -95,6 +95,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 */ @@ -187,6 +190,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 @@ -1864,6 +1868,163 @@ bool sev_is_gfn_in_unshared_region(unsigned long gfn) return true; } +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) -- Gitee From 98a84342005697d79cdf1edbff23ed12ae857f05 Mon Sep 17 00:00:00 2001 From: fangbaoshun Date: Mon, 2 Aug 2021 11:41:58 +0800 Subject: [PATCH 21/51] 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/csv.h | 1 + target/i386/sev.c | 88 +++++++++++++++++++++++ target/i386/sev.h | 3 + 5 files changed, 104 insertions(+) diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h index 8949568acce..c84f8c1efca 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 a61be972c7b..8595dd5ca72 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -1928,6 +1928,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, }; @@ -2030,6 +2033,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/csv.h b/target/i386/csv.h index 4c1ef200298..2a3a3119d9c 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -54,5 +54,6 @@ struct CsvBatchCmdList { }; 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); #endif diff --git a/target/i386/sev.c b/target/i386/sev.c index 027249e443e..cfbc9fb5917 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -191,6 +191,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 @@ -2011,6 +2012,69 @@ 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; + } + + 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) { @@ -2025,6 +2089,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 84e3bdf2df5..f7886116e7b 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); -- Gitee From ec780ff02209bc4fed0f517e4b8cc5962182e428 Mon Sep 17 00:00:00 2001 From: fangbaoshun Date: Mon, 2 Aug 2021 13:49:48 +0800 Subject: [PATCH 22/51] 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/csv.h | 1 + target/i386/sev.c | 92 +++++++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h index c84f8c1efca..101cc5220a3 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/csv.h b/target/i386/csv.h index 2a3a3119d9c..d1bcc8bc169 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -55,5 +55,6 @@ struct CsvBatchCmdList { 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); #endif diff --git a/target/i386/sev.c b/target/i386/sev.c index cfbc9fb5917..97e2e7153b6 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -192,6 +192,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 @@ -1940,6 +1941,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) @@ -2012,6 +2022,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) { @@ -2089,6 +2159,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) { -- Gitee From 2e1b9eedc61e2643aa74a3dd20abe6eef41fa492 Mon Sep 17 00:00:00 2001 From: fangbaoshun Date: Mon, 2 Aug 2021 14:11:43 +0800 Subject: [PATCH 23/51] 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/csv.h | 1 + target/i386/sev.c | 32 +++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h index 101cc5220a3..cb14b815cb6 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/csv.h b/target/i386/csv.h index d1bcc8bc169..977f08b9828 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -56,5 +56,6 @@ struct CsvBatchCmdList { 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); #endif diff --git a/target/i386/sev.c b/target/i386/sev.c index 97e2e7153b6..8e5da510d31 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -193,6 +193,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 @@ -2145,6 +2146,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) { @@ -2205,6 +2224,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) -- Gitee From f2e133370d44aaa49a74ee1481dc469619adef53 Mon Sep 17 00:00:00 2001 From: fangbaoshun Date: Mon, 2 Aug 2021 14:35:51 +0800 Subject: [PATCH 24/51] 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 | 119 +++++++++++++++++++++++ target/i386/csv.h | 2 + 4 files changed, 127 insertions(+) diff --git a/configs/devices/i386-softmmu/default.mak b/configs/devices/i386-softmmu/default.mak index db83ffcab9d..e948e54e4e9 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 08f3ae43f80..682e324f1cc 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -12,8 +12,13 @@ config SGX config CSV bool + select HYGON_CSV_MIG_ACCEL depends on SEV +config HYGON_CSV_MIG_ACCEL + bool + depends on CSV + config PC bool imply APPLESMC diff --git a/migration/ram.c b/migration/ram.c index 22f07a064aa..be8dca3261b 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -67,6 +67,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() */ @@ -2334,6 +2335,112 @@ out: return ret; } +#ifdef CONFIG_HYGON_CSV_MIG_ACCEL +/** + * ram_save_encrypted_pages_in_batch: send the given encrypted pages to + * the stream. + * + * Sending pages of 4K size in batch. The saving stops at the end of + * the block. + * + * The caller must be with ram_state.bitmap_mutex held to call this + * function. + * + * Returns the number of pages written or negative on error + * + * @rs: current RAM state + * @pss: data about the page we want to send + */ +static int +ram_save_encrypted_pages_in_batch(RAMState *rs, PageSearchStatus *pss) +{ + bool page_dirty; + int ret; + int tmppages, pages = 0; + uint8_t *p; + uint32_t host_len = 0; + uint64_t bytes_xmit = 0; + ram_addr_t offset, start_offset = 0; + 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 { + page_dirty = migration_bitmap_clear_dirty(rs, pss->block, pss->page); + + /* Check the pages is dirty and if it is send it */ + if (page_dirty) { + /* Process the unencrypted page */ + if (!encrypted_test_list(rs, pss->block, pss->page)) { + tmppages = migration_ops->ram_save_target_page(rs, pss); + } else { + /* Caculate the offset and host virtual address of the page */ + offset = ((ram_addr_t)pss->page) << TARGET_PAGE_BITS; + p = pss->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; + + stat64_add(&mig_stats.normal_pages, 1); + } + } else { + tmppages = 0; + } + + if (tmppages >= 0) { + pages += tmppages; + } else { + return tmppages; + } + + pss_find_next_dirty(pss); + } while (offset_in_ramblock(pss->block, + ((ram_addr_t)pss->page) << TARGET_PAGE_BITS) && + host_len < CSV_OUTGOING_PAGE_WINDOW_SIZE); + + /* Check if there are any queued pages */ + if (host_len != 0) { + ram_transferred_add(save_page_header(pss, pss->pss_channel, + pss->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(pss->pss_channel, RAM_SAVE_ENCRYPTED_PAGE_BATCH); + else + qemu_put_be32(pss->pss_channel, RAM_SAVE_ENCRYPTED_PAGE_BATCH_END); + ram_transferred_add(4); + /* Process the queued pages in batch */ + ret = ops->save_queued_outgoing_pages(pss->pss_channel, &bytes_xmit); + if (ret) { + return -1; + } + ram_transferred_add(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 * @@ -2369,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); +#endif + /* Update host page boundary information */ pss_host_page_prepare(pss); diff --git a/target/i386/csv.h b/target/i386/csv.h index 977f08b9828..74a54f9b9ca 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -44,6 +44,8 @@ static bool __attribute__((unused)) is_hygon_cpu(void) #endif +#define CSV_OUTGOING_PAGE_WINDOW_SIZE (4094 * TARGET_PAGE_SIZE) + typedef struct CsvBatchCmdList CsvBatchCmdList; typedef void (*CsvDestroyCmdNodeFn) (void *data); -- Gitee From d96daa85a535a5363592de1c3784ae744a2c4115 Mon Sep 17 00:00:00 2001 From: fangbaoshun Date: Mon, 2 Aug 2021 14:49:45 +0800 Subject: [PATCH 25/51] 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 be8dca3261b..c7245aa4dc9 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -1295,6 +1295,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; -- Gitee From c9e9fc38be4fdf97fdc6f6adce29699f3bef01da Mon Sep 17 00:00:00 2001 From: hanliyang Date: Tue, 7 Jun 2022 15:19:32 +0800 Subject: [PATCH 26/51] 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 | 42 +++++ target/i386/csv.h | 2 + target/i386/sev.c | 201 ++++++++++++++++++++++ target/i386/sev.h | 1 + target/i386/trace-events | 2 + 7 files changed, 270 insertions(+) diff --git a/include/exec/confidential-guest-support.h b/include/exec/confidential-guest-support.h index cb14b815cb6..2cba27642f7 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, uint64_t *bytes_sent); + + /* 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 8595dd5ca72..2ed7ae472f9 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -2009,6 +2009,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; @@ -2027,6 +2035,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; +}; + struct kvm_csv_batch_list_node { __u64 cmd_data_addr; __u64 addr; diff --git a/migration/ram.c b/migration/ram.c index c7245aa4dc9..198b06000ea 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -1279,6 +1279,33 @@ static int ram_save_shared_region_list(RAMState *rs, QEMUFile *f) return 0; } +/** + * ram_save_encrypted_cpu_state: send the encrypted cpu state + */ +static int ram_save_encrypted_cpu_state(RAMState *rs, QEMUFile *f) +{ + int ret; + uint64_t bytes_xmit = 0; + PageSearchStatus *pss = &rs->pss[RAM_CHANNEL_PRECOPY]; + MachineState *ms = MACHINE(qdev_get_machine()); + ConfidentialGuestSupportClass *cgs_class = + (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); + struct ConfidentialGuestMemoryEncryptionOps *ops = + cgs_class->memory_encryption_ops; + + ram_transferred_add(save_page_header(pss, f, + pss->last_sent_block, + RAM_SAVE_FLAG_ENCRYPTED_DATA)); + qemu_put_be32(f, RAM_SAVE_ENCRYPTED_CPU_STATE); + ret = ops->save_outgoing_cpu_state(f, &bytes_xmit); + if (ret < 0) { + return ret; + } + ram_transferred_add(4 + bytes_xmit); + + return 0; +} + static int load_encrypted_data(QEMUFile *f, uint8_t *ptr) { MachineState *ms = MACHINE(qdev_get_machine()); @@ -1303,6 +1330,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; @@ -3492,6 +3521,19 @@ static int ram_save_complete(QEMUFile *f, void *opaque) qemu_file_set_error(f, ret); return ret; } + + /* + * send the encrypted cpu state, for example, CSV2 guest's + * vmsa for each vcpu. + */ + if (is_hygon_cpu()) { + ret = ram_save_encrypted_cpu_state(rs, f); + if (ret < 0) { + error_report("Failed to save encrypted cpu state"); + qemu_file_set_error(f, ret); + return ret; + } + } } } diff --git a/target/i386/csv.h b/target/i386/csv.h index 74a54f9b9ca..47741a0a4f1 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -59,5 +59,7 @@ 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, uint64_t *bytes_sent); +int csv_load_incoming_cpu_state(QEMUFile *f); #endif diff --git a/target/i386/sev.c b/target/i386/sev.c index 8e5da510d31..52693ae8b89 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -90,6 +90,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; @@ -183,6 +187,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, @@ -194,6 +201,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 @@ -1047,6 +1056,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); } @@ -2237,6 +2249,195 @@ 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 = 0; + 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, uint64_t *bytes_sent) +{ + 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)); + *bytes_sent += 4 + sizeof(uint32_t); + + 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 csv_save_outgoing_cpu_state(QEMUFile *f, uint64_t *bytes_sent) +{ + 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); + *bytes_sent += 4; + ret = sev_send_update_vmsa(s, f, kvm_arch_vcpu_id(cpu), + cpu->cpu_index, TARGET_PAGE_SIZE, bytes_sent); + if (ret) { + goto err; + } + } + + qemu_put_be32(f, ENCRYPTED_CPU_STATE_END); + *bytes_sent += 4; + +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 f7886116e7b..209c92fd6f3 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 RAM_SAVE_ENCRYPTED_CPU_STATE 0x6 #ifdef CONFIG_SEV bool sev_enabled(void); diff --git a/target/i386/trace-events b/target/i386/trace-events index 475de65ad4a..87b765c73ce 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -17,3 +17,5 @@ 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" -- Gitee From 6503910c5467f56bd09a94af3c13b7b7284447f7 Mon Sep 17 00:00:00 2001 From: panpingsheng Date: Sat, 12 Jun 2021 15:15:29 +0800 Subject: [PATCH 27/51] 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 --- linux-headers/linux/kvm.h | 2 ++ target/i386/cpu.h | 5 +++++ target/i386/kvm/kvm.c | 11 +++++++++++ target/i386/kvm/sev-stub.c | 2 ++ target/i386/machine.c | 24 ++++++++++++++++++++++++ target/i386/sev.c | 10 ++++++++++ target/i386/sev.h | 2 ++ 7 files changed, 56 insertions(+) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 2ed7ae472f9..bdcd7a01433 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -1199,6 +1199,8 @@ struct kvm_ppc_resize_hpt { #define KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES 229 #define KVM_CAP_ARM_SUPPORTED_REG_MASK_RANGES 230 +#define KVM_CAP_SEV_ES_GHCB 500 + #define KVM_EXIT_HYPERCALL_VALID_MASK (1 << KVM_HC_MAP_GPA_RANGE) #ifdef KVM_CAP_IRQ_ROUTING diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 877fc2b6b1f..9fc24f7e4c4 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -521,6 +521,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 @@ -1888,6 +1890,9 @@ typedef struct CPUArchState { /* Number of dies within this CPU package. */ unsigned nr_dies; + + /* GHCB guest physical address info */ + uint64_t ghcb_gpa; } CPUX86State; struct kvm_msrs; diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 5730d0e0c0d..9e652427394 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -3625,6 +3625,10 @@ static int kvm_put_msrs(X86CPU *cpu, int level) } } + if (sev_kvm_has_msr_ghcb) { + kvm_msr_entry_add(cpu, MSR_AMD64_SEV_ES_GHCB, env->ghcb_gpa); + } + return kvm_buf_set_msrs(cpu); } @@ -3999,6 +4003,10 @@ static int kvm_get_msrs(X86CPU *cpu) } } + if (sev_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; @@ -4319,6 +4327,9 @@ static int kvm_get_msrs(X86CPU *cpu) case MSR_ARCH_LBR_INFO_0 ... MSR_ARCH_LBR_INFO_0 + 31: env->lbr_records[index - MSR_ARCH_LBR_INFO_0].info = msrs[i].data; break; + case MSR_AMD64_SEV_ES_GHCB: + env->ghcb_gpa = msrs[i].data; + break; } } diff --git a/target/i386/kvm/sev-stub.c b/target/i386/kvm/sev-stub.c index 99899688e4f..a0aac1117fb 100644 --- a/target/i386/kvm/sev-stub.c +++ b/target/i386/kvm/sev-stub.c @@ -14,6 +14,8 @@ #include "qemu/osdep.h" #include "sev.h" +bool sev_kvm_has_msr_ghcb; + int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) { /* If we get here, cgs must be some non-SEV thing */ diff --git a/target/i386/machine.c b/target/i386/machine.c index a1041ef828c..9a1cb8f3b81 100644 --- a/target/i386/machine.c +++ b/target/i386/machine.c @@ -1605,6 +1605,27 @@ static const VMStateDescription vmstate_triple_fault = { } }; +#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, @@ -1751,6 +1772,9 @@ const VMStateDescription vmstate_x86_cpu = { #endif &vmstate_arch_lbr, &vmstate_triple_fault, +#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 52693ae8b89..71d317e86be 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -152,6 +152,8 @@ QEMU_BUILD_BUG_ON(sizeof(PaddedSevHashTable) % 16 != 0); static SevGuestState *sev_guest; static Error *sev_mig_blocker; +bool sev_kvm_has_msr_ghcb; + static const char *const sev_fw_errlist[] = { [SEV_RET_SUCCESS] = "", [SEV_RET_INVALID_PLATFORM_STATE] = "Platform state is invalid", @@ -1198,6 +1200,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()) { + sev_kvm_has_msr_ghcb = + kvm_vm_check_extension(kvm_state, KVM_CAP_SEV_ES_GHCB); + } else { + sev_kvm_has_msr_ghcb = false; + } + cgs->ready = true; return 0; diff --git a/target/i386/sev.h b/target/i386/sev.h index 209c92fd6f3..0bfe3879ef4 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -78,4 +78,6 @@ void sev_del_migrate_blocker(void); int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); +extern bool sev_kvm_has_msr_ghcb; + #endif -- Gitee From 641fe421b552336c5dfb7f3e6840903e3e5b95ba Mon Sep 17 00:00:00 2001 From: hanliyang Date: Sun, 19 Jun 2022 16:49:45 +0800 Subject: [PATCH 28/51] target/i386/kvm: Fix the resettable info 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(). But the CSV2 guest is still not resettable if it was migrated to target machine. Signed-off-by: hanliyang --- target/i386/csv-sysemu-stub.c | 16 ++++++++++++++++ target/i386/csv.c | 20 ++++++++++++++++++++ target/i386/csv.h | 2 ++ target/i386/kvm/csv-stub.c | 17 +++++++++++++++++ target/i386/kvm/kvm.c | 4 ++++ target/i386/kvm/meson.build | 1 + target/i386/meson.build | 1 + target/i386/sev.c | 9 +++++++++ 8 files changed, 70 insertions(+) create mode 100644 target/i386/csv-sysemu-stub.c create mode 100644 target/i386/csv.c create mode 100644 target/i386/kvm/csv-stub.c diff --git a/target/i386/csv-sysemu-stub.c b/target/i386/csv-sysemu-stub.c new file mode 100644 index 00000000000..5874e4cc1da --- /dev/null +++ b/target/i386/csv-sysemu-stub.c @@ -0,0 +1,16 @@ +/* + * QEMU CSV system stub + * + * Copyright: Hygon Info Technologies Ltd. 2022 + * + * Author: + * Jiang Xin + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "sev.h" +#include "csv.h" diff --git a/target/i386/csv.c b/target/i386/csv.c new file mode 100644 index 00000000000..88fb05ac37b --- /dev/null +++ b/target/i386/csv.c @@ -0,0 +1,20 @@ +/* + * QEMU CSV support + * + * Copyright: Hygon Info Technologies Ltd. 2022 + * + * Author: + * Jiang Xin + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" + +#include "cpu.h" +#include "sev.h" +#include "csv.h" + +bool csv_kvm_cpu_reset_inhibit; diff --git a/target/i386/csv.h b/target/i386/csv.h index 47741a0a4f1..ac4bb5bee13 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -46,6 +46,8 @@ static bool __attribute__((unused)) is_hygon_cpu(void) #define CSV_OUTGOING_PAGE_WINDOW_SIZE (4094 * TARGET_PAGE_SIZE) +extern bool csv_kvm_cpu_reset_inhibit; + typedef struct CsvBatchCmdList CsvBatchCmdList; typedef void (*CsvDestroyCmdNodeFn) (void *data); diff --git a/target/i386/kvm/csv-stub.c b/target/i386/kvm/csv-stub.c new file mode 100644 index 00000000000..4d1376f2684 --- /dev/null +++ b/target/i386/kvm/csv-stub.c @@ -0,0 +1,17 @@ +/* + * QEMU CSV stub + * + * Copyright Hygon Info Technologies Ltd. 2024 + * + * Authors: + * Han Liyang + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "csv.h" + +bool csv_kvm_cpu_reset_inhibit; diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 9e652427394..2866a6d0ecf 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -32,6 +32,7 @@ #include "sysemu/runstate.h" #include "kvm_i386.h" #include "sev.h" +#include "csv.h" #include "xen-emu.h" #include "hyperv.h" #include "hyperv-proto.h" @@ -5710,6 +5711,9 @@ bool kvm_has_waitpkg(void) bool kvm_arch_cpu_check_are_resettable(void) { + if (is_hygon_cpu()) + return !csv_kvm_cpu_reset_inhibit; + return !sev_es_enabled(); } diff --git a/target/i386/kvm/meson.build b/target/i386/kvm/meson.build index 84d9143e602..3c3f8cf93c1 100644 --- a/target/i386/kvm/meson.build +++ b/target/i386/kvm/meson.build @@ -8,6 +8,7 @@ i386_kvm_ss.add(files( i386_kvm_ss.add(when: 'CONFIG_XEN_EMU', if_true: files('xen-emu.c')) i386_kvm_ss.add(when: 'CONFIG_SEV', if_false: files('sev-stub.c')) +i386_kvm_ss.add(when: 'CONFIG_CSV', if_false: files('csv-stub.c')) i386_system_ss.add(when: 'CONFIG_HYPERV', if_true: files('hyperv.c'), if_false: files('hyperv-stub.c')) diff --git a/target/i386/meson.build b/target/i386/meson.build index 7c74bfa8591..594a0a6abf7 100644 --- a/target/i386/meson.build +++ b/target/i386/meson.build @@ -21,6 +21,7 @@ i386_system_ss.add(files( 'cpu-sysemu.c', )) i386_system_ss.add(when: 'CONFIG_SEV', if_true: files('sev.c'), if_false: files('sev-sysemu-stub.c')) +i386_system_ss.add(when: 'CONFIG_CSV', if_true: files('csv.c'), if_false: files('csv-sysemu-stub.c')) i386_user_ss = ss.source_set() diff --git a/target/i386/sev.c b/target/i386/sev.c index 71d317e86be..3406861f672 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -1190,6 +1190,15 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) error_setg(errp, "%s: failed to create encryption context", __func__); goto err; } + } else { + /* + * The CSV2 guest is not resettable after migrated to target machine, + * set csv_kvm_cpu_reset_inhibit to true to indicate the CSV2 guest is + * not resettable. + */ + if (is_hygon_cpu() && sev_es_enabled()) { + csv_kvm_cpu_reset_inhibit = true; + } } ram_block_notifier_add(&sev_ram_notifier); -- Gitee From 63ef0cc04f7e80891de6aa80910980ebf3ff758f Mon Sep 17 00:00:00 2001 From: hanliyang Date: Thu, 15 Apr 2021 08:32:24 -0400 Subject: [PATCH 29/51] 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 ++++ system/cpus.c | 14 ++++++++++++++ system/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 6195150a0b4..54f19028b82 100644 --- a/accel/kvm/kvm-accel-ops.c +++ b/accel/kvm/kvm-accel-ops.c @@ -112,6 +112,9 @@ static void kvm_accel_ops_class_init(ObjectClass *oc, void *data) ops->remove_breakpoint = kvm_remove_breakpoint; ops->remove_all_breakpoints = kvm_remove_all_breakpoints; #endif + + 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 e39a810a4e9..e5ed69c3384 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -2761,6 +2761,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 ca40add32c3..27b9d0d9dbf 100644 --- a/accel/kvm/kvm-cpus.h +++ b/accel/kvm/kvm-cpus.h @@ -23,4 +23,7 @@ int kvm_insert_breakpoint(CPUState *cpu, int type, vaddr addr, vaddr len); int kvm_remove_breakpoint(CPUState *cpu, int type, vaddr addr, vaddr len); void kvm_remove_all_breakpoints(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 ef91fc28bbd..7a32e7f8203 100644 --- a/include/sysemu/accel-ops.h +++ b/include/sysemu/accel-ops.h @@ -53,6 +53,9 @@ struct AccelOpsClass { int (*insert_breakpoint)(CPUState *cpu, int type, vaddr addr, vaddr len); int (*remove_breakpoint)(CPUState *cpu, int type, vaddr addr, vaddr len); void (*remove_all_breakpoints)(CPUState *cpu); + + 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 b4a566cfe75..f24d27daf52 100644 --- a/include/sysemu/cpus.h +++ b/include/sysemu/cpus.h @@ -44,6 +44,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 bdcd7a01433..372d0bd148c 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -1583,6 +1583,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/system/cpus.c b/system/cpus.c index a444a747f01..cbeec13f391 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -193,6 +193,20 @@ void cpu_synchronize_pre_loadvm(CPUState *cpu) } } +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(); + } +} + bool cpus_are_resettable(void) { if (cpus_accel->cpus_are_resettable) { diff --git a/system/runstate.c b/system/runstate.c index ea9d6c2a32a..365f2f44b98 100644 --- a/system/runstate.c +++ b/system/runstate.c @@ -486,6 +486,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) { @@ -502,6 +504,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(); } /* -- Gitee From 87af3b6cab2252a01cb2f2eee7a85f74d3e90e0b Mon Sep 17 00:00:00 2001 From: jiangxin Date: Tue, 24 Aug 2021 14:57:28 +0800 Subject: [PATCH 30/51] target/i386: csv: Add CSV3 context CSV/CSV2/CSV3 are the secure virtualization features on Hygon CPUs. The CSV and CSV2 are compatible with the AMD SEV and SEV-ES, respectively. From CSV3, we introduced more secure features to protect the guest, users can bit 6 of the guest policy to run a CSV3 guest. Add the context and the build option. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- target/i386/csv.c | 11 +++++++++++ target/i386/csv.h | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/target/i386/csv.c b/target/i386/csv.c index 88fb05ac37b..9a1de04db7d 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -18,3 +18,14 @@ #include "csv.h" bool csv_kvm_cpu_reset_inhibit; + +Csv3GuestState csv3_guest = { 0 }; + +bool +csv3_enabled(void) +{ + if (!is_hygon_cpu()) + return false; + + return sev_es_enabled() && (csv3_guest.policy & GUEST_POLICY_CSV3_BIT); +} diff --git a/target/i386/csv.h b/target/i386/csv.h index ac4bb5bee13..7852fb8dc19 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -14,6 +14,8 @@ #ifndef I386_CSV_H #define I386_CSV_H +#include "qapi/qapi-commands-misc-target.h" + #ifdef CONFIG_CSV #include "cpu.h" @@ -38,9 +40,12 @@ static bool __attribute__((unused)) is_hygon_cpu(void) return false; } +bool csv3_enabled(void); + #else #define is_hygon_cpu() (false) +#define csv3_enabled() (false) #endif @@ -64,4 +69,17 @@ int csv_load_queued_incoming_pages(QEMUFile *f); int csv_save_outgoing_cpu_state(QEMUFile *f, uint64_t *bytes_sent); int csv_load_incoming_cpu_state(QEMUFile *f); +/* CSV3 */ +#define GUEST_POLICY_CSV3_BIT (1 << 6) + +struct Csv3GuestState { + uint32_t policy; + int sev_fd; + void *state; +}; + +typedef struct Csv3GuestState Csv3GuestState; + +extern struct Csv3GuestState csv3_guest; + #endif -- Gitee From 38d00f2f50a36352047f8ab60a62b211ffe6ca2c Mon Sep 17 00:00:00 2001 From: jiangxin Date: Wed, 25 Aug 2021 11:07:41 +0800 Subject: [PATCH 31/51] target/i386: csv: Add command to initialize CSV3 context When CSV3 is enabled, KVM_CSV3_INIT command is used to initialize the platform, which is implemented by reusing the SEV API framework and extending the functionality. The KVM_CSV3_INIT command should be performed earlier than any other command. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- linux-headers/linux/kvm.h | 11 +++++++++ target/i386/csv-sysemu-stub.c | 5 ++++ target/i386/csv.c | 45 +++++++++++++++++++++++++++++++++++ target/i386/csv.h | 4 ++++ target/i386/sev.c | 17 +++++++++++++ target/i386/sev.h | 7 ++++++ 6 files changed, 89 insertions(+) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 372d0bd148c..d976908bcab 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -2060,6 +2060,17 @@ struct kvm_csv_command_batch { __u64 csv_batch_list_uaddr; }; +/* CSV3 command */ +enum csv3_cmd_id { + KVM_CSV3_NR_MIN = 0xc0, + + KVM_CSV3_INIT = KVM_CSV3_NR_MIN, +}; + +struct kvm_csv3_init_data { + __u64 nodemask; +}; + #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/csv-sysemu-stub.c b/target/i386/csv-sysemu-stub.c index 5874e4cc1da..72f0f5c7721 100644 --- a/target/i386/csv-sysemu-stub.c +++ b/target/i386/csv-sysemu-stub.c @@ -14,3 +14,8 @@ #include "qemu/osdep.h" #include "sev.h" #include "csv.h" + +int csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops) +{ + return 0; +} diff --git a/target/i386/csv.c b/target/i386/csv.c index 9a1de04db7d..f02aadb54b8 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -12,6 +12,13 @@ */ #include "qemu/osdep.h" +#include "qemu/error-report.h" + +#include + +#ifdef CONFIG_NUMA +#include +#endif #include "cpu.h" #include "sev.h" @@ -21,6 +28,44 @@ bool csv_kvm_cpu_reset_inhibit; Csv3GuestState csv3_guest = { 0 }; +int +csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops) +{ + int fw_error; + int ret; + struct kvm_csv3_init_data data = { 0 }; + +#ifdef CONFIG_NUMA + int mode; + unsigned long nodemask; + + /* Set flags as 0 to retrieve the default NUMA policy. */ + ret = get_mempolicy(&mode, &nodemask, sizeof(nodemask) * 8, NULL, 0); + if (ret == 0 && (mode == MPOL_BIND)) + data.nodemask = nodemask; +#endif + + if (!ops || !ops->sev_ioctl || !ops->fw_error_to_str) + return -1; + + csv3_guest.policy = policy; + if (csv3_enabled()) { + ret = ops->sev_ioctl(fd, KVM_CSV3_INIT, &data, &fw_error); + if (ret) { + csv3_guest.policy = 0; + error_report("%s: Fail to initialize ret=%d fw_error=%d '%s'", + __func__, ret, fw_error, ops->fw_error_to_str(fw_error)); + return -1; + } + + csv3_guest.sev_fd = fd; + csv3_guest.state = state; + csv3_guest.sev_ioctl = ops->sev_ioctl; + csv3_guest.fw_error_to_str = ops->fw_error_to_str; + } + return 0; +} + bool csv3_enabled(void) { diff --git a/target/i386/csv.h b/target/i386/csv.h index 7852fb8dc19..cf125fe0fcc 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -15,6 +15,7 @@ #define I386_CSV_H #include "qapi/qapi-commands-misc-target.h" +#include "sev.h" #ifdef CONFIG_CSV @@ -76,10 +77,13 @@ struct Csv3GuestState { uint32_t policy; int sev_fd; void *state; + int (*sev_ioctl)(int fd, int cmd, void *data, int *error); + const char *(*fw_error_to_str)(int code); }; typedef struct Csv3GuestState Csv3GuestState; extern struct Csv3GuestState csv3_guest; +extern int csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops); #endif diff --git a/target/i386/sev.c b/target/i386/sev.c index 3406861f672..50f3429a423 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -1180,6 +1180,18 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) goto err; } + /* Support CSV3 */ + if (!ret && cmd == KVM_SEV_ES_INIT) { + ret = csv3_init(sev_guest->policy, sev->sev_fd, (void *)&sev->state, &sev_ops); + if (ret) { + error_setg(errp, "%s: failed to init csv3 context", __func__); + goto err; + } + /* The CSV3 guest is not resettable */ + if (csv3_enabled()) + csv_kvm_cpu_reset_inhibit = true; + } + /* * The LAUNCH context is used for new guest, if its an incoming guest * then RECEIVE context will be created after the connection is established. @@ -2589,6 +2601,11 @@ bool sev_add_kernel_loader_hashes(SevKernelLoaderContext *ctx, Error **errp) return ret; } +struct sev_ops sev_ops = { + .sev_ioctl = sev_ioctl, + .fw_error_to_str = fw_error_to_str, +}; + static void sev_register_types(void) { diff --git a/target/i386/sev.h b/target/i386/sev.h index 0bfe3879ef4..e91431e0f72 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -80,4 +80,11 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp); extern bool sev_kvm_has_msr_ghcb; +struct sev_ops { + int (*sev_ioctl)(int fd, int cmd, void *data, int *error); + const char *(*fw_error_to_str)(int code); +}; + +extern struct sev_ops sev_ops; + #endif -- Gitee From e7d21782f961f95f3fb2fa93b6135f51976ea040 Mon Sep 17 00:00:00 2001 From: jiangxin Date: Wed, 25 Aug 2021 09:59:16 +0800 Subject: [PATCH 32/51] target/i386: csv: Add command to load data to CSV3 guest memory The KVM_CSV3_LAUNCH_ENCRYPT_DATA command is used to load data to an encrypted guest memory in an isolated memory region that guest owns. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- linux-headers/linux/kvm.h | 7 ++++ target/i386/csv-sysemu-stub.c | 5 +++ target/i386/csv.c | 69 +++++++++++++++++++++++++++++++++++ target/i386/csv.h | 2 + target/i386/trace-events | 3 ++ 5 files changed, 86 insertions(+) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index d976908bcab..fa7b415819d 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -2065,6 +2065,13 @@ enum csv3_cmd_id { KVM_CSV3_NR_MIN = 0xc0, KVM_CSV3_INIT = KVM_CSV3_NR_MIN, + KVM_CSV3_LAUNCH_ENCRYPT_DATA, +}; + +struct kvm_csv3_launch_encrypt_data { + __u64 gpa; + __u64 uaddr; + __u32 len; }; struct kvm_csv3_init_data { diff --git a/target/i386/csv-sysemu-stub.c b/target/i386/csv-sysemu-stub.c index 72f0f5c7721..b0ccbd2f182 100644 --- a/target/i386/csv-sysemu-stub.c +++ b/target/i386/csv-sysemu-stub.c @@ -19,3 +19,8 @@ int csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops) { return 0; } + +int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp) +{ + g_assert_not_reached(); +} diff --git a/target/i386/csv.c b/target/i386/csv.c index f02aadb54b8..0e3f4478a52 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -13,6 +13,7 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qapi/error.h" #include @@ -20,6 +21,7 @@ #include #endif +#include "trace.h" #include "cpu.h" #include "sev.h" #include "csv.h" @@ -74,3 +76,70 @@ csv3_enabled(void) return sev_es_enabled() && (csv3_guest.policy & GUEST_POLICY_CSV3_BIT); } + +static bool +csv3_check_state(SevState state) +{ + return *((SevState *)csv3_guest.state) == state ? true : false; +} + +static int +csv3_ioctl(int cmd, void *data, int *error) +{ + if (csv3_guest.sev_ioctl) + return csv3_guest.sev_ioctl(csv3_guest.sev_fd, cmd, data, error); + else + return -1; +} + +static const char * +fw_error_to_str(int code) +{ + if (csv3_guest.fw_error_to_str) + return csv3_guest.fw_error_to_str(code); + else + return NULL; +} + +static int +csv3_launch_encrypt_data(uint64_t gpa, uint8_t *addr, uint64_t len) +{ + int ret, fw_error; + struct kvm_csv3_launch_encrypt_data update; + + if (!addr || !len) { + return 1; + } + + update.gpa = (__u64)gpa; + update.uaddr = (__u64)(unsigned long)addr; + update.len = len; + trace_kvm_csv3_launch_encrypt_data(gpa, addr, len); + ret = csv3_ioctl(KVM_CSV3_LAUNCH_ENCRYPT_DATA, &update, &fw_error); + if (ret) { + error_report("%s: CSV3 LAUNCH_ENCRYPT_DATA ret=%d fw_error=%d '%s'", + __func__, ret, fw_error, fw_error_to_str(fw_error)); + } + + return ret; +} + +int +csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp) +{ + int ret = 0; + + if (!csv3_enabled()) { + error_setg(errp, "%s: CSV3 is not enabled", __func__); + return -1; + } + + /* if CSV3 is in update state then load the data to secure memory */ + if (csv3_check_state(SEV_STATE_LAUNCH_UPDATE)) { + ret = csv3_launch_encrypt_data(gpa, ptr, len); + if (ret) + error_setg(errp, "%s: CSV3 fail to encrypt data", __func__); + } + + return ret; +} diff --git a/target/i386/csv.h b/target/i386/csv.h index cf125fe0fcc..928774f596e 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -86,4 +86,6 @@ typedef struct Csv3GuestState Csv3GuestState; extern struct Csv3GuestState csv3_guest; extern int csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops); +int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp); + #endif diff --git a/target/i386/trace-events b/target/i386/trace-events index 87b765c73ce..e07061bf392 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -19,3 +19,6 @@ kvm_sev_receive_update_data(void *src, void *dst, int len, void *hdr, int hdr_le 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_csv3_launch_encrypt_data(uint64_t gpa, void *addr, uint64_t len) "gpa 0x%" PRIx64 "addr %p len 0x%" PRIu64 -- Gitee From 70f106ff7e9aaf80a4e114d3027c5b9e41a3fdee Mon Sep 17 00:00:00 2001 From: jiangxin Date: Wed, 25 Aug 2021 12:25:05 +0800 Subject: [PATCH 33/51] target/i386: csv: Add command to load vmcb to CSV3 guest memory The KVM_CSV3_LAUNCH_ENCRYPT_VMCB command is used to load and encrypt the initial VMCB data to secure memory in an isolated region that guest owns. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- linux-headers/linux/kvm.h | 1 + target/i386/csv-sysemu-stub.c | 5 +++++ target/i386/csv.c | 21 +++++++++++++++++++++ target/i386/csv.h | 1 + target/i386/sev.c | 8 ++++++-- 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index fa7b415819d..061b2e7f0b7 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -2066,6 +2066,7 @@ enum csv3_cmd_id { KVM_CSV3_INIT = KVM_CSV3_NR_MIN, KVM_CSV3_LAUNCH_ENCRYPT_DATA, + KVM_CSV3_LAUNCH_ENCRYPT_VMCB, }; struct kvm_csv3_launch_encrypt_data { diff --git a/target/i386/csv-sysemu-stub.c b/target/i386/csv-sysemu-stub.c index b0ccbd2f182..23d885f0f37 100644 --- a/target/i386/csv-sysemu-stub.c +++ b/target/i386/csv-sysemu-stub.c @@ -24,3 +24,8 @@ int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp) { g_assert_not_reached(); } + +int csv3_launch_encrypt_vmcb(void) +{ + g_assert_not_reached(); +} diff --git a/target/i386/csv.c b/target/i386/csv.c index 0e3f4478a52..f423b898f5d 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -143,3 +143,24 @@ csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp) return ret; } + +int +csv3_launch_encrypt_vmcb(void) +{ + int ret, fw_error; + + if (!csv3_enabled()) { + error_report("%s: CSV3 is not enabled",__func__); + return -1; + } + + ret = csv3_ioctl(KVM_CSV3_LAUNCH_ENCRYPT_VMCB, NULL, &fw_error); + if (ret) { + error_report("%s: CSV3 LAUNCH_ENCRYPT_VMCB ret=%d fw_error=%d '%s'", + __func__, ret, fw_error, fw_error_to_str(fw_error)); + goto err; + } + +err: + return ret; +} diff --git a/target/i386/csv.h b/target/i386/csv.h index 928774f596e..6444d54efb9 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -85,6 +85,7 @@ typedef struct Csv3GuestState Csv3GuestState; extern struct Csv3GuestState csv3_guest; extern int csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops); +extern int csv3_launch_encrypt_vmcb(void); int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp); diff --git a/target/i386/sev.c b/target/i386/sev.c index 50f3429a423..b77572f0b08 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -857,8 +857,12 @@ sev_launch_get_measure(Notifier *notifier, void *unused) } if (sev_es_enabled()) { - /* measure all the VM save areas before getting launch_measure */ - ret = sev_launch_update_vmsa(sev); + if (csv3_enabled()) { + ret = csv3_launch_encrypt_vmcb(); + } else { + /* measure all the VM save areas before getting launch_measure */ + ret = sev_launch_update_vmsa(sev); + } if (ret) { exit(1); } -- Gitee From 59fb06b7cca8a87c9b3ab69c99649990843d1611 Mon Sep 17 00:00:00 2001 From: jiangxin Date: Tue, 24 Aug 2021 17:31:28 +0800 Subject: [PATCH 34/51] target/i386: cpu: Populate CPUID 0x8000_001F when CSV3 is active On Hygon platform, bit 30 of EAX indicates whether this feature is supported in hardware. When CSV3 is active, CPUID 0x8000_001F provides information for it. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- target/i386/cpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index ffdaf16bd3f..a373d8e7773 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -29,6 +29,7 @@ #include "hvf/hvf-i386.h" #include "kvm/kvm_i386.h" #include "sev.h" +#include "csv.h" #include "qapi/error.h" #include "qemu/error-report.h" #include "qapi/qapi-visit-machine.h" @@ -6808,6 +6809,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, if (sev_enabled()) { *eax = 0x2; *eax |= sev_es_enabled() ? 0x8 : 0; + *eax |= csv3_enabled() ? 0x40000000 : 0; /* bit 30 for CSV3 */ *ebx = sev_get_cbit_position() & 0x3f; /* EBX[5:0] */ *ebx |= (sev_get_reduced_phys_bits() & 0x3f) << 6; /* EBX[11:6] */ } -- Gitee From e0b432bb2db670059702311dc48749f83ab5a9a9 Mon Sep 17 00:00:00 2001 From: jiangxin Date: Wed, 25 Aug 2021 12:36:00 +0800 Subject: [PATCH 35/51] target/i386: csv: Do not register/unregister guest secure memory for CSV3 guest CSV3's guest memory is allocated by firmware in secure processor from dedicated memory reserved upon system boot up, consequently it is not necessary to add notifier to pin/unpin memory. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- target/i386/sev.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/target/i386/sev.c b/target/i386/sev.c index b77572f0b08..eb1026b575f 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -1217,7 +1217,10 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) } } - ram_block_notifier_add(&sev_ram_notifier); + /* CSV3 guest do not need notifier to reg/unreg memory */ + if (!csv3_enabled()) { + ram_block_notifier_add(&sev_ram_notifier); + } qemu_add_machine_init_done_notifier(&sev_machine_done_notify); qemu_add_vm_change_state_handler(sev_vm_state_change, sev); migration_add_notifier(&sev_migration_state, sev_migration_state_notifier); -- Gitee From 54026b362117c16811e0012b972bed0c25d48254 Mon Sep 17 00:00:00 2001 From: jiangxin Date: Wed, 25 Aug 2021 14:29:40 +0800 Subject: [PATCH 36/51] target/i386: csv: Load initial image to private memory for CSV3 guest The initial image of CSV3 guest should be loaded into private memory before boot the guest. Add APIs to implement the image load. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- hw/i386/pc_sysfw.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hw/i386/pc_sysfw.c b/hw/i386/pc_sysfw.c index c8d9e71b889..2bbcbb8d357 100644 --- a/hw/i386/pc_sysfw.c +++ b/hw/i386/pc_sysfw.c @@ -37,6 +37,7 @@ #include "hw/block/flash.h" #include "sysemu/kvm.h" #include "sev.h" +#include "csv.h" #define FLASH_SECTOR_SIZE 4096 @@ -263,7 +264,18 @@ void x86_firmware_configure(void *ptr, int size) error_report("failed to locate and/or save reset vector"); exit(1); } + if (csv3_enabled()) { + ram_addr_t offset = 0; + MemoryRegion *mr; - sev_encrypt_flash(ptr, size, &error_fatal); + mr = memory_region_from_host(ptr, &offset); + if (!mr) { + error_report("failed to get memory region of flash"); + exit(1); + } + csv3_load_data(mr->addr + offset, ptr, size, &error_fatal); + } else { + sev_encrypt_flash(ptr, size, &error_fatal); + } } } -- Gitee From e2717a7e847de3884927aa50de562b1e611c521a Mon Sep 17 00:00:00 2001 From: Xin Jiang Date: Thu, 13 Jul 2023 09:35:10 +0800 Subject: [PATCH 37/51] vga: Force full update for CSV3 guest As CSV3's NPT(nested page table) is managed by firmware, VMM is hard to track the dirty pages of vga buffer. Although VMM could perform a command to firmware to update read/write attribute of vga buffer in NPT, it costs more time due to communication between VMM and firmware. So the simplest method is to fully update vga buffer always. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- accel/kvm/kvm-all.c | 1 + accel/stubs/kvm-stub.c | 1 + hw/display/vga.c | 7 +++++++ include/sysemu/kvm.h | 8 ++++++++ target/i386/csv.c | 3 +++ 5 files changed, 20 insertions(+) diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index e5ed69c3384..25d23bba219 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -98,6 +98,7 @@ bool kvm_allowed; bool kvm_readonly_mem_allowed; bool kvm_vm_attributes_allowed; bool kvm_msi_use_devid; +bool kvm_csv3_allowed; bool kvm_has_guest_debug; static int kvm_sstep_flags; static bool kvm_immediate_exit; diff --git a/accel/stubs/kvm-stub.c b/accel/stubs/kvm-stub.c index 1b37d9a302c..45b23f61ce8 100644 --- a/accel/stubs/kvm-stub.c +++ b/accel/stubs/kvm-stub.c @@ -24,6 +24,7 @@ bool kvm_gsi_direct_mapping; bool kvm_allowed; bool kvm_readonly_mem_allowed; bool kvm_msi_use_devid; +bool kvm_csv3_allowed; void kvm_flush_coalesced_mmio_buffer(void) { diff --git a/hw/display/vga.c b/hw/display/vga.c index 37557c3442a..d70226a894a 100644 --- a/hw/display/vga.c +++ b/hw/display/vga.c @@ -39,6 +39,8 @@ #include "migration/vmstate.h" #include "trace.h" +#include "sysemu/kvm.h" + //#define DEBUG_VGA_MEM //#define DEBUG_VGA_REG @@ -1783,6 +1785,11 @@ static void vga_update_display(void *opaque) s->cursor_blink_time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); full_update = 1; } + + /* Force to full update in CSV guest. */ + if (kvm_csv3_enabled()) + full_update = 1; + switch(graphic_mode) { case GMODE_TEXT: vga_draw_text(s, full_update); diff --git a/include/sysemu/kvm.h b/include/sysemu/kvm.h index d6148781642..1e15cfe9dc4 100644 --- a/include/sysemu/kvm.h +++ b/include/sysemu/kvm.h @@ -42,6 +42,7 @@ extern bool kvm_gsi_routing_allowed; extern bool kvm_gsi_direct_mapping; extern bool kvm_readonly_mem_allowed; extern bool kvm_msi_use_devid; +extern bool kvm_csv3_allowed; #define kvm_enabled() (kvm_allowed) /** @@ -143,6 +144,12 @@ extern bool kvm_msi_use_devid; */ #define kvm_msi_devid_required() (kvm_msi_use_devid) +/** + * kvm_csv3_enabled: + * Returns: true if CSV3 feature is used for the VM. + */ +#define kvm_csv3_enabled() (kvm_csv3_allowed) + #else #define kvm_enabled() (0) @@ -157,6 +164,7 @@ extern bool kvm_msi_use_devid; #define kvm_gsi_direct_mapping() (false) #define kvm_readonly_mem_enabled() (false) #define kvm_msi_devid_required() (false) +#define kvm_csv3_enabled() (false) #endif /* CONFIG_KVM_IS_POSSIBLE */ diff --git a/target/i386/csv.c b/target/i386/csv.c index f423b898f5d..70900be8645 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -14,6 +14,7 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "qapi/error.h" +#include "sysemu/kvm.h" #include @@ -60,6 +61,8 @@ csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops) return -1; } + kvm_csv3_allowed = true; + csv3_guest.sev_fd = fd; csv3_guest.state = state; csv3_guest.sev_ioctl = ops->sev_ioctl; -- Gitee From 201ac3f953d0d30fba075fa74deca9030b242016 Mon Sep 17 00:00:00 2001 From: liuyafei Date: Mon, 22 May 2023 20:37:40 +0800 Subject: [PATCH 38/51] vfio: Only map shared region for CSV3 virtual machine qemu vfio listener map/unmap all of the virtual machine's memory. It does not work for CSV3 virtual machine, as only shared memory should be accessed by device. Signed-off-by: liuyafei Signed-off-by: hanliyang --- hw/vfio/container.c | 46 +++++++++++- include/exec/memory.h | 11 +++ system/memory.c | 18 +++++ target/i386/csv-sysemu-stub.c | 10 +++ target/i386/csv.c | 134 ++++++++++++++++++++++++++++++++++ target/i386/csv.h | 12 +++ target/i386/kvm/kvm.c | 2 + 7 files changed, 230 insertions(+), 3 deletions(-) diff --git a/hw/vfio/container.c b/hw/vfio/container.c index adc3005beb7..0d6e6b428c9 100644 --- a/hw/vfio/container.c +++ b/hw/vfio/container.c @@ -30,6 +30,7 @@ #include "qemu/error-report.h" #include "qemu/range.h" #include "sysemu/reset.h" +#include "sysemu/kvm.h" #include "trace.h" #include "qapi/error.h" #include "migration/migration.h" @@ -468,6 +469,32 @@ static void vfio_free_container(VFIOContainer *container) g_free(container); } +static SharedRegionListener *g_shl; + +static void shared_memory_listener_register(MemoryListener *listener, + AddressSpace *as) +{ + SharedRegionListener *shl; + + shl = g_new0(SharedRegionListener, 1); + + shl->listener = listener; + shl->as = as; + + shared_region_register_listener(shl); + g_shl = shl; +} + +static void shared_memory_listener_unregister(void) +{ + SharedRegionListener *shl = g_shl; + + shared_region_unregister_listener(shl); + + g_free(shl); + g_shl = NULL; +} + static int vfio_connect_container(VFIOGroup *group, AddressSpace *as, Error **errp) { @@ -613,7 +640,12 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as, container->listener = vfio_memory_listener; - memory_listener_register(&container->listener, container->space->as); + if (kvm_csv3_enabled()) { + shared_memory_listener_register(&container->listener, + container->space->as); + } else { + memory_listener_register(&container->listener, container->space->as); + } if (container->error) { ret = -1; @@ -629,7 +661,11 @@ listener_release_exit: QLIST_REMOVE(group, container_next); QLIST_REMOVE(container, next); vfio_kvm_device_del_group(group); - memory_listener_unregister(&container->listener); + if (kvm_csv3_enabled()) { + shared_memory_listener_unregister(); + } else { + memory_listener_unregister(&container->listener); + } if (container->iommu_type == VFIO_SPAPR_TCE_v2_IOMMU || container->iommu_type == VFIO_SPAPR_TCE_IOMMU) { vfio_spapr_container_deinit(container); @@ -663,7 +699,11 @@ static void vfio_disconnect_container(VFIOGroup *group) * group. */ if (QLIST_EMPTY(&container->group_list)) { - memory_listener_unregister(&container->listener); + if (kvm_csv3_enabled()) { + shared_memory_listener_unregister(); + } else { + memory_listener_unregister(&container->listener); + } if (container->iommu_type == VFIO_SPAPR_TCE_v2_IOMMU || container->iommu_type == VFIO_SPAPR_TCE_IOMMU) { vfio_spapr_container_deinit(container); diff --git a/include/exec/memory.h b/include/exec/memory.h index 831f7c996d9..3e65d8d9f54 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -775,6 +775,17 @@ bool memory_get_xlat_addr(IOMMUTLBEntry *iotlb, void **vaddr, ram_addr_t *ram_addr, bool *read_only, bool *mr_has_discard_manager); +typedef struct SharedRegionListener SharedRegionListener; +struct SharedRegionListener { + MemoryListener *listener; + AddressSpace *as; + QTAILQ_ENTRY(SharedRegionListener) next; +}; + +void shared_region_register_listener(SharedRegionListener *shl); +void shared_region_unregister_listener(SharedRegionListener *shl); +void *shared_region_listeners_get(void); + typedef struct CoalescedMemoryRange CoalescedMemoryRange; typedef struct MemoryRegionIoeventfd MemoryRegionIoeventfd; diff --git a/system/memory.c b/system/memory.c index 798b6c0a171..2ffb878eb8c 100644 --- a/system/memory.c +++ b/system/memory.c @@ -48,6 +48,9 @@ static QTAILQ_HEAD(, MemoryListener) memory_listeners static QTAILQ_HEAD(, AddressSpace) address_spaces = QTAILQ_HEAD_INITIALIZER(address_spaces); +static QTAILQ_HEAD(, SharedRegionListener) shared_region_listeners + = QTAILQ_HEAD_INITIALIZER(shared_region_listeners); + static GHashTable *flat_views; typedef struct AddrRange AddrRange; @@ -2226,6 +2229,21 @@ bool memory_get_xlat_addr(IOMMUTLBEntry *iotlb, void **vaddr, return true; } +void shared_region_register_listener(SharedRegionListener *shl) +{ + QTAILQ_INSERT_TAIL(&shared_region_listeners, shl, next); +} + +void shared_region_unregister_listener(SharedRegionListener *shl) +{ + QTAILQ_REMOVE(&shared_region_listeners, shl, next); +} + +void *shared_region_listeners_get(void) +{ + return &shared_region_listeners; +} + void memory_region_set_log(MemoryRegion *mr, bool log, unsigned client) { uint8_t mask = 1 << client; diff --git a/target/i386/csv-sysemu-stub.c b/target/i386/csv-sysemu-stub.c index 23d885f0f37..db22c299a62 100644 --- a/target/i386/csv-sysemu-stub.c +++ b/target/i386/csv-sysemu-stub.c @@ -29,3 +29,13 @@ int csv3_launch_encrypt_vmcb(void) { g_assert_not_reached(); } + +int csv3_shared_region_dma_map(uint64_t start, uint64_t end) +{ + return 0; +} + +void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end) +{ + +} diff --git a/target/i386/csv.c b/target/i386/csv.c index 70900be8645..5823c8994ce 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -15,6 +15,7 @@ #include "qemu/error-report.h" #include "qapi/error.h" #include "sysemu/kvm.h" +#include "exec/address-spaces.h" #include @@ -67,6 +68,8 @@ csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops) csv3_guest.state = state; csv3_guest.sev_ioctl = ops->sev_ioctl; csv3_guest.fw_error_to_str = ops->fw_error_to_str; + QTAILQ_INIT(&csv3_guest.dma_map_regions_list); + qemu_mutex_init(&csv3_guest.dma_map_regions_list_mutex); } return 0; } @@ -167,3 +170,134 @@ csv3_launch_encrypt_vmcb(void) err: return ret; } + +int csv3_shared_region_dma_map(uint64_t start, uint64_t end) +{ + MemoryRegionSection section; + AddressSpace *as; + QTAILQ_HEAD(, SharedRegionListener) *shared_region_listeners; + SharedRegionListener *shl; + MemoryListener *listener; + uint64_t size; + Csv3GuestState *s = &csv3_guest; + struct dma_map_region *region, *pos; + int ret = 0; + + if (!csv3_enabled()) + return 0; + + if (end <= start) + return 0; + + shared_region_listeners = shared_region_listeners_get(); + if (QTAILQ_EMPTY(shared_region_listeners)) + return 0; + + size = end - start; + + qemu_mutex_lock(&s->dma_map_regions_list_mutex); + QTAILQ_FOREACH(pos, &s->dma_map_regions_list, list) { + if (start >= (pos->start + pos->size)) { + continue; + } else if ((start + size) <= pos->start) { + break; + } else { + goto end; + } + } + QTAILQ_FOREACH(shl, shared_region_listeners, next) { + listener = shl->listener; + as = shl->as; + section = memory_region_find(as->root, start, size); + if (!section.mr) { + goto end; + } + + if (!memory_region_is_ram(section.mr)) { + memory_region_unref(section.mr); + goto end; + } + + if (listener->region_add) { + listener->region_add(listener, §ion); + } + memory_region_unref(section.mr); + } + + region = g_malloc0(sizeof(*region)); + if (!region) { + ret = -1; + goto end; + } + region->start = start; + region->size = size; + + if (pos) { + QTAILQ_INSERT_BEFORE(pos, region, list); + } else { + QTAILQ_INSERT_TAIL(&s->dma_map_regions_list, region, list); + } + +end: + qemu_mutex_unlock(&s->dma_map_regions_list_mutex); + return ret; +} + +void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end) +{ + MemoryRegionSection section; + AddressSpace *as; + QTAILQ_HEAD(, SharedRegionListener) *shared_region_listeners; + SharedRegionListener *shl; + MemoryListener *listener; + uint64_t size; + Csv3GuestState *s = &csv3_guest; + struct dma_map_region *pos, *next_pos; + + if (!csv3_enabled()) + return; + + if (end <= start) + return; + + shared_region_listeners = shared_region_listeners_get(); + if (QTAILQ_EMPTY(shared_region_listeners)) + return; + + size = end - start; + + qemu_mutex_lock(&s->dma_map_regions_list_mutex); + QTAILQ_FOREACH_SAFE(pos, &s->dma_map_regions_list, list, next_pos) { + uint64_t l, r; + uint64_t curr_end = pos->start + pos->size; + + l = MAX(start, pos->start); + r = MIN(start + size, pos->start + pos->size); + if (l < r) { + if ((start <= pos->start) && (start + size >= pos->start + pos->size)) { + QTAILQ_FOREACH(shl, shared_region_listeners, next) { + listener = shl->listener; + as = shl->as; + section = memory_region_find(as->root, pos->start, pos->size); + if (!section.mr) { + goto end; + } + if (listener->region_del) { + listener->region_del(listener, §ion); + } + memory_region_unref(section.mr); + } + + QTAILQ_REMOVE(&s->dma_map_regions_list, pos, list); + g_free(pos); + } + break; + } + if ((start + size) <= curr_end) { + break; + } + } +end: + qemu_mutex_unlock(&s->dma_map_regions_list_mutex); + return; +} diff --git a/target/i386/csv.h b/target/i386/csv.h index 6444d54efb9..0c402cefd97 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -15,6 +15,8 @@ #define I386_CSV_H #include "qapi/qapi-commands-misc-target.h" +#include "qemu/thread.h" +#include "qemu/queue.h" #include "sev.h" #ifdef CONFIG_CSV @@ -73,12 +75,19 @@ int csv_load_incoming_cpu_state(QEMUFile *f); /* CSV3 */ #define GUEST_POLICY_CSV3_BIT (1 << 6) +struct dma_map_region { + uint64_t start, size; + QTAILQ_ENTRY(dma_map_region) list; +}; + struct Csv3GuestState { uint32_t policy; int sev_fd; void *state; int (*sev_ioctl)(int fd, int cmd, void *data, int *error); const char *(*fw_error_to_str)(int code); + QTAILQ_HEAD(, dma_map_region) dma_map_regions_list; + QemuMutex dma_map_regions_list_mutex; }; typedef struct Csv3GuestState Csv3GuestState; @@ -89,4 +98,7 @@ extern int csv3_launch_encrypt_vmcb(void); int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp); +int csv3_shared_region_dma_map(uint64_t start, uint64_t end); +void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end); + #endif diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 2866a6d0ecf..925f4f80400 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -5026,8 +5026,10 @@ static int kvm_handle_exit_hypercall(X86CPU *cpu, struct kvm_run *run) if (enc) { sev_remove_shared_regions_list(gfn_start, gfn_end); + csv3_shared_region_dma_unmap(gpa, gfn_end << TARGET_PAGE_BITS); } else { sev_add_shared_regions_list(gfn_start, gfn_end); + csv3_shared_region_dma_map(gpa, gfn_end << TARGET_PAGE_BITS); } } return 0; -- Gitee From dc0446c0bfdd396b36e32a087d662e6923a54d8e Mon Sep 17 00:00:00 2001 From: jiangxin Date: Fri, 17 Jun 2022 09:25:19 +0800 Subject: [PATCH 39/51] linux-headers: update kernel headers to include CSV3 migration cmds Four new migration commands are added to support CSV3 migration. KVM_CSV3_SEND_ENCRYPT_DATA/KVM_CSV3_RECEIVE_ENCRYPT_DATA cmds are used to migrate guest's pages. KVM_CSV3_SEND_ENCRYPT_CONTEXT/KVM_CSV3_RECEIVE_ENCRYPT_CONTEXT cmds are used to migration guest's runtime context. Signed-off-by: Xin Jiang Signed-off-by: hanliyang --- linux-headers/linux/kvm.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 061b2e7f0b7..6edf0b33abe 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -2067,6 +2067,12 @@ enum csv3_cmd_id { KVM_CSV3_INIT = KVM_CSV3_NR_MIN, KVM_CSV3_LAUNCH_ENCRYPT_DATA, KVM_CSV3_LAUNCH_ENCRYPT_VMCB, + KVM_CSV3_SEND_ENCRYPT_DATA, + KVM_CSV3_SEND_ENCRYPT_CONTEXT, + KVM_CSV3_RECEIVE_ENCRYPT_DATA, + KVM_CSV3_RECEIVE_ENCRYPT_CONTEXT, + + KVM_CSV3_NR_MAX, }; struct kvm_csv3_launch_encrypt_data { @@ -2079,6 +2085,38 @@ struct kvm_csv3_init_data { __u64 nodemask; }; +struct kvm_csv3_send_encrypt_data { + __u64 hdr_uaddr; + __u32 hdr_len; + __u64 guest_addr_data; + __u32 guest_addr_len; + __u64 trans_uaddr; + __u32 trans_len; +}; + +struct kvm_csv3_send_encrypt_context { + __u64 hdr_uaddr; + __u32 hdr_len; + __u64 trans_uaddr; + __u32 trans_len; +}; + +struct kvm_csv3_receive_encrypt_data { + __u64 hdr_uaddr; + __u32 hdr_len; + __u64 guest_addr_data; + __u32 guest_addr_len; + __u64 trans_uaddr; + __u32 trans_len; +}; + +struct kvm_csv3_receive_encrypt_context { + __u64 hdr_uaddr; + __u32 hdr_len; + __u64 trans_uaddr; + __u32 trans_len; +}; + #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) -- Gitee From 70bfd06354b84594921edafc787cc8b56e38d32d Mon Sep 17 00:00:00 2001 From: jiangxin Date: Fri, 17 Jun 2022 09:37:56 +0800 Subject: [PATCH 40/51] target/i386: csv: Add support to migrate the outgoing page for CSV3 guest The csv3_send_encrypt_data() provides the method to encrypt the guest's private pages during migration. The routine is similar to CSV2's. Usually, it starts with a SEND_START command to create the migration context. Then SEND_ENCRYPT_DATA command is performed to encrypt guest pages. After migration is completed, a SEND_FINISH command is performed to the firmware. Signed-off-by: Jiang Xin Signed-off-by: hanliyang --- migration/ram.c | 87 ++++++++++++++++++ target/i386/csv.c | 184 +++++++++++++++++++++++++++++++++++++++ target/i386/csv.h | 22 +++++ target/i386/sev.c | 14 ++- target/i386/sev.h | 1 + target/i386/trace-events | 1 + 6 files changed, 308 insertions(+), 1 deletion(-) diff --git a/migration/ram.c b/migration/ram.c index 198b06000ea..71353bc90bf 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -2478,6 +2478,90 @@ ram_save_encrypted_pages_in_batch(RAMState *rs, PageSearchStatus *pss) } #endif +/** + * ram_save_csv3_pages - send the given csv3 VM pages to the stream + */ +static int ram_save_csv3_pages(RAMState *rs, PageSearchStatus *pss) +{ + bool page_dirty; + int ret; + int tmppages, pages = 0; + uint8_t *p; + uint32_t host_len = 0; + uint64_t bytes_xmit = 0; + RAMBlock *block = pss->block; + ram_addr_t offset = 0; + hwaddr paddr = RAM_ADDR_INVALID; + MachineState *ms = MACHINE(qdev_get_machine()); + ConfidentialGuestSupportClass *cgs_class = + (ConfidentialGuestSupportClass *) object_get_class(OBJECT(ms->cgs)); + struct ConfidentialGuestMemoryEncryptionOps *ops = + cgs_class->memory_encryption_ops; + + if (!kvm_csv3_enabled()) + return 0; + + do { + page_dirty = migration_bitmap_clear_dirty(rs, block, pss->page); + + /* Check the pages is dirty and if it is send it */ + if (page_dirty) { + ret = kvm_physical_memory_addr_from_host(kvm_state, + block->host + (pss->page << TARGET_PAGE_BITS), &paddr); + /* Process ROM or MMIO */ + if (paddr == RAM_ADDR_INVALID || + memory_region_is_rom(block->mr)) { + tmppages = migration_ops->ram_save_target_page(rs, pss); + } else { + /* Caculate the offset and host virtual address of the page */ + offset = pss->page << TARGET_PAGE_BITS; + p = block->host + offset; + + if (ops->queue_outgoing_page(p, TARGET_PAGE_SIZE, offset)) + return -1; + + tmppages = 1; + host_len += TARGET_PAGE_SIZE; + + stat64_add(&mig_stats.normal_pages, 1); + } + } else { + tmppages = 0; + } + + if (tmppages >= 0) { + pages += tmppages; + } else { + return tmppages; + } + + pss_find_next_dirty(pss); + } while (offset_in_ramblock(block, + ((ram_addr_t)pss->page) << TARGET_PAGE_BITS) && + host_len < CSV3_OUTGOING_PAGE_WINDOW_SIZE); + + /* Check if there are any queued pages */ + if (host_len != 0) { + /* Always set offset as 0 for csv3. */ + ram_transferred_add(save_page_header(pss, pss->pss_channel, + block, 0 | RAM_SAVE_FLAG_ENCRYPTED_DATA)); + + qemu_put_be32(pss->pss_channel, RAM_SAVE_ENCRYPTED_PAGE); + ram_transferred_add(4); + /* Process the queued pages in batch */ + ret = ops->save_queued_outgoing_pages(pss->pss_channel, &bytes_xmit); + if (ret) { + return -1; + } + ram_transferred_add(bytes_xmit); + } + + /* The offset we leave with is the last one we looked at */ + pss->page--; + + return pages; +} + /** * ram_save_host_page: save a whole host page * @@ -2513,6 +2597,9 @@ static int ram_save_host_page(RAMState *rs, PageSearchStatus *pss) return 0; } + if (kvm_csv3_enabled()) + return ram_save_csv3_pages(rs, pss); + #ifdef CONFIG_HYGON_CSV_MIG_ACCEL /* * If command_batch function is enabled and memory encryption is enabled diff --git a/target/i386/csv.c b/target/i386/csv.c index 5823c8994ce..ffa5a73a730 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -16,8 +16,13 @@ #include "qapi/error.h" #include "sysemu/kvm.h" #include "exec/address-spaces.h" +#include "migration/blocker.h" +#include "migration/qemu-file.h" +#include "migration/misc.h" +#include "monitor/monitor.h" #include +#include #ifdef CONFIG_NUMA #include @@ -30,6 +35,19 @@ bool csv_kvm_cpu_reset_inhibit; +struct ConfidentialGuestMemoryEncryptionOps csv3_memory_encryption_ops = { + .save_setup = sev_save_setup, + .save_outgoing_page = NULL, + .is_gfn_in_unshared_region = NULL, + .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 = csv3_queue_outgoing_page, + .save_queued_outgoing_pages = csv3_save_queued_outgoing_pages, +}; + +#define CSV3_OUTGOING_PAGE_NUM \ + (CSV3_OUTGOING_PAGE_WINDOW_SIZE / TARGET_PAGE_SIZE) + Csv3GuestState csv3_guest = { 0 }; int @@ -70,6 +88,7 @@ csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops) csv3_guest.fw_error_to_str = ops->fw_error_to_str; QTAILQ_INIT(&csv3_guest.dma_map_regions_list); qemu_mutex_init(&csv3_guest.dma_map_regions_list_mutex); + csv3_guest.sev_send_start = ops->sev_send_start; } return 0; } @@ -301,3 +320,168 @@ end: qemu_mutex_unlock(&s->dma_map_regions_list_mutex); return; } + +static inline hwaddr csv3_hva_to_gfn(uint8_t *ptr) +{ + ram_addr_t offset = RAM_ADDR_INVALID; + + kvm_physical_memory_addr_from_host(kvm_state, ptr, &offset); + + return offset >> TARGET_PAGE_BITS; +} + +static int +csv3_send_start(QEMUFile *f, uint64_t *bytes_sent) +{ + if (csv3_guest.sev_send_start) + return csv3_guest.sev_send_start(f, bytes_sent); + else + return -1; +} + +static int +csv3_send_get_packet_len(int *fw_err) +{ + int ret; + struct kvm_csv3_send_encrypt_data update = {0}; + + update.hdr_len = 0; + update.trans_len = 0; + ret = csv3_ioctl(KVM_CSV3_SEND_ENCRYPT_DATA, &update, fw_err); + if (*fw_err != SEV_RET_INVALID_LEN) { + error_report("%s: failed to get session length ret=%d fw_error=%d '%s'", + __func__, ret, *fw_err, fw_error_to_str(*fw_err)); + ret = 0; + goto err; + } + + if (update.hdr_len <= INT_MAX) + ret = update.hdr_len; + else + ret = 0; + +err: + return ret; +} + +static int +csv3_send_encrypt_data(Csv3GuestState *s, QEMUFile *f, + uint8_t *ptr, uint32_t size, uint64_t *bytes_sent) +{ + int ret, fw_error = 0; + guchar *trans; + uint32_t guest_addr_entry_num; + uint32_t i; + struct kvm_csv3_send_encrypt_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 = csv3_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); + } + + if (!s->guest_addr_len || !s->guest_addr_data) { + error_report("%s: invalid host address or size", __func__); + return 1; + } else { + guest_addr_entry_num = s->guest_addr_len / sizeof(struct guest_addr_entry); + } + + /* allocate transport buffer */ + trans = g_new(guchar, guest_addr_entry_num * TARGET_PAGE_SIZE); + + update.hdr_uaddr = (uintptr_t)s->send_packet_hdr; + update.hdr_len = s->send_packet_hdr_len; + update.guest_addr_data = (uintptr_t)s->guest_addr_data; + update.guest_addr_len = s->guest_addr_len; + update.trans_uaddr = (uintptr_t)trans; + update.trans_len = guest_addr_entry_num * TARGET_PAGE_SIZE; + + trace_kvm_csv3_send_encrypt_data(trans, update.trans_len); + + ret = csv3_ioctl(KVM_CSV3_SEND_ENCRYPT_DATA, &update, &fw_error); + if (ret) { + error_report("%s: SEND_ENCRYPT_DATA ret=%d fw_error=%d '%s'", + __func__, ret, fw_error, fw_error_to_str(fw_error)); + goto err; + } + + for (i = 0; i < guest_addr_entry_num; i++) { + if (s->guest_addr_data[i].share) + memcpy(trans + i * TARGET_PAGE_SIZE, (guchar *)s->guest_hva_data[i].hva, + TARGET_PAGE_SIZE); + } + + 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.guest_addr_len); + qemu_put_buffer(f, (uint8_t *)update.guest_addr_data, update.guest_addr_len); + *bytes_sent += 4 + update.guest_addr_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: + s->guest_addr_len = 0; + g_free(trans); + return ret; +} + +int +csv3_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr) +{ + Csv3GuestState *s = &csv3_guest; + uint32_t i = 0; + + (void) addr; + + if (!s->guest_addr_data) { + s->guest_hva_data = g_new0(struct guest_hva_entry, CSV3_OUTGOING_PAGE_NUM); + s->guest_addr_data = g_new0(struct guest_addr_entry, CSV3_OUTGOING_PAGE_NUM); + s->guest_addr_len = 0; + } + + if (s->guest_addr_len >= sizeof(struct guest_addr_entry) * CSV3_OUTGOING_PAGE_NUM) { + error_report("Failed to queue outgoing page"); + return 1; + } + + i = s->guest_addr_len / sizeof(struct guest_addr_entry); + s->guest_hva_data[i].hva = (uintptr_t)ptr; + s->guest_addr_data[i].share = 0; + s->guest_addr_data[i].reserved = 0; + s->guest_addr_data[i].gfn = csv3_hva_to_gfn(ptr); + s->guest_addr_len += sizeof(struct guest_addr_entry); + + return 0; +} + +int +csv3_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent) +{ + Csv3GuestState *s = &csv3_guest; + + /* + * If this is a first buffer then create outgoing encryption context + * and write our PDH, policy and session data. + */ + if (!csv3_check_state(SEV_STATE_SEND_UPDATE) && + csv3_send_start(f, bytes_sent)) { + error_report("Failed to create outgoing context"); + return 1; + } + + return csv3_send_encrypt_data(s, f, NULL, 0, bytes_sent); +} diff --git a/target/i386/csv.h b/target/i386/csv.h index 0c402cefd97..e808bea6149 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -80,6 +80,18 @@ struct dma_map_region { QTAILQ_ENTRY(dma_map_region) list; }; +#define CSV3_OUTGOING_PAGE_WINDOW_SIZE (512 * TARGET_PAGE_SIZE) + +struct guest_addr_entry { + uint64_t share: 1; + uint64_t reserved: 11; + uint64_t gfn: 52; +}; + +struct guest_hva_entry { + uint64_t hva; +}; + struct Csv3GuestState { uint32_t policy; int sev_fd; @@ -88,11 +100,19 @@ struct Csv3GuestState { const char *(*fw_error_to_str)(int code); QTAILQ_HEAD(, dma_map_region) dma_map_regions_list; QemuMutex dma_map_regions_list_mutex; + gchar *send_packet_hdr; + size_t send_packet_hdr_len; + struct guest_hva_entry *guest_hva_data; + struct guest_addr_entry *guest_addr_data; + size_t guest_addr_len; + + int (*sev_send_start)(QEMUFile *f, uint64_t *bytes_sent); }; typedef struct Csv3GuestState Csv3GuestState; extern struct Csv3GuestState csv3_guest; +extern struct ConfidentialGuestMemoryEncryptionOps csv3_memory_encryption_ops; extern int csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops); extern int csv3_launch_encrypt_vmcb(void); @@ -100,5 +120,7 @@ int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp); int csv3_shared_region_dma_map(uint64_t start, uint64_t end); void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end); +int csv3_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); +int csv3_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent); #endif diff --git a/target/i386/sev.c b/target/i386/sev.c index eb1026b575f..465b62cb710 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -1225,7 +1225,11 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) qemu_add_vm_change_state_handler(sev_vm_state_change, sev); migration_add_notifier(&sev_migration_state, sev_migration_state_notifier); - cgs_class->memory_encryption_ops = &sev_memory_encryption_ops; + if (csv3_enabled()) { + cgs_class->memory_encryption_ops = &csv3_memory_encryption_ops; + } else { + cgs_class->memory_encryption_ops = &sev_memory_encryption_ops; + } QTAILQ_INIT(&sev->shared_regions_list); /* Determine whether support MSR_AMD64_SEV_ES_GHCB */ @@ -2608,9 +2612,17 @@ bool sev_add_kernel_loader_hashes(SevKernelLoaderContext *ctx, Error **errp) return ret; } +static int _sev_send_start(QEMUFile *f, uint64_t *bytes_sent) +{ + SevGuestState *s = sev_guest; + + return sev_send_start(s, f, bytes_sent); +} + struct sev_ops sev_ops = { .sev_ioctl = sev_ioctl, .fw_error_to_str = fw_error_to_str, + .sev_send_start = _sev_send_start, }; static void diff --git a/target/i386/sev.h b/target/i386/sev.h index e91431e0f72..8ccef22a951 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -83,6 +83,7 @@ extern bool sev_kvm_has_msr_ghcb; struct sev_ops { int (*sev_ioctl)(int fd, int cmd, void *data, int *error); const char *(*fw_error_to_str)(int code); + int (*sev_send_start)(QEMUFile *f, uint64_t *bytes_sent); }; extern struct sev_ops sev_ops; diff --git a/target/i386/trace-events b/target/i386/trace-events index e07061bf392..6ebb644cba0 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -22,3 +22,4 @@ kvm_sev_receive_update_vmsa(uint32_t cpu_id, uint32_t cpu_index, void *src, int # csv.c kvm_csv3_launch_encrypt_data(uint64_t gpa, void *addr, uint64_t len) "gpa 0x%" PRIx64 "addr %p len 0x%" PRIu64 +kvm_csv3_send_encrypt_data(void *dst, int len) "trans %p len %d" -- Gitee From 3079e7490ee07f78222de62f16e1ba350cf2b0b4 Mon Sep 17 00:00:00 2001 From: jiangxin Date: Fri, 17 Jun 2022 09:45:45 +0800 Subject: [PATCH 41/51] target/i386: csv: Add support to migrate the incoming page for CSV3 guest The csv3_receive_encrypt_data() provides the method to read incoming guest private pages from socket and load them into guest memory. The routine is similar to CSV2's. Usually, it starts with a RECEIVE START command to create the migration context. Then RECEIVE ENCRYPT DATA command is performed to let the firmware load incoming pages into guest memory. After migration is completed, a RECEIVE FINISH command is performed to the firmware. Signed-off-by: Jiang Xin Signed-off-by: hanliyang --- target/i386/csv.c | 87 ++++++++++++++++++++++++++++++++++++++++ target/i386/csv.h | 2 + target/i386/sev.c | 8 ++++ target/i386/sev.h | 1 + target/i386/trace-events | 1 + 5 files changed, 99 insertions(+) diff --git a/target/i386/csv.c b/target/i386/csv.c index ffa5a73a730..81407e3c220 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -38,11 +38,14 @@ bool csv_kvm_cpu_reset_inhibit; struct ConfidentialGuestMemoryEncryptionOps csv3_memory_encryption_ops = { .save_setup = sev_save_setup, .save_outgoing_page = NULL, + .load_incoming_page = csv3_load_incoming_page, .is_gfn_in_unshared_region = NULL, .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 = csv3_queue_outgoing_page, .save_queued_outgoing_pages = csv3_save_queued_outgoing_pages, + .queue_incoming_page = NULL, + .load_queued_incoming_pages = NULL, }; #define CSV3_OUTGOING_PAGE_NUM \ @@ -89,6 +92,7 @@ csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops) QTAILQ_INIT(&csv3_guest.dma_map_regions_list); qemu_mutex_init(&csv3_guest.dma_map_regions_list_mutex); csv3_guest.sev_send_start = ops->sev_send_start; + csv3_guest.sev_receive_start = ops->sev_receive_start; } return 0; } @@ -485,3 +489,86 @@ csv3_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent) return csv3_send_encrypt_data(s, f, NULL, 0, bytes_sent); } + +static int +csv3_receive_start(QEMUFile *f) +{ + if (csv3_guest.sev_receive_start) + return csv3_guest.sev_receive_start(f); + else + return -1; +} + +static int csv3_receive_encrypt_data(QEMUFile *f, uint8_t *ptr) +{ + int ret = 1, fw_error = 0; + uint32_t i, guest_addr_entry_num; + gchar *hdr = NULL, *trans = NULL; + struct guest_addr_entry *guest_addr_data; + struct kvm_csv3_receive_encrypt_data update = {}; + void *hva = NULL; + MemoryRegion *mr = NULL; + + /* 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 = (uintptr_t)hdr; + + /* get guest addr data */ + update.guest_addr_len = qemu_get_be32(f); + + guest_addr_data = (struct guest_addr_entry *)g_new(gchar, update.guest_addr_len); + qemu_get_buffer(f, (uint8_t *)guest_addr_data, update.guest_addr_len); + update.guest_addr_data = (uintptr_t)guest_addr_data; + + /* get transport buffer */ + update.trans_len = qemu_get_be32(f); + + 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 share memory. */ + guest_addr_entry_num = update.guest_addr_len / sizeof(struct guest_addr_entry); + for (i = 0; i < guest_addr_entry_num; i++) { + if (guest_addr_data[i].share) { + hva = gpa2hva(&mr, + ((uint64_t)guest_addr_data[i].gfn << TARGET_PAGE_BITS), + TARGET_PAGE_SIZE, + NULL); + if (hva) + memcpy(hva, trans + i * TARGET_PAGE_SIZE, TARGET_PAGE_SIZE); + } + } + + trace_kvm_csv3_receive_encrypt_data(trans, update.trans_len, hdr, update.hdr_len); + + ret = csv3_ioctl(KVM_CSV3_RECEIVE_ENCRYPT_DATA, &update, &fw_error); + if (ret) { + error_report("Error RECEIVE_ENCRYPT_DATA ret=%d fw_error=%d '%s'", + ret, fw_error, fw_error_to_str(fw_error)); + goto err; + } + +err: + g_free(trans); + g_free(guest_addr_data); + g_free(hdr); + return ret; +} + +int csv3_load_incoming_page(QEMUFile *f, uint8_t *ptr) +{ + /* + * If this is first buffer and SEV is not in recieiving state then + * use RECEIVE_START command to create a encryption context. + */ + if (!csv3_check_state(SEV_STATE_RECEIVE_UPDATE) && + csv3_receive_start(f)) { + return 1; + } + + return csv3_receive_encrypt_data(f, ptr); +} diff --git a/target/i386/csv.h b/target/i386/csv.h index e808bea6149..b0adae0a8ab 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -107,6 +107,7 @@ struct Csv3GuestState { size_t guest_addr_len; int (*sev_send_start)(QEMUFile *f, uint64_t *bytes_sent); + int (*sev_receive_start)(QEMUFile *f); }; typedef struct Csv3GuestState Csv3GuestState; @@ -120,6 +121,7 @@ int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp); int csv3_shared_region_dma_map(uint64_t start, uint64_t end); void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end); +int csv3_load_incoming_page(QEMUFile *f, uint8_t *ptr); int csv3_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); int csv3_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent); diff --git a/target/i386/sev.c b/target/i386/sev.c index 465b62cb710..337f5441506 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -2619,10 +2619,18 @@ static int _sev_send_start(QEMUFile *f, uint64_t *bytes_sent) return sev_send_start(s, f, bytes_sent); } +static int _sev_receive_start(QEMUFile *f) +{ + SevGuestState *s = sev_guest; + + return sev_receive_start(s, f); +} + struct sev_ops sev_ops = { .sev_ioctl = sev_ioctl, .fw_error_to_str = fw_error_to_str, .sev_send_start = _sev_send_start, + .sev_receive_start = _sev_receive_start, }; static void diff --git a/target/i386/sev.h b/target/i386/sev.h index 8ccef22a951..647b426b16f 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -84,6 +84,7 @@ struct sev_ops { int (*sev_ioctl)(int fd, int cmd, void *data, int *error); const char *(*fw_error_to_str)(int code); int (*sev_send_start)(QEMUFile *f, uint64_t *bytes_sent); + int (*sev_receive_start)(QEMUFile *f); }; extern struct sev_ops sev_ops; diff --git a/target/i386/trace-events b/target/i386/trace-events index 6ebb644cba0..9609fe3d5cb 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -23,3 +23,4 @@ kvm_sev_receive_update_vmsa(uint32_t cpu_id, uint32_t cpu_index, void *src, int # csv.c kvm_csv3_launch_encrypt_data(uint64_t gpa, void *addr, uint64_t len) "gpa 0x%" PRIx64 "addr %p len 0x%" PRIu64 kvm_csv3_send_encrypt_data(void *dst, int len) "trans %p len %d" +kvm_csv3_receive_encrypt_data(void *dst, int len, void *hdr, int hdr_len) "trans %p len %d hdr %p hdr_len %d" -- Gitee From 709d9a2bbbde08209c706ee17351d4a6028fff1f Mon Sep 17 00:00:00 2001 From: jiangxin Date: Fri, 17 Jun 2022 09:52:31 +0800 Subject: [PATCH 42/51] target/i386: csv: Add support to migrate the outgoing context for CSV3 guest CSV3 needs to migrate guest cpu's context pages. Prior to migration of the context, it should query transfer buffer length and header data length by SEND ENCRYPT CONTEXT command. New migration flag RAM_SAVE_ENCRYPTED_CSV3_CONTEXT is defined for CSV3. Signed-off-by: Jiang Xin Signed-off-by: hanliyang --- target/i386/csv.c | 81 ++++++++++++++++++++++++++++++++++++++++ target/i386/csv.h | 1 + target/i386/trace-events | 1 + 3 files changed, 83 insertions(+) diff --git a/target/i386/csv.c b/target/i386/csv.c index 81407e3c220..1560db680a4 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -46,6 +46,7 @@ struct ConfidentialGuestMemoryEncryptionOps csv3_memory_encryption_ops = { .save_queued_outgoing_pages = csv3_save_queued_outgoing_pages, .queue_incoming_page = NULL, .load_queued_incoming_pages = NULL, + .save_outgoing_cpu_state = csv3_save_outgoing_context, }; #define CSV3_OUTGOING_PAGE_NUM \ @@ -572,3 +573,83 @@ int csv3_load_incoming_page(QEMUFile *f, uint8_t *ptr) return csv3_receive_encrypt_data(f, ptr); } + +static int +csv3_send_get_context_len(int *fw_err, int *context_len, int *hdr_len) +{ + int ret = 0; + struct kvm_csv3_send_encrypt_context update = { 0 }; + + ret = csv3_ioctl(KVM_CSV3_SEND_ENCRYPT_CONTEXT, &update, fw_err); + if (*fw_err != SEV_RET_INVALID_LEN) { + error_report("%s: failed to get context length ret=%d fw_error=%d '%s'", + __func__, ret, *fw_err, fw_error_to_str(*fw_err)); + ret = -1; + goto err; + } + + if (update.trans_len <= INT_MAX && update.hdr_len <= INT_MAX) { + *context_len = update.trans_len; + *hdr_len = update.hdr_len; + } + ret = 0; +err: + return ret; +} + +static int +csv3_send_encrypt_context(Csv3GuestState *s, QEMUFile *f, uint64_t *bytes_sent) +{ + int ret, fw_error = 0; + int context_len = 0; + int hdr_len = 0; + guchar *trans; + guchar *hdr; + struct kvm_csv3_send_encrypt_context update = { }; + + ret = csv3_send_get_context_len(&fw_error, &context_len, &hdr_len); + if (context_len < 1 || hdr_len < 1) { + error_report("%s: fail to get context length fw_error=%d '%s'", + __func__, fw_error, fw_error_to_str(fw_error)); + return 1; + } + + /* allocate transport buffer */ + trans = g_new(guchar, context_len); + hdr = g_new(guchar, hdr_len); + + update.hdr_uaddr = (uintptr_t)hdr; + update.hdr_len = hdr_len; + update.trans_uaddr = (uintptr_t)trans; + update.trans_len = context_len; + + trace_kvm_csv3_send_encrypt_context(trans, update.trans_len); + + ret = csv3_ioctl(KVM_CSV3_SEND_ENCRYPT_CONTEXT, &update, &fw_error); + if (ret) { + error_report("%s: SEND_ENCRYPT_CONTEXT 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); + g_free(hdr); + return ret; +} + +int csv3_save_outgoing_context(QEMUFile *f, uint64_t *bytes_sent) +{ + Csv3GuestState *s = &csv3_guest; + + /* send csv3 context. */ + return csv3_send_encrypt_context(s, f, bytes_sent); +} diff --git a/target/i386/csv.h b/target/i386/csv.h index b0adae0a8ab..e9b8e00c965 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -124,5 +124,6 @@ void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end); int csv3_load_incoming_page(QEMUFile *f, uint8_t *ptr); int csv3_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); int csv3_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent); +int csv3_save_outgoing_context(QEMUFile *f, uint64_t *bytes_sent); #endif diff --git a/target/i386/trace-events b/target/i386/trace-events index 9609fe3d5cb..31a2418bbcc 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -23,4 +23,5 @@ kvm_sev_receive_update_vmsa(uint32_t cpu_id, uint32_t cpu_index, void *src, int # csv.c kvm_csv3_launch_encrypt_data(uint64_t gpa, void *addr, uint64_t len) "gpa 0x%" PRIx64 "addr %p len 0x%" PRIu64 kvm_csv3_send_encrypt_data(void *dst, int len) "trans %p len %d" +kvm_csv3_send_encrypt_context(void *dst, int len) "trans %p len %d" kvm_csv3_receive_encrypt_data(void *dst, int len, void *hdr, int hdr_len) "trans %p len %d hdr %p hdr_len %d" -- Gitee From 54e03311a01d11debdd264930c790564bdb61381 Mon Sep 17 00:00:00 2001 From: jiangxin Date: Fri, 17 Jun 2022 10:00:46 +0800 Subject: [PATCH 43/51] target/i386: csv: Add support to migrate the incoming context for CSV3 guest The csv3_load_incoming_context() provides the method to read incoming guest's context from socket. It loads them into guest private memory. This is the last step during migration and RECEIVE FINISH command is performed by then to complete the whole migration. Signed-off-by: Jiang Xin Signed-off-by: hanliyang --- target/i386/csv.c | 45 ++++++++++++++++++++++++++++++++++++++++ target/i386/csv.h | 1 + target/i386/trace-events | 1 + 3 files changed, 47 insertions(+) diff --git a/target/i386/csv.c b/target/i386/csv.c index 1560db680a4..0593f9b19b3 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -47,6 +47,7 @@ struct ConfidentialGuestMemoryEncryptionOps csv3_memory_encryption_ops = { .queue_incoming_page = NULL, .load_queued_incoming_pages = NULL, .save_outgoing_cpu_state = csv3_save_outgoing_context, + .load_incoming_cpu_state = csv3_load_incoming_context, }; #define CSV3_OUTGOING_PAGE_NUM \ @@ -646,6 +647,42 @@ err: return ret; } +static int +csv3_receive_encrypt_context(Csv3GuestState *s, QEMUFile *f) +{ + int ret = 1, fw_error = 0; + gchar *hdr = NULL, *trans = NULL; + struct kvm_csv3_receive_encrypt_context update = {}; + + /* 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 = (uintptr_t)hdr; + + /* get transport buffer */ + update.trans_len = qemu_get_be32(f); + + 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_csv3_receive_encrypt_context(trans, update.trans_len, hdr, update.hdr_len); + + ret = csv3_ioctl(KVM_CSV3_RECEIVE_ENCRYPT_CONTEXT, &update, &fw_error); + if (ret) { + error_report("Error RECEIVE_ENCRYPT_CONTEXT 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 csv3_save_outgoing_context(QEMUFile *f, uint64_t *bytes_sent) { Csv3GuestState *s = &csv3_guest; @@ -653,3 +690,11 @@ int csv3_save_outgoing_context(QEMUFile *f, uint64_t *bytes_sent) /* send csv3 context. */ return csv3_send_encrypt_context(s, f, bytes_sent); } + +int csv3_load_incoming_context(QEMUFile *f) +{ + Csv3GuestState *s = &csv3_guest; + + /* receive csv3 context. */ + return csv3_receive_encrypt_context(s, f); +} diff --git a/target/i386/csv.h b/target/i386/csv.h index e9b8e00c965..bbe372498d5 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -122,6 +122,7 @@ int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp); int csv3_shared_region_dma_map(uint64_t start, uint64_t end); void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end); int csv3_load_incoming_page(QEMUFile *f, uint8_t *ptr); +int csv3_load_incoming_context(QEMUFile *f); int csv3_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); int csv3_save_queued_outgoing_pages(QEMUFile *f, uint64_t *bytes_sent); int csv3_save_outgoing_context(QEMUFile *f, uint64_t *bytes_sent); diff --git a/target/i386/trace-events b/target/i386/trace-events index 31a2418bbcc..515441c4f36 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -25,3 +25,4 @@ kvm_csv3_launch_encrypt_data(uint64_t gpa, void *addr, uint64_t len) "gpa 0x%" P kvm_csv3_send_encrypt_data(void *dst, int len) "trans %p len %d" kvm_csv3_send_encrypt_context(void *dst, int len) "trans %p len %d" kvm_csv3_receive_encrypt_data(void *dst, int len, void *hdr, int hdr_len) "trans %p len %d hdr %p hdr_len %d" +kvm_csv3_receive_encrypt_context(void *dst, int len, void *hdr, int hdr_len) "trans %p len %d hdr %p hdr_len %d" -- Gitee From 4130a65b96413c814cf32b7d4d72dbf523796a86 Mon Sep 17 00:00:00 2001 From: hanliyang Date: Fri, 1 Mar 2024 14:12:44 +0800 Subject: [PATCH 44/51] target/i386: sev: Fix incompatibility between SEV and CSV on the GET_ID API If the length of GET_ID request is too small, Hygon CSV will return SEV_RET_INVALID_PARAM. This return code doesn't comply with SEV API Spec. Hygon will consider to fix the compitibility issue of return value of the GET_ID API, so also check whether the return value is SEV_RET_INVALID_LEN on Hygon CPUs. Signed-off-by: hanliyang Change-Id: I204e69817fbb97c6c81bea086af53d4c312895b4 --- target/i386/sev.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/target/i386/sev.c b/target/i386/sev.c index 337f5441506..95b16cc6396 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -589,7 +589,8 @@ static int sev_get_cpu0_id(int fd, guchar **id, size_t *id_len, Error **errp) /* query the ID length */ r = sev_platform_ioctl(fd, SEV_GET_ID2, &get_id2, &err); - if (r < 0 && err != SEV_RET_INVALID_LEN) { + if (r < 0 && err != SEV_RET_INVALID_LEN && + !(is_hygon_cpu() && err == SEV_RET_INVALID_PARAM)) { error_setg(errp, "SEV: Failed to get ID ret=%d fw_err=%d (%s)", r, err, fw_error_to_str(err)); return 1; -- Gitee From f49ae1db13f64cf331b357df888bbe01c90da72b Mon Sep 17 00:00:00 2001 From: appleLin Date: Wed, 3 Aug 2022 21:02:41 +0800 Subject: [PATCH 45/51] target/i386: sev: Add support for reuse ASID for different CSV guests In you want to reuse one ASID for many CSV guests, you should provide a label (i.e. userid) and the length of the label when launch CSV guest. The CSV guests which were provided the same userid will share the same ASID. Signed-off-by: hanliyang Change-Id: I929a7489b310f08535df67c231ee7b3cd9cee51e --- linux-headers/linux/kvm.h | 5 +++++ qapi/qom.json | 5 ++++- qemu-options.hx | 5 ++++- target/i386/csv.h | 5 +++-- target/i386/sev.c | 47 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 6edf0b33abe..f67a7dde12d 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -2060,6 +2060,11 @@ struct kvm_csv_command_batch { __u64 csv_batch_list_uaddr; }; +struct kvm_csv_init { + __u64 userid_addr; + __u32 len; +}; + /* CSV3 command */ enum csv3_cmd_id { KVM_CSV3_NR_MIN = 0xc0, diff --git a/qapi/qom.json b/qapi/qom.json index c53ef978ff7..89a2516b42e 100644 --- a/qapi/qom.json +++ b/qapi/qom.json @@ -866,6 +866,8 @@ # designated guest firmware page for measured boot with -kernel # (default: false) (since 6.2) # +# @user-id: the user id of the guest owner, only support on Hygon CPUs +# # Since: 2.12 ## { 'struct': 'SevGuestProperties', @@ -876,7 +878,8 @@ '*handle': 'uint32', '*cbitpos': 'uint32', 'reduced-phys-bits': 'uint32', - '*kernel-hashes': 'bool' } } + '*kernel-hashes': 'bool', + '*user-id': 'str' } } ## # @ThreadContextProperties: diff --git a/qemu-options.hx b/qemu-options.hx index b6b4ad9e676..c260117a96c 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -5645,7 +5645,7 @@ SRST -object secret,id=sec0,keyid=secmaster0,format=base64,\\ data=$SECRET,iv=$(dh_cert_file = g_strdup(value); } +static char * +sev_guest_get_user_id(Object *obj, Error **errp) +{ + SevGuestState *s = SEV_GUEST(obj); + + return g_strdup(s->user_id); +} + +static void +sev_guest_set_user_id(Object *obj, const char *value, Error **errp) +{ + SevGuestState *s = SEV_GUEST(obj); + + s->user_id = g_strdup(value); +} + static char * sev_guest_get_sev_device(Object *obj, Error **errp) { @@ -426,6 +443,11 @@ sev_guest_class_init(ObjectClass *oc, void *data) sev_guest_set_kernel_hashes); object_class_property_set_description(oc, "kernel-hashes", "add kernel hashes to guest firmware for measured Linux boot"); + object_class_property_add_str(oc, "user-id", + sev_guest_get_user_id, + sev_guest_set_user_id); + object_class_property_set_description(oc, "user-id", + "user id of the guest owner"); } static void @@ -1178,7 +1200,30 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) } trace_kvm_sev_init(); - ret = sev_ioctl(sev->sev_fd, cmd, NULL, &fw_error); + + /* Only support reuse asid for CSV/CSV2 guest */ + if (is_hygon_cpu() && + (sev_guest->policy & GUEST_POLICY_REUSE_ASID) && + !(sev_guest->policy & GUEST_POLICY_CSV3_BIT)) { + char *user_id = NULL; + struct kvm_csv_init *init_cmd_buf = NULL; + + user_id = object_property_get_str(OBJECT(sev), "user-id", NULL); + if (user_id && strlen(user_id)) { + init_cmd_buf = g_new0(struct kvm_csv_init, 1); + init_cmd_buf->len = strlen(user_id); + init_cmd_buf->userid_addr = (__u64)user_id; + } + ret = sev_ioctl(sev->sev_fd, cmd, init_cmd_buf, &fw_error); + + if (user_id) { + g_free(user_id); + g_free(init_cmd_buf); + } + } else { + ret = sev_ioctl(sev->sev_fd, cmd, NULL, &fw_error); + } + if (ret) { error_setg(errp, "%s: failed to initialize ret=%d fw_error=%d '%s'", __func__, ret, fw_error, fw_error_to_str(fw_error)); -- Gitee From 77dff465daa65a3dac8f0987fe08162d80a78fc1 Mon Sep 17 00:00:00 2001 From: Yanjing Zhou Date: Tue, 16 Apr 2024 15:27:33 +0800 Subject: [PATCH 46/51] target/i386: Add Hygon Dhyana-v3 CPU model Add the following feature bits for Dhyana CPU model: perfctr-core, clzero, xsaveerptr, aes, pclmulqdq, sha-ni Disable xsaves feature bit for Erratum 1386 Signed-off-by: Yanjing Zhou --- target/i386/cpu.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index a373d8e7773..61f81adafc5 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -4805,6 +4805,20 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } }, }, + { .version = 3, + .props = (PropValue[]) { + { "xsaves", "off" }, + { "perfctr-core", "on" }, + { "clzero", "on" }, + { "xsaveerptr", "on" }, + { "aes", "on" }, + { "pclmulqdq", "on" }, + { "sha-ni", "on" }, + { "model-id", + "Hygon Dhyana-v3 processor" }, + { /* end of list */ } + }, + }, { /* end of list */ } } }, -- Gitee From 035ce0bc5b3f6e1621bd11fbc9218dba8ee88fc9 Mon Sep 17 00:00:00 2001 From: Yanjing Zhou Date: Tue, 16 Apr 2024 16:05:00 +0800 Subject: [PATCH 47/51] target/i386: Add new Hygon 'Dharma' CPU model Add the following feature bits compare to Dhyana CPU model: stibp, ibrs, umip, ssbd Signed-off-by: Yanjing Zhou --- target/i386/cpu.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 61f81adafc5..8649f9ebf58 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -2163,6 +2163,56 @@ static const CPUCaches epyc_genoa_cache_info = { }, }; +static const CPUCaches dharma_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + .type = DATA_CACHE, + .level = 1, + .size = 32 * KiB, + .line_size = 64, + .associativity = 8, + .partitions = 1, + .sets = 64, + .lines_per_tag = 1, + .self_init = 1, + .no_invd_sharing = true, + }, + .l1i_cache = &(CPUCacheInfo) { + .type = INSTRUCTION_CACHE, + .level = 1, + .size = 32 * KiB, + .line_size = 64, + .associativity = 8, + .partitions = 1, + .sets = 64, + .lines_per_tag = 1, + .self_init = 1, + .no_invd_sharing = true, + }, + .l2_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 2, + .size = 512 * KiB, + .line_size = 64, + .associativity = 8, + .partitions = 1, + .sets = 1024, + .lines_per_tag = 1, + }, + .l3_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 3, + .size = 16 * MiB, + .line_size = 64, + .associativity = 16, + .partitions = 1, + .sets = 16384, + .lines_per_tag = 1, + .self_init = true, + .inclusive = true, + .complex_indexing = true, + }, +}; + /* The following VMX features are not supported by KVM and are left out in the * CPU definitions: * @@ -5050,6 +5100,55 @@ static const X86CPUDefinition builtin_x86_defs[] = { .model_id = "AMD EPYC-Genoa Processor", .cache_info = &epyc_genoa_cache_info, }, + { + .name = "Dharma", + .level = 0xd, + .vendor = CPUID_VENDOR_HYGON, + .family = 24, + .model = 4, + .stepping = 0, + .features[FEAT_1_EDX] = + CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX | CPUID_CLFLUSH | + CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA | CPUID_PGE | + CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 | CPUID_MCE | + CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE | CPUID_DE | + CPUID_VME | CPUID_FP87, + .features[FEAT_1_ECX] = + CPUID_EXT_RDRAND | CPUID_EXT_F16C | CPUID_EXT_AVX | + CPUID_EXT_XSAVE | CPUID_EXT_AES | CPUID_EXT_POPCNT | + CPUID_EXT_MOVBE | CPUID_EXT_SSE42 | CPUID_EXT_SSE41 | + CPUID_EXT_CX16 | CPUID_EXT_FMA | CPUID_EXT_SSSE3 | + CPUID_EXT_MONITOR | CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3, + .features[FEAT_8000_0001_EDX] = + CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_PDPE1GB | + CPUID_EXT2_FFXSR | CPUID_EXT2_MMXEXT | CPUID_EXT2_NX | + CPUID_EXT2_SYSCALL, + .features[FEAT_8000_0001_ECX] = + CPUID_EXT3_OSVW | CPUID_EXT3_3DNOWPREFETCH | + CPUID_EXT3_MISALIGNSSE | CPUID_EXT3_SSE4A | CPUID_EXT3_ABM | + CPUID_EXT3_CR8LEG | CPUID_EXT3_SVM | CPUID_EXT3_LAHF_LM | + CPUID_EXT3_TOPOEXT | CPUID_EXT3_PERFCORE, + .features[FEAT_8000_0008_EBX] = + CPUID_8000_0008_EBX_CLZERO | CPUID_8000_0008_EBX_XSAVEERPTR | + CPUID_8000_0008_EBX_IBPB | CPUID_8000_0008_EBX_IBRS | + CPUID_8000_0008_EBX_STIBP | CPUID_8000_0008_EBX_AMD_SSBD, + .features[FEAT_7_0_EBX] = + CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 | CPUID_7_0_EBX_AVX2 | + CPUID_7_0_EBX_SMEP | CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_RDSEED | + CPUID_7_0_EBX_ADX | CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_CLFLUSHOPT | + CPUID_7_0_EBX_SHA_NI, + .features[FEAT_7_0_ECX] = CPUID_7_0_ECX_UMIP, + .features[FEAT_XSAVE] = + CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XSAVEC | + CPUID_XSAVE_XGETBV1, + .features[FEAT_6_EAX] = + CPUID_6_EAX_ARAT, + .features[FEAT_SVM] = + CPUID_SVM_NPT | CPUID_SVM_NRIPSAVE, + .xlevel = 0x8000001E, + .model_id = "Hygon Dharma Processor", + .cache_info = &dharma_cache_info, + }, }; /* -- Gitee From ca383d757fa3a1bd6f0af2d82555502e0fd75e4b Mon Sep 17 00:00:00 2001 From: Yabin Li Date: Fri, 4 Aug 2023 21:09:08 +0800 Subject: [PATCH 48/51] vfio: Add vfio based mediated hct support. add device hct used for simulate hygon ccp Signed-off-by: liyabin Signed-off-by: yangdepei Change-Id: I8606ad46b4fa3671233597e89c14589b96a9081b --- hw/vfio/Kconfig | 6 + hw/vfio/hct.c | 540 ++++++++++++++++++++++++++++++++++++++++++++ hw/vfio/meson.build | 1 + 3 files changed, 547 insertions(+) create mode 100644 hw/vfio/hct.c diff --git a/hw/vfio/Kconfig b/hw/vfio/Kconfig index 7cdba0560aa..5f0d3c2d2bf 100644 --- a/hw/vfio/Kconfig +++ b/hw/vfio/Kconfig @@ -41,3 +41,9 @@ config VFIO_IGD bool default y if PC_PCI depends on VFIO_PCI + +config VFIO_HCT + bool + default y + select VFIO + depends on LINUX && PCI diff --git a/hw/vfio/hct.c b/hw/vfio/hct.c new file mode 100644 index 00000000000..fb429271bb4 --- /dev/null +++ b/hw/vfio/hct.c @@ -0,0 +1,540 @@ +/* + * vfio based mediated ccp(hct) assignment support + * + * Copyright 2023 HYGON Corp. + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at + * your option) any later version. See the COPYING file in the top-level + * directory. + */ + +#include +#include +#include +#include +#include + +#include "qemu/osdep.h" +#include "qemu/queue.h" +#include "qemu/main-loop.h" +#include "qemu/log.h" +#include "trace.h" +#include "hw/pci/pci.h" +#include "hw/vfio/pci.h" +#include "qemu/range.h" +#include "sysemu/kvm.h" +#include "hw/pci/msi.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "hw/qdev-properties.h" + +#define MAX_CCP_CNT 48 +#define PAGE_SIZE 4096 +#define HCT_SHARED_MEMORY_SIZE (PAGE_SIZE * MAX_CCP_CNT) +#define CCP_INDEX_BYTES 4 +#define PATH_MAX 4096 +#define TYPE_HCT_DEV "hct" +#define PCI_HCT_DEV(obj) OBJECT_CHECK(HCTDevState, (obj), TYPE_HCT_DEV) +#define HCT_MMIO_SIZE (1 << 20) +#define HCT_MAX_PASID (1 << 8) + +#define PCI_VENDOR_ID_HYGON_CCP 0x1d94 +#define PCI_DEVICE_ID_HYGON_CCP 0x1468 + +#define HCT_SHARE_DEV "/dev/hct_share" + +#define HCT_VERSION_STRING "0.5" +#define DEF_VERSION_STRING "0.1" +#define VERSION_SIZE 16 + +#define HCT_SHARE_IOC_TYPE 'C' +#define HCT_SHARE_OP_TYPE 0x01 +#define HCT_SHARE_OP _IOWR(HCT_SHARE_IOC_TYPE, \ + HCT_SHARE_OP_TYPE, \ + struct hct_dev_ctrl) +#define HCT_SHARE_OP_DMA_MAP 0x01 +#define HCT_SHARE_OP_GET_ID 0x03 +#define HCT_SHARE_OP_GET_PASID 0x04 +#define HCT_SHARE_OP_DMA_UNMAP 0x05 +#define HCT_SHARE_OP_GET_VERSION 0x06 + +/* BARS */ +#define HCT_REG_BAR_IDX 2 +#define HCT_SHARED_BAR_IDX 3 +#define HCT_PASID_BAR_IDX 4 + +#define PASID_OFFSET 40 + +static volatile struct hct_data { + int init; + int hct_fd; + unsigned long pasid; + uint8_t *pasid_memory; + uint8_t *hct_shared_memory; + uint8_t ccp_index[MAX_CCP_CNT]; + uint8_t ccp_cnt; +} hct_data; + +typedef struct SharedDevice { + PCIDevice dev; + int shared_memory_offset; +} SharedDevice; + +typedef struct HctDevState { + SharedDevice sdev; + VFIODevice vdev; + MemoryRegion mmio; + MemoryRegion shared; + MemoryRegion pasid; + void *maps[PCI_NUM_REGIONS]; +} HCTDevState; + +struct hct_dev_ctrl { + unsigned char op; + unsigned char rsvd[3]; + union { + unsigned char version[VERSION_SIZE]; + struct { + unsigned long vaddr; + unsigned long iova; + unsigned long size; + }; + unsigned int id; + }; +}; + +static int pasid_get_and_init(HCTDevState *state) +{ + struct hct_dev_ctrl ctrl; + int ret; + + ctrl.op = HCT_SHARE_OP_GET_PASID; + ctrl.id = -1; + ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); + if (ret < 0) { + ret = -errno; + error_report("GET_PASID fail: %d", -errno); + goto out; + } + + *hct_data.pasid_memory = ctrl.id; + hct_data.pasid = ctrl.id; + +out: + return ret; +} + +static const MemoryRegionOps hct_mmio_ops = { + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = + { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void vfio_hct_detach_device(HCTDevState *state) +{ + vfio_detach_device(&state->vdev); + g_free(state->vdev.name); +} + +static void vfio_hct_exit(PCIDevice *dev) +{ + HCTDevState *state = PCI_HCT_DEV(dev); + + vfio_hct_detach_device(state); + + if (hct_data.hct_fd) { + qemu_close(hct_data.hct_fd); + hct_data.hct_fd = 0; + } +} + +static Property vfio_hct_properties[] = { + DEFINE_PROP_STRING("sysfsdev", HCTDevState, vdev.sysfsdev), + DEFINE_PROP_END_OF_LIST(), +}; + +static void vfio_ccp_compute_needs_reset(VFIODevice *vdev) +{ + vdev->needs_reset = false; +} + +struct VFIODeviceOps vfio_ccp_ops = { + .vfio_compute_needs_reset = vfio_ccp_compute_needs_reset, +}; + +/* create BAR2, BAR3 and BAR4 space for the virtual machine. */ +static int vfio_hct_region_mmap(HCTDevState *state) +{ + int ret; + int i; + struct vfio_region_info *info; + + for (i = 0; i < PCI_ROM_SLOT; i++) { + ret = vfio_get_region_info(&state->vdev, i, &info); + if (ret) + goto out; + + if (info->size) { + state->maps[i] = mmap(NULL, info->size, PROT_READ | PROT_WRITE, + MAP_SHARED, state->vdev.fd, info->offset); + if (state->maps[i] == MAP_FAILED) { + ret = -errno; + g_free(info); + error_report("vfio mmap fail\n"); + goto out; + } + } + g_free(info); + } + + memory_region_init_io(&state->mmio, OBJECT(state), &hct_mmio_ops, state, + "hct mmio", HCT_MMIO_SIZE); + memory_region_init_ram_device_ptr(&state->mmio, OBJECT(state), "hct mmio", + HCT_MMIO_SIZE, + state->maps[HCT_REG_BAR_IDX]); + + memory_region_init_io(&state->shared, OBJECT(state), &hct_mmio_ops, state, + "hct shared memory", PAGE_SIZE); + memory_region_init_ram_device_ptr( + &state->shared, OBJECT(state), "hct shared memory", PAGE_SIZE, + (void *)hct_data.hct_shared_memory + + state->sdev.shared_memory_offset * PAGE_SIZE); + + memory_region_init_io(&state->pasid, OBJECT(state), &hct_mmio_ops, state, + "hct pasid", PAGE_SIZE); + memory_region_init_ram_device_ptr(&state->pasid, OBJECT(state), "hct pasid", + PAGE_SIZE, hct_data.pasid_memory); + + pci_register_bar(&state->sdev.dev, HCT_REG_BAR_IDX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &state->mmio); + pci_register_bar(&state->sdev.dev, HCT_SHARED_BAR_IDX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &state->shared); + pci_register_bar(&state->sdev.dev, HCT_PASID_BAR_IDX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &state->pasid); +out: + return ret; +} + +static int hct_check_duplicated_index(int index) +{ + int cnt; + for (cnt = 0; cnt < hct_data.ccp_cnt; cnt++) { + if (hct_data.ccp_index[cnt] == index) { + error_report("many mdev shouldn't be mapped to one ccp in a " + "virtual machine!\n"); + return -1; + } + } + + hct_data.ccp_index[hct_data.ccp_cnt++] = index; + return 0; +} + +static int hct_get_ccp_index(HCTDevState *state) +{ + char path[PATH_MAX]; + char buf[CCP_INDEX_BYTES]; + int fd; + int ret; + int ccp_index; + + snprintf(path, PATH_MAX, "%s/vendor/id", state->vdev.sysfsdev); + fd = qemu_open_old(path, O_RDONLY); + if (fd < 0) { + error_report("open %s fail\n", path); + return -errno; + } + + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) { + ret = -errno; + error_report("read %s fail\n", path); + goto out; + } + + if (1 != sscanf(buf, "%d", &ccp_index)) { + ret = -errno; + error_report("format addr %s fail\n", buf); + goto out; + } + + if (!hct_check_duplicated_index(ccp_index)) { + state->sdev.shared_memory_offset = ccp_index; + } else { + ret = -1; + } + +out: + qemu_close(fd); + return ret; +} + +static int hct_api_version_check(void) +{ + struct hct_dev_ctrl ctrl; + int ret; + + ctrl.op = HCT_SHARE_OP_GET_VERSION; + memcpy(ctrl.version, DEF_VERSION_STRING, sizeof(DEF_VERSION_STRING)); + ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); + if (ret < 0) { + error_report("ret %d, errno %d: fail to get hct.ko version.\n", ret, + errno); + return -1; + } else if (memcmp(ctrl.version, HCT_VERSION_STRING, + sizeof(HCT_VERSION_STRING)) < 0) { + error_report("The hct.ko version is %s, please upgrade to version %s " + "or higher.\n", + ctrl.version, HCT_VERSION_STRING); + return -1; + } + + return 0; +} + +static int hct_shared_memory_init(void) +{ + int ret = 0; + + hct_data.hct_shared_memory = + mmap(NULL, HCT_SHARED_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, + hct_data.hct_fd, 0); + if (hct_data.hct_shared_memory == MAP_FAILED) { + ret = -errno; + error_report("map hct shared memory fail\n"); + goto out; + } + +out: + return ret; +} + +static void hct_listener_region_add(MemoryListener *listener, + MemoryRegionSection *section) +{ + struct hct_dev_ctrl ctrl; + hwaddr iova; + Int128 llend, llsize; + void *vaddr; + int ret; + + iova = REAL_HOST_PAGE_ALIGN(section->offset_within_address_space); + llend = int128_make64(section->offset_within_address_space); + llend = int128_add(llend, section->size); + llend = int128_add(llend, int128_exts64(qemu_real_host_page_mask())); + + if (int128_ge(int128_make64(iova), llend)) { + return; + } + + if (!section->mr->ram) { + return; + } + + vaddr = memory_region_get_ram_ptr(section->mr) + + section->offset_within_region + + (iova - section->offset_within_address_space); + llsize = int128_sub(llend, int128_make64(iova)); + + ctrl.op = HCT_SHARE_OP_DMA_MAP; + ctrl.iova = iova | (hct_data.pasid << PASID_OFFSET); + ctrl.vaddr = (uint64_t)vaddr; + ctrl.size = llsize; + ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); + if (ret < 0) + error_report("VFIO_MAP_DMA: %d, iova=%lx", -errno, iova); +} + +static void hct_listener_region_del(MemoryListener *listener, + MemoryRegionSection *section) +{ + struct hct_dev_ctrl ctrl; + hwaddr iova; + Int128 llend, llsize; + int ret; + + iova = REAL_HOST_PAGE_ALIGN(section->offset_within_address_space); + llend = int128_make64(section->offset_within_address_space); + llend = int128_add(llend, section->size); + llend = int128_add(llend, int128_exts64(qemu_real_host_page_mask())); + + if (int128_ge(int128_make64(iova), llend)) { + return; + } + + if (!section->mr->ram) { + return; + } + + llsize = int128_sub(llend, int128_make64(iova)); + + ctrl.op = HCT_SHARE_OP_DMA_UNMAP; + ctrl.iova = iova | (hct_data.pasid << PASID_OFFSET); + ctrl.size = llsize; + ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); + if (ret < 0) + error_report("VFIO_UNMAP_DMA: %d", -errno); +} + +static MemoryListener hct_memory_listener = { + .region_add = hct_listener_region_add, + .region_del = hct_listener_region_del, +}; + +static void hct_data_uninit(HCTDevState *state) +{ + if (hct_data.hct_fd) { + qemu_close(hct_data.hct_fd); + hct_data.hct_fd = 0; + } + + if (hct_data.pasid) { + hct_data.pasid = 0; + } + + if (hct_data.pasid_memory) { + munmap(hct_data.pasid_memory, PAGE_SIZE); + hct_data.pasid_memory = NULL; + } + + if (hct_data.hct_shared_memory) { + munmap((void *)hct_data.hct_shared_memory, HCT_SHARED_MEMORY_SIZE); + hct_data.hct_shared_memory = NULL; + } + + memory_listener_unregister(&hct_memory_listener); +} + +static int hct_data_init(HCTDevState *state) +{ + int ret; + + if (hct_data.init == 0) { + + hct_data.hct_fd = qemu_open_old(HCT_SHARE_DEV, O_RDWR); + if (hct_data.hct_fd < 0) { + error_report("fail to open %s, errno %d.", HCT_SHARE_DEV, errno); + ret = -errno; + goto out; + } + + /* The hct.ko version number needs not to be less than 0.2. */ + ret = hct_api_version_check(); + if (ret) + goto out; + + /* assign a page to the virtual BAR3 of each CCP. */ + ret = hct_shared_memory_init(); + if (ret) + goto out; + + hct_data.pasid_memory = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (hct_data.pasid_memory < 0) + goto unmap_shared_memory_exit; + + /* assign a unique pasid to each virtual machine. */ + ret = pasid_get_and_init(state); + if (ret < 0) + goto unmap_pasid_memory_exit; + + /* perform DMA_MAP and DMA_UNMAP operations on all memories of the + * virtual machine. */ + memory_listener_register(&hct_memory_listener, &address_space_memory); + + hct_data.init = 1; + } + + return hct_get_ccp_index(state); + +unmap_pasid_memory_exit: + munmap(hct_data.pasid_memory, PAGE_SIZE); + +unmap_shared_memory_exit: + munmap((void *)hct_data.hct_shared_memory, HCT_SHARED_MEMORY_SIZE); + +out: + return ret; +} + +/* When device is loaded */ +static void vfio_hct_realize(PCIDevice *pci_dev, Error **errp) +{ + int ret; + char *mdevid; + Error *err = NULL; + HCTDevState *state = PCI_HCT_DEV(pci_dev); + + /* parsing mdev device name from startup scripts */ + mdevid = g_path_get_basename(state->vdev.sysfsdev); + state->vdev.name = g_strdup_printf("%s", mdevid); + + ret = hct_data_init(state); + if (ret < 0) { + g_free(state->vdev.name); + goto out; + } + + ret = vfio_attach_device(state->vdev.name, &state->vdev, + pci_device_iommu_address_space(pci_dev), &err); + + if (ret) { + error_report("attach device failed, name = %s", state->vdev.name); + goto data_uninit_out; + } + + state->vdev.ops = &vfio_ccp_ops; + state->vdev.dev = &state->sdev.dev.qdev; + + ret = vfio_hct_region_mmap(state); + if (ret < 0) + goto detach_device_out; + + return; + +detach_device_out: + vfio_hct_detach_device(state); + +data_uninit_out: + hct_data_uninit(state); + +out: + return; +} + +static void hct_dev_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *pdc = PCI_DEVICE_CLASS(klass); + + dc->desc = "HCT Device"; + device_class_set_props(dc, vfio_hct_properties); + + pdc->realize = vfio_hct_realize; + pdc->exit = vfio_hct_exit; + pdc->vendor_id = PCI_VENDOR_ID_HYGON_CCP; + pdc->device_id = PCI_DEVICE_ID_HYGON_CCP; + pdc->class_id = PCI_CLASS_CRYPT_OTHER; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + + return; +} + +static const TypeInfo pci_hct_info = { + .name = TYPE_HCT_DEV, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(HCTDevState), + .class_init = hct_dev_class_init, + .interfaces = + (InterfaceInfo[]){ + {INTERFACE_CONVENTIONAL_PCI_DEVICE}, + {}, + }, +}; + +static void hct_register_types(void) { type_register_static(&pci_hct_info); } + +type_init(hct_register_types); diff --git a/hw/vfio/meson.build b/hw/vfio/meson.build index 2a6912c9402..b1db4c8605f 100644 --- a/hw/vfio/meson.build +++ b/hw/vfio/meson.build @@ -17,5 +17,6 @@ vfio_ss.add(when: 'CONFIG_VFIO_XGMAC', if_true: files('calxeda-xgmac.c')) vfio_ss.add(when: 'CONFIG_VFIO_AMD_XGBE', if_true: files('amd-xgbe.c')) vfio_ss.add(when: 'CONFIG_VFIO_AP', if_true: files('ap.c')) vfio_ss.add(when: 'CONFIG_VFIO_IGD', if_true: files('igd.c')) +vfio_ss.add(when: 'CONFIG_VFIO_HCT', if_true: files('hct.c')) specific_ss.add_all(when: 'CONFIG_VFIO', if_true: vfio_ss) -- Gitee From 5da94be3c83d1c4598d7d8fd061122db1df8db2a Mon Sep 17 00:00:00 2001 From: eastmoutain <14304864+eastmoutain@user.noreply.gitee.com> Date: Mon, 17 Jun 2024 10:00:46 +0800 Subject: [PATCH 49/51] hw/net: virtio-net: Update event idx if guest has made extra buffers during double check If guest has made some buffers available during double check, but the total buffer size available is lower than @bufsize, notify the guest with the latest available idx(event idx) seen by the host. Signed-off-by: yangwencheng --- hw/net/virtio-net.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index 73024babd45..5b0d8145d61 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -1653,6 +1653,7 @@ static int virtio_net_has_buffers(VirtIONetQueue *q, int bufsize) if (virtio_queue_empty(q->rx_vq) || (n->mergeable_rx_bufs && !virtqueue_avail_bytes(q->rx_vq, bufsize, 0))) { + virtio_queue_set_notification(q->rx_vq, 1); return 0; } } -- Gitee From 78c9048df55f4d8791ef3c79dbf652f8b682cd12 Mon Sep 17 00:00:00 2001 From: eastmoutain <14304864+eastmoutain@user.noreply.gitee.com> Date: Mon, 20 May 2024 21:12:23 +0800 Subject: [PATCH 50/51] target/i386: csv: Release CSV3 shared pages after unmapping DMA The shared pages are created for Device DMA access, release them once DMA mapping is removed. Signed-off-by: yangwencheng --- linux-headers/linux/kvm.h | 9 +++++++++ target/i386/csv-sysemu-stub.c | 5 +++++ target/i386/csv.c | 34 ++++++++++++++++++++++++++++++++++ target/i386/csv.h | 1 + target/i386/kvm/kvm.c | 1 + 5 files changed, 50 insertions(+) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index f67a7dde12d..36da75b925b 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -2076,6 +2076,7 @@ enum csv3_cmd_id { KVM_CSV3_SEND_ENCRYPT_CONTEXT, KVM_CSV3_RECEIVE_ENCRYPT_DATA, KVM_CSV3_RECEIVE_ENCRYPT_CONTEXT, + KVM_CSV3_HANDLE_MEMORY, KVM_CSV3_NR_MAX, }; @@ -2122,6 +2123,14 @@ struct kvm_csv3_receive_encrypt_context { __u32 trans_len; }; +#define KVM_CSV3_RELEASE_SHARED_MEMORY (0x0001) + +struct kvm_csv3_handle_memory { + __u64 gpa; + __u32 num_pages; + __u32 opcode; +}; + #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/csv-sysemu-stub.c b/target/i386/csv-sysemu-stub.c index db22c299a62..f3224a01544 100644 --- a/target/i386/csv-sysemu-stub.c +++ b/target/i386/csv-sysemu-stub.c @@ -39,3 +39,8 @@ void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end) { } + +void csv3_shared_region_relese(uint64_t gpa, uint32_t num_pages) +{ + +} diff --git a/target/i386/csv.c b/target/i386/csv.c index 0593f9b19b3..a869cc2a7ed 100644 --- a/target/i386/csv.c +++ b/target/i386/csv.c @@ -268,6 +268,40 @@ end: return ret; } +void csv3_shared_region_release(uint64_t gpa, uint32_t num_pages) +{ + struct kvm_csv3_handle_memory mem = { 0 }; + MemoryRegion *mr = NULL; + void *hva; + int ret; + + if (!csv3_enabled()) + return; + + if (!gpa || !num_pages) + return; + + mem.gpa = (__u64)gpa; + mem.num_pages = (__u32)num_pages; + mem.opcode = (__u32)KVM_CSV3_RELEASE_SHARED_MEMORY; + + /* unpin the pages */ + ret = csv3_ioctl(KVM_CSV3_HANDLE_MEMORY, &mem, NULL); + if (ret <= 0) { + if (ret < 0) + error_report("%s: CSV3 unpin failed ret %d", __func__, ret); + return; + } + + /* drop the pages */ + hva = gpa2hva(&mr, gpa, num_pages << TARGET_PAGE_BITS, NULL); + if (hva) { + ret = madvise(hva, num_pages << TARGET_PAGE_BITS, MADV_DONTNEED); + if (ret) + error_report("%s: madvise failed %d", __func__, ret); + } +} + void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end) { MemoryRegionSection section; diff --git a/target/i386/csv.h b/target/i386/csv.h index e5e05d00690..a32588ab9ac 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -122,6 +122,7 @@ int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp); int csv3_shared_region_dma_map(uint64_t start, uint64_t end); void csv3_shared_region_dma_unmap(uint64_t start, uint64_t end); +void csv3_shared_region_release(uint64_t gpa, uint32_t num_pages); int csv3_load_incoming_page(QEMUFile *f, uint8_t *ptr); int csv3_load_incoming_context(QEMUFile *f); int csv3_queue_outgoing_page(uint8_t *ptr, uint32_t sz, uint64_t addr); diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 925f4f80400..fdceecc846b 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -5027,6 +5027,7 @@ static int kvm_handle_exit_hypercall(X86CPU *cpu, struct kvm_run *run) if (enc) { sev_remove_shared_regions_list(gfn_start, gfn_end); csv3_shared_region_dma_unmap(gpa, gfn_end << TARGET_PAGE_BITS); + csv3_shared_region_release(gpa, npages); } else { sev_add_shared_regions_list(gfn_start, gfn_end); csv3_shared_region_dma_map(gpa, gfn_end << TARGET_PAGE_BITS); -- Gitee From ffab93e9daccdbb7352b64a5a18346a978c302de Mon Sep 17 00:00:00 2001 From: thomas Date: Fri, 12 Jul 2024 11:10:53 +0800 Subject: [PATCH 51/51] virtio-net: Fix network stall at the host side waiting for kick Patch 06b12970174 ("virtio-net: fix network stall under load") added double-check to test whether the available buffer size can satisfy the request or not, in case the guest has added some buffers to the avail ring simultaneously after the first check. It will be lucky if the available buffer size becomes okay after the double-check, then the host can send the packet to the guest. If the buffer size still can't satisfy the request, even if the guest has added some buffers, viritio-net would stall at the host side forever. The patch enables notification and checks whether the guest has added some buffers since last check of available buffers when the available buffers are insufficient. If no buffer is added, return false, else recheck the available buffers in the loop. If the available buffers are sufficient, disable notification and return true. Changes: 1. Change the return type of virtqueue_get_avail_bytes() from void to int, it returns an opaque that represents the shadow_avail_idx of the virtqueue on success, else -1 on error. 2. Add a new API: virtio_queue_enable_notification_and_check(), it takes an opaque as input arg which is returned from virtqueue_get_avail_bytes(). It enables notification firstly, then checks whether the guest has added some buffers since last check of available buffers or not by virtio_queue_poll(), return ture if yes. The patch also reverts patch "06b12970174". The patch also reverts patch 1052-hw-net-virtio-net-Update-event-idx-if-guest-has-made.patch. The case below can reproduce the stall. Guest 0 +--------+ | iperf | ---------------> | server | Host | +--------+ +--------+ | ... | iperf |---- | client |---- Guest n +--------+ | +--------+ | | iperf | ---------------> | server | +--------+ Boot many guests from qemu with virtio network: qemu ... -netdev tap,id=net_x \ -device virtio-net-pci-non-transitional,\ iommu_platform=on,mac=xx:xx:xx:xx:xx:xx,netdev=net_x Each guest acts as iperf server with commands below: iperf3 -s -D -i 10 -p 8001 iperf3 -s -D -i 10 -p 8002 The host as iperf client: iperf3 -c guest_IP -p 8001 -i 30 -w 256k -P 20 -t 40000 iperf3 -c guest_IP -p 8002 -i 30 -w 256k -P 20 -t 40000 After some time, the host loses connection to the guest, the guest can send packet to the host, but can't receive packet from the host. It's more likely to happen if SWIOTLB is enabled in the guest, allocating and freeing bounce buffer takes some CPU ticks, copying from/to bounce buffer takes more CPU ticks, compared with that there is no bounce buffer in the guest. Once the rate of producing packets from the host approximates the rate of receiveing packets in the guest, the guest would loop in NAPI. receive packets --- | | v | free buf virtnet_poll | | v | add buf to avail ring --- | | need kick the host? | NAPI continues v receive packets --- | | v | free buf virtnet_poll | | v | add buf to avail ring --- | v ... ... On the other hand, the host fetches free buf from avail ring, if the buf in the avail ring is not enough, the host notifies the guest the event by writing the avail idx read from avail ring to the event idx of used ring, then the host goes to sleep, waiting for the kick signal from the guest. Once the guest finds the host is waiting for kick singal (in virtqueue_kick_prepare_split()), it kicks the host. The host may stall forever at the sequences below: Host Guest ------------ ----------- fetch buf, send packet receive packet --- ... ... | fetch buf, send packet add buf | ... add buf virtnet_poll buf not enough avail idx-> add buf | read avail idx add buf | add buf --- receive packet --- write event idx ... | wait for kick add buf virtnet_poll ... | --- no more packet, exit NAPI In the first loop of NAPI above, indicated in the range of virtnet_poll above, the host is sending packets while the guest is receiving packets and adding buffers. step 1: The buf is not enough, for example, a big packet needs 5 buf, but the available buf count is 3. The host read current avail idx. step 2: The guest adds some buf, then checks whether the host is waiting for kick signal, not at this time. The used ring is not empty, the guest continues the second loop of NAPI. step 3: The host writes the avail idx read from avail ring to used ring as event idx via virtio_queue_set_notification(q->rx_vq, 1). step 4: At the end of the second loop of NAPI, recheck whether kick is needed, as the event idx in the used ring written by the host is beyound the range of kick condition, the guest will not send kick signal to the host. Fixes: 06b12970174 ("virtio-net: fix network stall under load") Cc: qemu-stable@nongnu.org Signed-off-by: Wencheng Yang Reviewed-by: Michael S. Tsirkin Signed-off-by: Jason Wang --- hw/net/virtio-net.c | 29 +++++++++-------- hw/virtio/virtio.c | 64 +++++++++++++++++++++++++++++++++++--- include/hw/virtio/virtio.h | 18 +++++++++-- 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index 5b0d8145d61..b6574f941ae 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -1640,25 +1640,28 @@ static bool virtio_net_can_receive(NetClientState *nc) static int virtio_net_has_buffers(VirtIONetQueue *q, int bufsize) { + int opaque; + unsigned int in_bytes; VirtIONet *n = q->n; - if (virtio_queue_empty(q->rx_vq) || - (n->mergeable_rx_bufs && - !virtqueue_avail_bytes(q->rx_vq, bufsize, 0))) { - virtio_queue_set_notification(q->rx_vq, 1); - - /* To avoid a race condition where the guest has made some buffers - * available after the above check but before notification was - * enabled, check for available buffers again. - */ - if (virtio_queue_empty(q->rx_vq) || - (n->mergeable_rx_bufs && - !virtqueue_avail_bytes(q->rx_vq, bufsize, 0))) { - virtio_queue_set_notification(q->rx_vq, 1); + + while (virtio_queue_empty(q->rx_vq) || n->mergeable_rx_bufs) { + opaque = virtqueue_get_avail_bytes(q->rx_vq, &in_bytes, NULL, + bufsize, 0); + /* Buffer is enough, disable notifiaction */ + if (bufsize <= in_bytes) { + break; + } + + if (virtio_queue_enable_notification_and_check(q->rx_vq, opaque)) { + /* Guest has added some buffers, try again */ + continue; + } else { return 0; } } virtio_queue_set_notification(q->rx_vq, 0); + return 1; } diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 356d690cc97..09e2de60c10 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -744,6 +744,60 @@ int virtio_queue_empty(VirtQueue *vq) } } +static bool virtio_queue_split_poll(VirtQueue *vq, unsigned shadow_idx) +{ + if (unlikely(!vq->vring.avail)) { + return false; + } + + return (uint16_t)shadow_idx != vring_avail_idx(vq); +} + +static bool virtio_queue_packed_poll(VirtQueue *vq, unsigned shadow_idx) +{ + VRingPackedDesc desc; + VRingMemoryRegionCaches *caches; + + if (unlikely(!vq->vring.desc)) { + return false; + } + + caches = vring_get_region_caches(vq); + if (!caches) { + return false; + } + + vring_packed_desc_read(vq->vdev, &desc, &caches->desc, + shadow_idx, true); + + return is_desc_avail(desc.flags, vq->shadow_avail_wrap_counter); +} + +static bool virtio_queue_poll(VirtQueue *vq, unsigned shadow_idx) +{ + if (virtio_device_disabled(vq->vdev)) { + return false; + } + + if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) { + return virtio_queue_packed_poll(vq, shadow_idx); + } else { + return virtio_queue_split_poll(vq, shadow_idx); + } +} + +bool virtio_queue_enable_notification_and_check(VirtQueue *vq, + int opaque) +{ + virtio_queue_set_notification(vq, 1); + + if (opaque >= 0) { + return virtio_queue_poll(vq, (unsigned)opaque); + } else { + return false; + } +} + static void virtqueue_unmap_sg(VirtQueue *vq, const VirtQueueElement *elem, unsigned int len) { @@ -1323,9 +1377,9 @@ err: goto done; } -void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, - unsigned int *out_bytes, - unsigned max_in_bytes, unsigned max_out_bytes) +int virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, + unsigned int *out_bytes, unsigned max_in_bytes, + unsigned max_out_bytes) { uint16_t desc_size; VRingMemoryRegionCaches *caches; @@ -1358,7 +1412,7 @@ void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, caches); } - return; + return (int)vq->shadow_avail_idx; err: if (in_bytes) { *in_bytes = 0; @@ -1366,6 +1420,8 @@ err: if (out_bytes) { *out_bytes = 0; } + + return -1; } int virtqueue_avail_bytes(VirtQueue *vq, unsigned int in_bytes, diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h index c8f72850bc0..d2f4ed160de 100644 --- a/include/hw/virtio/virtio.h +++ b/include/hw/virtio/virtio.h @@ -270,9 +270,13 @@ void qemu_put_virtqueue_element(VirtIODevice *vdev, QEMUFile *f, VirtQueueElement *elem); int virtqueue_avail_bytes(VirtQueue *vq, unsigned int in_bytes, unsigned int out_bytes); -void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, - unsigned int *out_bytes, - unsigned max_in_bytes, unsigned max_out_bytes); +/** + * Return <0 on error or an opaque >=0 to pass to + * virtio_queue_enable_notification_and_check on success. + */ +int virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes, + unsigned int *out_bytes, unsigned max_in_bytes, + unsigned max_out_bytes); void virtio_notify_irqfd(VirtIODevice *vdev, VirtQueue *vq); void virtio_notify(VirtIODevice *vdev, VirtQueue *vq); @@ -306,6 +310,14 @@ int virtio_queue_ready(VirtQueue *vq); int virtio_queue_empty(VirtQueue *vq); +/** + * Enable notification and check whether guest has added some + * buffers since last call to virtqueue_get_avail_bytes. + * + * @opaque: value returned from virtqueue_get_avail_bytes + */ +bool virtio_queue_enable_notification_and_check(VirtQueue *vq, + int opaque); /* Host binding interface. */ uint32_t virtio_config_readb(VirtIODevice *vdev, uint32_t addr); -- Gitee