Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 30 Jul 2026 14:06:08 +0000
From:      Kevin Bowling <kbowling@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: a77257d60058 - main - igbv: Sanitize retained VF queue state
Message-ID:  <6a6b5a50.1c9e9.1bdeef7c@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=a77257d6005839f2dd540e98f1e9ecd70879a38d

commit a77257d6005839f2dd540e98f1e9ecd70879a38d
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-07-30 08:10:31 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-07-30 14:06:00 +0000

    igbv: Sanitize retained VF queue state
    
    82576 and I350 VFLR leave queue configuration unchanged.  A previous
    VF owner can therefore leave a transmit head-writeback DMA destination
    and other queue policy for the next guest.
    
    After each reset attempt, disable all exposed VF queues and wait for
    their enable bits to clear before clearing SRRCTL, VFPSRTYPE, RXCTRL,
    TXCTRL, and TDWBAL/H.  Spin briefly and then sleep until the bounded
    queue-disable deadline.
    
    iflib cannot report initialization failure and marks an interface
    running after its init callback returns.  On sanitation failure, keep
    interrupts disabled and use the deferred admin task to clear RUNNING.
    Retry after 100 and 500 ms; after three total failures, leave the
    interface down until another administrative initialization starts a
    new bounded attempt set.
    
    igbv uses queue zero on both families, but 82576 exposes a second VF
    queue whose retained state must also be cleared.  Extend the INVARIANTS
    register validator for only those queue-one CSRs and only on 82576.
    
    This implements the VF side of Software Clarification 3 from the 82576
    and I350 specification updates.  It also means an igbv guest does not
    depend on its PF to sanitize a previous VF owner's state.
    
    Sponsored by:   BBOX.io
---
 sys/dev/e1000/e1000_osdep.h |  20 +++-
 sys/dev/e1000/e1000_regs.h  |   1 +
 sys/dev/e1000/if_em.c       |  21 ++++-
 sys/dev/e1000/if_em.h       |  12 +++
 sys/dev/e1000/if_igbv.c     | 216 +++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 265 insertions(+), 5 deletions(-)

diff --git a/sys/dev/e1000/e1000_osdep.h b/sys/dev/e1000/e1000_osdep.h
index 0f6364f9a7b5..1226c0264333 100644
--- a/sys/dev/e1000/e1000_osdep.h
+++ b/sys/dev/e1000/e1000_osdep.h
@@ -171,8 +171,10 @@ struct e1000_osdep
  * the OS accessors so a PF-only register that leaks into the shared VF path
  * fails at its first access instead of returning reserved-register garbage.
  *
- * The driver intentionally uses only queue pair zero.  Expanding VF queue
- * support must extend this predicate from the applicable device CSR map.
+ * The driver intentionally uses only queue pair zero.  It also clears the
+ * retained configuration of the unused second 82576 queue pair after VFLR.
+ * Expanding VF data-path queue support must extend this predicate from the
+ * applicable device CSR map.
  */
 static __inline bool
 e1000_vf_reg_valid(uint32_t reg, bool write, bool vf_82576)
@@ -212,6 +214,20 @@ e1000_vf_reg_valid(uint32_t reg, bool write, bool vf_82576)
 		return (true);
 	}
 
+	/* Retained configuration from 82576 virtual queue one. */
+	if (vf_82576) {
+		switch (reg) {
+		case 0x0290c:	/* SRRCTL(1) */
+		case 0x02914:	/* RXCTL(1) */
+		case 0x02928:	/* RXDCTL(1) */
+		case 0x03914:	/* TXCTL(1) */
+		case 0x03928:	/* TXDCTL(1) */
+		case 0x03938:	/* TDWBAL(1) */
+		case 0x0393c:	/* TDWBAH(1) */
+			return (true);
+		}
+	}
+
 	/*
 	 * 82576 exposes VFMPRC at 0xf3c.  I350 erratum 31 makes
 	 * its corrected 0xf38 address inaccessible to a VF.
diff --git a/sys/dev/e1000/e1000_regs.h b/sys/dev/e1000/e1000_regs.h
index 5af1142a1ff0..138155cfc5bc 100644
--- a/sys/dev/e1000/e1000_regs.h
+++ b/sys/dev/e1000/e1000_regs.h
@@ -286,6 +286,7 @@
 #define E1000_TXDMAC		0x03000  /* Tx DMA Control - RW */
 #define E1000_KABGTXD		0x03004  /* AFE Band Gap Transmit Ref Data */
 #define E1000_PSRTYPE(_i)	(0x05480 + ((_i) * 4))
