git: 28bbe1d28d6f - main - e1000: Recover from I350 memory errors

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

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

commit 28bbe1d28d6f1e68e2ee3b34686ccb6114c42ca7
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-12 23:34:56 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-16 09:41:25 +0000

    e1000: Recover from I350 memory errors
    
    I350 reports uncorrectable internal memory errors through ICR.FER and
    identifies the affected region in PEIND.  Depending on the region and
    memory, hardware stops transmit, receive, or all PCIe and DMA traffic
    until the port is reset and reinitialized.
    
    Enable FER and all regional indication masks.  Capture the read-clear
    status in the interrupt filter.
    Record the fatal PCIe, DMA, and LAN status registers, keep FER masked
    while recovery is pending, and expose per-region indication counters.
    
    Use the datasheet required port reset before master disable order for
    PCIe parity errors.  Reset for PCIe, DMA, and traffic-affecting LAN
    errors.  Statistics and VF-mailbox parity errors only require their
    status to be discarded and cleared; management-memory recovery remains
    under firmware control.
    
    Validated on an I350 (8086:1521 revision 1).  Three software-set FER
    interrupts each advanced the unknown-region counter once, requested a
    single reset, restored carrier and traffic, and left FER rearmed without
    a watchdog.  The software-set cause has no subordinate error status, so
    region attribution and region-specific clearing remain datasheet-based.
    
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
---
 sys/dev/e1000/e1000_defines.h |  15 ++--
 sys/dev/e1000/e1000_regs.h    |   2 +
 sys/dev/e1000/if_em.c         | 159 ++++++++++++++++++++++++++++++++----------
 sys/dev/e1000/if_em.h         |   2 +
 4 files changed, 136 insertions(+), 42 deletions(-)

diff --git a/sys/dev/e1000/e1000_defines.h b/sys/dev/e1000/e1000_defines.h
index fb2332462206..da06481be7de 100644
--- a/sys/dev/e1000/e1000_defines.h
+++ b/sys/dev/e1000/e1000_defines.h
@@ -528,24 +528,29 @@
 #define E1000_PBECCSTS_UNCORR_ERR_CNT_SHIFT	8
 #define E1000_PBECCSTS_ECC_ENABLE		0x00010000
 
-/* I210/I211 memory error status bits. */
+/* I350 and I210/I211 memory error status bits. */
 #define E1000_PEIND_LANPORT_PARITY_FATAL	0x00000001
 #define E1000_PEIND_MNG_PARITY_FATAL	0x00000002
 #define E1000_PEIND_PCIE_PARITY_FATAL	0x00000004
 #define E1000_PEIND_DMA_PARITY_FATAL	0x00000008
 #define E1000_PEIND_FATAL_MASK		0x0000000F
-#define E1000_PEIND_HOST_FATAL_MASK	(E1000_PEIND_LANPORT_PARITY_FATAL | \
-	E1000_PEIND_PCIE_PARITY_FATAL | E1000_PEIND_DMA_PARITY_FATAL)
 
 #define E1000_PBECCSTS_I210_ECC_ENABLE	0x00000001
 #define E1000_PBECCSTS_I210_CORR_ERR	0x00000004
 
-#define E1000_PCIEERRSTS_FATAL_MASK	0x00000078
+#define E1000_PCIEERRSTS_I210_FATAL_MASK	0x00000078
+#define E1000_PCIEERRSTS_I350_FATAL_MASK	0x0000007C
 #define E1000_PCIEECCSTS_TX_WR_DATA	0x00000010
 #define E1000_PCIEECCSTS_RETRY_BUF	0x00000020
-#define E1000_PCIEECCSTS_CORR_MASK	0x00000030
+#define E1000_PCIEECCSTS_I210_CORR_MASK	0x00000030
+
+#define E1000_DTPARS_FATAL_MASK		0x00000020
+#define E1000_DRPARS_FATAL_MASK		0x00000002
 
 #define E1000_LANPERRSTS_RETX_BUF		0x00000200
+#define E1000_LANPERRSTS_I350_NO_RESET_MASK	0x00008400
+#define E1000_LANPERRSTS_I350_RESET_MASK	0x00007BFE
+#define E1000_LANPERRSTS_I350_FATAL_MASK	0x0000FFFE
 
 #define IFS_MAX			80
 #define IFS_MIN			40
