git: 037d45a0526f - main - igbv: Fence DMA while sanitizing retained queues

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Mon, 31 Aug 2026 15:27:04 UTC
The branch main has been updated by kbowling:

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

commit 037d45a0526f452d96f44059e88f735298a9063d
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-26 09:21:56 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-31 15:26:23 +0000

    igbv: Fence DMA while sanitizing retained queues
    
    The 82576 and I350 retain VF queue enable and DMA address state
    across VFLR.  iflib enables PCI bus mastering before driver attach, so
    stale state left by a previous owner can otherwise issue DMA before
    igbvf has completed its first reset and queue sanitization.
    
    Disable PCI bus mastering immediately after mapping the VF BAR.  Keep
    it disabled until reset and queue sanitization succeed, verify both
    disable and enable through PCI command-register readback, and wait for
    pending transactions before treating the fence as complete.
    Resanitize on stop before iflib releases queue mappings.
    
    The sanitizer and recovery were exercised on I350 and 82576 VFs.  Forced
    queue-disable failure left the VF down, and a later administrative
    down/up recovered it; successful I350 VFs passed bidirectional traffic
    with no errors or drops.
    
    Sponsored by:   BBOX.io
---
 sys/dev/e1000/if_em.c   | 86 ++++++++++++++++++++++++++++++++++++++++++++++++-
 sys/dev/e1000/if_em.h   |  1 +
 sys/dev/e1000/if_igbv.c |  2 +-
 3 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/sys/dev/e1000/if_em.c b/sys/dev/e1000/if_em.c
index e27b804a3cb4..97040cc49d94 100644
--- a/sys/dev/e1000/if_em.c
+++ b/sys/dev/e1000/if_em.c
@@ -429,6 +429,8 @@ static uint64_t	em_if_get_vf_counter(if_ctx_t, ift_counter);
 static uint64_t	em_if_get_counter(if_ctx_t, ift_counter);
 static void	em_if_init(if_ctx_t);
 static void	em_if_stop(if_ctx_t);
+static void	em_fence_pci_busmaster(struct e1000_softc *);
+static int	em_enable_pci_busmaster(struct e1000_softc *);
 static void	em_if_media_status(if_ctx_t, struct ifmediareq *);
 static int	em_if_media_change(if_ctx_t);
 static int	em_if_mtu_set(if_ctx_t, uint32_t);
@@ -1432,6 +1434,13 @@ em_if_attach_pre(if_ctx_t ctx)
 		error = ENXIO;
 		goto err_pci;
 	}
+	/*
+	 * A VF can retain queue enable bits and DMA addresses across VFLR.
+	 * Fence bus mastering before the first mailbox reset so state left by
+	 * a previous owner cannot issue DMA while the driver attaches.
+	 */
+	if (sc->vf_ifp)
+		em_fence_pci_busmaster(sc);
 	/*
 	 * 82579 can lose a host CSR write while the Management Engine owns
 	 * the PCIm2PCI arbiter.  Enable the OS register write interlock before
@@ -1602,6 +1611,7 @@ em_if_attach_pre(if_ctx_t ctx)
 		    error == E1000_SUCCESS);
 		if (error != E1000_SUCCESS)
 			igbv_log_reset_failure(sc, error, true);
+		sc->vf_queues_sanitized = igbv_sanitize_queues(sc);
 	} else if (error != E1000_SUCCESS) {
 		device_printf(dev, "Hardware reset failed: %d\n", error);
 		error = EIO;
@@ -1952,6 +1962,7 @@ em_if_init(if_ctx_t ctx)
 		 * bounded callout retries initialization after iflib leaves the
 		 * failed initialization stopped.
 		 */
+		em_fence_pci_busmaster(sc);
 		igbv_queue_retry_failed(ctx);
 		return;
 	}
@@ -1960,6 +1971,16 @@ em_if_init(if_ctx_t ctx)
 		igbv_mbx_retry_failed(ctx);
 		return;
 	}
+	/*
+	 * Keep a fail-closed device fenced until reset and VF queue
+	 * sanitization have removed every stale DMA address.
+	 */
+	if (sc->vf_ifp && em_enable_pci_busmaster(sc) != 0) {
+		device_printf(sc->dev,
+		    "Unable to enable PCI bus mastering\n");
+		iflib_init_failed(ctx);
+		return;
+	}
 	if (sc->vf_ifp)
 		igbv_reconcile_mac(sc, ifp);
 	/* Re-arm a link-up transition deferred for this reset. */