+#define E1000_VFPSRTYPE		0x00F0C
 #define E1000_RAL(_i)		(((_i) <= 15) ? (0x05400 + ((_i) * 8)) : \
 				 (0x054E0 + ((_i - 16) * 8)))
 #define E1000_RAH(_i)		(((_i) <= 15) ? (0x05404 + ((_i) * 8)) : \
diff --git a/sys/dev/e1000/if_em.c b/sys/dev/e1000/if_em.c
index db84004643f2..43519dd72d25 100644
--- a/sys/dev/e1000/if_em.c
+++ b/sys/dev/e1000/if_em.c
@@ -1668,6 +1668,8 @@ em_if_detach(if_ctx_t ctx)
 	INIT_DEBUGOUT("em_if_detach: begin");
 
 	igb_iov_detach(sc);
+	if (sc->vf_ifp)
+		igbv_queue_retry_detach(sc);
 	if (!sc->vf_ifp)
 		e1000_phy_hw_reset(&sc->hw);
 
@@ -1700,6 +1702,8 @@ em_if_suspend(if_ctx_t ctx)
 {
 	struct e1000_softc *sc = iflib_get_softc(ctx);
 
+	if (sc->vf_ifp)
+		igbv_queue_retry_stop(sc);
 	em_release_manageability(sc);
 	em_release_hw_control(sc);
 	em_enable_wakeup(ctx);
@@ -1791,8 +1795,10 @@ em_if_init(if_ctx_t ctx)
 
 	INIT_DEBUGOUT("em_if_init: begin");
 	vf_mbx_ready = !sc->vf_ifp;
-	if (sc->vf_ifp)
+	if (sc->vf_ifp) {
+		igbv_queue_retry_prepare(sc);
 		sc->vf_reset_pending = true;
+	}
 
 	/* Get the latest mac address, User can use a LAA */
 	bcopy(if_getlladdr(ifp), sc->hw.mac.addr, ETHER_ADDR_LEN);
@@ -1824,6 +1830,16 @@ em_if_init(if_ctx_t ctx)
 	} else {
 		em_reset(ctx);
 	}
+	if (sc->vf_ifp && !sc->vf_queues_sanitized) {
+		/*
+		 * Do not program or enable rings while retained queue state
+		 * might still contain a previous VF owner's DMA address.  A
+		 * bounded callout retries initialization after iflib returns;
+		 * the deferred admin task clears its optimistic RUNNING flag.
+		 */
+		igbv_queue_retry_failed(ctx);
+		return;
+	}
 	if (sc->vf_ifp && vf_mbx_ready)
 		igbv_reconcile_mac(sc, ifp);
 	/* Re-arm a link-up transition deferred for this reset. */
@@ -2782,6 +2798,9 @@ em_if_stop(if_ctx_t ctx)
 
 	INIT_DEBUGOUT("em_if_stop: begin");
 
+	if (sc->vf_ifp)
+		igbv_queue_retry_stop(sc);
+
 	/* I219 needs special flushing to avoid hangs */
 	if (sc->hw.mac.type >= e1000_pch_spt && sc->hw.mac.type < igb_mac_min)
 		em_flush_desc_rings(sc);
diff --git a/sys/dev/e1000/if_em.h b/sys/dev/e1000/if_em.h
index 617ecaa8e294..6e9d17c7b7a0 100644
--- a/sys/dev/e1000/if_em.h
+++ b/sys/dev/e1000/if_em.h
@@ -646,7 +646,15 @@ struct e1000_softc {
 		struct e1000_vf_stats	vf_stats;	/* sc->vf_ifp */
 	} ustats;
 
+	struct callout		vf_queue_retry;
+	struct timeval		vf_last_queue_log;
+	u32			vf_queue_retry_new_epoch;
+	u32			vf_queue_retry_pending;
 	u16			vf_ifp;
+	u8			vf_queue_failures;
+	bool			vf_queue_gave_up;
+	bool			vf_queue_retry_initialized;
+	bool			vf_queues_sanitized;
 	bool			vf_reset_pending;
 	/* A PF can retain auxiliary filters across a VF reset. */
 	bool			vf_uc_filters_set;
