git: 17042fd31571 - main - e1000: Report 82576 memory ECC errors

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

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

commit 17042fd31571c7ceb955570ef43c9921d4a80f21
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-13 23:15:39 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-16 10:02:44 +0000

    e1000: Report 82576 memory ECC errors
    
    82576 exposes clear-on-read corrected error counters for RX, TX,
    switch, IPsec, descriptor-handler, PCIe retry, PCIe write, and
    MSI-X memories.  The packet and descriptor memories also count
    uncorrectable errors.
    
    Sample each status register exactly once from the regular hardware
    statistics update and immediately before handling a memory-error
    interrupt.  Group the counters by packet buffer, descriptor handler,
    and PCIe region.  Skip the absent IPsec block on 82576NS.
    
    PRBESTS and PMSIXESTS are shared by both LAN ports.  Attribute an
    indication to whichever attached port samples the clear-on-read
    register first so it is not counted twice.
    
    Hardware validation used an 82576EB revision 1.  All nine implemented
    status registers reported their ECC-enable bit set.  The sysctl
    counters remained clear across interface lifecycle, two-stream
    line-rate traffic, and NFER and FER cause injections.  Each reset
    preserved the ECC enables while the driver restored PEINDM reactions.
    
    ICR cause injection does not corrupt SRAM, and the only documented
    data injector is specific to the IPsec packet buffer.  Exact counter
    increments for the other memories were therefore validated against
    the register definitions rather than an injected ECC error.
    
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
---
 sys/dev/e1000/e1000_defines.h |  5 +++
 sys/dev/e1000/e1000_regs.h    |  7 ++++
 sys/dev/e1000/if_em.c         | 83 +++++++++++++++++++++++++++++++++++++++++++
 sys/dev/e1000/if_em.h         |  1 +
 4 files changed, 96 insertions(+)

diff --git a/sys/dev/e1000/e1000_defines.h b/sys/dev/e1000/e1000_defines.h
index cf23bbde846e..ab53546d5597 100644
--- a/sys/dev/e1000/e1000_defines.h
+++ b/sys/dev/e1000/e1000_defines.h
@@ -543,6 +543,11 @@
 /* 82576NS omits the IPsec key, FIFO, and packet-buffer memories. */
 #define E1000_PEIND_82576_IPSEC_MASK	0x40700600
 
+/* The 82576 clear-on-read ECC status registers share this count layout. */
+#define E1000_ECC_82576_CORR_CNT_MASK	0x000000FF
+#define E1000_ECC_82576_UNCORR_CNT_MASK	0x0000FF00
+#define E1000_ECC_82576_UNCORR_CNT_SHIFT	8
+
 #define E1000_PBECCSTS_I210_ECC_ENABLE	0x00000001
 #define E1000_PBECCSTS_I210_CORR_ERR	0x00000004
 
diff --git a/sys/dev/e1000/e1000_regs.h b/sys/dev/e1000/e1000_regs.h
index ec5df9ad774d..2141118c1246 100644
--- a/sys/dev/e1000/e1000_regs.h
+++ b/sys/dev/e1000/e1000_regs.h
@@ -117,6 +117,13 @@
 #define E1000_PEIND	0x01084  /* Parity and ECC Indication - RC */
 #define E1000_PEINDM	0x01088  /* Parity and ECC Indication Mask - RW */
 #define E1000_PBECCSTS_I210 0x0245C /* I210 Packet Buffer ECC Status */
+#define E1000_SWPBECCSTS_82576 0x0305C /* Switch Packet Buffer ECC Status */
+#define E1000_IPPBECCSTS_82576 0x0B470 /* IPsec Packet Buffer ECC Status */
+#define E1000_RDHESTS_82576 0x025C0 /* Rx Descriptor Handler ECC Status */
+#define E1000_TDHESTS_82576 0x035C0 /* Tx Descriptor Handler ECC Status */
+#define E1000_PRBESTS_82576 0x05BA0 /* PCIe Retry Buffer ECC Status */
+#define E1000_PMSIXESTS_82576 0x05BA8 /* PCIe MSI-X ECC Status */
+#define E1000_PWBESTS_82576 0x05BB0 /* PCIe Write Buffer ECC Status */
 #define E1000_IOSFPC	0x00F28  /* TX corrupted data  */
 #define E1000_EEMNGCTL	0x01010  /* MNG EEprom Control */
 #define E1000_EEMNGCTL_I210	0x01010  /* i210 MNG EEprom Mode Control */
diff --git a/sys/dev/e1000/if_em.c b/sys/dev/e1000/if_em.c
index 6aa2a634bd24..8f2502ea2015 100644
--- a/sys/dev/e1000/if_em.c
+++ b/sys/dev/e1000/if_em.c
@@ -2291,6 +2291,58 @@ em_fatal_error_intr_mask(struct e1000_softc *sc)
 	return (mask);
 }
 
