mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:10 +00:00
vfio/migration: Implement VFIO_PRECOPY_INFO_REINIT feature
According to VFIO uAPI, precopy initial_bytes is considered as critical data that should be transferred and loaded prior to moving to STOP_COPY state to ensure precopy phase would be effective. As currently defined, initial_bytes can only decrease as it's being read from the data fd. However, there are cases where a new chunk of initial_bytes should be transferred during precopy. The new VFIO_PRECOPY_INFO_REINIT feature addresses this and allows reporting a new value for initial_bytes regardless of any previously reported values. Implement VFIO_PRECOPY_INFO_REINIT feature: 1. Opt-in for VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 to make VFIO_PRECOPY_INFO_REINIT available. 2. Request a new switchover ACK if initial_bytes increases post of a previous switchover ACK. This ensures the device is not moved to STOP_COPY before initial_bytes has reached zero again. Acked-by: Peter Xu <peterx@redhat.com> Signed-off-by: Avihai Horon <avihaih@nvidia.com> Link: https://lore.kernel.org/qemu-devel/20260706085211.13905-13-avihaih@nvidia.com Signed-off-by: Cédric Le Goater <clg@redhat.com>
This commit is contained in:
committed by
Cédric Le Goater
parent
f4ba2df1d9
commit
5fcadd7d14
@@ -23,6 +23,20 @@ and recommends that the initial bytes are sent and loaded in the destination
|
||||
before stopping the source VM. Enabling this migration capability will
|
||||
guarantee that and thus, can potentially reduce downtime even further.
|
||||
|
||||
For example, in mlx5 devices, the initial bytes hold metadata used for time
|
||||
consuming pre-allocations of resources on the destination. Although init bytes
|
||||
may be small in size and sending them may take little time, loading them in the
|
||||
destination can take a significant amount of time. Switchover-ack guarantees
|
||||
that this pre-allocation doesn't happen during downtime.
|
||||
|
||||
Initial bytes was originally defined to be monotonically decreasing, however
|
||||
there are cases where a new chunk of initial bytes should be transferred during
|
||||
precopy, e.g., due to a device reconfiguration, etc. The
|
||||
VFIO_PRECOPY_INFO_REINIT feature addresses this and when supported, allows to
|
||||
report a new initial bytes value regardless of any previously reported values.
|
||||
In this case, a new switchover ACK will be requested to make sure the new
|
||||
initial bytes are loaded in the destination before switching over.
|
||||
|
||||
To support migration of multiple devices that might do P2P transactions between
|
||||
themselves, VFIO migration uAPI defines an intermediate P2P quiescent state.
|
||||
While in the P2P quiescent state, P2P DMA transactions cannot be initiated by
|
||||
|
||||
@@ -373,9 +373,11 @@ static int vfio_query_stop_copy_size(VFIODevice *vbasedev)
|
||||
|
||||
static int vfio_query_precopy_size(VFIOMigration *migration)
|
||||
{
|
||||
VFIODevice *vbasedev = migration->vbasedev;
|
||||
struct vfio_precopy_info precopy = {
|
||||
.argsz = sizeof(precopy),
|
||||
};
|
||||
bool reinit = false;
|
||||
int ret = 0;
|
||||
|
||||
if (ioctl(migration->data_fd, VFIO_MIG_GET_PRECOPY_INFO, &precopy)) {
|
||||
@@ -383,25 +385,43 @@ static int vfio_query_precopy_size(VFIOMigration *migration)
|
||||
migration->precopy_dirty_size = 0;
|
||||
ret = -errno;
|
||||
warn_report_once("VFIO device %s ioctl(VFIO_MIG_GET_PRECOPY_INFO) "
|
||||
"failed (%d)", migration->vbasedev->name, ret);
|
||||
"failed (%d)", vbasedev->name, ret);
|
||||
} else {
|
||||
bool overflow;
|
||||
|
||||
migration->precopy_init_size = precopy.initial_bytes;
|
||||
migration->precopy_dirty_size = precopy.dirty_bytes;
|
||||
/*
|
||||
* struct vfio_precopy_info.flags is valid only if
|
||||
* VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 is used.
|
||||
*/
|
||||
if (migration->precopy_info_v2_used) {
|
||||
reinit = precopy.flags & VFIO_PRECOPY_INFO_REINIT;
|
||||
}
|
||||
|
||||
overflow = vfio_migration_check_overflow(migration->vbasedev,
|
||||
overflow = vfio_migration_check_overflow(vbasedev,
|
||||
migration->precopy_init_size, "precopy init size");
|
||||
overflow |= vfio_migration_check_overflow(migration->vbasedev,
|
||||
overflow |= vfio_migration_check_overflow(vbasedev,
|
||||
migration->precopy_dirty_size, "precopy dirty size");
|
||||
if (overflow) {
|
||||
ret = -ERANGE;
|
||||
}
|
||||
}
|
||||
|
||||
trace_vfio_query_precopy_size(migration->vbasedev->name,
|
||||
migration->precopy_init_size,
|
||||
migration->precopy_dirty_size, ret);
|
||||
trace_vfio_query_precopy_size(vbasedev->name, migration->precopy_init_size,
|
||||
migration->precopy_dirty_size, reinit, ret);
|
||||
|
||||
/*
|
||||
* If we got new initial_bytes after previous initial_bytes were
|
||||
* transferred, request a new switchover ACK. Don't request if legacy
|
||||
* switchover-ack is used.
|
||||
*/
|
||||
if (reinit && migration->initial_data_sent &&
|
||||
!migrate_switchover_ack_legacy()) {
|
||||
migration->initial_data_sent = false;
|
||||
migration->request_switchover_ack = true;
|
||||
trace_vfio_query_precopy_size_request_switchover_ack(vbasedev->name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1054,6 +1074,27 @@ static int vfio_migration_query_flags(VFIODevice *vbasedev, uint64_t *mig_flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns 1 on success, 0 if not supported and negative errno on failure */
|
||||
static int vfio_migration_set_precopy_info_v2(VFIODevice *vbasedev)
|
||||
{
|
||||
uint64_t buf[DIV_ROUND_UP(sizeof(struct vfio_device_feature),
|
||||
sizeof(uint64_t))] = {};
|
||||
struct vfio_device_feature *feature = (struct vfio_device_feature *)buf;
|
||||
|
||||
feature->argsz = sizeof(buf);
|
||||
feature->flags =
|
||||
VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2;
|
||||
if (ioctl(vbasedev->fd, VFIO_DEVICE_FEATURE, feature)) {
|
||||
if (errno == ENOTTY) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool vfio_dma_logging_supported(VFIODevice *vbasedev)
|
||||
{
|
||||
uint64_t buf[DIV_ROUND_UP(sizeof(struct vfio_device_feature),
|
||||
@@ -1075,6 +1116,7 @@ static bool vfio_migration_init(VFIODevice *vbasedev, Error **errp)
|
||||
char id[256] = "";
|
||||
g_autofree char *path = NULL, *oid = NULL;
|
||||
uint64_t mig_flags = 0;
|
||||
bool precopy_info_v2_used = false;
|
||||
VMChangeStateHandler *prepare_cb;
|
||||
|
||||
if (!vbasedev->ops->vfio_get_object) {
|
||||
@@ -1106,12 +1148,22 @@ static bool vfio_migration_init(VFIODevice *vbasedev, Error **errp)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mig_flags & VFIO_MIGRATION_PRE_COPY) {
|
||||
ret = vfio_migration_set_precopy_info_v2(vbasedev);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "failed to set precopy info v2");
|
||||
return false;
|
||||
}
|
||||
precopy_info_v2_used = ret;
|
||||
}
|
||||
|
||||
vbasedev->migration = g_new0(VFIOMigration, 1);
|
||||
migration = vbasedev->migration;
|
||||
migration->vbasedev = vbasedev;
|
||||
migration->device_state = VFIO_DEVICE_STATE_RUNNING;
|
||||
migration->data_fd = -1;
|
||||
migration->mig_flags = mig_flags;
|
||||
migration->precopy_info_v2_used = precopy_info_v2_used;
|
||||
|
||||
vbasedev->dirty_pages_supported = vfio_dma_logging_supported(vbasedev);
|
||||
|
||||
@@ -1134,6 +1186,10 @@ static bool vfio_migration_init(VFIODevice *vbasedev, Error **errp)
|
||||
migration_add_notifier(&migration->migration_state,
|
||||
vfio_migration_state_notifier);
|
||||
|
||||
trace_vfio_migration_init(vbasedev->name, migration->mig_flags,
|
||||
migration->precopy_info_v2_used,
|
||||
vbasedev->dirty_pages_supported);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,11 +159,13 @@ vfio_load_state_device_buffer_starved(const char *name, uint32_t idx) " (%s) idx
|
||||
vfio_load_state_device_buffer_load_start(const char *name, uint32_t idx) " (%s) idx %"PRIu32
|
||||
vfio_load_state_device_buffer_load_end(const char *name, uint32_t idx) " (%s) idx %"PRIu32
|
||||
vfio_load_state_device_buffer_end(const char *name) " (%s)"
|
||||
vfio_migration_init(const char *name, uint64_t mig_flags, bool precopy_info_v2_used, bool dirty_pages_supported) " (%s) mig_flags 0x%"PRIx64", precopy_info_v2_used %d, dirty_pages_supported %d"
|
||||
vfio_migration_realize(const char *name) " (%s)"
|
||||
vfio_migration_set_device_state(const char *name, const char *state) " (%s) state %s"
|
||||
vfio_migration_set_state(const char *name, const char *new_state, const char *recover_state) " (%s) new state %s, recover state %s"
|
||||
vfio_migration_state_notifier(const char *name, int state) " (%s) state %d"
|
||||
vfio_query_precopy_size(const char *name, uint64_t init_size, uint64_t dirty_size, int ret) " (%s) init %"PRIu64" dirty %"PRIu64" ret %d"
|
||||
vfio_query_precopy_size(const char *name, uint64_t init_size, uint64_t dirty_size, bool reinit, int ret) " (%s) init %"PRIu64", dirty %"PRIu64", reinit %d, ret %d"
|
||||
vfio_query_precopy_size_request_switchover_ack(const char *name) " (%s)"
|
||||
vfio_query_stop_copy_size(const char *name, uint64_t size, int ret) " (%s) stopcopy size %"PRIu64" ret %d"
|
||||
vfio_save_block(const char *name, int data_size) " (%s) data_size %d"
|
||||
vfio_save_block_precopy_empty_hit(const char *name) " (%s)"
|
||||
|
||||
@@ -45,6 +45,7 @@ typedef struct VFIOMigration {
|
||||
void *data_buffer;
|
||||
size_t data_buffer_size;
|
||||
uint64_t mig_flags;
|
||||
bool precopy_info_v2_used;
|
||||
/*
|
||||
* NOTE: all three sizes cached are reported from VFIO's uAPI, which
|
||||
* are defined as estimate only. QEMU should not trust these values
|
||||
|
||||
Reference in New Issue
Block a user