diff --git a/sys/dev/e1000/e1000_regs.h b/sys/dev/e1000/e1000_regs.h
index fe7b62a9a493..d2ac267f0be9 100644
--- a/sys/dev/e1000/e1000_regs.h
+++ b/sys/dev/e1000/e1000_regs.h
@@ -695,6 +695,8 @@
 #define E1000_PCIEMISC	0x05BB8 /* PCIE misc config register */
 
 /* Memory error status registers */
+#define E1000_DTPARS	0x03F10 /* DMA Tx Parity and ECC Status - RW1C */
+#define E1000_DRPARS	0x03F14 /* DMA Rx Parity and ECC Status - RW1C */
 #define E1000_PCIEERRSTS	0x05BA8 /* PCIe Parity Status - RW1C */
 #define E1000_PCIEECCSTS 0x05BAC /* PCIe ECC Status - RW1C */
 #define E1000_LANPERRSTS 0x05F58 /* LAN Port Parity Status - RW1C */
diff --git a/sys/dev/e1000/if_em.c b/sys/dev/e1000/if_em.c
index 1d9ac22bb667..263bdd48a871 100644
--- a/sys/dev/e1000/if_em.c
+++ b/sys/dev/e1000/if_em.c
@@ -2187,6 +2187,14 @@ em_has_i210_memory_errors(const struct e1000_hw *hw)
 	return (hw->mac.type == e1000_i210 ||
 	    hw->mac.type == e1000_i211);
 }
+
+static bool
+em_has_i350_memory_errors(const struct e1000_hw *hw)
+{
+
+	return (hw->mac.type == e1000_i350);
+}
+
 static void
 em_configure_peind_memory_errors(struct e1000_softc *sc)
 {
@@ -2194,7 +2202,8 @@ em_configure_peind_memory_errors(struct e1000_softc *sc)
 	u32 peindm;
 
 	hw = &sc->hw;
-	if (!em_has_i210_memory_errors(hw))
+	if (!em_has_i350_memory_errors(hw) &&
+	    !em_has_i210_memory_errors(hw))
 		return;
 
 	/* Discard indications left by firmware before enabling reactions. */
@@ -2206,12 +2215,31 @@ em_configure_peind_memory_errors(struct e1000_softc *sc)
 	E1000_WRITE_FLUSH(hw);
 }
 
+static bool
+em_has_i210_i350_memory_errors(const struct e1000_hw *hw)
+{
+
+	return (em_has_i350_memory_errors(hw) ||
+	    em_has_i210_memory_errors(hw));
+}
+
+static u32
+em_pcie_fatal_error_mask(const struct e1000_hw *hw)
+{
+
+	if (em_has_i350_memory_errors(hw))
+		return (E1000_PCIEERRSTS_I350_FATAL_MASK);
+	if (em_has_i210_memory_errors(hw))
+		return (E1000_PCIEERRSTS_I210_FATAL_MASK);
+	return (0);
+}
+
 static u32
 em_fatal_error_intr_mask(struct e1000_softc *sc)
 {
 
 	if ((em_has_pch_ecc(&sc->hw) ||
-	    em_has_i210_memory_errors(&sc->hw)) &&
+	    em_has_i210_i350_memory_errors(&sc->hw)) &&
 	    atomic_load_acq_32(&sc->fatal_error_state) ==
 	    EM_FATAL_ERROR_NONE)
 		return (E1000_IMS_FER);
@@ -2246,7 +2274,7 @@ em_update_i210_ecc_stats(struct e1000_softc *sc)
 	}
 
 	pcieeccsts = E1000_READ_REG(hw, E1000_PCIEECCSTS) &
-	    E1000_PCIEECCSTS_CORR_MASK;
+	    E1000_PCIEECCSTS_I210_CORR_MASK;
 	if (pcieeccsts & E1000_PCIEECCSTS_TX_WR_DATA)
 		sc->corrected_error_pcie_tx_data_count++;
 	if (pcieeccsts & E1000_PCIEECCSTS_RETRY_BUF)
