Commit 2240c14c16 for qemu.org

commit 2240c14c16dfc6c3b9840677f0d153165af7d4ee
Author: Pankaj Kumar <pshit@nvidia.com>
Date:   Tue Oct 13 10:15:20 2026 +0530

    vfio/migration: Don't reset a device on a declined stop transition

    QEMU resets a VFIO device whenever a migration state transition it requests
    fails. This is done by vfio_migration_set_state_or_reset(), which passes
    VFIO_DEVICE_STATE_ERROR as the recover state, forcing an immediate
    VFIO_DEVICE_RESET.

    However, a device may legitimately decline a state transition, e.g. a vGPU
    that refuses to stop while a profiling/debug session is active in the guest.
    Such a device is still healthy and running the guest, so resetting it destroys
    a perfectly good VM.

    Instead of resetting on a declined stop, recover the device to its current
    state. The current device state is always a valid, already-reached state, so
    recovering to it is effectively a no-op transition that cannot itself fail and
    trigger a reset.

    Introduce a helper, vfio_migration_set_state_or_recover(), that selects the
    recover state based on the transition direction, and route the vmstate change
    and save-completion paths through it:

     - vfio_vmstate_change() and vfio_vmstate_change_prepare() recover to the
       current device state for the stop direction. The resume direction keeps
       ERROR as the recover state, since a device that cannot resume is genuinely
       broken.

     - vfio_save_complete_precopy() recovers to the current device state rather
       than STOP. If it runs after an earlier stop was declined (the device still
       in a running state), the stop-copy attempt is then rolled back instead of
       resetting the device.

    The existing vfio_migration_set_state_or_reset() is retained for the paths
    that must still reset on failure (save cleanup and the migration-failed
    notifier).

    This lets vendor drivers decline a stop as a matter of policy without losing
    the guest, and lets such drivers drop their own workarounds that swallowed the
    spurious reset.

    Signed-off-by: Pankaj Kumar <pshit@nvidia.com>
    Reviewed-by: Cédric Le Goater <clg@redhat.com>
    Link: https://lore.kernel.org/qemu-devel/20261013044520.2439021-1-pshit@nvidia.com
    Reviewed-by: Avihai Horon <avihaih@nvidia.com>
    Signed-off-by: Cédric Le Goater <clg@redhat.com>

diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c
index 73c49d8c24..69d50cebdd 100644
--- a/hw/vfio/migration.c
+++ b/hw/vfio/migration.c
@@ -246,6 +246,25 @@ vfio_migration_set_state_or_reset(VFIODevice *vbasedev,
                                     VFIO_DEVICE_STATE_ERROR, errp);
 }

+/*
+ * On a stop-direction transition failure, recover the device to its current
+ * state instead of resetting it: the current state is valid and already
+ * reached, so recovering to it is effectively a no-op that cannot itself fail.
+ * On a resume-direction (running) failure, the device is genuinely broken, so
+ * reset it by using ERROR as the recover state.
+ */
+static int
+vfio_migration_set_state_or_recover(VFIODevice *vbasedev,
+                                    enum vfio_device_mig_state new_state,
+                                    bool running, Error **errp)
+{
+    VFIOMigration *migration = vbasedev->migration;
+    enum vfio_device_mig_state recover_state =
+        running ? VFIO_DEVICE_STATE_ERROR : migration->device_state;
+
+    return vfio_migration_set_state(vbasedev, new_state, recover_state, errp);
+}
+
 static int vfio_load_buffer(QEMUFile *f, VFIODevice *vbasedev,
                             uint64_t data_size)
 {
@@ -771,9 +790,16 @@ static int vfio_save_complete_precopy(QEMUFile *f, void *opaque)

     trace_vfio_save_complete_precopy_start(vbasedev->name);

-    /* We reach here with device state STOP or STOP_COPY only */
-    ret = vfio_migration_set_state(vbasedev, VFIO_DEVICE_STATE_STOP_COPY,
-                                   VFIO_DEVICE_STATE_STOP, &local_err);
+    /*
+     * We normally reach here with the device in STOP or STOP_COPY. However, if
+     * an earlier stop transition was declined by the device, it may still be in
+     * a running state. Recover to the current device state rather than STOP, so
+     * a device that declines the stop-copy transition is rolled back to its
+     * current state instead of being reset out from under the guest.
+     */
+    ret = vfio_migration_set_state_or_recover(vbasedev,
+                                              VFIO_DEVICE_STATE_STOP_COPY,
+                                              false, &local_err);
     if (ret) {
         error_report_err(local_err);
         return ret;
@@ -1009,7 +1035,8 @@ static void vfio_vmstate_change_prepare(void *opaque, bool running,
         vfio_final_precopy_reinit_check(vbasedev);
     }

-    ret = vfio_migration_set_state_or_reset(vbasedev, new_state, &local_err);
+    ret = vfio_migration_set_state_or_recover(vbasedev, new_state, running,
+                                              &local_err);
     if (ret) {
         /*
          * Migration should be aborted in this case, but vm_state_notify()
@@ -1040,7 +1067,8 @@ static void vfio_vmstate_change(void *opaque, bool running, RunState state)
                 VFIO_DEVICE_STATE_STOP;
     }

-    ret = vfio_migration_set_state_or_reset(vbasedev, new_state, &local_err);
+    ret = vfio_migration_set_state_or_recover(vbasedev, new_state, running,
+                                              &local_err);
     if (ret) {
         /*
          * Migration should be aborted in this case, but vm_state_notify()