@@ -674,6 +682,10 @@ void	igbv_if_intr_disable(if_ctx_t);
 void	igbv_if_update_admin_status(if_ctx_t);
 void	igbv_initialize_receive_unit(if_ctx_t);
 void	igbv_initialize_transmit_unit(if_ctx_t);
+void	igbv_queue_retry_detach(struct e1000_softc *);
+void	igbv_queue_retry_failed(if_ctx_t);
+void	igbv_queue_retry_prepare(struct e1000_softc *);
+void	igbv_queue_retry_stop(struct e1000_softc *);
 void	igbv_reconcile_mac(struct e1000_softc *, if_t);
 bool	igbv_reset(if_ctx_t);
 void	igbv_update_uc_addr_list(struct e1000_softc *, if_t);
diff --git a/sys/dev/e1000/if_igbv.c b/sys/dev/e1000/if_igbv.c
index 01c586fdb4b0..c8ecd334d862 100644
--- a/sys/dev/e1000/if_igbv.c
+++ b/sys/dev/e1000/if_igbv.c
@@ -30,10 +30,25 @@
 
 #include <sys/sbuf.h>
 
+#define	IGBV_82576_QUEUES		2
+#define	IGBV_I350_QUEUES		1
 #define	IGBV_MAX_MAC_FILTERS	3
+#define	IGBV_QUEUE_DISABLE_BUSY_RETRIES	10
+#define	IGBV_QUEUE_DISABLE_DELAY_US	10
+#define	IGBV_QUEUE_DISABLE_PAUSE	(100 * SBT_1US)
+#define	IGBV_QUEUE_DISABLE_RETRIES	20
+#define	IGBV_QUEUE_SANITIZE_ATTEMPTS	3
 #define	IGBV_VLAN_RETRY_BATCH	4
 #define	IGBV_VLAN_RETRY_WINDOW	(8 * SBT_1S)
 
