Implement backend-transfer support for virtio-net device.
How it works:
1. Use DEFINE_NIC_PROPERTIES_NO_CONNECT(), so that backend
not being connected during netdev property setting.
2. If we are not in incoming migration, just call
net_backend_connect() in _realize()
3. If we are in incoming migration, postpone backend connect up
to pre-incoming. At this point we check migration parameters,
and if backend-transfer is NOT enabled for this virtio-net
device, do net_backend_connect(). Otherwise - do noting,
live backend will come in migration stream.
4. During virtio-load, we get backend state as part of virtio-net
state
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
---
hw/net/virtio-net.c | 157 ++++++++++++++++++++++++++++++++-
include/hw/virtio/virtio-net.h | 1 +
2 files changed, 156 insertions(+), 2 deletions(-)
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index 17ed0ef919..94e41c225a 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -38,8 +38,10 @@
#include "qapi/qapi-events-migration.h"
#include "hw/virtio/virtio-access.h"
#include "migration/misc.h"
+#include "migration/options.h"
#include "standard-headers/linux/ethtool.h"
#include "system/system.h"
+#include "system/runstate.h"
#include "system/replay.h"
#include "trace.h"
#include "monitor/qdev.h"
@@ -3060,7 +3062,17 @@ static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue)
n->multiqueue = multiqueue;
virtio_net_change_num_queues(n, max * 2 + 1);
- virtio_net_set_queue_pairs(n);
+ /*
+ * virtio_net_set_multiqueue() called from set_features(0) on early
+ * reset, when peer may wait for incoming (and is not initialized
+ * yet).
+ * Don't worry about it: virtio_net_set_queue_pairs() will be called
+ * later form virtio_net_post_load_device(), and anyway will be
+ * noop for local incoming migration with live backend passing.
+ */
+ if (!n->peers_wait_incoming) {
+ virtio_net_set_queue_pairs(n);
+ }
}
static int virtio_net_pre_load_queues(VirtIODevice *vdev, uint32_t n)
@@ -3089,6 +3101,17 @@ static void virtio_net_get_features(VirtIODevice *vdev, uint64_t *features,
virtio_add_feature_ex(features, VIRTIO_NET_F_MAC);
+ if (n->peers_wait_incoming) {
+ /*
+ * Excessive feature set is OK for early initialization when
+ * we wait for local incoming migration: actual guest-negotiated
+ * features will come with migration stream anyway. And we are sure
+ * that we support same host-features as source, because the backend
+ * is the same (the same TAP device, for example).
+ */
+ return;
+ }
+
if (!peer_has_vnet_hdr(n)) {
virtio_clear_feature_ex(features, VIRTIO_NET_F_CSUM);
virtio_clear_feature_ex(features, VIRTIO_NET_F_HOST_TSO4);
@@ -3180,6 +3203,18 @@ static void virtio_net_get_features(VirtIODevice *vdev, uint64_t *features,
}
}
+static bool virtio_net_update_host_features(VirtIONet *n, Error **errp)
+{
+ ERRP_GUARD();
+ VirtIODevice *vdev = VIRTIO_DEVICE(n);
+
+ peer_test_vnet_hdr(n);
+
+ virtio_net_get_features(vdev, &vdev->host_features, errp);
+
+ return !*errp;
+}
+
static int virtio_net_post_load_device(void *opaque, int version_id)
{
VirtIONet *n = opaque;
@@ -3301,6 +3336,9 @@ struct VirtIONetMigTmp {
uint16_t curr_queue_pairs_1;
uint8_t has_ufo;
uint32_t has_vnet_hdr;
+
+ NetClientState *ncs;
+ uint32_t max_queue_pairs;
};
/* The 2nd and subsequent tx_waiting flags are loaded later than
@@ -3570,6 +3608,57 @@ static const VMStateDescription vhost_user_net_backend_state = {
}
};
+static bool virtio_net_is_backend_transfer(void *opaque, int version_id)
+{
+ VirtIONet *n = opaque;
+
+ return migrate_backend_transfer(DEVICE(n));
+}
+
+static int virtio_net_nic_pre_save(void *opaque)
+{
+ struct VirtIONetMigTmp *tmp = opaque;
+
+ tmp->ncs = tmp->parent->nic->ncs;
+ tmp->max_queue_pairs = tmp->parent->max_queue_pairs;
+
+ return 0;
+}
+
+static int virtio_net_nic_pre_load(void *opaque)
+{
+ /* Reuse the pointer setup from save */
+ virtio_net_nic_pre_save(opaque);
+
+ return 0;
+}
+
+static int virtio_net_nic_post_load(void *opaque, int version_id)
+{
+ struct VirtIONetMigTmp *tmp = opaque;
+ Error *local_err = NULL;
+
+ if (!virtio_net_update_host_features(tmp->parent, &local_err)) {
+ error_report_err(local_err);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_virtio_net_nic = {
+ .name = "virtio-net-nic",
+ .pre_load = virtio_net_nic_pre_load,
+ .pre_save = virtio_net_nic_pre_save,
+ .post_load = virtio_net_nic_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_VARRAY_UINT32(ncs, struct VirtIONetMigTmp,
+ max_queue_pairs, 0, vmstate_net_peer_backend,
+ NetClientState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
static const VMStateDescription vmstate_virtio_net_device = {
.name = "virtio-net-device",
.version_id = VIRTIO_NET_VM_VERSION,
@@ -3602,6 +3691,9 @@ static const VMStateDescription vmstate_virtio_net_device = {
*/
VMSTATE_BUFFER_UNSAFE(vlans, VirtIONet, 0,
sizeof(typeof_field(VirtIONet, vlans))),
+ VMSTATE_WITH_TMP_TEST(VirtIONet, virtio_net_is_backend_transfer,
+ struct VirtIONetMigTmp,
+ vmstate_virtio_net_nic),
VMSTATE_WITH_TMP(VirtIONet, struct VirtIONetMigTmp,
vmstate_virtio_net_has_vnet),
VMSTATE_UINT8(mac_table.multi_overflow, VirtIONet),
@@ -4003,6 +4095,20 @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp)
n->nic->ncs[i].do_not_pad = true;
}
+ if (runstate_check(RUN_STATE_INMIGRATE)) {
+ n->peers_wait_incoming = true;
+ } else {
+ for (i = 0; i < n->max_queue_pairs; i++) {
+ nc = qemu_get_subqueue(n->nic, i);
+ if (!nc->peer) {
+ continue;
+ }
+ if (!net_backend_connect(nc->peer, errp)) {
+ return;
+ }
+ }
+ }
+
peer_test_vnet_hdr(n);
if (peer_has_vnet_hdr(n)) {
n->host_hdr_len = sizeof(struct virtio_net_hdr);
@@ -4176,6 +4282,30 @@ static bool dev_unplug_pending(void *opaque)
return vdc->primary_unplug_pending(dev);
}
+static bool vhost_user_blk_pre_incoming(void *opaque, Error **errp)
+{
+ VirtIONet *n = opaque;
+ int i;
+
+ if (!virtio_net_is_backend_transfer(opaque, 0) && n->peers_wait_incoming) {
+ for (i = 0; i < n->max_queue_pairs; i++) {
+ NetClientState *nc = qemu_get_subqueue(n->nic, i);
+ if (!nc->peer) {
+ continue;
+ }
+ if (!net_backend_connect(nc->peer, errp)) {
+ return false;
+ }
+ }
+
+ n->peers_wait_incoming = false;
+
+ return virtio_net_update_host_features(n, errp);
+ }
+
+ return true;
+}
+
static const VMStateDescription vmstate_virtio_net = {
.name = "virtio-net",
.minimum_version_id = VIRTIO_NET_VM_VERSION,
@@ -4184,6 +4314,7 @@ static const VMStateDescription vmstate_virtio_net = {
VMSTATE_VIRTIO_DEVICE,
VMSTATE_END_OF_LIST()
},
+ .pre_incoming = vhost_user_blk_pre_incoming,
.pre_save = virtio_net_pre_save,
.dev_unplug_pending = dev_unplug_pending,
};
@@ -4239,7 +4370,7 @@ static const Property virtio_net_properties[] = {
VIRTIO_NET_F_RSC_EXT, false),
DEFINE_PROP_UINT32("rsc_interval", VirtIONet, rsc_timeout,
VIRTIO_NET_RSC_DEFAULT_INTERVAL),
- DEFINE_NIC_PROPERTIES(VirtIONet, nic_conf),
+ DEFINE_NIC_PROPERTIES_NO_CONNECT(VirtIONet, nic_conf),
DEFINE_PROP_UINT32("x-txtimer", VirtIONet, net_conf.txtimer,
TX_TIMER_INTERVAL),
DEFINE_PROP_INT32("x-txburst", VirtIONet, net_conf.txburst, TX_BURST),
@@ -4314,6 +4445,27 @@ static const Property virtio_net_properties[] = {
false),
};
+static bool virtio_net_backend_transfer_support(DeviceState *dev, Error **errp)
+{
+ VirtIONet *n = VIRTIO_NET(dev);
+ NetClientState *nc = qemu_get_queue(n->nic);
+
+ if (!nc->peer) {
+ error_setg(errp, "Device %s has no attached backend",
+ qdev_get_dev_path(dev));
+ return false;
+ }
+
+ if (!nc->peer->info->backend_vmsd) {
+ error_setg(errp, "Device %s backend is %s, does not support backend"
+ " transfer", qdev_get_dev_path(dev),
+ NetClientDriver_str(nc->peer->info->type));
+ return false;
+ }
+
+ return true;
+}
+
static void virtio_net_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
@@ -4321,6 +4473,7 @@ static void virtio_net_class_init(ObjectClass *klass, const void *data)
device_class_set_props(dc, virtio_net_properties);
dc->vmsd = &vmstate_virtio_net;
+ dc->backend_transfer_support = virtio_net_backend_transfer_support;
set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
vdc->realize = virtio_net_device_realize;
vdc->unrealize = virtio_net_device_unrealize;
diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h
index f708355306..7e0e156908 100644
--- a/include/hw/virtio/virtio-net.h
+++ b/include/hw/virtio/virtio-net.h
@@ -231,6 +231,7 @@ struct VirtIONet {
struct EBPFRSSContext ebpf_rss;
uint32_t nr_ebpf_rss_fds;
char **ebpf_rss_fds;
+ bool peers_wait_incoming;
};
size_t virtio_net_handle_ctrl_iov(VirtIODevice *vdev,
--
2.48.1
© 2016 - 2025 Red Hat, Inc.