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:
Avihai Horon
2026-07-06 11:52:07 +03:00
committed by Cédric Le Goater
parent f4ba2df1d9
commit 5fcadd7d14
4 changed files with 80 additions and 7 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)"

View File

@@ -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