[PATCH v2 19/25] vhost: support backend-transfer migration

Vladimir Sementsov-Ogievskiy posted 25 patches 4 weeks, 1 day ago
[PATCH v2 19/25] vhost: support backend-transfer migration
Posted by Vladimir Sementsov-Ogievskiy 4 weeks, 1 day ago
Introduce vhost_dev.backend_transfer field,

Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
---
 hw/virtio/vhost.c         | 121 +++++++++++++++++++++++++++++++++-----
 include/hw/virtio/vhost.h |   7 +++
 2 files changed, 113 insertions(+), 15 deletions(-)

diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c
index 63036f8214..c46203eb9c 100644
--- a/hw/virtio/vhost.c
+++ b/hw/virtio/vhost.c
@@ -1325,6 +1325,8 @@ out:
     return ret;
 }
 
+static void vhost_virtqueue_error_notifier(EventNotifier *n);
+
 int vhost_virtqueue_start(struct vhost_dev *dev,
                           struct VirtIODevice *vdev,
                           struct vhost_virtqueue *vq,
@@ -1350,7 +1352,13 @@ int vhost_virtqueue_start(struct vhost_dev *dev,
         return r;
     }
 
-    vq->num = state.num = virtio_queue_get_num(vdev, idx);
+    vq->num = virtio_queue_get_num(vdev, idx);
+
+    if (dev->backend_transfer) {
+        return 0;
+    }
+
+    state.num = vq->num;
     r = dev->vhost_ops->vhost_set_vring_num(dev, &state);
     if (r) {
         VHOST_OPS_DEBUG(r, "vhost_set_vring_num failed");
@@ -1428,6 +1436,10 @@ static int do_vhost_virtqueue_stop(struct vhost_dev *dev,
 
     trace_vhost_virtque_stop_in(dev, vdev->name, idx);
 
+    if (dev->backend_transfer) {
+        return 0;
+    }
+
     if (virtio_queue_get_desc_addr(vdev, idx) == 0) {
         /* Don't stop the virtqueue which might have not been started */
         return 0;
@@ -1565,10 +1577,14 @@ fail_call:
 
 static void vhost_virtqueue_cleanup(struct vhost_virtqueue *vq)
 {
-    event_notifier_cleanup(&vq->masked_notifier);
+    if (!vq->dev->backend_transfer) {
+        event_notifier_cleanup(&vq->masked_notifier);
+    }
     if (vq->dev->vhost_ops->vhost_set_vring_err) {
         event_notifier_set_handler(&vq->error_notifier, NULL);
-        event_notifier_cleanup(&vq->error_notifier);
+        if (!vq->dev->backend_transfer) {
+            event_notifier_cleanup(&vq->error_notifier);
+        }
     }
 }
 
@@ -1635,6 +1651,7 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
 
     hdev->vdev = NULL;
     hdev->migration_blocker = NULL;
+    hdev->_features_wait_incoming = true;
     hdev->busyloop_timeout = busyloop_timeout;
 
     for (i = 0; i < hdev->nvqs; ++i) {
@@ -1717,6 +1734,8 @@ int vhost_dev_connect(struct vhost_dev *hdev, Error **errp)
         goto fail;
     }
 
+    hdev->_features_wait_incoming = false;
+
     for (i = 0; i < hdev->nvqs; ++i, ++n_initialized_vqs) {
         r = vhost_virtqueue_connect(hdev->vqs + i, hdev->vq_index + i);
         if (r < 0) {
@@ -1808,8 +1827,11 @@ void vhost_dev_disable_notifiers_nvqs(struct vhost_dev *hdev,
      */
     memory_region_transaction_commit();
 
-    for (i = 0; i < nvqs; ++i) {
-        virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), hdev->vq_index + i);
+    if (!hdev->backend_transfer) {
+        for (i = 0; i < nvqs; ++i) {
+            virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus),
+                                             hdev->vq_index + i);
+        }
     }
     virtio_device_release_ioeventfd(vdev);
 }
@@ -1967,6 +1989,11 @@ void vhost_get_features_ex(struct vhost_dev *hdev,
 {
     const int *bit = feature_bits;
 
+    if (hdev->_features_wait_incoming) {
+        /* Excessive set is enough for early initialization. */
+        return;
+    }
+
     while (*bit != VHOST_INVALID_FEATURE_BIT) {
         if (!vhost_dev_has_feature_ex(hdev, *bit)) {
             virtio_clear_feature_ex(features, *bit);
@@ -2001,6 +2028,54 @@ const VMStateDescription vmstate_backend_transfer_vhost_inflight = {
     }
 };
 
+const VMStateDescription vmstate_vhost_virtqueue = {
+    .name = "vhost-virtqueue",
+    .fields = (const VMStateField[]) {
+        VMSTATE_EVENT_NOTIFIER(error_notifier, struct vhost_virtqueue),
+        VMSTATE_EVENT_NOTIFIER(masked_notifier, struct vhost_virtqueue),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static int vhost_dev_post_load(void *opaque, int version_id)
+{
+    struct vhost_dev *hdev = opaque;
+    Error *err = NULL;
+    int i;
+
+    if (!check_memslots(hdev, &err)) {
+        error_report_err(err);
+        return -EINVAL;
+    }
+
+    hdev->_features_wait_incoming = false;
+
+    if (hdev->vhost_ops->vhost_set_vring_err) {
+        for (i = 0; i < hdev->nvqs; ++i) {
+            event_notifier_set_handler(&hdev->vqs[i].error_notifier,
+                                       vhost_virtqueue_error_notifier);
+        }
+    }
+
+
+    return 0;
+}
+
+const VMStateDescription vmstate_vhost_dev = {
+    .name = "vhost-dev",
+    .post_load = vhost_dev_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT64(_features, struct vhost_dev),
+        VMSTATE_UINT64(max_queues, struct vhost_dev),
+        VMSTATE_UINT32_EQUAL(nvqs, struct vhost_dev, NULL),
+        VMSTATE_STRUCT_VARRAY_POINTER_UINT32(vqs, struct vhost_dev,
+                                             nvqs,
+                                             vmstate_vhost_virtqueue,
+                                             struct vhost_virtqueue),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
 void vhost_ack_features_ex(struct vhost_dev *hdev, const int *feature_bits,
                            const uint64_t *features)
 {
@@ -2127,19 +2202,24 @@ int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings)
     hdev->started = true;
     hdev->vdev = vdev;
 
-    r = vhost_dev_set_features(hdev, hdev->log_enabled);
-    if (r < 0) {
-        goto fail_features;
+    if (!hdev->backend_transfer) {
+        r = vhost_dev_set_features(hdev, hdev->log_enabled);
+        if (r < 0) {
+            warn_report("%s %d", __func__, __LINE__);
+            goto fail_features;
+        }
     }
 
     if (vhost_dev_has_iommu(hdev)) {
         memory_listener_register(&hdev->iommu_listener, vdev->dma_as);
     }
 
-    r = hdev->vhost_ops->vhost_set_mem_table(hdev, hdev->mem);
-    if (r < 0) {
-        VHOST_OPS_DEBUG(r, "vhost_set_mem_table failed");
-        goto fail_mem;
+    if (!hdev->backend_transfer) {
+        r = hdev->vhost_ops->vhost_set_mem_table(hdev, hdev->mem);
+        if (r < 0) {
+            VHOST_OPS_DEBUG(r, "vhost_set_mem_table failed");
+            goto fail_mem;
+        }
     }
     for (i = 0; i < hdev->nvqs; ++i) {
         r = vhost_virtqueue_start(hdev,
@@ -2179,13 +2259,13 @@ int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings)
         }
         vhost_dev_elect_mem_logger(hdev, true);
     }
-    if (vrings) {
+    if (vrings && !hdev->backend_transfer) {
         r = vhost_dev_set_vring_enable(hdev, true);
         if (r) {
             goto fail_log;
         }
     }
-    if (hdev->vhost_ops->vhost_dev_start) {
+    if (hdev->vhost_ops->vhost_dev_start && !hdev->backend_transfer) {
         r = hdev->vhost_ops->vhost_dev_start(hdev, true);
         if (r) {
             goto fail_start;
@@ -2207,6 +2287,8 @@ int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings)
     }
     vhost_start_config_intr(hdev);
 
+    hdev->backend_transfer = false;
+
     trace_vhost_dev_start_out(hdev, vdev->name);
     return 0;
 fail_iotlb:
@@ -2262,9 +2344,18 @@ static int do_vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev,
     if (hdev->vhost_ops->vhost_dev_start) {
         hdev->vhost_ops->vhost_dev_start(hdev, false);
     }
-    if (vrings) {
+    if (vrings && !hdev->backend_transfer) {
         vhost_dev_set_vring_enable(hdev, false);
     }
+
+    if (hdev->backend_transfer) {
+        for (i = 0; i < hdev->nvqs; ++i) {
+            struct vhost_virtqueue *vq = hdev->vqs + i;
+
+            event_notifier_set_handler(&vq->error_notifier, NULL);
+        }
+    }
+
     for (i = 0; i < hdev->nvqs; ++i) {
         rc |= do_vhost_virtqueue_stop(hdev,
                                       vdev,
diff --git a/include/hw/virtio/vhost.h b/include/hw/virtio/vhost.h
index 94a0c75fc8..55ad822848 100644
--- a/include/hw/virtio/vhost.h
+++ b/include/hw/virtio/vhost.h
@@ -105,6 +105,9 @@ struct vhost_dev {
     VIRTIO_DECLARE_FEATURES(_features);
     VIRTIO_DECLARE_FEATURES(acked_features);
 
+    bool _features_wait_incoming;
+    bool backend_transfer;
+
     uint32_t busyloop_timeout;
     uint64_t max_queues;
     uint64_t backend_cap;
@@ -592,4 +595,8 @@ extern const VMStateDescription vmstate_backend_transfer_vhost_inflight;
     VMSTATE_STRUCT_POINTER(_field, _state, vmstate_inflight, \
                            struct vhost_inflight)
 
+extern const VMStateDescription vmstate_vhost_dev;
+#define VMSTATE_BACKEND_TRANSFER_VHOST(_field, _state) \
+    VMSTATE_STRUCT(_field, _state, 0, vmstate_vhost_dev, struct vhost_dev)
+
 #endif
-- 
2.48.1
Re: [PATCH v2 19/25] vhost: support backend-transfer migration
Posted by Raphael Norwitz 3 weeks, 3 days ago
Overall looks good. Just a nit.

Acked-by: Raphael Norwitz <raphael.s.norwitz@gmail.com>

On Thu, Oct 16, 2025 at 7:44 AM Vladimir Sementsov-Ogievskiy
<vsementsov@yandex-team.ru> wrote:
>
> Introduce vhost_dev.backend_transfer field,
>
> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
> ---
>  hw/virtio/vhost.c         | 121 +++++++++++++++++++++++++++++++++-----
>  include/hw/virtio/vhost.h |   7 +++
>  2 files changed, 113 insertions(+), 15 deletions(-)
>
> diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c
> index 63036f8214..c46203eb9c 100644
> --- a/hw/virtio/vhost.c
> +++ b/hw/virtio/vhost.c
> @@ -1325,6 +1325,8 @@ out:
>      return ret;
>  }
>
> +static void vhost_virtqueue_error_notifier(EventNotifier *n);
> +
>  int vhost_virtqueue_start(struct vhost_dev *dev,
>                            struct VirtIODevice *vdev,
>                            struct vhost_virtqueue *vq,
> @@ -1350,7 +1352,13 @@ int vhost_virtqueue_start(struct vhost_dev *dev,
>          return r;
>      }
>
> -    vq->num = state.num = virtio_queue_get_num(vdev, idx);
> +    vq->num = virtio_queue_get_num(vdev, idx);
> +
> +    if (dev->backend_transfer) {
> +        return 0;
> +    }
> +
> +    state.num = vq->num;
>      r = dev->vhost_ops->vhost_set_vring_num(dev, &state);
>      if (r) {
>          VHOST_OPS_DEBUG(r, "vhost_set_vring_num failed");
> @@ -1428,6 +1436,10 @@ static int do_vhost_virtqueue_stop(struct vhost_dev *dev,
>
>      trace_vhost_virtque_stop_in(dev, vdev->name, idx);
>
> +    if (dev->backend_transfer) {
> +        return 0;
> +    }
> +
>      if (virtio_queue_get_desc_addr(vdev, idx) == 0) {
>          /* Don't stop the virtqueue which might have not been started */
>          return 0;
> @@ -1565,10 +1577,14 @@ fail_call:
>
>  static void vhost_virtqueue_cleanup(struct vhost_virtqueue *vq)
>  {
> -    event_notifier_cleanup(&vq->masked_notifier);
> +    if (!vq->dev->backend_transfer) {
> +        event_notifier_cleanup(&vq->masked_notifier);
> +    }
>      if (vq->dev->vhost_ops->vhost_set_vring_err) {
>          event_notifier_set_handler(&vq->error_notifier, NULL);
> -        event_notifier_cleanup(&vq->error_notifier);
> +        if (!vq->dev->backend_transfer) {
> +            event_notifier_cleanup(&vq->error_notifier);
> +        }
>      }
>  }
>
> @@ -1635,6 +1651,7 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
>
>      hdev->vdev = NULL;
>      hdev->migration_blocker = NULL;
> +    hdev->_features_wait_incoming = true;
>      hdev->busyloop_timeout = busyloop_timeout;
>
>      for (i = 0; i < hdev->nvqs; ++i) {
> @@ -1717,6 +1734,8 @@ int vhost_dev_connect(struct vhost_dev *hdev, Error **errp)
>          goto fail;
>      }
>
> +    hdev->_features_wait_incoming = false;
> +
>      for (i = 0; i < hdev->nvqs; ++i, ++n_initialized_vqs) {
>          r = vhost_virtqueue_connect(hdev->vqs + i, hdev->vq_index + i);
>          if (r < 0) {
> @@ -1808,8 +1827,11 @@ void vhost_dev_disable_notifiers_nvqs(struct vhost_dev *hdev,
>       */
>      memory_region_transaction_commit();
>
> -    for (i = 0; i < nvqs; ++i) {
> -        virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), hdev->vq_index + i);
> +    if (!hdev->backend_transfer) {
> +        for (i = 0; i < nvqs; ++i) {
> +            virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus),
> +                                             hdev->vq_index + i);
> +        }
>      }
>      virtio_device_release_ioeventfd(vdev);
>  }
> @@ -1967,6 +1989,11 @@ void vhost_get_features_ex(struct vhost_dev *hdev,
>  {
>      const int *bit = feature_bits;
>
> +    if (hdev->_features_wait_incoming) {
> +        /* Excessive set is enough for early initialization. */
> +        return;
> +    }
> +
>      while (*bit != VHOST_INVALID_FEATURE_BIT) {
>          if (!vhost_dev_has_feature_ex(hdev, *bit)) {
>              virtio_clear_feature_ex(features, *bit);
> @@ -2001,6 +2028,54 @@ const VMStateDescription vmstate_backend_transfer_vhost_inflight = {
>      }
>  };
>
> +const VMStateDescription vmstate_vhost_virtqueue = {
> +    .name = "vhost-virtqueue",
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_EVENT_NOTIFIER(error_notifier, struct vhost_virtqueue),
> +        VMSTATE_EVENT_NOTIFIER(masked_notifier, struct vhost_virtqueue),
> +        VMSTATE_END_OF_LIST()
> +    },
> +};
> +
> +static int vhost_dev_post_load(void *opaque, int version_id)
> +{
> +    struct vhost_dev *hdev = opaque;
> +    Error *err = NULL;
> +    int i;
> +
> +    if (!check_memslots(hdev, &err)) {
> +        error_report_err(err);
> +        return -EINVAL;
> +    }
> +
> +    hdev->_features_wait_incoming = false;
> +
> +    if (hdev->vhost_ops->vhost_set_vring_err) {
> +        for (i = 0; i < hdev->nvqs; ++i) {
> +            event_notifier_set_handler(&hdev->vqs[i].error_notifier,
> +                                       vhost_virtqueue_error_notifier);
> +        }
> +    }
> +

nit: spurious newline

> +
> +    return 0;
> +}
> +
> +const VMStateDescription vmstate_vhost_dev = {
> +    .name = "vhost-dev",
> +    .post_load = vhost_dev_post_load,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_UINT64(_features, struct vhost_dev),
> +        VMSTATE_UINT64(max_queues, struct vhost_dev),
> +        VMSTATE_UINT32_EQUAL(nvqs, struct vhost_dev, NULL),
> +        VMSTATE_STRUCT_VARRAY_POINTER_UINT32(vqs, struct vhost_dev,
> +                                             nvqs,
> +                                             vmstate_vhost_virtqueue,
> +                                             struct vhost_virtqueue),
> +        VMSTATE_END_OF_LIST()
> +    },
> +};
> +
>  void vhost_ack_features_ex(struct vhost_dev *hdev, const int *feature_bits,
>                             const uint64_t *features)
>  {
> @@ -2127,19 +2202,24 @@ int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings)
>      hdev->started = true;
>      hdev->vdev = vdev;
>
> -    r = vhost_dev_set_features(hdev, hdev->log_enabled);
> -    if (r < 0) {
> -        goto fail_features;
> +    if (!hdev->backend_transfer) {
> +        r = vhost_dev_set_features(hdev, hdev->log_enabled);
> +        if (r < 0) {
> +            warn_report("%s %d", __func__, __LINE__);
> +            goto fail_features;
> +        }
>      }
>
>      if (vhost_dev_has_iommu(hdev)) {
>          memory_listener_register(&hdev->iommu_listener, vdev->dma_as);
>      }
>
> -    r = hdev->vhost_ops->vhost_set_mem_table(hdev, hdev->mem);
> -    if (r < 0) {
> -        VHOST_OPS_DEBUG(r, "vhost_set_mem_table failed");
> -        goto fail_mem;
> +    if (!hdev->backend_transfer) {
> +        r = hdev->vhost_ops->vhost_set_mem_table(hdev, hdev->mem);
> +        if (r < 0) {
> +            VHOST_OPS_DEBUG(r, "vhost_set_mem_table failed");
> +            goto fail_mem;
> +        }
>      }
>      for (i = 0; i < hdev->nvqs; ++i) {
>          r = vhost_virtqueue_start(hdev,
> @@ -2179,13 +2259,13 @@ int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings)
>          }
>          vhost_dev_elect_mem_logger(hdev, true);
>      }
> -    if (vrings) {
> +    if (vrings && !hdev->backend_transfer) {
>          r = vhost_dev_set_vring_enable(hdev, true);
>          if (r) {
>              goto fail_log;
>          }
>      }
> -    if (hdev->vhost_ops->vhost_dev_start) {
> +    if (hdev->vhost_ops->vhost_dev_start && !hdev->backend_transfer) {
>          r = hdev->vhost_ops->vhost_dev_start(hdev, true);
>          if (r) {
>              goto fail_start;
> @@ -2207,6 +2287,8 @@ int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings)
>      }
>      vhost_start_config_intr(hdev);
>
> +    hdev->backend_transfer = false;
> +
>      trace_vhost_dev_start_out(hdev, vdev->name);
>      return 0;
>  fail_iotlb:
> @@ -2262,9 +2344,18 @@ static int do_vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev,
>      if (hdev->vhost_ops->vhost_dev_start) {
>          hdev->vhost_ops->vhost_dev_start(hdev, false);
>      }
> -    if (vrings) {
> +    if (vrings && !hdev->backend_transfer) {
>          vhost_dev_set_vring_enable(hdev, false);
>      }
> +
> +    if (hdev->backend_transfer) {
> +        for (i = 0; i < hdev->nvqs; ++i) {
> +            struct vhost_virtqueue *vq = hdev->vqs + i;
> +
> +            event_notifier_set_handler(&vq->error_notifier, NULL);
> +        }
> +    }
> +
>      for (i = 0; i < hdev->nvqs; ++i) {
>          rc |= do_vhost_virtqueue_stop(hdev,
>                                        vdev,
> diff --git a/include/hw/virtio/vhost.h b/include/hw/virtio/vhost.h
> index 94a0c75fc8..55ad822848 100644
> --- a/include/hw/virtio/vhost.h
> +++ b/include/hw/virtio/vhost.h
> @@ -105,6 +105,9 @@ struct vhost_dev {
>      VIRTIO_DECLARE_FEATURES(_features);
>      VIRTIO_DECLARE_FEATURES(acked_features);
>
> +    bool _features_wait_incoming;
> +    bool backend_transfer;
> +
>      uint32_t busyloop_timeout;
>      uint64_t max_queues;
>      uint64_t backend_cap;
> @@ -592,4 +595,8 @@ extern const VMStateDescription vmstate_backend_transfer_vhost_inflight;
>      VMSTATE_STRUCT_POINTER(_field, _state, vmstate_inflight, \
>                             struct vhost_inflight)
>
> +extern const VMStateDescription vmstate_vhost_dev;
> +#define VMSTATE_BACKEND_TRANSFER_VHOST(_field, _state) \
> +    VMSTATE_STRUCT(_field, _state, 0, vmstate_vhost_dev, struct vhost_dev)
> +
>  #endif
> --
> 2.48.1
>
>