Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 30 Jul 2026 14:06:07 +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: 8c8724705613 - main - igb: Sanitize retained VF queue state
Message-ID:  <6a6b5a4f.1dd35.63cb957f@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=8c8724705613c856836c3928e7240baae03e690c

commit 8c8724705613c856836c3928e7240baae03e690c
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-07-30 07:40:55 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-07-30 14:06:00 +0000

    igb: Sanitize retained VF queue state
    
    82576 and I350 VFLR leave the VF queue configuration unchanged.  A VF
    can program transmit head write-back and leave its DMA destination for
    a later VF owner; mainstream VF drivers do not overwrite TDWBAL/H.
    
    Disable every receive and transmit queue assigned to the VF, wait for
    the enable bits to clear, then clear SRRCTL, PSRTYPE, RXCTRL, TXCTRL,
    and TDWBAL/H.  Spin briefly for the normal transition, then sleep at
    100 microsecond intervals with an approximately 1 ms bound.  This
    prevents a VF that keeps asserting QUEUE_ENABLE from busy-waiting the
    PF context lock for 10 ms.
    
    If a queue does not quiesce, leave the VF disabled and NACK its reset
    rather than programming an active queue.  Rate-limit this diagnostic
    independently from mailbox and malicious-driver notifications.
    
    I350 maps pool n to queue n.  82576 assigns physical queues n and n+8
    to VF n, so sanitize both queues while clearing per-pool PSRTYPE once.
    An incoming VF initializes its active ring base, head, and tail while
    enabling each queue.
    
    This is also required by malicious-driver recovery, which deliberately
    does not assert VTCTRL.RST because doing so would discard the VF's
    admin-vector routing before the PF can notify it.
    
    This implements Software Clarification 3 from the 82576 and I350
    specification updates.
    
    Sponsored by:   BBOX.io
---
 sys/dev/e1000/if_igb_iov.c | 132 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 124 insertions(+), 8 deletions(-)

diff --git a/sys/dev/e1000/if_igb_iov.c b/sys/dev/e1000/if_igb_iov.c
index f43e994c0440..3e03d056865e 100644
--- a/sys/dev/e1000/if_igb_iov.c
+++ b/sys/dev/e1000/if_igb_iov.c
@@ -25,14 +25,24 @@
 /* 82576 Datasheet rev. 2.0, Section 8.14.16: VMOLR[31] must be one. */
 #define	IGB_82576_VMOLR_RSV		(1U << 31)
 #define	IGB_82576_LVMMC_BLOCK_MASK	0x1c
+#define	IGB_82576_NUM_QUEUES		16
 #define	IGB_82576_QUEUE_MASK		0xffff
 #define	IGB_82576_STAGGERED_QUEUE_SHIFT	8
+#define	IGB_82576_VF_QUEUE_STRIDE	8
+#define	IGB_82576_VF_QUEUES		2
 #define	IGB_I350_DTXCTL_ENABLE_SPOOF_QUEUE	(1U << 2)
 #define	IGB_I350_LVMMC_MAC_VLAN_SPOOF	(1U << 25)
 #define	IGB_I350_LVMMC_LAST_Q_SHIFT	29
 #define	IGB_I350_LVMMC_LAST_Q_MASK	0x7
+#define	IGB_I350_NUM_QUEUES		8
 #define	IGB_I350_QUEUE_MASK		0xff
 #define	IGB_I350_RESET_ACK_TIMEOUT	(100 * SBT_1MS)
+#define	IGB_I350_VF_QUEUES		1
+#define	IGB_IOV_QUEUE_DISABLE_BUSY_RETRIES	10
+#define	IGB_IOV_QUEUE_DISABLE_DELAY_US	10
+#define	IGB_IOV_QUEUE_DISABLE_PAUSE	(100 * SBT_1US)
+#define	IGB_IOV_QUEUE_DISABLE_RETRIES	20
+#define	IGB_IOV_VF_QUEUES_MAX		2
 
 #define	IGB_VF_CTS			(1U << 0)
 #define	IGB_VF_CAP_MAC			(1U << 1)