@@ -2263,10 +2291,10 @@ static void
 em_handle_fatal_error_intr(struct e1000_softc *sc, u32 icr)
 {
 	struct e1000_hw *hw;
-	u32 lanerr, pcieerr, peind;
+	u32 dma_rx, dma_tx, lanerr, pcieerr, peind;
 
 	if ((!em_has_pch_ecc(&sc->hw) &&
-	    !em_has_i210_memory_errors(&sc->hw)) ||
+	    !em_has_i210_i350_memory_errors(&sc->hw)) ||
 	    (icr & E1000_ICR_FER) == 0)
 		return;
 
@@ -2283,16 +2311,31 @@ em_handle_fatal_error_intr(struct e1000_softc *sc, u32 icr)
 		peind = E1000_READ_REG(hw, E1000_PEIND) &
 		    E1000_PEIND_FATAL_MASK;
 		pcieerr = E1000_READ_REG(hw, E1000_PCIEERRSTS) &
-		    E1000_PCIEERRSTS_FATAL_MASK;
-		lanerr = E1000_READ_REG(hw, E1000_LANPERRSTS) &
-		    E1000_LANPERRSTS_RETX_BUF;
+		    em_pcie_fatal_error_mask(hw);
+		if (em_has_i350_memory_errors(hw)) {
+			dma_tx = E1000_READ_REG(hw, E1000_DTPARS) &
+			    E1000_DTPARS_FATAL_MASK;
+			dma_rx = E1000_READ_REG(hw, E1000_DRPARS) &
+			    E1000_DRPARS_FATAL_MASK;
+			lanerr = E1000_READ_REG(hw, E1000_LANPERRSTS) &
+			    E1000_LANPERRSTS_I350_FATAL_MASK;
+		} else {
+			dma_tx = 0;
+			dma_rx = 0;
+			lanerr = E1000_READ_REG(hw, E1000_LANPERRSTS) &
+			    E1000_LANPERRSTS_RETX_BUF;
+		}
 		if (pcieerr != 0)
 			peind |= E1000_PEIND_PCIE_PARITY_FATAL;
 		if (lanerr != 0)
 			peind |= E1000_PEIND_LANPORT_PARITY_FATAL;
+		if (dma_tx != 0 || dma_rx != 0)
+			peind |= E1000_PEIND_DMA_PARITY_FATAL;
 		sc->fatal_error_peind = peind;
 		sc->fatal_error_pcie = pcieerr;
 		sc->fatal_error_lan = lanerr;
+		sc->fatal_error_dma_tx = dma_tx;
+		sc->fatal_error_dma_rx = dma_rx;
 	}
 	atomic_store_rel_32(&sc->fatal_error_state,
 	    EM_FATAL_ERROR_DETECTED);
@@ -2303,6 +2346,7 @@ static bool
 em_handle_fatal_error_admin(struct e1000_softc *sc)
 {
 	u32 peind;
+	bool reset_required;
 
 	if (!atomic_cmpset_acq_32(&sc->fatal_error_state,
 	    EM_FATAL_ERROR_DETECTED, EM_FATAL_ERROR_RESET_REQUESTED))
@@ -2329,14 +2373,35 @@ em_handle_fatal_error_admin(struct e1000_softc *sc)
 			sc->fatal_error_unknown_count++;
 		device_printf(sc->dev,
 		    "fatal internal memory error: PEIND %#x, "
-		    "PCIEERRSTS %#x, LANPERRSTS %#x\n", peind,
-		    sc->fatal_error_pcie, sc->fatal_error_lan);
+		    "PCIEERRSTS %#x, DTPARS %#x, DRPARS %#x, "
+		    "LANPERRSTS %#x\n", peind, sc->fatal_error_pcie,
+		    sc->fatal_error_dma_tx, sc->fatal_error_dma_rx,
+		    sc->fatal_error_lan);
+
+		reset_required = (peind &
+		    (E1000_PEIND_PCIE_PARITY_FATAL |
+		    E1000_PEIND_DMA_PARITY_FATAL)) != 0;
+		if (peind == 0)
+			reset_required = true;
+		if (peind & E1000_PEIND_LANPORT_PARITY_FATAL) {
+			if (!em_has_i350_memory_errors(&sc->hw) ||
+			    sc->fatal_error_lan == 0 ||
+			    (sc->fatal_error_lan &
+			    E1000_LANPERRSTS_I350_RESET_MASK) != 0)
+				reset_required = true;
+		}
 		/* Management-memory recovery belongs to management firmware. */
