From 13bd2629b78f528b0b4684a643f59d30b7274aa8 Mon Sep 17 00:00:00 2001 From: jiangxin Date: Fri, 17 Jun 2022 09:37:56 +0800 Subject: [PATCH] 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 | 182 +++++++++++++++++++++++++++++++++++++++ target/i386/csv.h | 22 +++++ target/i386/sev.c | 14 ++- target/i386/sev.h | 1 + target/i386/trace-events | 1 + 6 files changed, 306 insertions(+), 1 deletion(-) diff --git a/migration/ram.c b/migration/ram.c index 1377b9eb37..1f9348fd06 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -2480,6 +2480,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 * @@ -2515,6 +2599,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 e4706efa27..22e709a95c 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,166 @@ 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; + + 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 12733341b3..12c1b22659 100644 --- a/target/i386/csv.h +++ b/target/i386/csv.h @@ -81,6 +81,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; @@ -89,11 +101,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); @@ -101,5 +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_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 0012a5efb0..5a96b0b452 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -1270,7 +1270,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 */ @@ -2654,9 +2658,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 e91431e0f7..8ccef22a95 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 34c205ffda..a4a58b12a1 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%" PRIx64 +kvm_csv3_send_encrypt_data(void *dst, int len) "trans %p len %d" -- 2.41.0.windows.1