diff mbox series

[RFC,v2,1/9] igb: Add x-vf-migration property and DVSEC extended capability

Message ID 20260902192054.3329753-2-clg@redhat.com
State New
Headers show
Series igb: Add experimental VF live migration support | expand

Commit Message

Cédric Le Goater Sept. 2, 2026, 7:20 p.m. UTC
Add an "x-vf-migration" property to the IGB PF device and expose a
DVSEC (Designated Vendor-Specific Extended Capability) at offset 0x160
in VF extended config space when migration is enabled.

The DVSEC provides the register interface for VF live migration:
 - CAPS:       supported features (state migration)
 - CTRL:       command doorbell
 - STATUS:     state and error reporting
 - BUF_ADDR:   shared DMA buffer address (GPA)

Move IgbVfState from igbvf.c to igb_common.h so it can be shared with
the migration module, and add the migration state field.

AI-used-for: code (prototype)
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 MAINTAINERS            |   5 ++
 hw/net/igb_common.h    |  16 +++++++
 hw/net/igb_migration.h |  72 ++++++++++++++++++++++++++++
 hw/net/igb.c           |   2 +
 hw/net/igb_migration.c | 106 +++++++++++++++++++++++++++++++++++++++++
 hw/net/igbvf.c         |  52 ++++++++++++++++----
 hw/net/meson.build     |   2 +-
 7 files changed, 245 insertions(+), 10 deletions(-)
 create mode 100644 hw/net/igb_migration.h
 create mode 100644 hw/net/igb_migration.c

Comments

Alex Williamson Sept. 3, 2026, 7:57 p.m. UTC | #1
On Wed,  2 Sep 2026 21:20:46 +0200
Cédric Le Goater <clg@redhat.com> wrote:

> diff --git a/hw/net/igb_migration.c b/hw/net/igb_migration.c
> new file mode 100644
> index 000000000000..4dfebd82344c
> --- /dev/null
> +++ b/hw/net/igb_migration.c
> @@ -0,0 +1,106 @@
> +/*
> + * QEMU Intel 82576 SR/IOV VF Migration Support
> + *
> + * Copyright (c) 2026 Red Hat, Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/pci/pci_device.h"
> +#include "hw/pci/pcie.h"
> +#include "igb_common.h"
> +#include "igb_migration.h"
> +
> +static void igbvf_mig_update_status(IgbVfState *s, uint8_t err)
> +{
> +    IgbVfMigState *ms = &s->mig;
> +    PCIDevice *dev = PCI_DEVICE(s);
> +    uint32_t status;
> +
> +    status = ms->mig_state & IGB_MIG_STATUS_STATE_MASK;
> +
> +    if (err) {
> +        status = IGB_MIG_STATE_ERROR | IGB_MIG_STATUS_ERR(err);
> +    }
> +
> +    pci_set_long(dev->config + IGB_MIG_DVSEC_OFFSET + IGB_MIG_STATUS, status);
> +}
> +
> +
> +bool igbvf_add_migration_dvsec(PCIDevice *dev, Error **errp)
> +{
> +    uint16_t offset = IGB_MIG_DVSEC_OFFSET;
> +    uint32_t caps;
> +
> +    pcie_add_capability(dev, PCI_EXT_CAP_ID_DVSEC, 1, offset,
> +                        IGB_MIG_DVSEC_SIZE);
> +
> +    /* DVSEC header 1: length[31:20] | rev[19:16] | vendor_id[15:0] */
> +    pci_set_long(dev->config + offset + 0x4,
> +                 (IGB_MIG_DVSEC_SIZE << 20) |
> +                 (IGB_MIG_DVSEC_VER << 16) |
> +                 PCI_VENDOR_ID_INTEL);

Let's not co-opt an Intel DVSEC ID, we should use a vendor ID that we
have a claim to, the RedHat/Qumranet one, I'd guess.  We may need to
add DVSEC IDs to the spreadsheet for whoever is tracking Device IDs.
Gerd?