-		if (peind != 0 &&
-		    (peind & E1000_PEIND_HOST_FATAL_MASK) == 0) {
+		if (!reset_required) {
+			if (em_has_i350_memory_errors(&sc->hw) &&
+			    sc->fatal_error_lan != 0)
+				E1000_WRITE_REG(&sc->hw, E1000_LANPERRSTS,
+				    sc->fatal_error_lan &
+				    E1000_LANPERRSTS_I350_NO_RESET_MASK);
 			sc->fatal_error_peind = 0;
 			sc->fatal_error_pcie = 0;
 			sc->fatal_error_lan = 0;
+			sc->fatal_error_dma_tx = 0;
+			sc->fatal_error_dma_rx = 0;
 			atomic_store_rel_32(&sc->fatal_error_state,
 			    EM_FATAL_ERROR_NONE);
 			E1000_WRITE_REG(&sc->hw, E1000_IMS,
@@ -2355,9 +2420,9 @@ em_handle_fatal_error_admin(struct e1000_softc *sc)
 }
 
 /*
- * A PCIe-region parity failure stops PCIe and DMA traffic.  I210 and I211
- * require a port reset before master disable in this case, unlike the normal
- * reset path, which disables the bus master first.
+ * A PCIe-region parity failure stops PCIe and DMA traffic.  I350, I210, and
+ * I211 require a port reset before master disable in this case, unlike the
+ * normal reset path, which disables the bus master first.
  */
 static void
 em_prepare_fatal_error_reset(struct e1000_softc *sc)
@@ -2367,14 +2432,14 @@ em_prepare_fatal_error_reset(struct e1000_softc *sc)
 	u32 ctrl, pcieerr;
 	int i;
 
-	if (!em_has_i210_memory_errors(&sc->hw) ||
+	if (!em_has_i210_i350_memory_errors(&sc->hw) ||
 	    atomic_load_acq_32(&sc->fatal_error_state) !=
 	    EM_FATAL_ERROR_RESET_REQUESTED)
 		return;
 
 	pcieerr = sc->fatal_error_pcie |
 	    (E1000_READ_REG(&sc->hw, E1000_PCIEERRSTS) &
-	    E1000_PCIEERRSTS_FATAL_MASK);
+	    em_pcie_fatal_error_mask(&sc->hw));
 	if ((sc->fatal_error_peind & E1000_PEIND_PCIE_PARITY_FATAL) == 0 &&
 	    pcieerr == 0)
 		return;
@@ -2401,7 +2466,7 @@ em_prepare_fatal_error_reset(struct e1000_softc *sc)
 		    "PCIe master disable failed during parity recovery: %d\n",
 		    error);
 	pcieerr |= E1000_READ_REG(hw, E1000_PCIEERRSTS) &
-	    E1000_PCIEERRSTS_FATAL_MASK;
+	    em_pcie_fatal_error_mask(hw);
 	if (pcieerr != 0)
 		E1000_WRITE_REG(hw, E1000_PCIEERRSTS, pcieerr);
 	atomic_store_rel_32(&sc->fatal_error_state,
@@ -2412,7 +2477,7 @@ static void
 em_finish_fatal_error_reset(struct e1000_softc *sc)
 {
 	struct e1000_hw *hw;
-	u32 lanerr, pcieerr;
+	u32 dma_rx, dma_tx, lanerr, pcieerr;
 	u32 state;
 
 	state = atomic_load_acq_32(&sc->fatal_error_state);
@@ -2421,15 +2486,31 @@ em_finish_fatal_error_reset(struct e1000_softc *sc)
 		return;
 
 	hw = &sc->hw;
-	if (em_has_i210_memory_errors(hw)) {
+	if (em_has_i210_i350_memory_errors(hw)) {
 		pcieerr = sc->fatal_error_pcie |
 		    (E1000_READ_REG(hw, E1000_PCIEERRSTS) &
-		    E1000_PCIEERRSTS_FATAL_MASK);
+		    em_pcie_fatal_error_mask(hw));
 		if (pcieerr != 0)
 			E1000_WRITE_REG(hw, E1000_PCIEERRSTS, pcieerr);
-		lanerr = sc->fatal_error_lan |
-		    (E1000_READ_REG(hw, E1000_LANPERRSTS) &
-		    E1000_LANPERRSTS_RETX_BUF);
+		if (em_has_i350_memory_errors(hw)) {
+			dma_tx = sc->fatal_error_dma_tx |
+			    (E1000_READ_REG(hw, E1000_DTPARS) &
+			    E1000_DTPARS_FATAL_MASK);
+			if (dma_tx != 0)
+				E1000_WRITE_REG(hw, E1000_DTPARS, dma_tx);
+			dma_rx = sc->fatal_error_dma_rx |
+			    (E1000_READ_REG(hw, E1000_DRPARS) &
+			    E1000_DRPARS_FATAL_MASK);
+			if (dma_rx != 0)
+				E1000_WRITE_REG(hw, E1000_DRPARS, dma_rx);
+			lanerr = sc->fatal_error_lan |
+			    (E1000_READ_REG(hw, E1000_LANPERRSTS) &
+			    E1000_LANPERRSTS_I350_FATAL_MASK);
+		} else {
+			lanerr = sc->fatal_error_lan |
+			    (E1000_READ_REG(hw, E1000_LANPERRSTS) &
+			    E1000_LANPERRSTS_RETX_BUF);
+		}
 		if (lanerr != 0)
 			E1000_WRITE_REG(hw, E1000_LANPERRSTS, lanerr);
 		/*
@@ -2441,6 +2522,8 @@ em_finish_fatal_error_reset(struct e1000_softc *sc)
 		sc->fatal_error_peind = 0;
 		sc->fatal_error_pcie = 0;
 		sc->fatal_error_lan = 0;
+		sc->fatal_error_dma_tx = 0;
+		sc->fatal_error_dma_rx = 0;
 	}
 	sc->fatal_error_pbeccsts = 0;
 	atomic_store_rel_32(&sc->fatal_error_state, EM_FATAL_ERROR_NONE);
@@ -6398,7 +6481,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_i210_memory_errors(&sc->hw)) {
+	    em_has_i210_i350_memory_errors(&sc->hw)) {
 		struct sysctl_oid *memerr_node;
 		struct sysctl_oid_list *memerr_list;
 
@@ -6440,18 +6523,20 @@ em_add_hw_stats(struct e1000_softc *sc)
 			    "fatal_unknown", CTLFLAG_RD,
 			    &sc->fatal_error_unknown_count,
 			    "Fatal memory errors without a reported region");
-			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
-			    "corrected_dma", CTLFLAG_RD,
-			    &sc->corrected_error_dma_count,
-			    "Corrected DMA memory error indications");
-			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
-			    "corrected_pcie_tx_data", CTLFLAG_RD,
-			    &sc->corrected_error_pcie_tx_data_count,
-			    "Corrected PCIe transmit-data memory indications");
-			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
-			    "corrected_pcie_retry", CTLFLAG_RD,
-			    &sc->corrected_error_pcie_retry_count,
-			    "Corrected PCIe retry-buffer memory indications");
+			if (em_has_i210_memory_errors(&sc->hw)) {
+				SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+				    "corrected_dma", CTLFLAG_RD,
+				    &sc->corrected_error_dma_count,
+				    "Corrected DMA memory error indications");
+				SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+				    "corrected_pcie_tx_data", CTLFLAG_RD,
+				    &sc->corrected_error_pcie_tx_data_count,
+				    "Corrected PCIe transmit-data memory indications");
+				SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+				    "corrected_pcie_retry", CTLFLAG_RD,
+				    &sc->corrected_error_pcie_retry_count,
+				    "Corrected PCIe retry-buffer memory indications");
+			}
 		}
 	}
 
diff --git a/sys/dev/e1000/if_em.h b/sys/dev/e1000/if_em.h
index 0dd413dafcc8..ccb23afb48a2 100644
--- a/sys/dev/e1000/if_em.h
+++ b/sys/dev/e1000/if_em.h
@@ -629,6 +629,8 @@ struct e1000_softc {
 	u32			fatal_error_peind;
 	u32			fatal_error_pcie;
 	u32			fatal_error_lan;
+	u32			fatal_error_dma_tx;
+	u32			fatal_error_dma_rx;
 	u64			fatal_error_reset_count;
 	u64			fatal_error_lan_count;
 	u64			fatal_error_mng_count;