git: 4c2dda4c70e2 - main - e1000: Recover from 82576 memory errors

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Sun, 16 Aug 2026 10:02:32 UTC
The branch main has been updated by kbowling:

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

commit 4c2dda4c70e210be4efb4527fc32fbb79f23d452
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-13 23:10:47 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-16 10:01:33 +0000

    e1000: Recover from 82576 memory errors
    
    82576 reports fatal and non-fatal internal memory errors through
    ICR.FER and ICR.NFER and identifies the affected memory in its
    native PEIND layout.  Fatal errors can stop transmit, receive, or
    both until software resets and reinitializes the port.
    
    Enable the controller-wide parity detector and implemented PEINDM
    reaction bits after hardware initialization, while preserving unrelated
    register state and omitting the absent IPsec memories on 82576NS.
    Enable both interrupt causes and capture the read-clear PEIND register
    in the interrupt filter.
    
    Keep the causes masked while the iflib admin task owns the event.
    Acknowledge non-fatal packet data errors without disrupting the port.
    Request normal port reinitialization for FER, a fatal PEIND source, or
    the memory hang indication.  Do not apply the later I210/I350 register
    layout or their special PCIe parity reset order.
    
    Hardware validation used a dual-port 82576EB revision 1.  Firmware
    left PEINDM at its 0x80000000 default; initialization explicitly
    programmed the parity-enable bit and produced 0xffffff07 on both ports.
    An NFER during two-stream TCP sustained line rate without a reset,
    watchdog, or carrier event.  FER on the linked and disconnected ports
    each requested exactly one reset.  The linked port resumed the existing
    TCP sessions after autonegotiation.  PEINDM and both interrupt causes
    were restored after every reset.
    
    The injections set the ICR causes without corrupting SRAM, so their
    empty PEIND values deliberately exercised the unknown source path.
    
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
---
 sys/dev/e1000/e1000_defines.h |  10 ++++
 sys/dev/e1000/if_em.c         | 113 +++++++++++++++++++++++++++++++++++++-----
 sys/dev/e1000/if_em.h         |   1 +
 3 files changed, 112 insertions(+), 12 deletions(-)

diff --git a/sys/dev/e1000/e1000_defines.h b/sys/dev/e1000/e1000_defines.h
index 761ce02d06af..cf23bbde846e 100644
--- a/sys/dev/e1000/e1000_defines.h
+++ b/sys/dev/e1000/e1000_defines.h
@@ -535,6 +535,14 @@
 #define E1000_PEIND_DMA_PARITY_FATAL	0x00000008
 #define E1000_PEIND_FATAL_MASK		0x0000000F
 
+/* 82576 uses PEIND directly rather than the later four-region layout. */
+#define E1000_PEIND_82576_NONFATAL_MASK	0x00000007
+#define E1000_PEIND_82576_FATAL_MASK	0x7FFFFF00
+#define E1000_PEIND_82576_MEMORY_HANG	0x80000000
+#define E1000_PEINDM_82576_PARITY_ENABLE	0x80000000
+/* 82576NS omits the IPsec key, FIFO, and packet-buffer memories. */
+#define E1000_PEIND_82576_IPSEC_MASK	0x40700600
+
 #define E1000_PBECCSTS_I210_ECC_ENABLE	0x00000001
 #define E1000_PBECCSTS_I210_CORR_ERR	0x00000004
 
@@ -591,6 +599,7 @@
 #define E1000_ICR_TXD_LOW	0x00008000
 #define E1000_ICR_MNG		0x00040000 /* Manageability event */
 #define E1000_ICR_ECCER		0x00400000 /* Uncorrectable ECC Error */
+#define E1000_ICR_NFER		0x00800000 /* Non-Fatal Error (82576) */
 #define E1000_ICR_TS		0x00080000 /* Time Sync Interrupt */
 #define E1000_ICR_DRSTA		0x40000000 /* Device Reset Asserted */
 /* If this bit asserted, the driver should claim the interrupt */
@@ -659,6 +668,7 @@
 #define E1000_IMS_RXT0		E1000_ICR_RXT0    /* Rx timer intr */
 #define E1000_IMS_TXD_LOW	E1000_ICR_TXD_LOW
 #define E1000_IMS_ECCER		E1000_ICR_ECCER   /* Uncorrectable ECC Error */
+#define E1000_IMS_NFER		E1000_ICR_NFER /* Non-Fatal Error (82576) */
 #define E1000_IMS_TS		E1000_ICR_TS      /* Time Sync Interrupt */
 #define E1000_IMS_DRSTA		E1000_ICR_DRSTA   /* Device Reset Asserted */
 #define E1000_IMS_DOUTSYNC	E1000_ICR_DOUTSYNC /* NIC DMA out of sync */
diff --git a/sys/dev/e1000/if_em.c b/sys/dev/e1000/if_em.c
index e29b34116b5d..6aa2a634bd24 100644
--- a/sys/dev/e1000/if_em.c
+++ b/sys/dev/e1000/if_em.c
@@ -2180,6 +2180,44 @@ em_has_pch_ecc(const struct e1000_hw *hw)
 	    hw->mac.type < e1000_82575);
 }
 
