@@ -40,6 +40,8 @@
#ifndef HW_NET_IGB_CORE_H
#define HW_NET_IGB_CORE_H
+#include "igb_migration.h"
+
#define E1000E_MAC_SIZE (0x8000)
#define IGB_EEPROM_SIZE (1024)
@@ -99,6 +101,8 @@ struct IGBCore {
void (*owner_start_recv)(PCIDevice *d);
int64_t timadj;
+
+ IGBVfDirtyState vf_dirty[IGB_MAX_VF_FUNCTIONS];
};
void
@@ -21,7 +21,8 @@
* +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])
+ * +0x0C CAPS (RO: F_STATE[0], F_DIRTY[1],
+ * max_ranges[11:8], pgsize[16:12])
* +0x10 CTRL (WO: doorbell command)
* +0x14 STATUS (RO: state[7:0], error_code[15:8])
* +0x18 BUF_ADDR_LO (RW: shared buffer GPA low)
@@ -44,6 +45,12 @@
/* CAPS register layout */
#define IGB_MIG_CAP_F_STATE (1u << 0)
+#define IGB_MIG_CAP_F_DIRTY (1u << 1)
+#define IGB_MIG_CAPS_MAX_RANGES_SHIFT 8
+#define IGB_MIG_CAPS_MAX_RANGES 4
+#define IGB_MIG_CAPS_PGSIZE_SHIFT 12
+#define IGB_MIG_CAPS_PGSIZE_4K (1u << 12)
+#define IGB_MIG_CAPS_PGSIZE_64K (1u << 16)
/* CTRL register: command in [7:0] */
#define IGB_MIG_CTRL_CMD_MASK 0xFF
@@ -53,6 +60,9 @@
#define IGB_MIG_CMD_SET_STATE 1
#define IGB_MIG_CMD_SAVE 2
#define IGB_MIG_CMD_LOAD 3
+#define IGB_MIG_CMD_DIRTY_ENABLE 4
+#define IGB_MIG_CMD_DIRTY_DISABLE 5
+#define IGB_MIG_CMD_DIRTY_QUERY 6
/* STATUS register: state in [7:0], error code [15:8] */
#define IGB_MIG_STATUS_STATE_MASK 0xFF
@@ -66,6 +76,7 @@
#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
/* Error codes */
#define IGB_MIG_ERR_UNK_CMD 1
@@ -75,10 +86,29 @@
#define IGB_MIG_ERR_BAD_SIZE 5
#define IGB_MIG_ERR_BAD_MAGIC 6
#define IGB_MIG_ERR_BAD_VERSION 7
+#define IGB_MIG_ERR_TOO_MANY_RANGES 8
+#define IGB_MIG_ERR_BAD_RANGE 9
+#define IGB_MIG_ERR_BAD_PGSIZE 10
+#define IGB_MIG_ERR_NOT_ENABLED 11
/* Shared buffer constants */
#define IGB_VF_STATE_MAX_SIZE 4096
+#define IGB_MIG_DIRTY_DEFAULT_PGSIZE 4096
+
+typedef struct IGBVfDirtyRange {
+ uint64_t iova;
+ uint64_t size;
+ uint64_t page_size;
+ unsigned long *bitmap;
+ uint64_t nbits;
+} IGBVfDirtyRange;
+
+typedef struct IGBVfDirtyState {
+ IGBVfDirtyRange ranges[IGB_MIG_CAPS_MAX_RANGES];
+ uint32_t num_ranges;
+} IGBVfDirtyState;
+
typedef struct IgbVfMigState {
uint32_t mig_state;
uint32_t mig_data[IGB_VF_STATE_MAX_SIZE / sizeof(uint32_t)];
@@ -86,6 +116,36 @@ typedef struct IgbVfMigState {
uint64_t mig_data_buf_addr;
} IgbVfMigState;
+/*
+ * DMA buffer layouts for dirty tracking commands.
+ *
+ * DIRTY_ENABLE: driver writes igb_mig_dirty_enable_req to buffer
+ * before cmd.
+ * DIRTY_QUERY: driver writes iova/size fields, device writes
+ * response + bitmap.
+ */
+struct igb_mig_dirty_enable_req {
+ uint32_t len;
+ uint32_t flags;
+ uint64_t pgsize;
+ uint64_t range_iova;
+ uint64_t range_size;
+ uint32_t reserved[4];
+};
+
+struct igb_mig_dirty_query {
+ uint32_t len;
+ uint32_t flags;
+ uint64_t iova;
+ uint64_t size;
+ uint32_t bitmap_size;
+ uint32_t dirty_page_count;
+ uint64_t dma_writes;
+ uint32_t reserved[6];
+ uint8_t bitmap[];
+};
+
+typedef struct IGBCore IGBCore;
typedef struct IgbVfState IgbVfState;
bool igbvf_add_migration_dvsec(PCIDevice *dev, Error **errp);
@@ -94,4 +154,7 @@ 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);
+void igb_core_dirty_track_dma(IGBCore *core, int vfn,
+ dma_addr_t addr, dma_addr_t len);
+
#endif
@@ -824,6 +824,16 @@ igb_rx_ring_init(IGBCore *core, E1000E_RxRing *rxr, int idx)
rxr->i = &i[idx];
}
+static inline void
+igb_pci_dma_write(IGBCore *core, PCIDevice *dev,
+ dma_addr_t addr, const void *buf, dma_addr_t len)
+{
+ pci_dma_write(dev, addr, buf, len);
+ if (pci_is_vf(dev)) {
+ igb_core_dirty_track_dma(core, pcie_sriov_vf_number(dev), addr, len);
+ }
+}
+
static uint32_t
igb_txdesc_writeback(IGBCore *core, dma_addr_t base,
union e1000_adv_tx_desc *tx_desc,
@@ -847,13 +857,15 @@ igb_txdesc_writeback(IGBCore *core, dma_addr_t base,
if (tdwba & 1) {
uint32_t buffer = cpu_to_le32(core->mac[txi->dh]);
- pci_dma_write(d, tdwba & ~3, &buffer, sizeof(buffer));
+ igb_pci_dma_write(core, d,
+ tdwba & ~3, &buffer, sizeof(buffer));
} else {
uint32_t status = le32_to_cpu(tx_desc->wb.status) | E1000_TXD_STAT_DD;
tx_desc->wb.status = cpu_to_le32(status);
- pci_dma_write(d, base + offsetof(union e1000_adv_tx_desc, wb),
- &tx_desc->wb, sizeof(tx_desc->wb));
+ igb_pci_dma_write(core, d,
+ base + offsetof(union e1000_adv_tx_desc, wb),
+ &tx_desc->wb, sizeof(tx_desc->wb));
}
return igb_tx_wb_eic(core, txi->idx);
@@ -1598,11 +1610,12 @@ igb_pci_dma_write_rx_desc(IGBCore *core, PCIDevice *dev, dma_addr_t addr,
uint8_t status = d->status;
d->status &= ~E1000_RXD_STAT_DD;
- pci_dma_write(dev, addr, desc, len);
+ igb_pci_dma_write(core, dev, addr, desc, len);
if (status & E1000_RXD_STAT_DD) {
d->status = status;
- pci_dma_write(dev, addr + offset, &status, sizeof(status));
+ igb_pci_dma_write(core, dev,
+ addr + offset, &status, sizeof(status));
}
} else {
union e1000_adv_rx_desc *d = &desc->adv;
@@ -1611,11 +1624,12 @@ igb_pci_dma_write_rx_desc(IGBCore *core, PCIDevice *dev, dma_addr_t addr,
uint32_t status = d->wb.upper.status_error;
d->wb.upper.status_error &= ~E1000_RXD_STAT_DD;
- pci_dma_write(dev, addr, desc, len);
+ igb_pci_dma_write(core, dev, addr, desc, len);
if (status & E1000_RXD_STAT_DD) {
d->wb.upper.status_error = status;
- pci_dma_write(dev, addr + offset, &status, sizeof(status));
+ igb_pci_dma_write(core, dev,
+ addr + offset, &status, sizeof(status));
}
}
}
@@ -1737,9 +1751,9 @@ igb_write_hdr_frag_to_rx_buffers(IGBCore *core,
{
assert(data_len <= pdma_st->rx_desc_header_buf_size -
pdma_st->bastate.written[0]);
- pci_dma_write(d,
- pdma_st->ba[0] + pdma_st->bastate.written[0],
- data, data_len);
+ igb_pci_dma_write(core, d,
+ pdma_st->ba[0] + pdma_st->bastate.written[0],
+ data, data_len);
pdma_st->bastate.written[0] += data_len;
pdma_st->bastate.cur_idx = 1;
}
@@ -1804,10 +1818,10 @@ igb_write_payload_frag_to_rx_buffers(IGBCore *core,
data,
bytes_to_write);
- pci_dma_write(d,
- pdma_st->ba[pdma_st->bastate.cur_idx] +
- pdma_st->bastate.written[pdma_st->bastate.cur_idx],
- data, bytes_to_write);
+ igb_pci_dma_write(core, d,
+ pdma_st->ba[pdma_st->bastate.cur_idx] +
+ pdma_st->bastate.written[pdma_st->bastate.cur_idx],
+ data, bytes_to_write);
pdma_st->bastate.written[pdma_st->bastate.cur_idx] += bytes_to_write;
data += bytes_to_write;
@@ -8,6 +8,8 @@
#include "qemu/osdep.h"
#include "qemu/log.h"
+#include "qemu/bitmap.h"
+#include "qemu/units.h"
#include "hw/pci/pci_device.h"
#include "hw/pci/pcie.h"
#include "net/eth.h"
@@ -400,6 +402,285 @@ static int igbvf_mig_load(IgbVfState *s, const void *buf, size_t size)
return 0;
}
+/*
+ * Per-VF dirty page tracking
+ *
+ * All VF DMA writes in igb_core.c go through igb_pci_dma_write(),
+ * which calls igb_core_dirty_track_dma() to mark the target page in a
+ * per-range bitmap before performing the actual DMA.
+ *
+ * The IGBCore::vf_dirty[] bitmaps live in IGBCore so they are easily
+ * accessible from the core TX and RX paths without reaching back into
+ * VF state.
+ */
+
+void igb_core_dirty_track_dma(IGBCore *core, int vfn,
+ dma_addr_t addr, dma_addr_t len)
+{
+ IGBVfDirtyState *ds = &core->vf_dirty[vfn];
+ bool matched = false;
+ uint32_t i;
+
+ if (!ds->num_ranges) {
+ return;
+ }
+
+ trace_igb_core_dirty_track_dma(vfn, addr, len);
+
+ for (i = 0; i < ds->num_ranges; i++) {
+ IGBVfDirtyRange *r = &ds->ranges[i];
+ uint64_t r_end = r->iova + r->size;
+ uint64_t dma_end = addr + len;
+ uint64_t start, end, start_page, end_page, page;
+
+ if (addr >= r_end || dma_end <= r->iova) {
+ continue;
+ }
+
+ matched = true;
+ start = MAX(addr, r->iova);
+ end = MIN(dma_end, r_end);
+
+ start_page = (start - r->iova) / r->page_size;
+ end_page = (end - 1 - r->iova) / r->page_size;
+
+ for (page = start_page; page <= end_page; page++) {
+ if (page < r->nbits) {
+ set_bit(page, r->bitmap);
+ }
+ }
+ }
+
+ if (!matched) {
+ trace_igb_core_dirty_track_dma_drop(vfn, addr, len);
+ }
+}
+
+static IGBVfDirtyState *igb_core_vf_dirty_state(IgbVfState *s)
+{
+ IGBCore *core = igbvf_get_core(s);
+ return &core->vf_dirty[s->vfn];
+}
+
+#define IGB_MIG_DIRTY_MAX_PAGES ((256ULL * GiB) / (4 * KiB))
+
+static uint32_t igb_core_vf_dirty_enable(IgbVfState *s, uint64_t pgsize,
+ uint64_t range_iova,
+ uint64_t range_size)
+{
+ uint32_t caps = pci_get_long(PCI_DEVICE(s)->config +
+ IGB_MIG_DVSEC_OFFSET + IGB_MIG_CAPS);
+ IGBVfDirtyState *ds = igb_core_vf_dirty_state(s);
+ IGBVfDirtyRange *r;
+
+ if (ds->num_ranges >= IGB_MIG_CAPS_MAX_RANGES) {
+ return IGB_MIG_ERR_TOO_MANY_RANGES;
+ }
+
+ if (!range_size) {
+ return IGB_MIG_ERR_BAD_RANGE;
+ }
+
+ /* Validate page size against CAPS supported page size bitmask */
+ if (!is_power_of_2(pgsize) || !(pgsize & caps)) {
+ return IGB_MIG_ERR_BAD_PGSIZE;
+ }
+
+ if ((range_iova % pgsize) || (range_size % pgsize)) {
+ return IGB_MIG_ERR_BAD_PGSIZE;
+ }
+
+ if (range_size / pgsize > IGB_MIG_DIRTY_MAX_PAGES) {
+ return IGB_MIG_ERR_BAD_RANGE;
+ }
+
+ r = &ds->ranges[ds->num_ranges];
+ r->iova = range_iova;
+ r->size = range_size;
+ r->page_size = pgsize;
+ r->nbits = range_size / pgsize;
+ r->bitmap = bitmap_new(r->nbits);
+ ds->num_ranges++;
+ return 0;
+}
+
+static void igb_core_vf_dirty_disable(IgbVfState *s)
+{
+ IGBVfDirtyState *ds = igb_core_vf_dirty_state(s);
+ uint32_t i;
+
+ for (i = 0; i < ds->num_ranges; i++) {
+ IGBVfDirtyRange *r = &ds->ranges[i];
+
+ g_free(r->bitmap);
+ r->bitmap = NULL;
+ r->nbits = 0;
+ }
+ ds->num_ranges = 0;
+ trace_igbvf_mig_dirty_disable(s->vfn);
+}
+
+static bool igb_core_vf_dirty_enabled(IgbVfState *s)
+{
+ return igb_core_vf_dirty_state(s)->num_ranges > 0;
+}
+
+static void igb_core_vf_dirty_query(IGBVfDirtyRange *r,
+ uint64_t range_iova, uint64_t range_size,
+ void *buf, size_t buf_size,
+ size_t *out_size)
+{
+ uint64_t start_page = (range_iova - r->iova) / r->page_size;
+ uint64_t range_pages = range_size / r->page_size;
+ uint64_t count = MIN(range_pages, (uint64_t)buf_size * 8);
+
+ memset(buf, 0, buf_size);
+
+ if (start_page < r->nbits) {
+ uint64_t avail = r->nbits - start_page;
+ uint64_t n = MIN(count, avail);
+
+ bitmap_copy_with_src_offset(buf, r->bitmap, start_page, n);
+ }
+ *out_size = bitmap_empty(buf, count) ? 0 : DIV_ROUND_UP(count, 8);
+}
+
+static void igb_core_vf_dirty_query_commit(IGBVfDirtyRange *r,
+ uint64_t range_iova,
+ uint64_t range_size)
+{
+ uint64_t start_page = (range_iova - r->iova) / r->page_size;
+
+ if (start_page < r->nbits) {
+ uint64_t avail = r->nbits - start_page;
+ uint64_t range_pages = range_size / r->page_size;
+ uint64_t n = MIN(range_pages, avail);
+
+ bitmap_clear(r->bitmap, start_page, n);
+ }
+}
+
+static uint8_t igbvf_mig_cmd_dirty_enable(IgbVfState *s)
+{
+ IgbVfMigState *ms = &s->mig;
+ struct igb_mig_dirty_enable_req req;
+ MemTxResult r;
+ uint32_t status;
+
+ if (!ms->mig_data_buf_addr) {
+ return IGB_MIG_ERR_NO_BUFFER;
+ }
+
+ r = address_space_read(&address_space_memory, ms->mig_data_buf_addr,
+ MEMTXATTRS_UNSPECIFIED, &req, sizeof(req));
+ if (r != MEMTX_OK) {
+ return IGB_MIG_ERR_DMA_FAILED;
+ }
+
+ /* TODO: validate req.len and req.flags */
+
+ status = igb_core_vf_dirty_enable(s, le64_to_cpu(req.pgsize),
+ le64_to_cpu(req.range_iova),
+ le64_to_cpu(req.range_size));
+ if (status != 0) {
+ return status;
+ }
+ trace_igbvf_mig_dirty_enable(s->vfn, le64_to_cpu(req.pgsize),
+ le64_to_cpu(req.range_size) /
+ le64_to_cpu(req.pgsize));
+ return 0;
+}
+
+static IGBVfDirtyRange *igb_core_vf_dirty_range_valid(IGBVfDirtyState *ds,
+ uint64_t range_iova,
+ uint64_t range_size)
+{
+ if (!range_size) {
+ return NULL;
+ }
+
+ for (uint32_t i = 0; i < ds->num_ranges; i++) {
+ IGBVfDirtyRange *r = &ds->ranges[i];
+
+ if (range_iova >= r->iova &&
+ range_iova + range_size <= r->iova + r->size &&
+ QEMU_IS_ALIGNED(range_iova, r->page_size) &&
+ QEMU_IS_ALIGNED(range_size, r->page_size)) {
+ return r;
+ }
+ }
+ return NULL;
+}
+
+static uint8_t igbvf_mig_cmd_dirty_query(IgbVfState *s)
+{
+ IgbVfMigState *ms = &s->mig;
+ IGBVfDirtyState *ds = &igbvf_get_core(s)->vf_dirty[s->vfn];
+ uint64_t buf_addr = ms->mig_data_buf_addr;
+ uint64_t range_iova = 0, range_size = 0;
+ IGBVfDirtyRange *range;
+ uint32_t bmp_bytes, dirty_pages;
+ uint32_t val32;
+ size_t out_size;
+ g_autofree void *bitmap = NULL;
+
+ if (!buf_addr) {
+ return IGB_MIG_ERR_NO_BUFFER;
+ }
+
+ if (!igb_core_vf_dirty_enabled(s)) {
+ return IGB_MIG_ERR_NOT_ENABLED;
+ }
+
+ address_space_read(&address_space_memory,
+ buf_addr + offsetof(struct igb_mig_dirty_query, iova),
+ MEMTXATTRS_UNSPECIFIED, &range_iova, sizeof(range_iova));
+ range_iova = le64_to_cpu(range_iova);
+ address_space_read(&address_space_memory,
+ buf_addr + offsetof(struct igb_mig_dirty_query, size),
+ MEMTXATTRS_UNSPECIFIED, &range_size, sizeof(range_size));
+ range_size = le64_to_cpu(range_size);
+
+ range = igb_core_vf_dirty_range_valid(ds, range_iova, range_size);
+ if (!range) {
+ return IGB_MIG_ERR_BAD_RANGE;
+ }
+
+ bmp_bytes = BITS_TO_LONGS(range_size / range->page_size) *
+ sizeof(unsigned long);
+ bitmap = g_malloc0(bmp_bytes);
+
+ igb_core_vf_dirty_query(range, range_iova, range_size,
+ bitmap, bmp_bytes, &out_size);
+
+ if (out_size) {
+ if (address_space_write(&address_space_memory,
+ buf_addr +
+ offsetof(struct igb_mig_dirty_query, bitmap),
+ MEMTXATTRS_UNSPECIFIED, bitmap, out_size)) {
+ return IGB_MIG_ERR_DMA_FAILED;
+ }
+ }
+
+ igb_core_vf_dirty_query_commit(range, range_iova, range_size);
+
+ dirty_pages = bitmap_count_one(bitmap, range_size / range->page_size);
+
+ val32 = cpu_to_le32(out_size);
+ address_space_write(&address_space_memory,
+ buf_addr + offsetof(struct igb_mig_dirty_query,
+ bitmap_size),
+ MEMTXATTRS_UNSPECIFIED, &val32, sizeof(val32));
+ val32 = cpu_to_le32(dirty_pages);
+ address_space_write(&address_space_memory,
+ buf_addr + offsetof(struct igb_mig_dirty_query,
+ dirty_page_count),
+ MEMTXATTRS_UNSPECIFIED, &val32, sizeof(val32));
+
+ trace_igbvf_mig_dirty_query(s->vfn, (uint64_t)out_size, dirty_pages);
+ return 0;
+}
+
/*
* Migration command handlers
*/
@@ -419,7 +700,8 @@ static uint8_t igbvf_mig_cmd_save(IgbVfState *s)
MemTxResult r;
int ret;
- if (ms->mig_state != IGB_MIG_STATE_STOP_COPY) {
+ if (ms->mig_state != IGB_MIG_STATE_STOP_COPY &&
+ ms->mig_state != IGB_MIG_STATE_PRE_COPY) {
return IGB_MIG_ERR_BAD_STATE;
}
@@ -487,22 +769,33 @@ static uint8_t igbvf_mig_set_state(IgbVfState *s, uint32_t new_state)
case IGB_MIG_STATE_STOP:
if (old != IGB_MIG_STATE_RUNNING &&
old != IGB_MIG_STATE_STOP_COPY &&
+ old != IGB_MIG_STATE_PRE_COPY &&
old != IGB_MIG_STATE_RESUMING &&
old != IGB_MIG_STATE_ERROR) {
return IGB_MIG_ERR_BAD_STATE;
}
+ if (old == IGB_MIG_STATE_PRE_COPY ||
+ old == IGB_MIG_STATE_STOP_COPY ||
+ old == IGB_MIG_STATE_ERROR) {
+ igb_core_vf_dirty_disable(s);
+ }
/* Restore DATA_SIZE to max, same as at reset */
igbvf_mig_update_data_size(s, igb_core_vf_max_data_size(s));
break;
case IGB_MIG_STATE_RUNNING:
- if (old != IGB_MIG_STATE_STOP) {
+ if (old != IGB_MIG_STATE_STOP &&
+ old != IGB_MIG_STATE_PRE_COPY) {
return IGB_MIG_ERR_BAD_STATE;
}
+ if (old == IGB_MIG_STATE_PRE_COPY) {
+ igb_core_vf_dirty_disable(s);
+ }
break;
case IGB_MIG_STATE_STOP_COPY:
- if (old != IGB_MIG_STATE_STOP) {
+ if (old != IGB_MIG_STATE_STOP &&
+ old != IGB_MIG_STATE_PRE_COPY) {
return IGB_MIG_ERR_BAD_STATE;
}
ret = igb_core_vf_save_state(s, ms->mig_data, sizeof(ms->mig_data));
@@ -520,6 +813,12 @@ static uint8_t igbvf_mig_set_state(IgbVfState *s, uint32_t new_state)
igbvf_mig_update_data_size(s, 0);
break;
+ case IGB_MIG_STATE_PRE_COPY:
+ if (old != IGB_MIG_STATE_RUNNING) {
+ return IGB_MIG_ERR_BAD_STATE;
+ }
+ break;
+
default:
return IGB_MIG_ERR_BAD_STATE;
}
@@ -564,6 +863,18 @@ static void igbvf_mig_cmd_ctrl(IgbVfState *s, uint32_t val)
err = igbvf_mig_cmd_load(s);
break;
+ case IGB_MIG_CMD_DIRTY_ENABLE:
+ err = igbvf_mig_cmd_dirty_enable(s);
+ break;
+
+ case IGB_MIG_CMD_DIRTY_DISABLE:
+ igb_core_vf_dirty_disable(s);
+ break;
+
+ case IGB_MIG_CMD_DIRTY_QUERY:
+ err = igbvf_mig_cmd_dirty_query(s);
+ break;
+
default:
err = IGB_MIG_ERR_UNK_CMD;
break;
@@ -594,8 +905,10 @@ bool igbvf_add_migration_dvsec(PCIDevice *dev, Error **errp)
/* 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;
+ /* CAPS: features | max_ranges | supported page sizes (4K) */
+ caps = IGB_MIG_CAP_F_STATE | IGB_MIG_CAP_F_DIRTY |
+ (IGB_MIG_CAPS_MAX_RANGES << IGB_MIG_CAPS_MAX_RANGES_SHIFT) |
+ IGB_MIG_CAPS_PGSIZE_4K;
pci_set_long(dev->config + offset + IGB_MIG_CAPS, caps);
/* STATUS: initial state is RUNNING */
@@ -657,6 +970,9 @@ void igbvf_mig_state_reset(IgbVfState *s)
IgbVfMigState *ms = &s->mig;
trace_igbvf_mig_reset(s->vfn);
+
+ igb_core_vf_dirty_disable(s);
+
ms->mig_state = IGB_MIG_STATE_RUNNING;
ms->mig_data_buf_addr = 0;
igbvf_mig_update_data_size(s, igb_core_vf_max_data_size(s));
@@ -300,6 +300,11 @@ igbvf_mig_set_state(uint16_t vfn, uint32_t old_state, uint32_t new_state) "VF%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"
+igbvf_mig_dirty_enable(uint16_t vfn, uint64_t pgsize, uint64_t nbits) "VF%u: dirty tracking enabled pgsize=%"PRIu64" nbits=%"PRIu64
+igbvf_mig_dirty_disable(uint16_t vfn) "VF%u: dirty tracking disabled"
+igbvf_mig_dirty_query(uint16_t vfn, uint64_t size, uint32_t dirty_pages) "VF%u: dirty query returned %"PRIu64" bytes, %u dirty pages"
+igb_core_dirty_track_dma(int vfn, uint64_t addr, uint64_t len) "VF%d: dirty DMA addr=0x%"PRIx64" len=%"PRIu64
+igb_core_dirty_track_dma_drop(int vfn, uint64_t addr, uint64_t len) "VF%d: dirty DMA dropped addr=0x%"PRIx64" len=%"PRIu64" no matching range"
# 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
Add per-VF dirty page tracking using bitmaps allocated per IOVA range. The DMA data path is instrumented via igb_core_dirty_track_dma() calls at all five DMA write sites: TX descriptor writeback (head pointer and status), RX descriptor writeback, RX header fragment, and RX payload fragment. The migration DVSEC exposes DIRTY_ENABLE, DIRTY_DISABLE and DIRTY_QUERY commands. Range parameters and query results are exchanged through the shared DMA buffer. Add the PRE_COPY device state to support pre-copy live migration with concurrent dirty tracking. PRE_COPY transitions: - RUNNING -> PRE_COPY - PRE_COPY -> STOP, RUNNING, STOP_COPY Dirty tracking is automatically disabled when leaving PRE_COPY or STOP_COPY. AI-used-for: code (prototype) Signed-off-by: Cédric Le Goater <clg@redhat.com> --- hw/net/igb_core.h | 4 + hw/net/igb_migration.h | 65 +++++++- hw/net/igb_core.c | 42 ++++-- hw/net/igb_migration.c | 326 ++++++++++++++++++++++++++++++++++++++++- hw/net/trace-events | 5 + 5 files changed, 422 insertions(+), 20 deletions(-)