Quiesce VFs by clearing their VFRE/VFTE bits on transitions to STOP (from RUNNING, PRE_COPY, or ERROR) and to STOP_COPY (from PRE_COPY). This prevents further DMA while device state is being serialized. Restore the saved VFRE/VFTE state on STOP->RUNNING so the VF can resume normal operation.
Include the per-VF VFRE/VFTE enable bits in the migration blob so the destination knows whether receive and transmit were active before quiesce. On reset, both default to true. Add a QUIESCED bit to the STATUS register so the driver can confirm DMA has been drained before reading device state. Clear the VFLRE (VF Level Reset Event) bit before restoring state on the destination to prevent the PF watchdog from seeing a stale reset indication and overwriting the loaded registers. AI-used-for: analysis, code (prototype) Signed-off-by: Cédric Le Goater <[email protected]> --- hw/net/igb_migration.h | 6 ++- hw/net/igb_migration.c | 86 +++++++++++++++++++++++++++++++++++++++++- hw/net/trace-events | 6 ++- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/hw/net/igb_migration.h b/hw/net/igb_migration.h index 996de2d2f50b..2bb9a0ed36ce 100644 --- a/hw/net/igb_migration.h +++ b/hw/net/igb_migration.h @@ -24,7 +24,8 @@ * +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]) + * +0x14 STATUS (RO: state[7:0], error_code[15:8], + * QUIESCED[16]) * +0x18 BUF_ADDR_LO (RW: shared buffer GPA low) * +0x1C BUF_ADDR_HI (RW: shared buffer GPA high) * +0x20 DATA_SIZE (RO: max state blob size in bytes) @@ -69,6 +70,7 @@ #define IGB_MIG_STATUS_ERROR_CODE_SHIFT 8 #define IGB_MIG_STATUS_ERR(code) \ ((uint32_t)(code) << IGB_MIG_STATUS_ERROR_CODE_SHIFT) +#define IGB_MIG_STATUS_QUIESCED (1u << 16) /* Device states (based on VFIO migration v2) */ #define IGB_MIG_STATE_ERROR 0 @@ -114,6 +116,8 @@ typedef struct IgbVfMigState { uint32_t mig_data[IGB_VF_STATE_MAX_SIZE / sizeof(uint32_t)]; uint32_t mig_data_size; uint64_t mig_data_buf_addr; + bool mig_saved_vfre; + bool mig_saved_vfte; } IgbVfMigState; /* diff --git a/hw/net/igb_migration.c b/hw/net/igb_migration.c index cfa73673e39a..435bd0d1623b 100644 --- a/hw/net/igb_migration.c +++ b/hw/net/igb_migration.c @@ -58,6 +58,8 @@ typedef struct IgbMigBlob { IgbMigRegPair ra[IGB_VF_MAX_RA_REGS]; uint32_t num_tx_ctx; IgbMigTxCtx tx_ctx[2]; + uint32_t vfre; + uint32_t vfte; } IgbMigBlob; #define IGB_MIG_BLOB_SIZE sizeof(IgbMigBlob) @@ -210,6 +212,7 @@ static void igb_core_vf_save_tx_ctx(IGBCore *core, int queue, static int igb_core_vf_save_state(IgbVfState *s, void *buf, size_t buf_size) { int size = IGB_MIG_BLOB_SIZE; + IgbVfMigState *ms = &s->mig; IGBCore *core = igbvf_get_core(s); IgbMigBlob *blob = buf; uint32_t offsets[IGB_VF_MAX_FIXED_REGS]; @@ -248,7 +251,12 @@ static int igb_core_vf_save_state(IgbVfState *s, void *buf, size_t buf_size) igb_core_vf_save_tx_ctx(core, q0, &blob->tx_ctx[0]); igb_core_vf_save_tx_ctx(core, q1, &blob->tx_ctx[1]); - trace_igbvf_mig_save_state(s->vfn, size); + blob->vfre = cpu_to_le32(ms->mig_saved_vfre); + blob->vfte = cpu_to_le32(ms->mig_saved_vfte); + + trace_igbvf_mig_save_state(s->vfn, size, ms->mig_saved_vfre, + ms->mig_saved_vfte, + core->mac[VFRE]); return size; } @@ -291,6 +299,7 @@ static uint32_t igb_vf_relocate_offset(uint32_t offset, static int igb_core_vf_load_state(IgbVfState *s, const void *buf, size_t size) { + IgbVfMigState *ms = &s->mig; IGBCore *core = igbvf_get_core(s); uint32_t src_offsets[IGB_VF_MAX_FIXED_REGS]; uint32_t dst_offsets[IGB_VF_MAX_FIXED_REGS]; @@ -379,7 +388,12 @@ static int igb_core_vf_load_state(IgbVfState *s, const void *buf, size_t size) igb_core_vf_load_tx_ctx(core, q0, &blob->tx_ctx[0]); igb_core_vf_load_tx_ctx(core, q1, &blob->tx_ctx[1]); - trace_igbvf_mig_load_state(s->vfn, (uint32_t)size); + ms->mig_saved_vfre = !!le32_to_cpu(blob->vfre); + ms->mig_saved_vfte = !!le32_to_cpu(blob->vfte); + + trace_igbvf_mig_load_state(s->vfn, (uint32_t)size, + ms->mig_saved_vfre, + ms->mig_saved_vfte); return 0; } @@ -388,6 +402,12 @@ static int igbvf_mig_load(IgbVfState *s, const void *buf, size_t size) int ret; IGBCore *core = igbvf_get_core(s); + /* + * Pre-load: Clear the VFLRE bit before restoring state so the PF + * watchdog does not overwrite what we are about to load. + */ + core->mac[VFLRE] &= ~BIT(s->vfn); + ret = igb_core_vf_load_state(s, buf, size); if (ret < 0) { return ret; @@ -759,6 +779,45 @@ static uint8_t igbvf_mig_cmd_load(IgbVfState *s) return 0; } +/* Quiesce a VF by disabling its RX and TX at the PF level. */ +static void igb_core_vf_quiesce(IgbVfState *s) +{ + IgbVfMigState *ms = &s->mig; + IGBCore *core = igbvf_get_core(s); + + ms->mig_saved_vfre = !!(core->mac[VFRE] & BIT(s->vfn)); + ms->mig_saved_vfte = !!(core->mac[VFTE] & BIT(s->vfn)); + + core->mac[VFRE] &= ~BIT(s->vfn); + core->mac[VFTE] &= ~BIT(s->vfn); + trace_igbvf_mig_quiesce(s->vfn, core->mac[VFRE], core->mac[VFTE]); +} + +static void igb_core_vf_unquiesce(IgbVfState *s) +{ + IgbVfMigState *ms = &s->mig; + IGBCore *core = igbvf_get_core(s); + bool re = ms->mig_saved_vfre; + bool te = ms->mig_saved_vfte; + + if (re) { + core->mac[VFRE] |= BIT(s->vfn); + } else { + core->mac[VFRE] &= ~BIT(s->vfn); + } + if (te) { + core->mac[VFTE] |= BIT(s->vfn); + } else { + core->mac[VFTE] &= ~BIT(s->vfn); + } + + trace_igbvf_mig_unquiesce(s->vfn, core->mac[VFRE], core->mac[VFTE]); + + if (re) { + igb_start_recv(core); + } +} + static uint8_t igbvf_mig_set_state(IgbVfState *s, uint32_t new_state) { IgbVfMigState *ms = &s->mig; @@ -779,6 +838,11 @@ static uint8_t igbvf_mig_set_state(IgbVfState *s, uint32_t new_state) old == IGB_MIG_STATE_ERROR) { igb_core_vf_dirty_disable(s); } + if (old == IGB_MIG_STATE_RUNNING || + old == IGB_MIG_STATE_PRE_COPY || + old == IGB_MIG_STATE_ERROR) { + igb_core_vf_quiesce(s); + } /* Restore DATA_SIZE to max, same as at reset */ igbvf_mig_update_data_size(s, igb_core_vf_max_data_size(s)); break; @@ -791,6 +855,9 @@ static uint8_t igbvf_mig_set_state(IgbVfState *s, uint32_t new_state) if (old == IGB_MIG_STATE_PRE_COPY) { igb_core_vf_dirty_disable(s); } + if (old == IGB_MIG_STATE_STOP) { + igb_core_vf_unquiesce(s); + } break; case IGB_MIG_STATE_STOP_COPY: @@ -798,6 +865,9 @@ static uint8_t igbvf_mig_set_state(IgbVfState *s, uint32_t new_state) old != IGB_MIG_STATE_PRE_COPY) { return IGB_MIG_ERR_BAD_STATE; } + if (old == IGB_MIG_STATE_PRE_COPY) { + igb_core_vf_quiesce(s); + } ret = igb_core_vf_save_state(s, ms->mig_data, sizeof(ms->mig_data)); if (ret < 0) { return -ret; @@ -840,6 +910,16 @@ static void igbvf_mig_update_status(IgbVfState *s, uint8_t err) status = IGB_MIG_STATE_ERROR | IGB_MIG_STATUS_ERR(err); } + /* + * QUIESCED tells the driver it is safe to read device state. + * In STOP and STOP_COPY, igb_core_vf_quiesce() has already + * cleared VFRE/VFTE so no further VF DMA can occur. + */ + if (ms->mig_state == IGB_MIG_STATE_STOP || + ms->mig_state == IGB_MIG_STATE_STOP_COPY) { + status |= IGB_MIG_STATUS_QUIESCED; + } + pci_set_long(dev->config + IGB_MIG_DVSEC_OFFSET + IGB_MIG_STATUS, status); } @@ -977,6 +1057,8 @@ void igbvf_mig_state_reset(IgbVfState *s) ms->mig_data_buf_addr = 0; igbvf_mig_update_data_size(s, igb_core_vf_max_data_size(s)); memset(ms->mig_data, 0, sizeof(ms->mig_data)); + ms->mig_saved_vfre = true; + ms->mig_saved_vfte = true; pci_set_long(PCI_DEVICE(s)->config + IGB_MIG_DVSEC_OFFSET + IGB_MIG_BUF_ADDR_LO, 0); diff --git a/hw/net/trace-events b/hw/net/trace-events index c606191df2ef..eb5c61a8f6fb 100644 --- a/hw/net/trace-events +++ b/hw/net/trace-events @@ -297,14 +297,16 @@ igbvf_wrn_io_addr_unknown(uint64_t addr) "IO unknown register 0x%"PRIx64 # igb_migration.c igbvf_mig_set_state(uint16_t vfn, uint32_t old_state, uint32_t new_state) "VF%u: state %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_save_state(uint16_t vfn, int size, bool vfre, bool vfte, uint32_t reg_vfre) "VF%u: saved %d bytes vfre=%d vfte=%d VFRE=0x%x" +igbvf_mig_load_state(uint16_t vfn, uint32_t size, bool vfre, bool vfte) "VF%u: loaded %u bytes vfre=%d vfte=%d" 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" +igbvf_mig_quiesce(uint16_t vfn, uint32_t vfre, uint32_t vfte) "VF%u: quiesce VFRE=0x%x VFTE=0x%x" +igbvf_mig_unquiesce(uint16_t vfn, uint32_t vfre, uint32_t vfte) "VF%u: unquiesce VFRE=0x%x VFTE=0x%x" # 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 -- 2.55.0