> +
> +    /* DVSEC header 2: DVSEC ID */
> +    pci_set_word(dev->config + offset + 0x8, IGB_MIG_DVSEC_ID);
> +
> +    /* CAPS: features (state migration only) */
> +    caps = IGB_MIG_CAP_F_STATE;
> +    pci_set_long(dev->config + offset + IGB_MIG_CAPS, caps);
> +
> +    /* STATUS: initial state is RUNNING */
> +    pci_set_long(dev->config + offset + IGB_MIG_STATUS,
> +                 IGB_MIG_STATE_RUNNING);
> +
> +    /* BUF_ADDR_LO and BUF_ADDR_HI are writable */
> +    memset(dev->wmask + offset + IGB_MIG_BUF_ADDR_LO, 0xff, 4);
> +    memset(dev->wmask + offset + IGB_MIG_BUF_ADDR_HI, 0xff, 4);
> +
> +    return true;
> +}
...
> diff --git a/hw/net/igbvf.c b/hw/net/igbvf.c
> index 9a165c7063ee..30dfdb574ac7 100644
> --- a/hw/net/igbvf.c
> +++ b/hw/net/igbvf.c
> @@ -38,27 +38,21 @@
>   */
>  
>  #include "qemu/osdep.h"
> +#include "qemu/range.h"
>  #include "hw/core/hw-error.h"
>  #include "hw/net/mii.h"
>  #include "hw/pci/pci_device.h"
>  #include "hw/pci/pcie.h"
> +#include "hw/pci/pcie_sriov.h"
>  #include "hw/pci/msix.h"
>  #include "net/eth.h"
>  #include "net/net.h"
>  #include "igb_common.h"
>  #include "igb_core.h"
> +#include "igb_migration.h"
>  #include "trace.h"
>  #include "qapi/error.h"
>  
> -OBJECT_DECLARE_SIMPLE_TYPE(IgbVfState, IGBVF)
> -
> -struct IgbVfState {
> -    PCIDevice parent_obj;
> -
> -    MemoryRegion mmio;
> -    MemoryRegion msix;
> -};
> -
>  static hwaddr vf_to_pf_addr(hwaddr addr, uint16_t vfn, bool write)
>  {
>      switch (addr) {
> @@ -199,10 +193,35 @@ static hwaddr vf_to_pf_addr(hwaddr addr, uint16_t vfn, bool write)
>      return HWADDR_MAX;
>  }
>  
> +static bool igbvf_addr_in_dvsec(uint32_t addr, int len)
> +{
> +    return ranges_overlap(addr, len,
> +                          IGB_MIG_DVSEC_OFFSET, IGB_MIG_DVSEC_SIZE);
> +}
> +
> +static uint32_t igbvf_read_config(PCIDevice *dev, uint32_t addr, int size)
> +{
> +    IgbVfState *s = IGBVF(dev);
> +
> +    if (s->migration_enabled && igbvf_addr_in_dvsec(addr, size)) {
> +        return igbvf_mig_config_read(s, addr, size);
> +    }
> +
> +    return pci_default_read_config(dev, addr, size);
> +}
> +
>  static void igbvf_write_config(PCIDevice *dev, uint32_t addr, uint32_t val,
>      int len)
>  {
> +    IgbVfState *s = IGBVF(dev);
> +
>      trace_igbvf_write_config(addr, val, len);
> +
> +    if (s->migration_enabled && igbvf_addr_in_dvsec(addr, len)) {
> +        igbvf_mig_config_write(s, addr, val, len);
> +        return;
> +    }
> +
>      pci_default_write_config(dev, addr, val, len);
>      if (object_property_get_bool(OBJECT(pcie_sriov_get_pf(dev)),
>                                   "x-pcie-flr-init", &error_abort)) {
> @@ -282,13 +301,27 @@ static void igbvf_pci_realize(PCIDevice *dev, Error **errp)
>      }
>  
>      pcie_ari_init(dev, 0x150);
> +
> +    if (object_property_get_bool(OBJECT(pcie_sriov_get_pf(dev)),
> +                                 "x-vf-migration", &error_abort)) {
> +        s->vfn = pcie_sriov_vf_number(dev);
> +        s->migration_enabled = true;
> +        if (!igbvf_add_migration_dvsec(dev, errp)) {
> +            return;
> +        }

The migration blocker noted later in the docs should be added here.
Trivial to add, avoids the internal migration state being reset by the
L0 VM being migrated, doesn't seem worth extending this driver's VMState
while the feature is experimental.

> +    }
>  }
>  
>  static void igbvf_qdev_reset_hold(Object *obj, ResetType type)
>  {
>      PCIDevice *vf = PCI_DEVICE(obj);
> +    IgbVfState *s = IGBVF(vf);
>  
>      igb_vf_reset(pcie_sriov_get_pf(vf), pcie_sriov_vf_number(vf));
> +
> +    if (s->migration_enabled) {
> +        igbvf_mig_state_reset(s);
> +    }

Hmm, I think reset is more complicated that this.  This seems to define
that any device reset will reset the migration state.  The vfio
migration protocol only defines that a VFIO_DEVICE_RESET returns the
device to running.

Consider the case of an FLR triggered by the L2 guest.  L1 QEMU passes
through the config space write, that lands in vfio-pci core config
space handling in the L1 variant driver, which turns into a
pci_reset_function() in the L1 kernel and I think lands here in the L0
QEMU.  Therefore, it seems like the L2 guest can corrupt the migration
state.

As above, the migration state is defined to be reset via the RESET
ioctl, which also turns into a pci_reset_function() in the L1 kernel.
So L0 QEMU can't tell the difference here.

I think that means that the variant driver itself needs to own this
part of the protocol, performing the migration state housekeeping on
RESET ioctl, while both allow the state to persist on other resets.  In
that sense QEMU cannot emulate this DVSEC as normal config space, it's
a persistent control plane that lives in the VMM and happens to be
accessed through config space.  Thanks,

Alex

>  }
>  
>  static void igbvf_pci_uninit(PCIDevice *dev)
> @@ -309,6 +342,7 @@ static void igbvf_class_init(ObjectClass *class, const void *data)
>  
>      c->realize = igbvf_pci_realize;
>      c->exit = igbvf_pci_uninit;
> +    c->config_read = igbvf_read_config;
>      c->vendor_id = PCI_VENDOR_ID_INTEL;
>      c->device_id = E1000_DEV_ID_82576_VF;
>      c->revision = 1;
> diff --git a/hw/net/meson.build b/hw/net/meson.build
> index 84f142df222a..bb4b449b25ba 100644
> --- a/hw/net/meson.build
> +++ b/hw/net/meson.build
> @@ -11,7 +11,7 @@ system_ss.add(when: 'CONFIG_E1000_PCI', if_true: files('e1000.c', 'e1000x_common
>  system_ss.add(when: 'CONFIG_E1000E_PCI_EXPRESS', if_true: files('net_tx_pkt.c', 'net_rx_pkt.c'))
>  system_ss.add(when: 'CONFIG_E1000E_PCI_EXPRESS', if_true: files('e1000e.c', 'e1000e_core.c', 'e1000x_common.c'))
>  system_ss.add(when: 'CONFIG_IGB_PCI_EXPRESS', if_true: files('net_tx_pkt.c', 'net_rx_pkt.c'))
> -system_ss.add(when: 'CONFIG_IGB_PCI_EXPRESS', if_true: files('igb.c', 'igbvf.c', 'igb_core.c'))
> +system_ss.add(when: 'CONFIG_IGB_PCI_EXPRESS', if_true: files('igb.c', 'igbvf.c', 'igb_core.c', 'igb_migration.c'))
>  system_ss.add(when: 'CONFIG_RTL8139_PCI', if_true: files('rtl8139.c'))
>  system_ss.add(when: 'CONFIG_TULIP', if_true: files('tulip.c'))
>  system_ss.add(when: 'CONFIG_VMXNET3_PCI', if_true: files('net_tx_pkt.c', 'net_rx_pkt.c'))
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 4a49a40294eb..f88b526be238 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2816,6 +2816,11 @@  F: tests/functional/x86_64/test_netdev_ethtool.py
 F: tests/qtest/igb-test.c
 F: tests/qtest/libqos/igb.c
 
+igb VF migration
+M: Cédric Le Goater <clg@redhat.com>
+S: Maintained
+F: hw/net/igb_migration.*
+
 eepro100
 M: Stefan Weil <sw@weilnetz.de>
 S: Maintained
diff --git a/hw/net/igb_common.h b/hw/net/igb_common.h
index b316a5bcfa5c..f0e2529e757b 100644
--- a/hw/net/igb_common.h
+++ b/hw/net/igb_common.h
@@ -26,7 +26,9 @@ 
 #ifndef HW_NET_IGB_COMMON_H
 #define HW_NET_IGB_COMMON_H
 
+#include "hw/pci/pci_device.h"
 #include "igb_regs.h"
+#include "igb_migration.h"
 
 #define TYPE_IGBVF "igbvf"
 
@@ -154,4 +156,18 @@  uint64_t igb_mmio_read(void *opaque, hwaddr addr, unsigned size);
 void igb_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size);
 void igb_vf_reset(void *opaque, uint16_t vfn);
 
+OBJECT_DECLARE_SIMPLE_TYPE(IgbVfState, IGBVF)
+
+struct IgbVfState {
+    PCIDevice parent_obj;
+
+    uint16_t vfn;
+    bool migration_enabled;
+
+    MemoryRegion mmio;
+    MemoryRegion msix;
+
+    IgbVfMigState mig;
+};
+
 #endif
diff --git a/hw/net/igb_migration.h b/hw/net/igb_migration.h
new file mode 100644
index 000000000000..3da28e11e49e
--- /dev/null
+++ b/hw/net/igb_migration.h
@@ -0,0 +1,72 @@ 
+/*
+ * QEMU Intel 82576 SR/IOV VF Migration Support
+ *
+ * Copyright (c) 2026 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_NET_IGB_MIGRATION_H
+#define HW_NET_IGB_MIGRATION_H
+
+#include "hw/pci/pci_device.h"
+
+/*
+ * Migration interface exposed as a DVSEC (Designated Vendor-Specific
+ * Extended Capability) in VF extended config space.
+ *
+ * DVSEC layout at IGB_MIG_DVSEC_OFFSET (0x160):
+ *
+ *   +0x00  PCIe extended cap header   (cap_id=0x23, ver=1, next)
+ *   +0x04  DVSEC header 1             (len | rev | vendor_id)
+ *   +0x08  DVSEC header 2             (DVSEC ID)
+ *   +0x0A  Reserved                   (padding for DWORD alignment)
+ *   +0x0C  CAPS                       (RO: F_STATE[0])
+ *   +0x10  CTRL                       (WO: doorbell command)
+ *   +0x14  STATUS                     (RO: state[7:0], error_code[15:8])
+ *   +0x18  BUF_ADDR_LO                (RW: shared buffer GPA low)
+ *   +0x1C  BUF_ADDR_HI                (RW: shared buffer GPA high)
+ */
+
+#define IGB_MIG_DVSEC_OFFSET    0x160
+#define IGB_MIG_DVSEC_SIZE      0x20
+#define IGB_MIG_DVSEC_VER       1
+#define IGB_MIG_DVSEC_ID        1
+
+/* Register offsets relative to DVSEC base */
+#define IGB_MIG_CAPS            0x0C
+#define IGB_MIG_CTRL            0x10
+#define IGB_MIG_STATUS          0x14
+#define IGB_MIG_BUF_ADDR_LO     0x18
+#define IGB_MIG_BUF_ADDR_HI     0x1C
+
+/* CAPS register layout */
+#define IGB_MIG_CAP_F_STATE             (1u << 0)
+
+/* STATUS register: state in [7:0], error code [15:8] */
+#define IGB_MIG_STATUS_STATE_MASK       0xFF
+#define IGB_MIG_STATUS_ERROR_CODE_SHIFT 8
+#define IGB_MIG_STATUS_ERR(code) \
+    ((uint32_t)(code) << IGB_MIG_STATUS_ERROR_CODE_SHIFT)
+
+/* Device states (based on VFIO migration v2) */
+#define IGB_MIG_STATE_ERROR             0
+#define IGB_MIG_STATE_STOP              1
+#define IGB_MIG_STATE_RUNNING           2
+#define IGB_MIG_STATE_STOP_COPY         3
+#define IGB_MIG_STATE_RESUMING          4
+
+typedef struct IgbVfMigState {
+    uint32_t mig_state;
+    uint64_t mig_data_buf_addr;
+} IgbVfMigState;
+
+typedef struct IgbVfState IgbVfState;
+
+bool igbvf_add_migration_dvsec(PCIDevice *dev, Error **errp);
+void igbvf_mig_state_reset(IgbVfState *s);
+uint32_t igbvf_mig_config_read(IgbVfState *s, uint32_t addr, int size);
+bool igbvf_mig_config_write(IgbVfState *s, uint32_t addr, uint32_t val,
+                            int size);
+
+#endif
diff --git a/hw/net/igb.c b/hw/net/igb.c
index c076807e7110..7268e5473fc3 100644
--- a/hw/net/igb.c
+++ b/hw/net/igb.c
@@ -79,6 +79,7 @@  struct IGBState {
 
     IGBCore core;
     bool has_flr;
+    bool vf_migration;
 };
 
 #define IGB_CAP_SRIOV_OFFSET    (0x160)
@@ -597,6 +598,7 @@  static const VMStateDescription igb_vmstate = {
 static const Property igb_properties[] = {
     DEFINE_NIC_PROPERTIES(IGBState, conf),
     DEFINE_PROP_BOOL("x-pcie-flr-init", IGBState, has_flr, true),
+    DEFINE_PROP_BOOL("x-vf-migration", IGBState, vf_migration, false),
 };
 
 static void igb_class_init(ObjectClass *class, const void *data)
diff --git a/hw/net/igb_migration.c b/hw/net/igb_migration.c
new file mode 100644
index 000000000000..4dfebd82344c
--- /dev/null
+++ b/hw/net/igb_migration.c
@@ -0,0 +1,106 @@ 
+/*
+ * QEMU Intel 82576 SR/IOV VF Migration Support
+ *
+ * Copyright (c) 2026 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci_device.h"
+#include "hw/pci/pcie.h"
+#include "igb_common.h"
+#include "igb_migration.h"
+
+static void igbvf_mig_update_status(IgbVfState *s, uint8_t err)
+{
+    IgbVfMigState *ms = &s->mig;
+    PCIDevice *dev = PCI_DEVICE(s);
+    uint32_t status;
+
+    status = ms->mig_state & IGB_MIG_STATUS_STATE_MASK;
+
+    if (err) {
+        status = IGB_MIG_STATE_ERROR | IGB_MIG_STATUS_ERR(err);
+    }
+
+    pci_set_long(dev->config + IGB_MIG_DVSEC_OFFSET + IGB_MIG_STATUS, status);
+}
+
+
+bool igbvf_add_migration_dvsec(PCIDevice *dev, Error **errp)
+{
+    uint16_t offset = IGB_MIG_DVSEC_OFFSET;
+    uint32_t caps;
+
+    pcie_add_capability(dev, PCI_EXT_CAP_ID_DVSEC, 1, offset,
+                        IGB_MIG_DVSEC_SIZE);
+
+    /* DVSEC header 1: length[31:20] | rev[19:16] | vendor_id[15:0] */
+    pci_set_long(dev->config + offset + 0x4,
+                 (IGB_MIG_DVSEC_SIZE << 20) |
+                 (IGB_MIG_DVSEC_VER << 16) |
+                 PCI_VENDOR_ID_INTEL);
+
+    /* DVSEC header 2: DVSEC ID */
+    pci_set_word(dev->config + offset + 0x8, IGB_MIG_DVSEC_ID);
+
+    /* CAPS: features (state migration only) */
+    caps = IGB_MIG_CAP_F_STATE;
+    pci_set_long(dev->config + offset + IGB_MIG_CAPS, caps);
+
+    /* STATUS: initial state is RUNNING */
+    pci_set_long(dev->config + offset + IGB_MIG_STATUS,
+                 IGB_MIG_STATE_RUNNING);
+
+    /* BUF_ADDR_LO and BUF_ADDR_HI are writable */
+    memset(dev->wmask + offset + IGB_MIG_BUF_ADDR_LO, 0xff, 4);
+    memset(dev->wmask + offset + IGB_MIG_BUF_ADDR_HI, 0xff, 4);
+
+    return true;
+}
+
+uint32_t igbvf_mig_config_read(IgbVfState *s, uint32_t addr, int size)
+{
+    PCIDevice *dev = PCI_DEVICE(s);
+
+    return pci_default_read_config(dev, addr, size);
+}
+
+bool igbvf_mig_config_write(IgbVfState *s, uint32_t addr, uint32_t val,
+                            int size)
+{
+    PCIDevice *dev = PCI_DEVICE(s);
+    uint32_t offset = addr - IGB_MIG_DVSEC_OFFSET;
+
+    switch (offset) {
+    case IGB_MIG_CTRL:
+        /* Command handling will be added in a later commit */
+        break;
+
+    case IGB_MIG_BUF_ADDR_LO:
+    case IGB_MIG_BUF_ADDR_HI:
+        pci_default_write_config(dev, addr, val, size);
+        break;
+
+    default:
+        break;
+    }
+
+    return true;
+}
+
+void igbvf_mig_state_reset(IgbVfState *s)
+{
+    IgbVfMigState *ms = &s->mig;
+
+    ms->mig_state = IGB_MIG_STATE_RUNNING;
+    ms->mig_data_buf_addr = 0;
+
+    pci_set_long(PCI_DEVICE(s)->config +
+                 IGB_MIG_DVSEC_OFFSET + IGB_MIG_BUF_ADDR_LO, 0);
+    pci_set_long(PCI_DEVICE(s)->config +
+                 IGB_MIG_DVSEC_OFFSET + IGB_MIG_BUF_ADDR_HI, 0);
+
+    igbvf_mig_update_status(s, 0);
+}
diff --git a/hw/net/igbvf.c b/hw/net/igbvf.c
index 9a165c7063ee..30dfdb574ac7 100644
--- a/hw/net/igbvf.c
+++ b/hw/net/igbvf.c
@@ -38,27 +38,21 @@ 
  */
 
 #include "qemu/osdep.h"
+#include "qemu/range.h"
 #include "hw/core/hw-error.h"
 #include "hw/net/mii.h"
 #include "hw/pci/pci_device.h"
 #include "hw/pci/pcie.h"
+#include "hw/pci/pcie_sriov.h"
 #include "hw/pci/msix.h"
 #include "net/eth.h"
 #include "net/net.h"
 #include "igb_common.h"
 #include "igb_core.h"
+#include "igb_migration.h"
 #include "trace.h"
 #include "qapi/error.h"
 
-OBJECT_DECLARE_SIMPLE_TYPE(IgbVfState, IGBVF)
-
-struct IgbVfState {
-    PCIDevice parent_obj;
-
-    MemoryRegion mmio;
-    MemoryRegion msix;
-};
-
 static hwaddr vf_to_pf_addr(hwaddr addr, uint16_t vfn, bool write)
 {
     switch (addr) {
@@ -199,10 +193,35 @@  static hwaddr vf_to_pf_addr(hwaddr addr, uint16_t vfn, bool write)
     return HWADDR_MAX;
 }
 
+static bool igbvf_addr_in_dvsec(uint32_t addr, int len)
+{
+    return ranges_overlap(addr, len,
+                          IGB_MIG_DVSEC_OFFSET, IGB_MIG_DVSEC_SIZE);
+}
+
+static uint32_t igbvf_read_config(PCIDevice *dev, uint32_t addr, int size)
+{
+    IgbVfState *s = IGBVF(dev);
+
+    if (s->migration_enabled && igbvf_addr_in_dvsec(addr, size)) {
+        return igbvf_mig_config_read(s, addr, size);
+    }
+
+    return pci_default_read_config(dev, addr, size);
+}
+
 static void igbvf_write_config(PCIDevice *dev, uint32_t addr, uint32_t val,
     int len)
 {
+    IgbVfState *s = IGBVF(dev);
+
     trace_igbvf_write_config(addr, val, len);
+
+    if (s->migration_enabled && igbvf_addr_in_dvsec(addr, len)) {
+        igbvf_mig_config_write(s, addr, val, len);
+        return;
+    }
+
     pci_default_write_config(dev, addr, val, len);
     if (object_property_get_bool(OBJECT(pcie_sriov_get_pf(dev)),
                                  "x-pcie-flr-init", &error_abort)) {
@@ -282,13 +301,27 @@  static void igbvf_pci_realize(PCIDevice *dev, Error **errp)
     }
 
     pcie_ari_init(dev, 0x150);
+
+    if (object_property_get_bool(OBJECT(pcie_sriov_get_pf(dev)),
+                                 "x-vf-migration", &error_abort)) {
+        s->vfn = pcie_sriov_vf_number(dev);
+        s->migration_enabled = true;
+        if (!igbvf_add_migration_dvsec(dev, errp)) {
+            return;
+        }
+    }
 }
 
 static void igbvf_qdev_reset_hold(Object *obj, ResetType type)
 {
     PCIDevice *vf = PCI_DEVICE(obj);
+    IgbVfState *s = IGBVF(vf);
 
     igb_vf_reset(pcie_sriov_get_pf(vf), pcie_sriov_vf_number(vf));
+
+    if (s->migration_enabled) {
+        igbvf_mig_state_reset(s);
+    }
 }
 
 static void igbvf_pci_uninit(PCIDevice *dev)
@@ -309,6 +342,7 @@  static void igbvf_class_init(ObjectClass *class, const void *data)
 
     c->realize = igbvf_pci_realize;
     c->exit = igbvf_pci_uninit;
+    c->config_read = igbvf_read_config;
     c->vendor_id = PCI_VENDOR_ID_INTEL;
     c->device_id = E1000_DEV_ID_82576_VF;
     c->revision = 1;
diff --git a/hw/net/meson.build b/hw/net/meson.build
index 84f142df222a..bb4b449b25ba 100644
--- a/hw/net/meson.build
+++ b/hw/net/meson.build
@@ -11,7 +11,7 @@  system_ss.add(when: 'CONFIG_E1000_PCI', if_true: files('e1000.c', 'e1000x_common
 system_ss.add(when: 'CONFIG_E1000E_PCI_EXPRESS', if_true: files('net_tx_pkt.c', 'net_rx_pkt.c'))
 system_ss.add(when: 'CONFIG_E1000E_PCI_EXPRESS', if_true: files('e1000e.c', 'e1000e_core.c', 'e1000x_common.c'))
 system_ss.add(when: 'CONFIG_IGB_PCI_EXPRESS', if_true: files('net_tx_pkt.c', 'net_rx_pkt.c'))
-system_ss.add(when: 'CONFIG_IGB_PCI_EXPRESS', if_true: files('igb.c', 'igbvf.c', 'igb_core.c'))
+system_ss.add(when: 'CONFIG_IGB_PCI_EXPRESS', if_true: files('igb.c', 'igbvf.c', 'igb_core.c', 'igb_migration.c'))
 system_ss.add(when: 'CONFIG_RTL8139_PCI', if_true: files('rtl8139.c'))
 system_ss.add(when: 'CONFIG_TULIP', if_true: files('tulip.c'))
 system_ss.add(when: 'CONFIG_VMXNET3_PCI', if_true: files('net_tx_pkt.c', 'net_rx_pkt.c'))