Implement per-VF state serialization and deserialization for the SAVE
and LOAD commands. The wire format consists of a header (magic,
version, VF number, register count), per-VF register offset/value
pairs from a whitelist, RA table entries owned by the VF, and TX
queue contexts.
Register offsets are relocated on load so a VF can migrate to a
different VF number on the destination. RA pool ownership bits are
swapped accordingly.
AI-used-for: analysis, code (prototype)
Signed-off-by: Cédric Le Goater <[email protected]>
---
hw/net/igb_core.h | 2 +
hw/net/igb_migration.h | 2 +
hw/net/igb.c | 5 +
hw/net/igb_migration.c | 347 ++++++++++++++++++++++++++++++++++++++++-
4 files changed, 354 insertions(+), 2 deletions(-)
diff --git a/hw/net/igb_core.h b/hw/net/igb_core.h
index d70b54e318f1..60724e2824ab 100644
--- a/hw/net/igb_core.h
+++ b/hw/net/igb_core.h
@@ -143,4 +143,6 @@ igb_receive_iov(IGBCore *core, const struct iovec
*iov, int iovcnt);
void
igb_start_recv(IGBCore *core);
+IGBCore *igb_pf_get_core(void *pf);
+
#endif
diff --git a/hw/net/igb_migration.h b/hw/net/igb_migration.h
index ea40ac65c54b..b2f601e74346 100644
--- a/hw/net/igb_migration.h
+++ b/hw/net/igb_migration.h
@@ -73,6 +73,8 @@
#define IGB_MIG_ERR_NO_BUFFER 3
#define IGB_MIG_ERR_DMA_FAILED 4
#define IGB_MIG_ERR_BAD_SIZE 5
+#define IGB_MIG_ERR_BAD_MAGIC 6
+#define IGB_MIG_ERR_BAD_VERSION 7
/* Shared buffer constants */
#define IGB_VF_STATE_MAX_SIZE 4096
diff --git a/hw/net/igb.c b/hw/net/igb.c
index 7268e5473fc3..f39f2bc3a04e 100644
--- a/hw/net/igb.c
+++ b/hw/net/igb.c
@@ -133,6 +133,11 @@ void igb_vf_reset(void *opaque, uint16_t vfn)
igb_core_vf_reset(&s->core, vfn);
}
+IGBCore *igb_pf_get_core(void *pf)
+{
+ return &IGB(pf)->core;
+}
+
static bool
igb_io_get_reg_index(IGBState *s, uint32_t *idx)
{
diff --git a/hw/net/igb_migration.c b/hw/net/igb_migration.c
index 8e7e6fac9b9b..c34035974620 100644
--- a/hw/net/igb_migration.c
+++ b/hw/net/igb_migration.c
@@ -10,18 +10,241 @@
#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 "system/address-spaces.h"
#include "trace.h"
+static IGBCore *igbvf_get_core(IgbVfState *s)
+{
+ return igb_pf_get_core(pcie_sriov_get_pf(PCI_DEVICE(s)));
+}
+
/*
* Per-VF state serialization / deserialization
*/
+#define IGB_MIG_BLOB_MAGIC 0x4D494742 /* "MIGB" */
+#define IGB_MIG_BLOB_VERSION 1
+
+typedef struct IgbMigRegPair {
+ uint32_t offset;
+ uint32_t value;
+} IgbMigRegPair;
+
+typedef struct IgbMigTxCtx {
+ uint32_t ctx_desc[8]; /* 2 × adv_tx_context_desc (4
dwords each) */
+ uint32_t first_cmd_type_len;
+ uint32_t first_olinfo_status;
+ uint32_t first;
+ uint32_t skip_cp;
+} IgbMigTxCtx;
+
+#define IGB_VF_MAX_FIXED_REGS 64
+#define IGB_VF_MAX_RA_REGS 48 /* (16 + 8) RA entries × 2
(RAL+RAH) */
+
+typedef struct IgbMigBlob {
+ uint32_t magic;
+ uint32_t version;
+ uint32_t vfn;
+ uint32_t num_regs;
+ IgbMigRegPair regs[IGB_VF_MAX_FIXED_REGS];
+ uint32_t num_ra;
+ IgbMigRegPair ra[IGB_VF_MAX_RA_REGS];
+ uint32_t num_tx_ctx;
+ IgbMigTxCtx tx_ctx[2];
+} IgbMigBlob;
+
+#define IGB_MIG_BLOB_SIZE sizeof(IgbMigBlob)
+
+QEMU_BUILD_BUG_ON(IGB_MIG_BLOB_SIZE > IGB_VF_STATE_MAX_SIZE);
+
+/* Register offsets that constitute a VF's state slice */
+static int igb_vf_reg_list(uint16_t vfn, uint32_t *offsets)
+{
+ int n = 0;
+ int q0 = vfn;
+ int q1 = vfn + IGB_NUM_VM_POOLS;
+
+ /* Per-VF control and interrupt registers */
+ offsets[n++] = E1000_PVTCTRL(vfn) >> 2;
+ offsets[n++] = E1000_PVTEICS(vfn) >> 2;
+ offsets[n++] = E1000_PVTEIMS(vfn) >> 2;
+ offsets[n++] = E1000_PVTEIMC(vfn) >> 2;
+ offsets[n++] = E1000_PVTEIAC(vfn) >> 2;
+ offsets[n++] = E1000_PVTEIAM(vfn) >> 2;
+ offsets[n++] = E1000_PVTEICR(vfn) >> 2;
+
+ /* Per-VF statistics */
+ offsets[n++] = E1000_PVFGPRC(vfn) >> 2;
+ offsets[n++] = E1000_PVFGPTC(vfn) >> 2;
+ offsets[n++] = E1000_PVFGORC(vfn) >> 2;
+ offsets[n++] = E1000_PVFGOTC(vfn) >> 2;
+ offsets[n++] = E1000_PVFMPRC(vfn) >> 2;
+ offsets[n++] = E1000_PVFGPRLBC(vfn) >> 2;
+ offsets[n++] = E1000_PVFGPTLBC(vfn) >> 2;
+ offsets[n++] = E1000_PVFGORLBC(vfn) >> 2;
+ offsets[n++] = E1000_PVFGOTLBC(vfn) >> 2;
+
+ /*
+ * Mailbox control registers only - the 16-dword payload buffer
+ * (VMBMEM) is transient and drained on quiesce.
+ */
+ offsets[n++] = E1000_V2PMAILBOX(vfn) >> 2;
+ offsets[n++] = E1000_P2VMAILBOX(vfn) >> 2;
+
+ /* Per-VF config */
+ offsets[n++] = E1000_VMOLR(vfn) >> 2;
+ offsets[n++] = E1000_VMVIR(vfn) >> 2;
+ offsets[n++] = E1000_PSRTYPE(vfn) >> 2;
+
+ /*
+ * VF receive addresses (RA/RA2) are saved dynamically in
+ * igb_core_vf_save_state by scanning for entries whose pool
+ * bits match this VF - the PF driver chooses the RA slot.
+ */
+
+ /* Interrupt routing */
+ offsets[n++] = (E1000_VTIVAR + vfn * 4) >> 2;
+ offsets[n++] = (E1000_VTIVAR_MISC + vfn * 4) >> 2;
+
+ /*
+ * EITR (Extended Interrupt Throttle Register) - 3 vectors per VF.
+ * Each VF has 3 MSI-X vectors, each with its own EITR controlling
+ * interrupt coalescing. Without saving these, interrupt
+ * throttling resets to zero after migration which can cause
+ * interrupt storms or latency changes. VF N uses PF EITR indices
+ * (22 - N*3) .. (24 - N*3).
+ */
+ {
+ int eitr_base = 22 - vfn * 3;
+ offsets[n++] = E1000_EITR(eitr_base) >> 2;
+ offsets[n++] = E1000_EITR(eitr_base + 1) >> 2;
+ offsets[n++] = E1000_EITR(eitr_base + 2) >> 2;
+ }
+
+ /* RX and TX queue registers for queues q0 and q1 */
+#define ADD_QUEUE_REGS(q) do { \
+ offsets[n++] = E1000_RDBAL(q) >> 2; \
+ offsets[n++] = E1000_RDBAH(q) >> 2; \
+ offsets[n++] = E1000_RDLEN(q) >> 2; \
+ offsets[n++] = E1000_SRRCTL(q) >> 2; \
+ offsets[n++] = E1000_RDH(q) >> 2; \
+ offsets[n++] = E1000_RDT(q) >> 2; \
+ offsets[n++] = E1000_RXDCTL(q) >> 2; \
+ offsets[n++] = E1000_RXCTL(q) >> 2; \
+ offsets[n++] = E1000_RQDPC(q) >> 2; \
+ offsets[n++] = E1000_TDBAL(q) >> 2; \
+ offsets[n++] = E1000_TDBAH(q) >> 2; \
+ offsets[n++] = E1000_TDLEN(q) >> 2; \
+ offsets[n++] = E1000_TDH(q) >> 2; \
+ offsets[n++] = E1000_TDT(q) >> 2; \
+ offsets[n++] = E1000_TXDCTL(q) >> 2; \
+ offsets[n++] = E1000_TXCTL(q) >> 2; \
+ offsets[n++] = E1000_TDWBAL(q) >> 2; \
+ offsets[n++] = E1000_TDWBAH(q) >> 2; \
+} while (0)
+
+ ADD_QUEUE_REGS(q0);
+ ADD_QUEUE_REGS(q1);
+#undef ADD_QUEUE_REGS
+
+ g_assert(n <= IGB_VF_MAX_FIXED_REGS);
+ return n;
+}
+
+/*
+ * Scan RA and RA2 arrays for receive address entries assigned to
+ * this VF. The PF driver picks the RA slot, so we cannot use a
+ * fixed index - instead check each entry's pool bits.
+ */
+static int igb_core_vf_save_ra(IGBCore *core, uint16_t vfn,
+ IgbMigRegPair *regs)
+{
+ uint32_t vf_pool_bit = E1000_RAH_POOL_1 << vfn;
+ int n = 0;
+ static const struct {
+ uint32_t base;
+ int count;
+ } ra_banks[] = {
+ { RA, 16 },
+ { RA2, 8 },
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(ra_banks); i++) {
+ for (int j = 0; j < ra_banks[i].count; j++) {
+ uint32_t ral_off = ra_banks[i].base + j * 2;
+ uint32_t rah_off = ra_banks[i].base + j * 2 + 1;
+ uint32_t rah_val = core->mac[rah_off];
+
+ if ((rah_val & E1000_RAH_AV) && (rah_val & vf_pool_bit)) {
+ regs[n].offset = cpu_to_le32(ral_off);
+ regs[n].value = cpu_to_le32(core->mac[ral_off]);
+ n++;
+ regs[n].offset = cpu_to_le32(rah_off);
+ regs[n].value = cpu_to_le32(rah_val);
+ n++;
+ }
+ }
+ }
+ return n;
+}
+
+static void igb_core_vf_save_tx_ctx(IGBCore *core, int queue,
+ IgbMigTxCtx *tx)
+{
+ struct igb_tx *src = &core->tx[queue];
+
+ memcpy(tx->ctx_desc, src->ctx, sizeof(tx->ctx_desc));
+ tx->first_cmd_type_len = cpu_to_le32(src->first_cmd_type_len);
+ tx->first_olinfo_status = cpu_to_le32(src->first_olinfo_status);
+ tx->first = cpu_to_le32(src->first);
+ tx->skip_cp = cpu_to_le32(src->skip_cp);
+}
+
static int igb_core_vf_save_state(IgbVfState *s, void *buf, size_t
buf_size)
{
- int size = 0;
+ int size = IGB_MIG_BLOB_SIZE;
+ IGBCore *core = igbvf_get_core(s);
+ IgbMigBlob *blob = buf;
+ uint32_t offsets[IGB_VF_MAX_FIXED_REGS];
+ int num_regs;
+ int q0 = s->vfn;
+ int q1 = s->vfn + IGB_NUM_VM_POOLS;
+
+ /*
+ * Save PVT shadow registers (PVTEIMS/PVTEIAC/PVTEIAM) instead of
+ * extracting from PF aggregates - the L1 PF driver may have
+ * transiently cleared EIMS via EIMC. The load path ORs them back.
+ */
+ num_regs = igb_vf_reg_list(s->vfn, offsets);
+
+ if (!buf) {
+ return size;
+ }
+
+ if (size > buf_size) {
+ return -IGB_MIG_ERR_BAD_SIZE;
+ }
+
+ blob->magic = cpu_to_le32(IGB_MIG_BLOB_MAGIC);
+ blob->version = cpu_to_le32(IGB_MIG_BLOB_VERSION);
+ blob->vfn = cpu_to_le32(s->vfn);
+
+ blob->num_regs = cpu_to_le32(num_regs);
+ for (int i = 0; i < num_regs; i++) {
+ blob->regs[i].offset = cpu_to_le32(offsets[i]);
+ blob->regs[i].value = cpu_to_le32(core->mac[offsets[i]]);
+ }
+
+ blob->num_ra = cpu_to_le32(igb_core_vf_save_ra(core, s->vfn,
blob->ra));