@@ -4028,6 +4049,65 @@ em_if_update_admin_status(if_ctx_t ctx)
 		lem_smartspeed(sc);
 }
 
+/*
+ * Last-resort DMA fence.  iflib releases DMA mappings after the driver's
+ * stop callback, so continuing with bus mastering still enabled would turn
+ * a recoverable NIC failure into memory corruption.  Treat failure of the
+ * PCI command bit as a fail-stop invariant violation.
+ */
+static void
+em_fence_pci_busmaster(struct e1000_softc *sc)
+{
+	device_t dev;
+	u_int timeout;
+	u16 command;
+	int error;
+
+	dev = sc->dev;
+	error = pci_disable_busmaster(dev);
+	command = pci_read_config(dev, PCIR_COMMAND, 2);
+	if (command != 0xffff && (command & PCIM_CMD_BUSMASTEREN) != 0)
+		panic("%s: unable to fence device DMA (error %d)",
+		    device_get_nameunit(dev), error);
+	if (error != 0 && command != 0xffff)
+		device_printf(dev,
+		    "PCI bus-master disable returned %d; readback is disabled\n",
+		    error);
+
+	timeout = max(pcie_get_max_completion_timeout(dev) / 1000, 10);
+	if (command != 0xffff &&
+	    !pcie_wait_for_pending_transactions(dev, timeout)) {
+		/* A function removed during the wait can no longer issue DMA. */
+		command = pci_read_config(dev, PCIR_COMMAND, 2);
+		if (command != 0xffff)
+			panic("%s: DMA transactions remain pending after fencing",
+			    device_get_nameunit(dev));
+	}
+}
+
+static int
+em_enable_pci_busmaster(struct e1000_softc *sc)
+{
+	device_t dev;
+	u16 command;
+	int error;
+
+	dev = sc->dev;
+	command = pci_read_config(dev, PCIR_COMMAND, 2);
+	if (command == 0xffff)
+		return (ENXIO);
+	if ((command & PCIM_CMD_BUSMASTEREN) != 0)
+		return (0);
+
+	error = pci_enable_busmaster(dev);
+	command = pci_read_config(dev, PCIR_COMMAND, 2);
+	if (command == 0xffff)
+		return (ENXIO);
+	if ((command & PCIM_CMD_BUSMASTEREN) == 0)
+		return (error != 0 ? error : EIO);
+	return (0);
+}
+
 /*********************************************************************
  *
  *  This routine disables all traffic on the adapter by issuing a
@@ -4063,8 +4143,12 @@ em_if_stop(if_ctx_t ctx)
 			return;
 		}
 	}
-	if (sc->vf_ifp)
+	if (sc->vf_ifp) {
+		sc->vf_queues_sanitized = igbv_sanitize_queues(sc);
 		atomic_store_rel_32(&sc->vf_mbx_ready, 0);
+		if (!sc->vf_queues_sanitized)
+			em_fence_pci_busmaster(sc);
+	}
 	if (sc->hw.mac.type >= e1000_82544 && !sc->vf_ifp)
 		E1000_WRITE_REG(&sc->hw, E1000_WUFC, 0);
 
diff --git a/sys/dev/e1000/if_em.h b/sys/dev/e1000/if_em.h
index 6e97d0420100..3f3663b8d9a0 100644
--- a/sys/dev/e1000/if_em.h
+++ b/sys/dev/e1000/if_em.h
@@ -757,6 +757,7 @@ 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);
+bool	igbv_sanitize_queues(struct e1000_softc *);
 void	igbv_log_reset_failure(struct e1000_softc *, s32, bool);
 void	igbv_update_uc_addr_list(struct e1000_softc *, if_t);
 void	igbv_vlan_retry_add(struct e1000_softc *, u16);
diff --git a/sys/dev/e1000/if_igbv.c b/sys/dev/e1000/if_igbv.c
index 2d57a39f934e..9947f4c19e2e 100644
--- a/sys/dev/e1000/if_igbv.c
+++ b/sys/dev/e1000/if_igbv.c
@@ -531,7 +531,7 @@ igbv_tx_pending(struct e1000_softc *sc)
 	return (false);
 }
 
-static bool
+bool
 igbv_sanitize_queues(struct e1000_softc *sc)
 {
 	struct e1000_hw *hw;