+static void
+em_update_82576_ecc_counter(struct e1000_softc *sc, u32 reg,
+    u64 *corrected, u64 *uncorrected)
+{
+	u32 status;
+
+	status = E1000_READ_REG(&sc->hw, reg);
+	*corrected += status & E1000_ECC_82576_CORR_CNT_MASK;
+	if (uncorrected != NULL)
+		*uncorrected +=
+		    (status & E1000_ECC_82576_UNCORR_CNT_MASK) >>
+		    E1000_ECC_82576_UNCORR_CNT_SHIFT;
+}
+
+static void
+em_update_82576_ecc_stats(struct e1000_softc *sc)
+{
+
+	/*
+	 * These counters are clear-on-read.  PRBESTS and PMSIXESTS are
+	 * controller-shared, so whichever LAN port samples them first owns
+	 * the software count.
+	 */
+	em_update_82576_ecc_counter(sc, E1000_RPBECCSTS,
+	    &sc->corrected_error_packet_buffer_count,
+	    &sc->uncorrected_error_packet_buffer_count);
+	em_update_82576_ecc_counter(sc, E1000_TPBECCSTS,
+	    &sc->corrected_error_packet_buffer_count,
+	    &sc->uncorrected_error_packet_buffer_count);
+	em_update_82576_ecc_counter(sc, E1000_SWPBECCSTS_82576,
+	    &sc->corrected_error_packet_buffer_count,
+	    &sc->uncorrected_error_packet_buffer_count);
+	if (em_82576_has_ipsec(&sc->hw))
+		em_update_82576_ecc_counter(sc, E1000_IPPBECCSTS_82576,
+		    &sc->corrected_error_packet_buffer_count,
+		    &sc->uncorrected_error_packet_buffer_count);
+
+	em_update_82576_ecc_counter(sc, E1000_RDHESTS_82576,
+	    &sc->corrected_error_dma_count,
+	    &sc->uncorrected_error_dma_count);
+	em_update_82576_ecc_counter(sc, E1000_TDHESTS_82576,
+	    &sc->corrected_error_dma_count,
+	    &sc->uncorrected_error_dma_count);
+
+	em_update_82576_ecc_counter(sc, E1000_PRBESTS_82576,
+	    &sc->corrected_error_pcie_retry_count, NULL);
+	em_update_82576_ecc_counter(sc, E1000_PWBESTS_82576,
+	    &sc->corrected_error_pcie_tx_data_count, NULL);
+	em_update_82576_ecc_counter(sc, E1000_PMSIXESTS_82576,
+	    &sc->corrected_error_pcie_other_count, NULL);
+}
+
 static void
 em_update_pch_ecc_stats(struct e1000_softc *sc, u32 pbeccsts)
 {
@@ -2469,6 +2521,7 @@ em_handle_fatal_error_admin(struct e1000_softc *sc)
 		    sc->fatal_error_pbeccsts);
 	} else if (em_has_82576_memory_errors(&sc->hw)) {
 		peind = sc->fatal_error_peind;
+		em_update_82576_ecc_stats(sc);
 		reset_required =
 		    (sc->fatal_error_icr & E1000_ICR_FER) != 0 ||
 		    (peind & (E1000_PEIND_82576_FATAL_MASK |
@@ -6205,6 +6258,8 @@ em_update_stats_counters(struct e1000_softc *sc)
 	if (em_has_pch_ecc(&sc->hw))
 		em_update_pch_ecc_stats(sc,
 		    E1000_READ_REG(&sc->hw, E1000_PBECCSTS));
+	else if (em_has_82576_memory_errors(&sc->hw))
+		em_update_82576_ecc_stats(sc);
 	else if (em_has_i350_memory_errors(&sc->hw))
 		em_update_i350_ecc_stats(sc);
 	else if (em_has_i210_memory_errors(&sc->hw))
@@ -6649,6 +6704,34 @@ em_add_hw_stats(struct e1000_softc *sc)
 			    "fatal_unknown", CTLFLAG_RD,
 			    &sc->fatal_error_unknown_count,
 			    "Fatal memory errors without a reported source");
+			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+			    "corrected_packet_buffer", CTLFLAG_RD,
+			    &sc->corrected_error_packet_buffer_count,
+			    "Corrected packet and switch-buffer ECC errors");
+			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+			    "uncorrected_packet_buffer", CTLFLAG_RD,
+			    &sc->uncorrected_error_packet_buffer_count,
+			    "Uncorrected packet and switch-buffer ECC errors");
+			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+			    "corrected_descriptor_handler", CTLFLAG_RD,
+			    &sc->corrected_error_dma_count,
+			    "Corrected descriptor-handler ECC errors");
+			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+			    "uncorrected_descriptor_handler", CTLFLAG_RD,
+			    &sc->uncorrected_error_dma_count,
+			    "Uncorrected descriptor-handler ECC errors");
+			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+			    "corrected_pcie_write_buffer", CTLFLAG_RD,
+			    &sc->corrected_error_pcie_tx_data_count,
+			    "Corrected PCIe write-buffer ECC errors");
+			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+			    "corrected_pcie_retry_buffer", CTLFLAG_RD,
+			    &sc->corrected_error_pcie_retry_count,
+			    "Corrected controller-shared PCIe retry-buffer errors");
+			SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO,
+			    "corrected_pcie_msix", CTLFLAG_RD,
+			    &sc->corrected_error_pcie_other_count,
+			    "Corrected controller-shared PCIe MSI-X errors");
 		} 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 5515fcd3b4b2..9c441edd320b 100644
--- a/sys/dev/e1000/if_em.h
+++ b/sys/dev/e1000/if_em.h
@@ -644,6 +644,7 @@ struct e1000_softc {
 	u64			corrected_error_pcie_other_count;
 	u64			corrected_error_packet_buffer_count;
 	u64			uncorrected_error_packet_buffer_count;
+	u64			uncorrected_error_dma_count;
 
 #ifdef PCI_IOV
 	struct igb_vf		*vfs;