@@ -59,6 +69,7 @@ struct igb_vf {
 	struct timeval	last_mbx_log;
 	struct timeval	last_spoof_log;
 	struct timeval	last_mdd_log;
+	struct timeval	last_queue_log;
 	sbintime_t	mbx_retry_at;
 	sbintime_t	mdd_notify_at;
 	sbintime_t	vlan_token_time;
@@ -1022,12 +1033,103 @@ igb_iov_vf_vlan_is_default(const struct igb_vf *vf)
 	return (true);
 }
 
-static void
+static bool
+igb_iov_sanitize_vf_queues(struct e1000_softc *sc,
+    struct igb_vf *vf)
+{
+	struct e1000_hw *hw;
+	u16 qid[IGB_IOV_VF_QUEUES_MAX];
+	u32 rxdctl, txdctl;
+	int i, nqueues, retry;
+
+	hw = &sc->hw;
+	switch (hw->mac.type) {
+	case e1000_82576:
+		nqueues = IGB_82576_VF_QUEUES;
+		qid[0] = vf->pool;
+		qid[1] = vf->pool + IGB_82576_VF_QUEUE_STRIDE;
+		break;
+	case e1000_i350:
+		nqueues = IGB_I350_VF_QUEUES;
+		qid[0] = vf->pool;
+		break;
+	default:
+		return (true);
+	}
+
+	/*
+	 * I350 maps pool n to queue n.  82576 gives VF n physical queues n
+	 * and n + 8, so both retained queue configurations must be cleared.
+	 */
+	for (i = 0; i < nqueues; i++)
+		KASSERT(qid[i] < (hw->mac.type == e1000_82576 ?
+		    IGB_82576_NUM_QUEUES : IGB_I350_NUM_QUEUES),
+		    ("%s: invalid VF queue %u", __func__, qid[i]));
+
+	/*
+	 * The 82576 and I350 specification updates, Software Clarification 3,
+	 * note that VFLR does not reset the VF queue configuration.  Clear the
+	 * PF-programmable state before acknowledging the reset so a new VF
+	 * owner cannot inherit it, particularly a descriptor-head write-back
+	 * DMA address.  The new VF driver initializes its active ring pointers
+	 * during queue setup.
+	 *
+	 * Disable every queue first, then wait for outstanding DMA activity to
+	 * stop before clearing TDWBAL/H and the remaining retained state.
+	 * Spin only for the normal fast transition, then sleep so a VF that
+	 * keeps asserting QUEUE_ENABLE cannot busy-wait the PF for 10 ms.
+	 */
+	for (i = 0; i < nqueues; i++) {
+		E1000_WRITE_REG(hw, E1000_RXDCTL(qid[i]), 0);
+		E1000_WRITE_REG(hw, E1000_TXDCTL(qid[i]), 0);
+	}
+	E1000_WRITE_FLUSH(hw);
+	for (retry = 0; retry < IGB_IOV_QUEUE_DISABLE_RETRIES; retry++) {
+		for (i = 0; i < nqueues; i++) {
+			rxdctl = E1000_READ_REG(hw, E1000_RXDCTL(qid[i]));
+			txdctl = E1000_READ_REG(hw, E1000_TXDCTL(qid[i]));
+			if ((rxdctl & E1000_RXDCTL_QUEUE_ENABLE) != 0 ||
+			    (txdctl & E1000_TXDCTL_QUEUE_ENABLE) != 0)
+				break;
+		}
+		if (i == nqueues)
+			break;
+		if (retry + 1 < IGB_IOV_QUEUE_DISABLE_RETRIES) {
+			if (retry < IGB_IOV_QUEUE_DISABLE_BUSY_RETRIES)
+				DELAY(IGB_IOV_QUEUE_DISABLE_DELAY_US);
+			else
+				pause_sbt("igbqds",
+				    IGB_IOV_QUEUE_DISABLE_PAUSE, 0,
+				    C_PREL(1));
+		}
+	}
+	if (retry == IGB_IOV_QUEUE_DISABLE_RETRIES) {
+		if (ratecheck(&vf->last_queue_log,
+		    &igb_iov_mbx_log_interval))
+			device_printf(sc->dev,
+			    "could not disable queues for VF %u; "
+			    "reset deferred\n", vf->pool);
+		return (false);
+	}
+
+	for (i = 0; i < nqueues; i++) {
+		E1000_WRITE_REG(hw, E1000_SRRCTL(qid[i]), 0);
+		E1000_WRITE_REG(hw, E1000_DCA_RXCTRL(qid[i]), 0);
+		E1000_WRITE_REG(hw, E1000_TDWBAL(qid[i]), 0);
+		E1000_WRITE_REG(hw, E1000_TDWBAH(qid[i]), 0);
+		E1000_WRITE_REG(hw, E1000_DCA_TXCTRL(qid[i]), 0);
+	}
+	E1000_WRITE_REG(hw, E1000_PSRTYPE(vf->pool), 0);
+	E1000_WRITE_FLUSH(hw);
+	return (true);
+}
+
+static bool
 igb_iov_reset_event_common(struct e1000_softc *sc, struct igb_vf *vf,
     bool reset_intrs)
 {
 	struct e1000_hw *hw;
-	bool rebuild_mta, rebuild_vlan;
+	bool rebuild_mta, rebuild_vlan, sanitized;
 	u32 reg;
 
 	hw = &sc->hw;
@@ -1040,6 +1142,7 @@ igb_iov_reset_event_common(struct e1000_softc *sc, struct igb_vf *vf,
 	if (reset_intrs)
 		E1000_WRITE_REG(hw, E1000_VTCTRL(vf->pool),
 		    E1000_VTCTRL_RST);
+	sanitized = igb_iov_sanitize_vf_queues(sc, vf);
 	E1000_WRITE_REG(hw, E1000_VMVIR(vf->pool), 0);
 	igb_iov_clear_mac_filters(sc, vf);
 	igb_iov_clear_rar(sc, vf->rar_index);
@@ -1048,12 +1151,13 @@ igb_iov_reset_event_common(struct e1000_softc *sc, struct igb_vf *vf,
 		igb_iov_rebuild_mta(sc);
 	if (rebuild_vlan)
 		igb_iov_rebuild_vlan(sc);
+	return (sanitized);
 }
 
-static void
+static bool
 igb_iov_reset_event(struct e1000_softc *sc, struct igb_vf *vf)
 {
-	igb_iov_reset_event_common(sc, vf, true);
+	return (igb_iov_reset_event_common(sc, vf, true));
 }
 
 static void
@@ -1070,8 +1174,11 @@ igb_iov_mdd_reset_event(struct e1000_softc *sc, struct igb_vf *vf)
 	 * message to make the guest reinitialize.  FreeBSD and DPDK consume
 	 * that message directly; Linux ACKs it and the PF's non-CTS ACK path
 	 * replies with the NACK that schedules igbvf's reset task.
+	 *
+	 * Sanitization failure leaves the pool disabled.  The VF reset
+	 * handshake retries it and is NACKed while a queue remains active.
 	 */
-	igb_iov_reset_event_common(sc, vf, false);
+	(void)igb_iov_reset_event_common(sc, vf, false);
 }
 
 static void
@@ -1081,7 +1188,11 @@ igb_iov_reset_msg(struct e1000_softc *sc, struct igb_vf *vf)
 	u32 msg[3], reg;
 
 	hw = &sc->hw;
-	igb_iov_reset_event(sc, vf);
+	if (!igb_iov_reset_event(sc, vf)) {
+		msg[0] = E1000_VF_RESET | E1000_VT_MSGTYPE_NACK;
+		e1000_write_mbx(hw, msg, 1, vf->pool);
+		return;
+	}
 	igb_iov_map_rar(sc, vf->rar_index, vf->mac, vf->pool);
 	igb_iov_set_anti_spoof(sc, vf);
 
@@ -1369,8 +1480,13 @@ igb_iov_handle_mbx(struct e1000_softc *sc)
 		if (!(vf->flags & IGB_VF_ACTIVE))
 			continue;
 		now = getsbinuptime();
-		if (e1000_check_for_rst(hw, vf->pool) == 0)
-			igb_iov_reset_event(sc, vf);
+		if (e1000_check_for_rst(hw, vf->pool) == 0) {
+			/*
+			 * The old VF is gone.  A new owner's reset handshake
+			 * reruns sanitization before enabling its pool.
+			 */
+			(void)igb_iov_reset_event(sc, vf);
+		}
 		if ((vf->flags &
 		    (IGB_VF_MBX_PENDING | IGB_VF_MBX_GAVE_UP)) == 0 &&
 		    e1000_check_for_msg(hw, vf->pool) == 0) {


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a6b5a4f.1dd35.63cb957f>