It will segfault when cleanup migration streams before they are setup, to avoid this, use tdx_mig.nr_streams rather than nr_channels. Opportunistically remove the unnecessary parameter "nr_channels" in following 2 callback functions in cgs_mig: cgs_mig.savevm_state_cleanup cgs_mig.loadvm_state_cleanup This fixes LFE-9211 and LFE-9481. Signed-off-by: Lei Wang <lei4.wang@intel.com>
719 lines
21 KiB
C
719 lines
21 KiB
C
/*
|
|
* QEMU Migration for Intel TDX Guests
|
|
*
|
|
* Copyright (C) 2022 Intel Corp.
|
|
*
|
|
* Authors:
|
|
* Wei Wang <wei.w.wang@intel.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 "qemu-file.h"
|
|
#include "cgs.h"
|
|
#include "target/i386/kvm/tdx.h"
|
|
#include "migration/misc.h"
|
|
#include "qemu/error-report.h"
|
|
|
|
/* MBMD, gpa_list and 2 pages of mac_list */
|
|
#define MULTIFD_EXTRA_IOV_NUM 4
|
|
|
|
/* Bytes of the MBMD for memory page, calculated from the spec */
|
|
#define TDX_MBMD_MEM_BYTES 48
|
|
|
|
#define KVM_TDX_MIG_MBMD_TYPE_IMMUTABLE_STATE 0
|
|
#define KVM_TDX_MIG_MBMD_TYPE_TD_STATE 1
|
|
#define KVM_TDX_MIG_MBMD_TYPE_VCPU_STATE 2
|
|
#define KVM_TDX_MIG_MBMD_TYPE_MEMORY_STATE 16
|
|
#define KVM_TDX_MIG_MBMD_TYPE_EPOCH_TOKEN 32
|
|
#define KVM_TDX_MIG_MBMD_TYPE_ABORT_TOKEN 33
|
|
|
|
#define GPA_LIST_OP_EXPORT 1
|
|
#define GPA_LIST_OP_CANCEL 2
|
|
|
|
#define TDX_MIG_F_CONTINUE 0x1
|
|
|
|
typedef struct TdxMigHdr {
|
|
uint16_t flags;
|
|
uint16_t buf_list_num;
|
|
} TdxMigHdr;
|
|
|
|
typedef union GpaListEntry {
|
|
uint64_t val;
|
|
struct {
|
|
uint64_t level:2;
|
|
uint64_t pending:1;
|
|
uint64_t reserved_0:4;
|
|
uint64_t l2_map:3;
|
|
#define GPA_LIST_ENTRY_MIG_TYPE_4KB 0
|
|
uint64_t mig_type:2;
|
|
uint64_t gfn:40;
|
|
uint64_t operation:2;
|
|
uint64_t reserved_1:2;
|
|
uint64_t status:5;
|
|
uint64_t reserved_2:3;
|
|
};
|
|
} GpaListEntry;
|
|
|
|
typedef struct TdxMigStream {
|
|
int fd;
|
|
void *mbmd;
|
|
void *buf_list;
|
|
void *mac_list;
|
|
void *gpa_list;
|
|
} TdxMigStream;
|
|
|
|
typedef struct TdxMigState {
|
|
uint32_t nr_streams;
|
|
TdxMigStream *streams;
|
|
} TdxMigState;
|
|
|
|
TdxMigState tdx_mig;
|
|
|
|
static int tdx_mig_stream_ioctl(TdxMigStream *stream, int cmd_id,
|
|
__u32 metadata, void *data)
|
|
{
|
|
struct kvm_tdx_cmd tdx_cmd;
|
|
int ret;
|
|
|
|
memset(&tdx_cmd, 0x0, sizeof(tdx_cmd));
|
|
|
|
tdx_cmd.id = cmd_id;
|
|
tdx_cmd.flags = metadata;
|
|
tdx_cmd.data = (__u64)(unsigned long)data;
|
|
|
|
ret = kvm_device_ioctl(stream->fd, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd);
|
|
if (ret) {
|
|
error_report("Failed to send migration cmd %d to the driver: %s",
|
|
cmd_id, strerror(ret));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint64_t tdx_mig_put_mig_hdr(QEMUFile *f, uint64_t num, uint16_t flags)
|
|
{
|
|
TdxMigHdr hdr = {
|
|
.flags = flags,
|
|
.buf_list_num = (uint16_t)num,
|
|
};
|
|
|
|
qemu_put_buffer(f, (uint8_t *)&hdr, sizeof(hdr));
|
|
|
|
return sizeof(hdr);
|
|
}
|
|
|
|
static inline uint64_t tdx_mig_stream_get_mbmd_bytes(TdxMigStream *stream)
|
|
{
|
|
/*
|
|
* The first 2 bytes in MBMD buffer tells the overall size of the mbmd
|
|
* data (see TDX module v1.5 ABI spec).
|
|
*/
|
|
uint16_t bytes = *(uint16_t *)stream->mbmd;
|
|
|
|
return (uint64_t)bytes;
|
|
}
|
|
|
|
static uint8_t tdx_mig_stream_get_mbmd_type(TdxMigStream *stream)
|
|
{
|
|
/* TDX module v1.5 ABI spec: MB_TYPE at byte offset 6 */
|
|
return *((uint8_t *)stream->mbmd + 6);
|
|
}
|
|
|
|
static int tdx_mig_savevm_state_start(QEMUFile *f)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[0];
|
|
uint64_t mbmd_bytes, buf_list_bytes, exported_num = 0;
|
|
int ret;
|
|
|
|
/* Export mbmd and buf_list */
|
|
ret = tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_EXPORT_STATE_IMMUTABLE,
|
|
0, &exported_num);
|
|
if (ret) {
|
|
error_report("Failed to export immutable states: %s", strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
mbmd_bytes = tdx_mig_stream_get_mbmd_bytes(stream);
|
|
buf_list_bytes = exported_num * TARGET_PAGE_SIZE;
|
|
|
|
tdx_mig_put_mig_hdr(f, exported_num, 0);
|
|
qemu_put_buffer(f, (uint8_t *)stream->mbmd, mbmd_bytes);
|
|
qemu_put_buffer(f, (uint8_t *)stream->buf_list, buf_list_bytes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long tdx_mig_save_epoch(QEMUFile *f, bool in_order_done)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[0];
|
|
uint64_t flags = in_order_done ? TDX_MIG_EXPORT_TRACK_F_IN_ORDER_DONE : 0;
|
|
long tdx_hdr_bytes, mbmd_bytes;
|
|
int ret;
|
|
|
|
ret = tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_EXPORT_TRACK, 0, &flags);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
mbmd_bytes = tdx_mig_stream_get_mbmd_bytes(stream);
|
|
|
|
/* Epoch only has mbmd data */
|
|
tdx_hdr_bytes = tdx_mig_put_mig_hdr(f, 0, 0);
|
|
qemu_put_buffer(f, (uint8_t *)stream->mbmd, mbmd_bytes);
|
|
|
|
return tdx_hdr_bytes + mbmd_bytes;
|
|
}
|
|
|
|
static long tdx_mig_savevm_state_ram_start_epoch(QEMUFile *f)
|
|
{
|
|
return tdx_mig_save_epoch(f, false);
|
|
}
|
|
|
|
static void tdx_mig_gpa_list_setup(union GpaListEntry *gpa_list, hwaddr *gpa,
|
|
uint64_t gpa_num, int operation)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < gpa_num; i++) {
|
|
gpa_list[i].val = 0;
|
|
gpa_list[i].gfn = gpa[i] >> TARGET_PAGE_BITS;
|
|
gpa_list[i].mig_type = GPA_LIST_ENTRY_MIG_TYPE_4KB;
|
|
gpa_list[i].operation = operation;
|
|
}
|
|
}
|
|
|
|
static long tdx_mig_save_ram(QEMUFile *f, TdxMigStream *stream)
|
|
{
|
|
uint64_t num = 1;
|
|
uint64_t hdr_bytes, mbmd_bytes, gpa_list_bytes,
|
|
buf_list_bytes, mac_list_bytes;
|
|
int ret;
|
|
|
|
/* Export mbmd, buf list, mac list and gpa list */
|
|
ret = tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_EXPORT_MEM, 0, &num);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
mbmd_bytes = tdx_mig_stream_get_mbmd_bytes(stream);
|
|
buf_list_bytes = TARGET_PAGE_SIZE;
|
|
mac_list_bytes = sizeof(Int128);
|
|
gpa_list_bytes = sizeof(GpaListEntry);
|
|
|
|
hdr_bytes = tdx_mig_put_mig_hdr(f, 1, 0);
|
|
qemu_put_buffer(f, (uint8_t *)stream->mbmd, mbmd_bytes);
|
|
qemu_put_buffer(f, (uint8_t *)stream->buf_list, buf_list_bytes);
|
|
qemu_put_buffer(f, (uint8_t *)stream->gpa_list, gpa_list_bytes);
|
|
qemu_put_buffer(f, (uint8_t *)stream->mac_list, mac_list_bytes);
|
|
|
|
return hdr_bytes + mbmd_bytes + gpa_list_bytes +
|
|
buf_list_bytes + mac_list_bytes;
|
|
}
|
|
|
|
static long tdx_mig_savevm_state_ram(QEMUFile *f,
|
|
uint32_t channel_id, hwaddr gpa)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[channel_id];
|
|
|
|
tdx_mig_gpa_list_setup((GpaListEntry *)stream->gpa_list,
|
|
&gpa, 1, GPA_LIST_OP_EXPORT);
|
|
return tdx_mig_save_ram(f, stream);
|
|
}
|
|
|
|
static long tdx_mig_savevm_state_ram_cancel(QEMUFile *f, hwaddr gpa)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[0];
|
|
|
|
tdx_mig_gpa_list_setup((GpaListEntry *)stream->gpa_list, &gpa, 1,
|
|
GPA_LIST_OP_CANCEL);
|
|
return tdx_mig_save_ram(f, stream);
|
|
}
|
|
|
|
static int tdx_mig_savevm_state_pause(void)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[0];
|
|
|
|
return tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_EXPORT_PAUSE, 0, 0);
|
|
}
|
|
|
|
static int tdx_mig_save_td(QEMUFile *f, TdxMigStream *stream)
|
|
{
|
|
int ret;
|
|
uint64_t mbmd_bytes, buf_list_bytes, exported_num = 0;
|
|
|
|
ret = tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_EXPORT_STATE_TD, 0,
|
|
&exported_num);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
mbmd_bytes = tdx_mig_stream_get_mbmd_bytes(stream);
|
|
buf_list_bytes = exported_num * TARGET_PAGE_SIZE;
|
|
|
|
/*
|
|
* The TD-scope states and vCPU states are sent together, so add the
|
|
* CONTINUE flag to have the destination side continue the loading.
|
|
*/
|
|
tdx_mig_put_mig_hdr(f, exported_num, TDX_MIG_F_CONTINUE);
|
|
qemu_put_buffer(f, (uint8_t *)stream->mbmd, mbmd_bytes);
|
|
qemu_put_buffer(f, (uint8_t *)stream->buf_list, buf_list_bytes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tdx_mig_save_one_vcpu(QEMUFile *f, TdxMigStream *stream)
|
|
{
|
|
uint64_t mbmd_bytes, buf_list_bytes, exported_num = 0;
|
|
int ret;
|
|
|
|
ret = tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_EXPORT_STATE_VP, 0,
|
|
&exported_num);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
mbmd_bytes = tdx_mig_stream_get_mbmd_bytes(stream);
|
|
buf_list_bytes = exported_num * TARGET_PAGE_SIZE;
|
|
/* Ask the destination to continue to load the next vCPU states */
|
|
tdx_mig_put_mig_hdr(f, exported_num, TDX_MIG_F_CONTINUE);
|
|
|
|
qemu_put_buffer(f, (uint8_t *)stream->mbmd, mbmd_bytes);
|
|
qemu_put_buffer(f, (uint8_t *)stream->buf_list, buf_list_bytes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tdx_mig_save_vcpus(QEMUFile *f, TdxMigStream *stream)
|
|
{
|
|
CPUState *cpu;
|
|
int ret;
|
|
|
|
CPU_FOREACH(cpu) {
|
|
ret = tdx_mig_save_one_vcpu(f, stream);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tdx_mig_savevm_state_end(QEMUFile *f)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[0];
|
|
int ret;
|
|
|
|
ret = tdx_mig_save_td(f, stream);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = tdx_mig_save_vcpus(f, stream);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = tdx_mig_save_epoch(f, true);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool tdx_mig_is_ready(void)
|
|
{
|
|
return tdx_premig_is_done();
|
|
}
|
|
|
|
static int tdx_mig_stream_create(TdxMigStream *stream)
|
|
{
|
|
int ret;
|
|
|
|
ret = kvm_create_device(kvm_state, KVM_DEV_TYPE_TDX_MIG_STREAM, false);
|
|
if (ret < 0) {
|
|
error_report("Failed to create stream due to %s", strerror(errno));
|
|
return ret;
|
|
}
|
|
stream->fd = ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tdx_mig_do_stream_setup(TdxMigStream *stream, uint32_t nr_pages)
|
|
{
|
|
int ret;
|
|
struct kvm_dev_tdx_mig_attr tdx_mig_attr;
|
|
struct kvm_device_attr attr = {
|
|
.group = KVM_DEV_TDX_MIG_ATTR,
|
|
.addr = (uint64_t)&tdx_mig_attr,
|
|
.attr = sizeof(struct kvm_dev_tdx_mig_attr),
|
|
};
|
|
size_t map_size;
|
|
off_t map_offset;
|
|
|
|
ret = tdx_mig_stream_create(stream);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Tell the tdx_mig driver the number of pages to add to buffer list for
|
|
* TD private page export/import. Currently, TD private pages are migrated
|
|
* one by one.
|
|
*/
|
|
tdx_mig_attr.buf_list_pages = nr_pages;
|
|
tdx_mig_attr.version = KVM_DEV_TDX_MIG_ATTR_VERSION;
|
|
if (kvm_device_ioctl(stream->fd, KVM_SET_DEVICE_ATTR, &attr) < 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
memset(&tdx_mig_attr, 0, sizeof(struct kvm_dev_tdx_mig_attr));
|
|
tdx_mig_attr.version = KVM_DEV_TDX_MIG_ATTR_VERSION;
|
|
if (kvm_device_ioctl(stream->fd, KVM_GET_DEVICE_ATTR, &attr) < 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
map_offset = TDX_MIG_STREAM_MBMD_MAP_OFFSET;
|
|
map_size = (TDX_MIG_STREAM_GPA_LIST_MAP_OFFSET -
|
|
TDX_MIG_STREAM_MBMD_MAP_OFFSET) * TARGET_PAGE_SIZE;
|
|
stream->mbmd = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
stream->fd, map_offset);
|
|
if (stream->mbmd == MAP_FAILED) {
|
|
ret = -errno;
|
|
error_report("Failed to map mbmd due to %s", strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
map_offset = TDX_MIG_STREAM_GPA_LIST_MAP_OFFSET * TARGET_PAGE_SIZE;
|
|
map_size = (TDX_MIG_STREAM_MAC_LIST_MAP_OFFSET -
|
|
TDX_MIG_STREAM_GPA_LIST_MAP_OFFSET) * TARGET_PAGE_SIZE;
|
|
stream->gpa_list = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
stream->fd, map_offset);
|
|
if (stream->gpa_list == MAP_FAILED) {
|
|
ret = -errno;
|
|
error_report("Failed to map gpa list due to %s", strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
map_offset = TDX_MIG_STREAM_MAC_LIST_MAP_OFFSET * TARGET_PAGE_SIZE;
|
|
map_size = (TDX_MIG_STREAM_BUF_LIST_MAP_OFFSET -
|
|
TDX_MIG_STREAM_MAC_LIST_MAP_OFFSET) * TARGET_PAGE_SIZE;
|
|
stream->mac_list = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
stream->fd, map_offset);
|
|
if (stream->mac_list == MAP_FAILED) {
|
|
ret = -errno;
|
|
error_report("Failed to map mac list due to %s", strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
map_offset = TDX_MIG_STREAM_BUF_LIST_MAP_OFFSET * TARGET_PAGE_SIZE;
|
|
map_size = tdx_mig_attr.buf_list_pages * TARGET_PAGE_SIZE;
|
|
stream->buf_list = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
stream->fd, map_offset);
|
|
if (stream->buf_list == MAP_FAILED) {
|
|
ret = -errno;
|
|
error_report("Failed to map buf list due to %s", strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tdx_mig_stream_setup(uint32_t nr_channels, uint32_t nr_pages)
|
|
{
|
|
TdxMigStream *stream;
|
|
int i, ret;
|
|
|
|
tdx_mig.streams = g_malloc0(sizeof(struct TdxMigStream) * nr_channels);
|
|
|
|
for (i = 0; i < nr_channels; i++) {
|
|
stream = &tdx_mig.streams[i];
|
|
ret = tdx_mig_do_stream_setup(stream, nr_pages);
|
|
if (!ret) {
|
|
tdx_mig.nr_streams++;
|
|
} else {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tdx_mig_stream_cleanup(TdxMigStream *stream)
|
|
{
|
|
struct kvm_dev_tdx_mig_attr tdx_mig_attr;
|
|
struct kvm_device_attr attr = {
|
|
.group = KVM_DEV_TDX_MIG_ATTR,
|
|
.addr = (uint64_t)&tdx_mig_attr,
|
|
.attr = sizeof(struct kvm_dev_tdx_mig_attr),
|
|
};
|
|
size_t unmap_size;
|
|
int ret;
|
|
|
|
memset(&tdx_mig_attr, 0, sizeof(struct kvm_dev_tdx_mig_attr));
|
|
ret = kvm_device_ioctl(stream->fd, KVM_GET_DEVICE_ATTR, &attr);
|
|
if (ret < 0) {
|
|
error_report("tdx mig cleanup failed: %s", strerror(ret));
|
|
return;
|
|
}
|
|
|
|
unmap_size = (TDX_MIG_STREAM_GPA_LIST_MAP_OFFSET -
|
|
TDX_MIG_STREAM_MBMD_MAP_OFFSET) * TARGET_PAGE_SIZE;
|
|
munmap(stream->mbmd, unmap_size);
|
|
|
|
unmap_size = (TDX_MIG_STREAM_MAC_LIST_MAP_OFFSET -
|
|
TDX_MIG_STREAM_GPA_LIST_MAP_OFFSET) * TARGET_PAGE_SIZE;
|
|
munmap(stream->gpa_list, unmap_size);
|
|
|
|
unmap_size = (TDX_MIG_STREAM_BUF_LIST_MAP_OFFSET -
|
|
TDX_MIG_STREAM_MAC_LIST_MAP_OFFSET) * TARGET_PAGE_SIZE;
|
|
munmap(stream->mac_list, unmap_size);
|
|
|
|
unmap_size = tdx_mig_attr.buf_list_pages * TARGET_PAGE_SIZE;
|
|
munmap(stream->buf_list, unmap_size);
|
|
close(stream->fd);
|
|
}
|
|
|
|
static void tdx_mig_cleanup(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < tdx_mig.nr_streams; i++) {
|
|
tdx_mig_stream_cleanup(&tdx_mig.streams[i]);
|
|
}
|
|
|
|
tdx_mig.nr_streams = 0;
|
|
|
|
g_free(tdx_mig.streams);
|
|
tdx_mig.streams = NULL;
|
|
}
|
|
|
|
static void tdx_mig_loadvm_state_cleanup(void)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[0];
|
|
|
|
tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_IMPORT_END, 0, 0);
|
|
tdx_mig_cleanup();
|
|
}
|
|
|
|
static int tdx_mig_savevm_state_ram_abort(hwaddr gfn_end)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[0];
|
|
int ret;
|
|
|
|
/* No page has been exported yet. */
|
|
if (!gfn_end) {
|
|
return 0;
|
|
}
|
|
|
|
ret = tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_EXPORT_ABORT, 0, &gfn_end);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tdx_mig_loadvm_state(QEMUFile *f, uint32_t channel_id)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[channel_id];
|
|
uint64_t mbmd_bytes, buf_list_bytes, mac_list_bytes, gpa_list_bytes;
|
|
uint64_t buf_list_num = 0;
|
|
bool should_continue = true;
|
|
uint8_t mbmd_type;
|
|
int ret, cmd_id;
|
|
TdxMigHdr hdr;
|
|
|
|
while (should_continue) {
|
|
if (should_continue && qemu_peek_le16(f, sizeof(hdr)) == 0) {
|
|
continue;
|
|
}
|
|
|
|
qemu_get_buffer(f, (uint8_t *)&hdr, sizeof(hdr));
|
|
mbmd_bytes = qemu_peek_le16(f, 0);
|
|
qemu_get_buffer(f, (uint8_t *)stream->mbmd, mbmd_bytes);
|
|
mbmd_type = tdx_mig_stream_get_mbmd_type(stream);
|
|
|
|
buf_list_num = hdr.buf_list_num;
|
|
buf_list_bytes = buf_list_num * TARGET_PAGE_SIZE;
|
|
if (buf_list_num) {
|
|
qemu_get_buffer(f, (uint8_t *)stream->buf_list, buf_list_bytes);
|
|
}
|
|
|
|
switch (mbmd_type) {
|
|
case KVM_TDX_MIG_MBMD_TYPE_IMMUTABLE_STATE:
|
|
cmd_id = KVM_TDX_MIG_IMPORT_STATE_IMMUTABLE;
|
|
break;
|
|
case KVM_TDX_MIG_MBMD_TYPE_MEMORY_STATE:
|
|
cmd_id = KVM_TDX_MIG_IMPORT_MEM;
|
|
mac_list_bytes = buf_list_num * sizeof(Int128);
|
|
gpa_list_bytes = buf_list_num * sizeof(GpaListEntry);
|
|
qemu_get_buffer(f, (uint8_t *)stream->gpa_list, gpa_list_bytes);
|
|
qemu_get_buffer(f, (uint8_t *)stream->mac_list, mac_list_bytes);
|
|
break;
|
|
case KVM_TDX_MIG_MBMD_TYPE_EPOCH_TOKEN:
|
|
cmd_id = KVM_TDX_MIG_IMPORT_TRACK;
|
|
break;
|
|
case KVM_TDX_MIG_MBMD_TYPE_TD_STATE:
|
|
cmd_id = KVM_TDX_MIG_IMPORT_STATE_TD;
|
|
break;
|
|
case KVM_TDX_MIG_MBMD_TYPE_VCPU_STATE:
|
|
cmd_id = KVM_TDX_MIG_IMPORT_STATE_VP;
|
|
break;
|
|
default:
|
|
error_report("%s: unsupported mb_type %d", __func__, mbmd_type);
|
|
return -1;
|
|
}
|
|
|
|
ret = tdx_mig_stream_ioctl(stream, cmd_id, 0, &buf_list_num);
|
|
|
|
if (ret) {
|
|
if (buf_list_num != 0) {
|
|
error_report("%s: buf_list_num=%lx", __func__, buf_list_num);
|
|
}
|
|
break;
|
|
}
|
|
should_continue = hdr.flags & TDX_MIG_F_CONTINUE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t tdx_mig_iov_num(uint32_t page_batch_num)
|
|
{
|
|
uint32_t iov_num;
|
|
|
|
/* The TDX migration architecture supports up to 512 pages */
|
|
if (page_batch_num > 512) {
|
|
error_report("%u is larger than the max (512)", page_batch_num);
|
|
return 0;
|
|
}
|
|
|
|
if (page_batch_num > 256) {
|
|
iov_num = MULTIFD_EXTRA_IOV_NUM + page_batch_num;
|
|
} else {
|
|
/* One less MAC page is used */
|
|
iov_num = MULTIFD_EXTRA_IOV_NUM - 1 + page_batch_num;
|
|
}
|
|
|
|
return iov_num;
|
|
}
|
|
|
|
static int tdx_mig_multifd_send_prepare(MultiFDSendParams *p, Error **errp)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[p->id];
|
|
MultiFDPages_t *pages = p->pages;
|
|
uint32_t i, iovs_num = p->iovs_num, packet_size = 0;
|
|
uint64_t page_num = pages->num;
|
|
int ret;
|
|
|
|
tdx_mig_gpa_list_setup((GpaListEntry *)stream->gpa_list,
|
|
pages->private_gpa, page_num, GPA_LIST_OP_EXPORT);
|
|
|
|
ret = tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_EXPORT_MEM, 0, &page_num);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
page_num = pages->num;
|
|
|
|
/* MBMD */
|
|
p->iov[iovs_num].iov_base = stream->mbmd;
|
|
p->iov[iovs_num++].iov_len = TDX_MBMD_MEM_BYTES;
|
|
packet_size = TDX_MBMD_MEM_BYTES;
|
|
|
|
/* GPA list */
|
|
p->iov[iovs_num].iov_base = stream->gpa_list;
|
|
p->iov[iovs_num].iov_len = sizeof(GpaListEntry) * page_num;
|
|
packet_size += p->iov[iovs_num++].iov_len;
|
|
|
|
/* TODO: check if there is a 2nd MAC list */
|
|
/* MAC list */
|
|
p->iov[iovs_num].iov_base = stream->mac_list;
|
|
p->iov[iovs_num].iov_len = sizeof(Int128) * page_num;
|
|
packet_size += p->iov[iovs_num++].iov_len;
|
|
|
|
/* Buffer list */
|
|
for (i = 0; i < page_num; i++) {
|
|
p->iov[iovs_num].iov_base = stream->buf_list + TARGET_PAGE_SIZE * i;
|
|
p->iov[iovs_num].iov_len = TARGET_PAGE_SIZE;
|
|
packet_size += p->iov[iovs_num++].iov_len;
|
|
}
|
|
|
|
p->iovs_num = iovs_num;
|
|
p->next_packet_size = packet_size;
|
|
return 0;
|
|
}
|
|
|
|
static int tdx_mig_multifd_recv_pages(MultiFDRecvParams *p, Error **errp)
|
|
{
|
|
TdxMigStream *stream = &tdx_mig.streams[p->id];
|
|
uint32_t i, iovs_num = 0;
|
|
uint64_t gfn_num = p->normal_num;
|
|
uint8_t mbmd_type;
|
|
int ret;
|
|
|
|
/* MBMD */
|
|
p->iov[iovs_num].iov_base = stream->mbmd;
|
|
p->iov[iovs_num++].iov_len = TDX_MBMD_MEM_BYTES;
|
|
|
|
/* GPA list */
|
|
p->iov[iovs_num].iov_base = stream->gpa_list;
|
|
p->iov[iovs_num++].iov_len = sizeof(GpaListEntry) * gfn_num;
|
|
|
|
/* MAC list */
|
|
p->iov[iovs_num].iov_base = stream->mac_list;
|
|
p->iov[iovs_num++].iov_len = sizeof(Int128) * gfn_num;
|
|
|
|
/* Buffer list */
|
|
for (i = 0; i < gfn_num; i++) {
|
|
p->iov[iovs_num].iov_base = stream->buf_list + TARGET_PAGE_SIZE * i;
|
|
p->iov[iovs_num++].iov_len = TARGET_PAGE_SIZE;
|
|
}
|
|
|
|
ret = qio_channel_readv_all(p->c, p->iov, iovs_num, errp);
|
|
if (ret) {
|
|
error_report("%s: channel read: %s\n", __func__, strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
mbmd_type = tdx_mig_stream_get_mbmd_type(stream);
|
|
if (mbmd_type != KVM_TDX_MIG_MBMD_TYPE_MEMORY_STATE) {
|
|
error_report("%s: packet received wrong, mbmd=%d\n", __func__, mbmd_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = tdx_mig_stream_ioctl(stream, KVM_TDX_MIG_IMPORT_MEM, 0, &gfn_num);
|
|
if (ret) {
|
|
error_report("%s failed: %s, gfn_num=%lu\n",
|
|
__func__, strerror(ret), gfn_num);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void tdx_mig_init(CgsMig *cgs_mig)
|
|
{
|
|
cgs_mig->is_ready = tdx_mig_is_ready;
|
|
cgs_mig->savevm_state_setup = tdx_mig_stream_setup;
|
|
cgs_mig->savevm_state_start = tdx_mig_savevm_state_start;
|
|
cgs_mig->savevm_state_ram_start_epoch =
|
|
tdx_mig_savevm_state_ram_start_epoch;
|
|
cgs_mig->savevm_state_ram = tdx_mig_savevm_state_ram;
|
|
cgs_mig->savevm_state_pause = tdx_mig_savevm_state_pause;
|
|
cgs_mig->savevm_state_end = tdx_mig_savevm_state_end;
|
|
cgs_mig->savevm_state_cleanup = tdx_mig_cleanup;
|
|
cgs_mig->savevm_state_ram_abort = tdx_mig_savevm_state_ram_abort;
|
|
cgs_mig->savevm_state_ram_cancel = tdx_mig_savevm_state_ram_cancel;
|
|
cgs_mig->loadvm_state_setup = tdx_mig_stream_setup;
|
|
cgs_mig->loadvm_state = tdx_mig_loadvm_state;
|
|
cgs_mig->loadvm_state_cleanup = tdx_mig_loadvm_state_cleanup;
|
|
cgs_mig->multifd_send_prepare = tdx_mig_multifd_send_prepare;
|
|
cgs_mig->multifd_recv_pages = tdx_mig_multifd_recv_pages;
|
|
cgs_mig->iov_num = tdx_mig_iov_num;
|
|
}
|