On 2026/07/27 14:39, Cédric Le Goater wrote:
When x-vf-migration=on, register a 64KB migration BAR (BAR2) on each
emulated VF. This BAR implements a VFIO-like migration state machine
that the igb-vfio-pci variant driver uses to serialize/deserialize VF
device state during live migration.
The migration register region is laid out as:
0x000 DEVICE_STATE (RW) - migration state machine control
0x004 MIG_STATUS (RO) - status flags (DATA_AVAIL, ERROR,
QUIESCED) + error code in [15:8]
0x008 MIG_CAPS (RO) - advertised capabilities
0x00C MIG_VERSION (RO) - interface version
0x010 DATA_SIZE (RW) - max state size at reset, actual after save
0x014 DATA_XFER (WO) - trigger DMA save or DMA load
0x018 DATA_BUF_ADDR_LO (WO) - low 32 bits of state DMA buffer
0x01C DATA_BUF_ADDR_HI (WO) - high 32 bits of state DMA buffer
State data is transferred via a driver-provided DMA buffer. The driver
writes its PF DMA address to DATA_BUF_ADDR_LO/HI and triggers the
transfer with DATA_XFER. The device DMA-writes the serialized state on
save and DMA-reads it on restore. DMA is performed through the PF
device (pcie_sriov_get_pf) because VFIO owns the VF's IOMMU domain.
VF state serialization is added in the next patch.
Assisted-by: Claude
Suggested-by: Alex Williamson <[email protected]>
Signed-off-by: Cédric Le Goater <[email protected]>
---
docs/system/devices/igb-migration.rst | 43 ++++
hw/net/igb_common.h | 11 +
hw/net/igb_migration.h | 52 +++++
hw/net/igb_migration.c | 293 ++++++++++++++++++++++++++
hw/net/igbvf.c | 18 +-
hw/net/trace-events | 8 +
6 files changed, 416 insertions(+), 9 deletions(-)
diff --git a/docs/system/devices/igb-migration.rst
b/docs/system/devices/igb-migration.rst
index 017b47c4544e..04c44ef072af 100644
--- a/docs/system/devices/igb-migration.rst
+++ b/docs/system/devices/igb-migration.rst
@@ -18,3 +18,46 @@ feature flags indicating which migration features are
supported.
This feature is experimental and the ``x-`` prefix indicates the interface
may change.
+
+Migration BAR layout
+~~~~~~~~~~~~~~~~~~~~
+
+The migration BAR (BAR2, 64 KB) implements a VFIO-like state machine with
+the following register layout::
+
+ Offset Name Access Description
+ 0x000 DEVICE_STATE RW Migration state (RUNNING=2, STOP=1,
+ STOP_COPY=3, RESUMING=4, PRE_COPY=5)
+ 0x004 STATUS RO Flags[2:0]: DATA_AVAIL, ERROR, QUIESCED
+ Error code[15:8] (when ERROR is set)
+ 0x008 CAPS RO F_STATE, F_DIRTY, max_ranges[11:8],
+ pgsizes[31:12]
+ 0x00C VERSION RO Interface version (1)
+ 0x010 DATA_SIZE RW Max state size at reset, actual after
save
+ 0x014 DATA_XFER WO Trigger DMA save or DMA load
+ 0x018 DATA_BUF_ADDR_LO WO Low 32 bits of state DMA buffer address
+ 0x01C DATA_BUF_ADDR_HI WO High 32 bits of state DMA buffer address
+
+State transitions follow the VFIO migration state machine: the driver
+writes to ``DEVICE_STATE`` to move between states and reads ``STATUS``
+to check for completion.
+
+State data is transferred via a driver-provided DMA buffer. The driver
+writes its PF DMA address to ``DATA_BUF_ADDR_LO/HI`` and triggers the
+transfer with ``DATA_XFER``. The device DMA-writes the serialized state
+on save and DMA-reads it on restore. DMA is performed through the PF
+device because VFIO owns the VF's IOMMU domain.
+
+The state blob is a versioned sequence of register (offset, value)
+pairs.
+
+When ``STATUS`` has the ``ERROR`` bit set, bits [15:8] contain an error
+code identifying the failure::
+
+ 0 (none) No error
+ 1 BAD_MAGIC State blob magic mismatch
+ 2 BAD_VERSION State blob version mismatch
+ 3 BAD_SIZE State blob too large or empty
+ 4 BAD_VFN VF number mismatch (source != destination)
+ 5 DMA_FAILED DMA transfer to/from state buffer failed
+ 6 NO_BUFFER DATA_XFER without buffer address set
diff --git a/hw/net/igb_common.h b/hw/net/igb_common.h
index b316a5bcfa5c..01816e002c23 100644
--- a/hw/net/igb_common.h
+++ b/hw/net/igb_common.h
@@ -27,6 +27,7 @@
#define HW_NET_IGB_COMMON_H
#include "igb_regs.h"
+#include "igb_migration.h"
#define TYPE_IGBVF "igbvf"
@@ -36,6 +37,16 @@
#define IGBVF_MMIO_SIZE (16 * 1024)
#define IGBVF_MSIX_SIZE (16 * 1024)
+struct IgbVfState {
+ PCIDevice parent_obj;
+ uint16_t vfn;
+
+ MemoryRegion mmio;
+ MemoryRegion msix;
+
+ IgbVfMigState mig;
+};
+
#define defreg(x) x = (E1000_##x >> 2)
#define defreg_indexed(x, i) x##i = (E1000_##x(i) >> 2)
#define defreg_indexeda(x, i) x##i##_A = (E1000_##x##_A(i) >> 2)
diff --git a/hw/net/igb_migration.h b/hw/net/igb_migration.h
index e79892436b21..739a189810b0 100644
--- a/hw/net/igb_migration.h
+++ b/hw/net/igb_migration.h
@@ -32,12 +32,64 @@
#define IGB_MIG_CAP_OFF_BARID 8 /* offset within cap for BAR id */
#define IGB_MIG_CAP_OFF_FLAGS 12 /* offset within cap for feature flags */
+/*
+ * Maximum serialized VF state size, sized to hold all per-VF
+ * registers plus TX context descriptors with room to spare.
+ */
+#define IGB_VF_STATE_MAX_SIZE 4096
+
+/*
+ * Migration BAR register offsets.
+ */
+#define IGB_MIG_DEVICE_STATE 0x000
+#define IGB_MIG_STATUS 0x004
+#define IGB_MIG_CAPS 0x008
+#define IGB_MIG_VERSION 0x00C
+#define IGB_MIG_DATA_SIZE 0x010
+#define IGB_MIG_DATA_XFER 0x014
+#define IGB_MIG_DATA_BUF_ADDR_LO 0x018
+#define IGB_MIG_DATA_BUF_ADDR_HI 0x01C
+
+/* DEVICE_STATE values - mirrors VFIO migration states */
+#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
+#define IGB_MIG_STATE_PRE_COPY 5
+
+/* MIG_STATUS bits */
+#define IGB_MIG_STATUS_DATA_AVAIL (1u << 0)
+#define IGB_MIG_STATUS_ERROR (1u << 1)
+
+/* MIG_STATUS error codes in bits [15:8], valid when ERROR bit is set */
+#define IGB_MIG_STATUS_ERR_SHIFT 8
+#define IGB_MIG_STATUS_ERR_MASK (0xffu << IGB_MIG_STATUS_ERR_SHIFT)
+#define IGB_MIG_STATUS_ERR(code) (IGB_MIG_STATUS_ERROR | \
+ ((uint32_t)(code) <<
IGB_MIG_STATUS_ERR_SHIFT))
+
+#define IGB_MIG_ERR_BAD_MAGIC 1
+#define IGB_MIG_ERR_BAD_VERSION 2
+#define IGB_MIG_ERR_BAD_SIZE 3
+#define IGB_MIG_ERR_BAD_VFN 4
+#define IGB_MIG_ERR_DMA_FAILED 5
+#define IGB_MIG_ERR_NO_BUFFER 6
+
typedef struct IgbVfMigState {
bool migration_cap;
MemoryRegion mig_bar;
+
+ uint32_t mig_state;
+ uint8_t mig_error;
+ uint8_t mig_data[IGB_VF_STATE_MAX_SIZE];
+ uint32_t mig_data_size;
+ uint64_t mig_data_buf_addr;
} IgbVfMigState;
+typedef struct IgbVfState IgbVfState;
void igb_pf_init_migration_bar(PCIDevice *dev);
bool igbvf_add_migration_cap(PCIDevice *dev, Error **errp);
+void igbvf_mig_bar_init(IgbVfState *s);
+void igbvf_mig_state_reset(IgbVfState *s);
#endif
diff --git a/hw/net/igb_migration.c b/hw/net/igb_migration.c
index 794f1217d3e5..a0d044815fd9 100644
--- a/hw/net/igb_migration.c
+++ b/hw/net/igb_migration.c
@@ -7,9 +7,13 @@
*/
#include "qemu/osdep.h"
+#include "qemu/log.h"
#include "hw/pci/pci_device.h"
#include "hw/pci/pcie.h"
+#include "net/eth.h"
+#include "net/net.h"
#include "igb_common.h"
+#include "igb_core.h"
#include "igb_migration.h"
#include "trace.h"
@@ -70,3 +74,292 @@ bool igbvf_add_migration_cap(PCIDevice *dev, Error **errp)
trace_igbvf_mig_cap_add(pcie_sriov_vf_number(dev), offset);
return true;
}
+
+/*
+ * =====================================================================
+ * Per-VF state serialization / deserialization
+ * =====================================================================
+ */
+
+static int igb_core_vf_save_state(IgbVfState *s,
+ void *buf, size_t buf_size)
+{
+ int size = 0;
+
+ trace_igbvf_mig_save_state(s->vfn, size);
+ return size;
+}
+
+static int igb_core_vf_max_data_size(IgbVfState *s)
+{
+ int size = igb_core_vf_save_state(s, NULL, 0);
+
+ g_assert(size > 0 && size <= IGB_VF_STATE_MAX_SIZE);
Enabling x-vf-migration aborts QEMU during realization: the state-save
stub returns zero and igb_core_vf_max_data_size() asserts that the
result is positive. Patch 4 happens to repair this, so patch 3 is not
independently usable.
Regards,
Akihiko Odaki
+ return size;
+}
+
+static int igb_core_vf_load_state(IgbVfState *s,
+ const void *buf, size_t size)
+{
+ trace_igbvf_mig_load_state(s->vfn, (uint32_t)size);
+ return 0;
+}
+
+static int igbvf_mig_load(IgbVfState *s, const void *buf, size_t size)
+{
+ int ret;
+
+ ret = igb_core_vf_load_state(s, buf, size);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+/* ================================================================
+ * Migration BAR register read/write handlers
+ * ================================================================ */
+
+static bool igbvf_mig_set_state(IgbVfState *s, uint32_t new_state)
+{
+ IgbVfMigState *ms = &s->mig;
+ uint32_t old = ms->mig_state;
+ int ret;
+
+ switch (new_state) {
+ case IGB_MIG_STATE_STOP:
+ if (old != IGB_MIG_STATE_RUNNING &&
+ old != IGB_MIG_STATE_STOP_COPY &&
+ old != IGB_MIG_STATE_RESUMING &&
+ old != IGB_MIG_STATE_ERROR) {
+ return false;
+ }
+ /* Restore DATA_SIZE to max, same as at reset */
+ ms->mig_data_size = igb_core_vf_max_data_size(s);
+ break;
+
+ case IGB_MIG_STATE_RUNNING:
+ if (old != IGB_MIG_STATE_STOP) {
+ return false;
+ }
+ break;
+
+ case IGB_MIG_STATE_STOP_COPY:
+ if (old != IGB_MIG_STATE_STOP) {
+ return false;
+ }
+ ret = igb_core_vf_save_state(s, ms->mig_data, sizeof(ms->mig_data));
+ if (ret < 0) {
+ ms->mig_error = -ret;
+ ms->mig_state = IGB_MIG_STATE_ERROR;
+ return false;
+ }
+ ms->mig_data_size = ret;
+ break;
+
+ case IGB_MIG_STATE_RESUMING:
+ if (old != IGB_MIG_STATE_STOP) {
+ return false;
+ }
+ memset(ms->mig_data, 0, sizeof(ms->mig_data));
+ ms->mig_data_size = 0;
+ break;
+
+ default:
+ trace_igbvf_mig_set_state_err(s->vfn, old, new_state);
+ return false;
+ }
+
+ ms->mig_state = new_state;
+ trace_igbvf_mig_set_state(s->vfn, old, new_state);
+ return true;
+}
+
+static uint32_t igbvf_mig_get_status(IgbVfState *s)
+{
+ IgbVfMigState *ms = &s->mig;
+ uint32_t status = 0;
+
+ if (ms->mig_state == IGB_MIG_STATE_ERROR) {
+ status |= IGB_MIG_STATUS_ERR(ms->mig_error);
+ }
+ if (ms->mig_state == IGB_MIG_STATE_STOP_COPY && ms->mig_data_size > 0) {
+ status |= IGB_MIG_STATUS_DATA_AVAIL;
+ }
+
+ return status;
+}
+
+static void igbvf_mig_data_xfer(IgbVfState *s, uint32_t val)
+{
+ IgbVfMigState *ms = &s->mig;
+ MemTxResult r;
+ int ret;
+
+ if (!ms->mig_data_buf_addr) {
+ ms->mig_error = IGB_MIG_ERR_NO_BUFFER;
+ ms->mig_state = IGB_MIG_STATE_ERROR;
+ return;
+ }
+
+ switch (ms->mig_state) {
+ case IGB_MIG_STATE_STOP_COPY:
+ /* Save: DMA-write serialized state to driver buffer */
+ r = pci_dma_write(pcie_sriov_get_pf(PCI_DEVICE(s)),
+ ms->mig_data_buf_addr,
+ ms->mig_data, ms->mig_data_size);
+ if (r != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "igbvf: VF%u state write failed at 0x%" PRIx64 "\n",
+ s->vfn, ms->mig_data_buf_addr);
+ ms->mig_error = IGB_MIG_ERR_DMA_FAILED;
+ ms->mig_state = IGB_MIG_STATE_ERROR;
+ }
+ break;
+
+ case IGB_MIG_STATE_RESUMING:
+ /* Restore: DMA-read state from driver buffer and deserialize */
+ if (ms->mig_data_size == 0 ||
+ ms->mig_data_size > sizeof(ms->mig_data)) {
+ ms->mig_error = IGB_MIG_ERR_BAD_SIZE;
+ ms->mig_state = IGB_MIG_STATE_ERROR;
+ break;
+ }
+
+ r = pci_dma_read(pcie_sriov_get_pf(PCI_DEVICE(s)),
+ ms->mig_data_buf_addr,
+ ms->mig_data, ms->mig_data_size);
+ if (r != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "igbvf: VF%u state read failed at 0x%" PRIx64 "\n",
+ s->vfn, ms->mig_data_buf_addr);
+ ms->mig_error = IGB_MIG_ERR_DMA_FAILED;
+ ms->mig_state = IGB_MIG_STATE_ERROR;
+ break;
+ }
+
+ ret = igbvf_mig_load(s, ms->mig_data, ms->mig_data_size);
+ if (ret < 0) {
+ ms->mig_error = -ret;
+ ms->mig_state = IGB_MIG_STATE_ERROR;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static uint64_t igbvf_mig_read(void *opaque, hwaddr addr, unsigned size)
+{
+ IgbVfState *s = opaque;
+ IgbVfMigState *ms = &s->mig;
+ uint64_t val = 0;
+
+ switch (addr) {
+ case IGB_MIG_DEVICE_STATE:
+ val = ms->mig_state;
+ break;
+ case IGB_MIG_STATUS:
+ val = igbvf_mig_get_status(s);
+ break;
+ case IGB_MIG_CAPS:
+ val = IGB_MIG_CAP_F_STATE;
+ break;
+ case IGB_MIG_VERSION:
+ val = IGB_MIG_CAP_VERSION;
+ break;
+ case IGB_MIG_DATA_SIZE:
+ val = ms->mig_data_size;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "igbvf: VF%u bad migration BAR read at 0x%"
+ HWADDR_PRIx "\n", s->vfn, addr);
+ break;
+ }
+
+ trace_igbvf_mig_bar_read(s->vfn, addr, val);
+
+ return val;
+}
+
+static void igbvf_mig_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ IgbVfState *s = opaque;
+ IgbVfMigState *ms = &s->mig;
+
+ trace_igbvf_mig_bar_write(s->vfn, addr, val);
+
+ switch (addr) {
+ case IGB_MIG_DEVICE_STATE:
+ igbvf_mig_set_state(s, (uint32_t)val);
+ break;
+ case IGB_MIG_DATA_SIZE:
+ if (val <= sizeof(ms->mig_data)) {
+ ms->mig_data_size = (uint32_t)val;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "igbvf: VF%u DATA_SIZE %" PRIu64 " exceeds max
%zu\n",
+ s->vfn, val, sizeof(ms->mig_data));
+ }
+ break;
+ case IGB_MIG_DATA_XFER:
+ igbvf_mig_data_xfer(s, (uint32_t)val);
+ break;
+ case IGB_MIG_DATA_BUF_ADDR_LO:
+ ms->mig_data_buf_addr =
+ deposit64(ms->mig_data_buf_addr, 0, 32, val);
+ break;
+ case IGB_MIG_DATA_BUF_ADDR_HI:
+ ms->mig_data_buf_addr =
+ deposit64(ms->mig_data_buf_addr, 32, 32, val);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "igbvf: VF%u bad migration BAR write at 0x%"
+ HWADDR_PRIx "\n", s->vfn, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps mig_bar_ops = {
+ .read = igbvf_mig_read,
+ .write = igbvf_mig_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+/*
+ * Use the QEM-internal PCI_BASE_ADDRESS_MEM_ALWAYS_ON BAR type flag
+ * to keep the memory BAR always mapped.
+ */
+void igbvf_mig_bar_init(IgbVfState *s)
+{
+ IgbVfMigState *ms = &s->mig;
+
+ memory_region_init_io(&ms->mig_bar, OBJECT(s), &mig_bar_ops, s,
+ "igbvf-mig", IGB_MIG_BAR_SIZE);
+ pci_register_bar(PCI_DEVICE(s), IGB_MIG_BAR_IDX,
+ PCI_BASE_ADDRESS_MEM_PREFETCH |
+ PCI_BASE_ADDRESS_MEM_ALWAYS_ON,
+ &ms->mig_bar);
+ trace_igbvf_mig_bar_init(s->vfn);
+}
+
+void igbvf_mig_state_reset(IgbVfState *s)
+{
+ IgbVfMigState *ms = &s->mig;
+
+ ms->mig_state = IGB_MIG_STATE_RUNNING;
+ ms->mig_error = 0;
+ ms->mig_data_size = igb_core_vf_max_data_size(s);
+ ms->mig_data_buf_addr = 0;
+ memset(ms->mig_data, 0, sizeof(ms->mig_data));
+ trace_igbvf_mig_reset(s->vfn);
+}
diff --git a/hw/net/igbvf.c b/hw/net/igbvf.c
index 94c9739cd58c..e9f9fc3369d8 100644
--- a/hw/net/igbvf.c
+++ b/hw/net/igbvf.c
@@ -53,15 +53,6 @@
OBJECT_DECLARE_SIMPLE_TYPE(IgbVfState, IGBVF)
-struct IgbVfState {
- PCIDevice parent_obj;
-
- MemoryRegion mmio;
- MemoryRegion msix;
-
- IgbVfMigState mig;
-};
-
static hwaddr vf_to_pf_addr(hwaddr addr, uint16_t vfn, bool write)
{
switch (addr) {
@@ -281,6 +272,10 @@ static void igbvf_pci_realize(PCIDevice *dev, Error **errp)
if (!igbvf_add_migration_cap(dev, errp)) {
return;
}
+
+ s->vfn = pcie_sriov_vf_number(dev);
+ igbvf_mig_bar_init(s);
+ igbvf_mig_state_reset(s);
}
if (object_property_get_bool(OBJECT(pcie_sriov_get_pf(dev)),
@@ -298,8 +293,13 @@ static void igbvf_pci_realize(PCIDevice *dev, Error **errp)
static void igbvf_qdev_reset_hold(Object *obj, ResetType type)
{
PCIDevice *vf = PCI_DEVICE(obj);
+ IgbVfState *s = IGBVF(obj);
igb_vf_reset(pcie_sriov_get_pf(vf), pcie_sriov_vf_number(vf));
+
+ if (s->mig.migration_cap) {
+ igbvf_mig_state_reset(s);
+ }
}
static void igbvf_pci_uninit(PCIDevice *dev)
diff --git a/hw/net/trace-events b/hw/net/trace-events
index 06d8848023e5..0b13a99b3f32 100644
--- a/hw/net/trace-events
+++ b/hw/net/trace-events
@@ -295,6 +295,14 @@ igb_wrn_rx_desc_modes_not_supp(int desc_type) "Not
supported descriptor type: %d
# igbvf.c
igbvf_wrn_io_addr_unknown(uint64_t addr) "IO unknown register 0x%"PRIx64
igbvf_mig_cap_add(uint16_t vfn, int offset) "VF%u: added migration vendor cap at
config offset 0x%x"
+igbvf_mig_bar_init(uint16_t vfn) "VF%u: migration BAR initialized"
+igbvf_mig_bar_read(uint16_t vfn, uint64_t addr, uint64_t val) "VF%u: BAR read
addr=0x%"PRIx64" val=0x%"PRIx64
+igbvf_mig_bar_write(uint16_t vfn, uint64_t addr, uint64_t val) "VF%u: BAR write
addr=0x%"PRIx64" val=0x%"PRIx64
+igbvf_mig_set_state(uint16_t vfn, uint32_t old_state, uint32_t new_state) "VF%u: state
%u -> %u"
+igbvf_mig_set_state_err(uint16_t vfn, uint32_t old_state, uint32_t new_state) "VF%u:
invalid transition %u -> %u"
+igbvf_mig_save_state(uint16_t vfn, uint32_t size) "VF%u: saved %u bytes of device
state"
+igbvf_mig_load_state(uint16_t vfn, uint32_t size) "VF%u: loaded %u bytes of device
state"
+igbvf_mig_reset(uint16_t vfn) "VF%u: migration state reset"
# spapr_llan.c
spapr_vlan_get_rx_bd_from_pool_found(int pool, int32_t count, uint32_t rx_bufs) "pool=%d
count=%"PRId32" rxbufs=%"PRIu32