+static const struct timeval igbv_queue_log_interval = { 2, 0 };
+static const sbintime_t igbv_queue_retry_delay[] = {
+	100 * SBT_1MS,
+	500 * SBT_1MS,
+};
+_Static_assert(nitems(igbv_queue_retry_delay) + 1 ==
+    IGBV_QUEUE_SANITIZE_ATTEMPTS, "missing queue retry delay");
+
 struct igb_vf_uc_addr_list {
 	struct e1000_softc	*sc;
 	u8			addrs[IGBV_MAX_MAC_FILTERS][ETHER_ADDR_LEN];
@@ -43,6 +58,101 @@ static bool	igbv_tx_pending(struct e1000_softc *);
 static bool	igbv_vlan_retry_pending(const struct e1000_softc *);
 static void	igbv_vlan_retry_tick(struct e1000_softc *);
 
+static void
+igbv_queue_retry_callout(void *arg)
+{
+	struct e1000_softc *sc;
+	if_t ifp;
+
+	sc = arg;
+	if (atomic_readandclear_32(&sc->vf_queue_retry_pending) == 0)
+		return;
+	ifp = iflib_get_ifp(sc->ctx);
+	if ((if_getflags(ifp) & IFF_UP) == 0) {
+		atomic_set_32(&sc->vf_queue_retry_new_epoch, 1);
+		return;
+	}
+	iflib_request_reset(sc->ctx);
+	iflib_admin_intr_deferred(sc->ctx);
+}
+
+void
+igbv_queue_retry_detach(struct e1000_softc *sc)
+{
+
+	if (!sc->vf_queue_retry_initialized)
+		return;
+	atomic_readandclear_32(&sc->vf_queue_retry_pending);
+	callout_drain(&sc->vf_queue_retry);
+	sc->vf_queue_retry_initialized = false;
+}
+
+void
+igbv_queue_retry_stop(struct e1000_softc *sc)
+{
+
+	if (!sc->vf_queue_retry_initialized)
+		return;
+	if (atomic_readandclear_32(&sc->vf_queue_retry_pending) != 0)
+		atomic_set_32(&sc->vf_queue_retry_new_epoch, 1);
+	callout_stop(&sc->vf_queue_retry);
+}
+
+void
+igbv_queue_retry_prepare(struct e1000_softc *sc)
+{
+	bool new_epoch;
+
+	new_epoch =
+	    atomic_readandclear_32(&sc->vf_queue_retry_new_epoch) != 0;
+	if (!sc->vf_queue_gave_up && !new_epoch)
+		return;
+	sc->vf_queue_failures = 0;
+	sc->vf_queue_gave_up = false;
+}
+
+static void
+igbv_queue_retry_succeeded(struct e1000_softc *sc)
+{
+
+	atomic_readandclear_32(&sc->vf_queue_retry_pending);
+	atomic_readandclear_32(&sc->vf_queue_retry_new_epoch);
+	if (sc->vf_queue_retry_initialized)
+		callout_stop(&sc->vf_queue_retry);
+	sc->vf_queue_failures = 0;
+	sc->vf_queue_gave_up = false;
+}
+
+void
+igbv_queue_retry_failed(if_ctx_t ctx)
+{
+	struct e1000_softc *sc;
+	sbintime_t delay;
+
+	sc = iflib_get_softc(ctx);
+	KASSERT(sc->vf_ifp, ("%s called for a PF", __func__));
+
+	if (sc->vf_queue_failures < IGBV_QUEUE_SANITIZE_ATTEMPTS)
+		sc->vf_queue_failures++;
+	if (sc->vf_queue_failures < IGBV_QUEUE_SANITIZE_ATTEMPTS) {
+		delay = igbv_queue_retry_delay[sc->vf_queue_failures - 1];
+		atomic_set_32(&sc->vf_queue_retry_pending, 1);
+		callout_reset_sbt(&sc->vf_queue_retry, delay, 0,
+		    igbv_queue_retry_callout, sc, C_PREL(1));
+	} else if (!sc->vf_queue_gave_up) {
+		atomic_readandclear_32(&sc->vf_queue_retry_pending);
+		callout_stop(&sc->vf_queue_retry);
+		sc->vf_queue_gave_up = true;
+		device_printf(sc->dev,
+		    "retained VF queues remained active after %u attempts; "
+		    "interface left down; toggle it down/up to retry\n",
+		    sc->vf_queue_failures);
+	}
+
+	iflib_link_state_change(ctx, LINK_STATE_DOWN, 0);
+	iflib_admin_intr_deferred(ctx);
+}
+
 void
 igbv_vlan_retry_add(struct e1000_softc *sc, u16 vid)
 {
@@ -125,6 +235,7 @@ igbv_vlan_retry_tick(struct e1000_softc *sc)
 int
 igbv_if_attach_pre(if_ctx_t ctx)
 {
+	struct e1000_softc *sc;
 	device_t dev;
 	int error;
 
@@ -137,7 +248,11 @@ igbv_if_attach_pre(if_ctx_t ctx)
 	if (error != 0)
 		return (error);
 
-	KASSERT(((struct e1000_softc *)iflib_get_softc(ctx))->vf_ifp &&
+	sc = iflib_get_softc(ctx);
+	callout_init(&sc->vf_queue_retry, 1);
+	sc->vf_queue_retry_initialized = true;
+
+	KASSERT(sc->vf_ifp &&
 	    (iflib_get_sctx(ctx)->isc_flags & IFLIB_IS_VF) != 0,
 	    ("%s: igbv attached without VF policy", __func__));
 	return (0);
@@ -188,6 +303,18 @@ igbv_if_update_admin_status(if_ctx_t ctx)
 	dev = iflib_get_dev(ctx);
 	KASSERT(sc->vf_ifp, ("%s called for a PF", __func__));
 
+	/*
+	 * iflib's init callback cannot report failure and marks the interface
+	 * running after it returns.  Complete the failed-init transition from
+	 * this deferred task, after iflib has set its driver flags.
+	 */
+	if (!sc->vf_queues_sanitized) {
+		igbv_if_intr_disable(ctx);
+		if_setdrvflagbits(iflib_get_ifp(ctx), IFF_DRV_OACTIVE,
+		    IFF_DRV_RUNNING);
+		return;
+	}
+
 	if (!sc->vf_reset_pending &&
 	    atomic_readandclear_32(&sc->promisc_pending) != 0)
 		(void)em_if_set_promisc_impl(ctx,
@@ -258,11 +385,87 @@ igbv_tx_pending(struct e1000_softc *sc)
 	return (false);
 }
 
+static bool
+igbv_sanitize_queues(struct e1000_softc *sc)
+{
+	struct e1000_hw *hw;
+	u32 rxdctl, txdctl;
+	int i, nqueues, retry;
+
+	hw = &sc->hw;
+	switch (hw->mac.type) {
+	case e1000_vfadapt:
+		nqueues = IGBV_82576_QUEUES;
+		break;
+	case e1000_vfadapt_i350:
+		nqueues = IGBV_I350_QUEUES;
+		break;
+	default:
+		return (true);
+	}
+
+	/*
+	 * The 82576 and I350 specification updates, Software Clarification 3,
+	 * note that VFLR leaves this queue configuration intact.  Clear it
+	 * before programming the new rings so igbv does not depend on its PF
+	 * to sanitize state left by a previous VF owner.  igbv uses only queue
+	 * zero, but must also clear the unused second 82576 queue.
+	 *
+	 * Disable every queue first and wait for outstanding DMA activity to
+	 * stop before programming TDWBAL/H.  Spin only for the normal fast
+	 * transition, then sleep until the bounded deadline.
+	 */
+	for (i = 0; i < nqueues; i++) {
+		E1000_WRITE_REG(hw, E1000_RXDCTL(i), 0);
+		E1000_WRITE_REG(hw, E1000_TXDCTL(i), 0);
+	}
+	E1000_WRITE_FLUSH(hw);
+	for (retry = 0; retry < IGBV_QUEUE_DISABLE_RETRIES; retry++) {
+		for (i = 0; i < nqueues; i++) {
+			rxdctl = E1000_READ_REG(hw, E1000_RXDCTL(i));
+			txdctl = E1000_READ_REG(hw, E1000_TXDCTL(i));
+			if ((rxdctl & E1000_RXDCTL_QUEUE_ENABLE) != 0 ||
+			    (txdctl & E1000_TXDCTL_QUEUE_ENABLE) != 0)
+				break;
+		}
+		if (i == nqueues)
+			break;
+		if (retry + 1 < IGBV_QUEUE_DISABLE_RETRIES) {
+			if (retry < IGBV_QUEUE_DISABLE_BUSY_RETRIES)
+				DELAY(IGBV_QUEUE_DISABLE_DELAY_US);
+			else
+				pause_sbt("igbvqds",
+				    IGBV_QUEUE_DISABLE_PAUSE, 0,
+				    C_PREL(1));
+		}
+	}
+	if (retry == IGBV_QUEUE_DISABLE_RETRIES) {
+		if (ratecheck(&sc->vf_last_queue_log,
+		    &igbv_queue_log_interval))
+			device_printf(sc->dev,
+			    "could not disable retained VF queues; "
+			    "reset deferred\n");
+		return (false);
+	}
+
+	for (i = 0; i < nqueues; i++) {
+		E1000_WRITE_REG(hw, E1000_SRRCTL(i), 0);
+		E1000_WRITE_REG(hw, E1000_DCA_RXCTRL(i), 0);
+		E1000_WRITE_REG(hw, E1000_TDWBAL(i), 0);
+		E1000_WRITE_REG(hw, E1000_TDWBAH(i), 0);
+		E1000_WRITE_REG(hw, E1000_DCA_TXCTRL(i), 0);
+	}
+	E1000_WRITE_REG(hw, E1000_VFPSRTYPE, 0);
+	E1000_WRITE_FLUSH(hw);
+	return (true);
+}
+
 bool
 igbv_reset(if_ctx_t ctx)
 {
 	struct e1000_softc *sc;
 	struct e1000_hw *hw;
+	s32 error;
 
 	sc = iflib_get_softc(ctx);
 	hw = &sc->hw;
@@ -278,7 +481,14 @@ igbv_reset(if_ctx_t ctx)
 		.requested_mode = e1000_fc_none,
 	};
 
-	if (e1000_reset_hw(hw) != E1000_SUCCESS) {
+	error = e1000_reset_hw(hw);
+	sc->vf_queues_sanitized = igbv_sanitize_queues(sc);
+	if (!sc->vf_queues_sanitized) {
+		e1000_check_for_link(hw);
+		return (false);
+	}
+	igbv_queue_retry_succeeded(sc);
+	if (error != E1000_SUCCESS) {
 		e1000_check_for_link(hw);
 		return (false);
 	}
@@ -322,6 +532,8 @@ igbv_if_intr_enable(if_ctx_t ctx)
 	sc = iflib_get_softc(ctx);
 	hw = &sc->hw;
 	KASSERT(sc->vf_ifp, ("%s called for a PF", __func__));
+	if (!sc->vf_queues_sanitized)
+		return;
 	mask = sc->que_mask | sc->link_mask;
 
 	E1000_WRITE_REG(hw, E1000_EIAC, mask);


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a6b5a50.1c9e9.1bdeef7c>