| /* |
| * Multifd RAM migration without compression |
| * |
| * Copyright (c) 2019-2020 Red Hat Inc |
| * |
| * Authors: |
| * Juan Quintela <quintela@redhat.com> |
| * |
| * 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 "exec/ramblock.h" |
| #include "exec/target_page.h" |
| #include "file.h" |
| #include "multifd.h" |
| #include "options.h" |
| #include "qapi/error.h" |
| #include "qemu/cutils.h" |
| #include "qemu/error-report.h" |
| #include "trace.h" |
| |
| static MultiFDSendData *multifd_ram_send; |
| |
| size_t multifd_ram_payload_size(void) |
| { |
| uint32_t n = multifd_ram_page_count(); |
| |
| /* |
| * We keep an array of page offsets at the end of MultiFDPages_t, |
| * add space for it in the allocation. |
| */ |
| return sizeof(MultiFDPages_t) + n * sizeof(ram_addr_t); |
| } |
| |
| void multifd_ram_save_setup(void) |
| { |
| multifd_ram_send = multifd_send_data_alloc(); |
| } |
| |
| void multifd_ram_save_cleanup(void) |
| { |
| g_free(multifd_ram_send); |
| multifd_ram_send = NULL; |
| } |
| |
| static void multifd_set_file_bitmap(MultiFDSendParams *p) |
| { |
| MultiFDPages_t *pages = &p->data->u.ram; |
| |
| assert(pages->block); |
| |
| for (int i = 0; i < pages->normal_num; i++) { |
| ramblock_set_file_bmap_atomic(pages->block, pages->offset[i], true); |
| } |
| |
| for (int i = pages->normal_num; i < pages->num; i++) { |
| ramblock_set_file_bmap_atomic(pages->block, pages->offset[i], false); |
| } |
| } |
| |
| static int multifd_nocomp_send_setup(MultiFDSendParams *p, Error **errp) |
| { |
| uint32_t page_count = multifd_ram_page_count(); |
| |
| if (migrate_zero_copy_send()) { |
| p->write_flags |= QIO_CHANNEL_WRITE_FLAG_ZERO_COPY; |
| } |
| |
| if (!migrate_mapped_ram()) { |
| /* We need one extra place for the packet header */ |
| p->iov = g_new0(struct iovec, page_count + 1); |
| } else { |
| p->iov = g_new0(struct iovec, page_count); |
| } |
| |
| return 0; |
| } |
| |
| static void multifd_nocomp_send_cleanup(MultiFDSendParams *p, Error **errp) |
| { |
| g_free(p->iov); |
| p->iov = NULL; |
| return; |
| } |
| |
| static void multifd_send_prepare_iovs(MultiFDSendParams *p) |
| { |
| MultiFDPages_t *pages = &p->data->u.ram; |
| uint32_t page_size = multifd_ram_page_size(); |
| |
| for (int i = 0; i < pages->normal_num; i++) { |
| p->iov[p->iovs_num].iov_base = pages->block->host + pages->offset[i]; |
| p->iov[p->iovs_num].iov_len = page_size; |
| p->iovs_num++; |
| } |
| |
| p->next_packet_size = pages->normal_num * page_size; |
| } |
| |
| static int multifd_nocomp_send_prepare(MultiFDSendParams *p, Error **errp) |
| { |
| bool use_zero_copy_send = migrate_zero_copy_send(); |
| int ret; |
| |
| multifd_send_zero_page_detect(p); |
| |
| if (migrate_mapped_ram()) { |
| multifd_send_prepare_iovs(p); |
| multifd_set_file_bitmap(p); |
| |
| return 0; |
| } |
| |
| if (!use_zero_copy_send) { |
| /* |
| * Only !zerocopy needs the header in IOV; zerocopy will |
| * send it separately. |
| */ |
| multifd_send_prepare_header(p); |
| } |
| |
| multifd_send_prepare_iovs(p); |
| p->flags |= MULTIFD_FLAG_NOCOMP; |
| |
| multifd_send_fill_packet(p); |
| |
| if (use_zero_copy_send) { |
| /* Send header first, without zerocopy */ |
| ret = qio_channel_write_all(p->c, (void *)p->packet, |
| p->packet_len, errp); |
| if (ret != 0) { |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int multifd_nocomp_recv_setup(MultiFDRecvParams *p, Error **errp) |
| { |
| p->iov = g_new0(struct iovec, multifd_ram_page_count()); |
| return 0; |
| } |
| |
| static void multifd_nocomp_recv_cleanup(MultiFDRecvParams *p) |
| { |
| g_free(p->iov); |
| p->iov = NULL; |
| } |
| |
| static int multifd_nocomp_recv(MultiFDRecvParams *p, Error **errp) |
| { |
| uint32_t flags; |
| |
| if (migrate_mapped_ram()) { |
| return multifd_file_recv_data(p, errp); |
| } |
| |
| flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK; |
| |
| if (flags != MULTIFD_FLAG_NOCOMP) { |
| error_setg(errp, "multifd %u: flags received %x flags expected %x", |
| p->id, flags, MULTIFD_FLAG_NOCOMP); |
| return -1; |
| } |
| |
| multifd_recv_zero_page_process(p); |
| |
| if (!p->normal_num) { |
| return 0; |
| } |
| |
| for (int i = 0; i < p->normal_num; i++) { |
| p->iov[i].iov_base = p->host + p->normal[i]; |
| p->iov[i].iov_len = multifd_ram_page_size(); |
| ramblock_recv_bitmap_set_offset(p->block, p->normal[i]); |
| } |
| return qio_channel_readv_all(p->c, p->iov, p->normal_num, errp); |
| } |
| |
| static void multifd_pages_reset(MultiFDPages_t *pages) |
| { |
| /* |
| * We don't need to touch offset[] array, because it will be |
| * overwritten later when reused. |
| */ |
| pages->num = 0; |
| pages->normal_num = 0; |
| pages->block = NULL; |
| } |
| |
| void multifd_ram_fill_packet(MultiFDSendParams *p) |
| { |
| MultiFDPacket_t *packet = p->packet; |
| MultiFDPages_t *pages = &p->data->u.ram; |
| uint32_t zero_num = pages->num - pages->normal_num; |
| |
| packet->pages_alloc = cpu_to_be32(multifd_ram_page_count()); |
| packet->normal_pages = cpu_to_be32(pages->normal_num); |
| packet->zero_pages = cpu_to_be32(zero_num); |
| |
| if (pages->block) { |
| pstrcpy(packet->ramblock, sizeof(packet->ramblock), |
| pages->block->idstr); |
| } |
| |
| for (int i = 0; i < pages->num; i++) { |
| /* there are architectures where ram_addr_t is 32 bit */ |
| uint64_t temp = pages->offset[i]; |
| |
| packet->offset[i] = cpu_to_be64(temp); |
| } |
| |
| trace_multifd_send_ram_fill(p->id, pages->normal_num, |
| zero_num); |
| } |
| |
| int multifd_ram_unfill_packet(MultiFDRecvParams *p, Error **errp) |
| { |
| MultiFDPacket_t *packet = p->packet; |
| uint32_t page_count = multifd_ram_page_count(); |
| uint32_t page_size = multifd_ram_page_size(); |
| uint32_t pages_per_packet = be32_to_cpu(packet->pages_alloc); |
| int i; |
| |
| if (pages_per_packet > page_count) { |
| error_setg(errp, "multifd: received packet with %u pages, expected %u", |
| pages_per_packet, page_count); |
| return -1; |
| } |
| |
| p->normal_num = be32_to_cpu(packet->normal_pages); |
| if (p->normal_num > pages_per_packet) { |
| error_setg(errp, "multifd: received packet with %u non-zero pages, " |
| "which exceeds maximum expected pages %u", |
| p->normal_num, pages_per_packet); |
| return -1; |
| } |
| |
| p->zero_num = be32_to_cpu(packet->zero_pages); |
| if (p->zero_num > pages_per_packet - p->normal_num) { |
| error_setg(errp, |
| "multifd: received packet with %u zero pages, expected maximum %u", |
| p->zero_num, pages_per_packet - p->normal_num); |
| return -1; |
| } |
| |
| if (p->normal_num == 0 && p->zero_num == 0) { |
| return 0; |
| } |
| |
| /* make sure that ramblock is 0 terminated */ |
| packet->ramblock[255] = 0; |
| p->block = qemu_ram_block_by_name(packet->ramblock); |
| if (!p->block) { |
| error_setg(errp, "multifd: unknown ram block %s", |
| packet->ramblock); |
| return -1; |
| } |
| |
| p->host = p->block->host; |
| for (i = 0; i < p->normal_num; i++) { |
| uint64_t offset = be64_to_cpu(packet->offset[i]); |
| |
| if (offset > (p->block->used_length - page_size)) { |
| error_setg(errp, "multifd: offset too long %" PRIu64 |
| " (max " RAM_ADDR_FMT ")", |
| offset, p->block->used_length); |
| return -1; |
| } |
| p->normal[i] = offset; |
| } |
| |
| for (i = 0; i < p->zero_num; i++) { |
| uint64_t offset = be64_to_cpu(packet->offset[p->normal_num + i]); |
| |
| if (offset > (p->block->used_length - page_size)) { |
| error_setg(errp, "multifd: offset too long %" PRIu64 |
| " (max " RAM_ADDR_FMT ")", |
| offset, p->block->used_length); |
| return -1; |
| } |
| p->zero[i] = offset; |
| } |
| |
| return 0; |
| } |
| |
| static inline bool multifd_queue_empty(MultiFDPages_t *pages) |
| { |
| return pages->num == 0; |
| } |
| |
| static inline bool multifd_queue_full(MultiFDPages_t *pages) |
| { |
| return pages->num == multifd_ram_page_count(); |
| } |
| |
| static inline void multifd_enqueue(MultiFDPages_t *pages, ram_addr_t offset) |
| { |
| pages->offset[pages->num++] = offset; |
| } |
| |
| /* Returns true if enqueue successful, false otherwise */ |
| bool multifd_queue_page(RAMBlock *block, ram_addr_t offset) |
| { |
| MultiFDPages_t *pages; |
| |
| retry: |
| pages = &multifd_ram_send->u.ram; |
| |
| if (multifd_payload_empty(multifd_ram_send)) { |
| multifd_pages_reset(pages); |
| multifd_set_payload_type(multifd_ram_send, MULTIFD_PAYLOAD_RAM); |
| } |
| |
| /* If the queue is empty, we can already enqueue now */ |
| if (multifd_queue_empty(pages)) { |
| pages->block = block; |
| multifd_enqueue(pages, offset); |
| return true; |
| } |
| |
| /* |
| * Not empty, meanwhile we need a flush. It can because of either: |
| * |
| * (1) The page is not on the same ramblock of previous ones, or, |
| * (2) The queue is full. |
| * |
| * After flush, always retry. |
| */ |
| if (pages->block != block || multifd_queue_full(pages)) { |
| if (!multifd_send(&multifd_ram_send)) { |
| return false; |
| } |
| goto retry; |
| } |
| |
| /* Not empty, and we still have space, do it! */ |
| multifd_enqueue(pages, offset); |
| return true; |
| } |
| |
| int multifd_ram_flush_and_sync(void) |
| { |
| if (!migrate_multifd()) { |
| return 0; |
| } |
| |
| if (!multifd_payload_empty(multifd_ram_send)) { |
| if (!multifd_send(&multifd_ram_send)) { |
| error_report("%s: multifd_send fail", __func__); |
| return -1; |
| } |
| } |
| |
| return multifd_send_sync_main(); |
| } |
| |
| bool multifd_send_prepare_common(MultiFDSendParams *p) |
| { |
| MultiFDPages_t *pages = &p->data->u.ram; |
| multifd_send_zero_page_detect(p); |
| |
| if (!pages->normal_num) { |
| p->next_packet_size = 0; |
| return false; |
| } |
| |
| multifd_send_prepare_header(p); |
| |
| return true; |
| } |
| |
| static const MultiFDMethods multifd_nocomp_ops = { |
| .send_setup = multifd_nocomp_send_setup, |
| .send_cleanup = multifd_nocomp_send_cleanup, |
| .send_prepare = multifd_nocomp_send_prepare, |
| .recv_setup = multifd_nocomp_recv_setup, |
| .recv_cleanup = multifd_nocomp_recv_cleanup, |
| .recv = multifd_nocomp_recv |
| }; |
| |
| static void multifd_nocomp_register(void) |
| { |
| multifd_register_ops(MULTIFD_COMPRESSION_NONE, &multifd_nocomp_ops); |
| } |
| |
| migration_init(multifd_nocomp_register); |