+static bool
+em_has_82576_memory_errors(const struct e1000_hw *hw)
+{
+
+	return (hw->mac.type == e1000_82576);
+}
+
+static bool
+em_82576_has_ipsec(const struct e1000_hw *hw)
+{
+
+	return (hw->device_id != E1000_DEV_ID_82576_NS &&
+	    hw->device_id != E1000_DEV_ID_82576_NS_SERDES);
+}
+
+static void
+em_configure_82576_memory_errors(struct e1000_softc *sc)
+{
+	struct e1000_hw *hw;
+	u32 peindm, reactions;
+
+	hw = &sc->hw;
+	if (!em_has_82576_memory_errors(hw))
+		return;
+
+	reactions = E1000_PEIND_82576_NONFATAL_MASK |
+	    E1000_PEIND_82576_FATAL_MASK |
+	    E1000_PEINDM_82576_PARITY_ENABLE;
+	if (!em_82576_has_ipsec(hw))
+		reactions &= ~E1000_PEIND_82576_IPSEC_MASK;
+
+	/* Discard indications left by firmware before enabling reactions. */
+	(void)E1000_READ_REG(hw, E1000_PEIND);
+	peindm = E1000_READ_REG(hw, E1000_PEINDM);
+	E1000_WRITE_REG(hw, E1000_PEINDM, peindm | reactions);
+	E1000_WRITE_FLUSH(hw);
+}
+
 static bool
 em_has_i210_memory_errors(const struct e1000_hw *hw)
 {
@@ -2237,13 +2275,20 @@ em_pcie_fatal_error_mask(const struct e1000_hw *hw)
 static u32
 em_fatal_error_intr_mask(struct e1000_softc *sc)
 {
+	u32 mask;
 
-	if ((em_has_pch_ecc(&sc->hw) ||
-	    em_has_i210_i350_memory_errors(&sc->hw)) &&
-	    atomic_load_acq_32(&sc->fatal_error_state) ==
+	if (!em_has_pch_ecc(&sc->hw) &&
+	    !em_has_82576_memory_errors(&sc->hw) &&
+	    !em_has_i210_i350_memory_errors(&sc->hw))
+		return (0);
+	if (atomic_load_acq_32(&sc->fatal_error_state) !=
 	    EM_FATAL_ERROR_NONE)
-		return (E1000_IMS_FER);
-	return (0);
+		return (0);
+
+	mask = E1000_IMS_FER;
+	if (em_has_82576_memory_errors(&sc->hw))
+		mask |= E1000_IMS_NFER;
+	return (mask);
 }
 
 static void
@@ -2340,29 +2385,36 @@ em_update_i350_ecc_stats(struct e1000_softc *sc)
 }
 
 /*
- * Fatal internal-memory errors stop part or all of the MAC.  Capture the
- * read-clear indication before handing recovery to the iflib admin task.
+ * Internal-memory error causes are read-clear.  Capture them before handing
+ * fatal recovery or 82576 non-fatal acknowledgement to the iflib admin task.
  */
 static void
 em_handle_fatal_error_intr(struct e1000_softc *sc, u32 icr)
 {
 	struct e1000_hw *hw;
-	u32 dma_rx, dma_tx, lanerr, pcieerr, peind;
+	u32 dma_rx, dma_tx, error_mask, lanerr, pcieerr, peind;
 
+	error_mask = E1000_ICR_FER;
+	if (em_has_82576_memory_errors(&sc->hw))
+		error_mask |= E1000_ICR_NFER;
 	if ((!em_has_pch_ecc(&sc->hw) &&
+	    !em_has_82576_memory_errors(&sc->hw) &&
 	    !em_has_i210_i350_memory_errors(&sc->hw)) ||
-	    (icr & E1000_ICR_FER) == 0)
+	    (icr & error_mask) == 0)
 		return;
 
 	hw = &sc->hw;
-	E1000_WRITE_REG(hw, E1000_IMC, E1000_IMS_FER);
+	E1000_WRITE_REG(hw, E1000_IMC, error_mask);
 	if (!atomic_cmpset_32(&sc->fatal_error_state,
 	    EM_FATAL_ERROR_NONE, EM_FATAL_ERROR_CAPTURING))
 		return;
 
+	sc->fatal_error_icr = icr & error_mask;
 	if (em_has_pch_ecc(hw)) {
 		sc->fatal_error_pbeccsts =
 		    E1000_READ_REG(hw, E1000_PBECCSTS);
+	} else if (em_has_82576_memory_errors(hw)) {
+		sc->fatal_error_peind = E1000_READ_REG(hw, E1000_PEIND);
 	} else {
 		peind = E1000_READ_REG(hw, E1000_PEIND) &
 		    E1000_PEIND_FATAL_MASK;
@@ -2401,7 +2453,7 @@ em_handle_fatal_error_intr(struct e1000_softc *sc, u32 icr)
 static bool
 em_handle_fatal_error_admin(struct e1000_softc *sc)
 {
-	u32 peind;
+	u32 error_mask, peind;
 	bool reset_required;
 
 	if (!atomic_cmpset_acq_32(&sc->fatal_error_state,
@@ -2415,6 +2467,31 @@ em_handle_fatal_error_admin(struct e1000_softc *sc)
 		    "uncorrectable packet-buffer ECC error: "
 		    "PBECCSTS %#x; requesting reset\n",
 		    sc->fatal_error_pbeccsts);
+	} else if (em_has_82576_memory_errors(&sc->hw)) {
+		peind = sc->fatal_error_peind;
+		reset_required =
+		    (sc->fatal_error_icr & E1000_ICR_FER) != 0 ||
+		    (peind & (E1000_PEIND_82576_FATAL_MASK |
+		    E1000_PEIND_82576_MEMORY_HANG)) != 0;
+		if (!reset_required) {
+			device_printf(sc->dev,
+			    "non-fatal internal memory error: PEIND %#x\n",
+			    peind);
+			sc->fatal_error_icr = 0;
+			sc->fatal_error_peind = 0;
+			atomic_store_rel_32(&sc->fatal_error_state,
+			    EM_FATAL_ERROR_NONE);
+			error_mask = E1000_IMS_FER | E1000_IMS_NFER;
+			E1000_WRITE_REG(&sc->hw, E1000_IMS, error_mask);
+			E1000_WRITE_FLUSH(&sc->hw);
+			return (true);
+		}
+		if ((peind & (E1000_PEIND_82576_FATAL_MASK |
+		    E1000_PEIND_82576_MEMORY_HANG)) == 0)
+			sc->fatal_error_unknown_count++;
+		device_printf(sc->dev,
+		    "fatal internal memory error: PEIND %#x; "
+		    "requesting reset\n", peind);
 	} else {
 		peind = sc->fatal_error_peind;
 		if (peind & E1000_PEIND_LANPORT_PARITY_FATAL)
@@ -2542,7 +2619,11 @@ em_finish_fatal_error_reset(struct e1000_softc *sc)
 		return;
 
 	hw = &sc->hw;
-	if (em_has_i210_i350_memory_errors(hw)) {
+	if (em_has_82576_memory_errors(hw)) {
+		/* Drain any indication relatched while the port was resetting. */
+		(void)E1000_READ_REG(hw, E1000_PEIND);
+		sc->fatal_error_peind = 0;
+	} else if (em_has_i210_i350_memory_errors(hw)) {
 		pcieerr = sc->fatal_error_pcie |
 		    (E1000_READ_REG(hw, E1000_PCIEERRSTS) &
 		    em_pcie_fatal_error_mask(hw));
@@ -2581,6 +2662,7 @@ em_finish_fatal_error_reset(struct e1000_softc *sc)
 		sc->fatal_error_dma_tx = 0;
 		sc->fatal_error_dma_rx = 0;
 	}
+	sc->fatal_error_icr = 0;
 	sc->fatal_error_pbeccsts = 0;
 	atomic_store_rel_32(&sc->fatal_error_state, EM_FATAL_ERROR_NONE);
 }
@@ -4265,6 +4347,7 @@ em_reset(if_ctx_t ctx)
 		device_printf(dev, "Hardware Initialization Failed\n");
 		return;
 	}
+	em_configure_82576_memory_errors(sc);
 	em_finish_fatal_error_reset(sc);
 	if (hw->mac.type >= igb_mac_min)
 		igb_init_dmac(sc, pba);
@@ -6539,6 +6622,7 @@ em_add_hw_stats(struct e1000_softc *sc)
 		    CTLFLAG_RD, &stats->rlpic, "RX LPI event count");
 	}
 	if (em_has_pch_ecc(&sc->hw) ||
+	    em_has_82576_memory_errors(&sc->hw) ||
 	    em_has_i210_i350_memory_errors(&sc->hw)) {
 		struct sysctl_oid *memerr_node;
 		struct sysctl_oid_list *memerr_list;
@@ -6560,6 +6644,11 @@ em_add_hw_stats(struct e1000_softc *sc)
 			    "uncorrected_packet_buffer", CTLFLAG_RD,
 			    &sc->uncorrected_error_packet_buffer_count,
 			    "Uncorrected packet-buffer ECC errors");
+		} else if (em_has_82576_memory_errors(&sc->hw)) {
+			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+			    "fatal_unknown", CTLFLAG_RD,
+			    &sc->fatal_error_unknown_count,
+			    "Fatal memory errors without a reported source");
 		} else {
 			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
 			    "fatal_lan", CTLFLAG_RD,
diff --git a/sys/dev/e1000/if_em.h b/sys/dev/e1000/if_em.h
index 3273d4c6b7fe..5515fcd3b4b2 100644
--- a/sys/dev/e1000/if_em.h
+++ b/sys/dev/e1000/if_em.h
@@ -625,6 +625,7 @@ struct e1000_softc {
 	u32			promisc_pending;
 	u32			stats_pending;
 	u32			fatal_error_state;
+	u32			fatal_error_icr;
 	u32			fatal_error_pbeccsts;
 	u32			fatal_error_peind;
 	u32			fatal_error_pcie;