git: bbf93227fe9e - main - igc: Recover from fatal internal memory errors
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 12 Aug 2026 19:40:07 UTC
The branch main has been updated by kbowling:
URL: https://cgit.FreeBSD.org/src/commit/?id=bbf93227fe9ee1f38b7db5477398c3564e5c84bc
commit bbf93227fe9ee1f38b7db5477398c3564e5c84bc
Author: Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-12 04:12:49 +0000
Commit: Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-12 19:39:54 +0000
igc: Recover from fatal internal memory errors
I225 and I226 report uncorrectable internal memory errors through
ICR.FER and identify the affected region in PEIND. Depending on the
region, hardware stops transmit or all PCIe and DMA traffic until the
port is reset and reinitialized.
Enable the fatal error interrupt and capture its read clear status in
the interrupt filter. Mask the cause while an iflib reset is pending,
report the affected memory regions, and expose per region indication
counters.
PCIe region parity failures require a different recovery order from a
normal reset: assert DEV_RST, wait at least 3 ms, disable PCIe master
requests, clear PCIEERRSTS, and then reinitialize the port. Follow that
sequence before entering the normal reset path and clear the remaining
LAN status afterward.
The I225/I226 PBECCSTS layout is unrelated to the PCH layout previously
copied into the igc headers. Replace those unused definitions with the
I225/I226 memory error register definitions.
Hardware validation used an I225-IT revision 3 and a debug kernel that
wrote only the documented self-clearing injection bits. It did not
synthesize interrupt or status state.
Coverage, notably DMA and Mgmt are not fully testable in my setup:
Region Observed hardware status Result
LAN PEIND 0x1, LANPERRSTS 0x200 Reset and recovered
PCIe PEIND 0x4, PCIEERRSTS 0x8 Reset and recovered
DMA DRPARC injection read back zero DFT-gated on test NIC
Mgmt Host debug strap unavailable Not injectable
The repeated LAN and PCIe tests recovered without a panic or watchdog.
A PCIe-to-LAN sequence also verified that reset-time PEIND indications
are drained before FER is unmasked.
MFC after: 2 weeks
Sponsored by: BBOX.io
---
sys/dev/igc/if_igc.c | 226 +++++++++++++++++++++++++++++++++++++++++++++-
sys/dev/igc/if_igc.h | 10 ++
sys/dev/igc/igc_defines.h | 25 ++++-
sys/dev/igc/igc_regs.h | 9 +-
4 files changed, 258 insertions(+), 12 deletions(-)
diff --git a/sys/dev/igc/if_igc.c b/sys/dev/igc/if_igc.c
index 359cd1d6dc39..add391f9195f 100644
--- a/sys/dev/igc/if_igc.c
+++ b/sys/dev/igc/if_igc.c
@@ -127,6 +127,10 @@ static void igc_if_intr_enable(if_ctx_t);
static void igc_if_intr_disable(if_ctx_t);
static int igc_if_rx_queue_intr_enable(if_ctx_t, uint16_t);
static int igc_if_tx_queue_intr_enable(if_ctx_t, uint16_t);
+static void igc_handle_fatal_error_intr(struct igc_softc *, u32);
+static bool igc_handle_fatal_error_admin(struct igc_softc *);
+static void igc_prepare_fatal_error_reset(struct igc_softc *);
+static void igc_finish_fatal_error_reset(struct igc_softc *);
static void igc_if_multi_set(if_ctx_t);
static void igc_if_update_admin_status(if_ctx_t);
static void igc_apply_i225_ipg_workaround(struct igc_softc *);
@@ -162,6 +166,13 @@ static void igc_enable_wakeup(if_ctx_t);
int igc_intr(void *);
+enum igc_fatal_error_state {
+ IGC_FATAL_ERROR_NONE,
+ IGC_FATAL_ERROR_CAPTURING,
+ IGC_FATAL_ERROR_DETECTED,
+ IGC_FATAL_ERROR_RESET_REQUESTED,
+};
+
/* MSI-X handlers */
static int igc_if_msix_intr_assign(if_ctx_t, int);
static int igc_msix_link(void *);
@@ -1120,6 +1131,8 @@ igc_intr(void *arg)
if (reg_icr & IGC_ICR_RXO)
sc->rx_overruns++;
+ igc_handle_fatal_error_intr(sc, reg_icr);
+
igc_neweitr(sc, que, rxr);
return (FILTER_SCHEDULE_THREAD);
@@ -1185,8 +1198,13 @@ igc_msix_link(void *arg)
if (reg_icr & (IGC_ICR_RXSEQ | IGC_ICR_LSC)) {
igc_handle_link(sc->ctx);
}
+ igc_handle_fatal_error_intr(sc, reg_icr);
- IGC_WRITE_REG(&sc->hw, IGC_IMS, IGC_IMS_LSC);
+ reg_icr = IGC_IMS_LSC;
+ if (atomic_load_acq_32(&sc->fatal_error_state) ==
+ IGC_FATAL_ERROR_NONE)
+ reg_icr |= IGC_IMS_FER;
+ IGC_WRITE_REG(&sc->hw, IGC_IMS, reg_icr);
IGC_WRITE_REG(&sc->hw, IGC_EIMS, sc->link_mask);
return (FILTER_HANDLED);
@@ -1202,6 +1220,93 @@ igc_handle_link(void *context)
iflib_admin_intr_deferred(ctx);
}
+/*
+ * Fatal internal memory errors stop some or all device traffic. Capture the
+ * read-clear indication before handing recovery to the iflib admin task.
+ */
+static void
+igc_handle_fatal_error_intr(struct igc_softc *sc, u32 icr)
+{
+ struct igc_hw *hw;
+ u32 lanerr, mngerr, pcieerr, peind;
+
+ if ((icr & IGC_ICR_FER) == 0)
+ return;
+
+ hw = &sc->hw;
+ IGC_WRITE_REG(hw, IGC_IMC, IGC_IMS_FER);
+ if (!atomic_cmpset_32(&sc->fatal_error_state,
+ IGC_FATAL_ERROR_NONE, IGC_FATAL_ERROR_CAPTURING))
+ return;
+
+ peind = IGC_READ_REG(hw, IGC_PEIND) & IGC_PEIND_FATAL_MASK;
+ pcieerr = IGC_READ_REG(hw, IGC_PCIEERRSTS) &
+ IGC_PCIEERRSTS_FATAL_MASK;
+ lanerr = IGC_READ_REG(hw, IGC_LANPERRSTS) &
+ IGC_LANPERRSTS_RETX_BUF;
+ mngerr = IGC_READ_REG(hw, IGC_MNGPARSTS) &
+ IGC_MNGPARSTS_FATAL_MASK;
+ if (pcieerr != 0)
+ peind |= IGC_PEIND_PCIE_PARITY_FATAL;
+ if (lanerr != 0)
+ peind |= IGC_PEIND_LANPORT_PARITY_FATAL;
+
+ sc->fatal_error_peind = peind;
+ sc->fatal_error_pcie = pcieerr;
+ sc->fatal_error_lan = lanerr;
+ sc->fatal_error_mng = mngerr;
+ atomic_store_rel_32(&sc->fatal_error_state,
+ IGC_FATAL_ERROR_DETECTED);
+ iflib_admin_intr_deferred(sc->ctx);
+}
+
+static bool
+igc_handle_fatal_error_admin(struct igc_softc *sc)
+{
+ u32 peind;
+
+ if (!atomic_cmpset_acq_32(&sc->fatal_error_state,
+ IGC_FATAL_ERROR_DETECTED, IGC_FATAL_ERROR_RESET_REQUESTED))
+ return (atomic_load_acq_32(&sc->fatal_error_state) !=
+ IGC_FATAL_ERROR_NONE);
+
+ peind = sc->fatal_error_peind;
+ if (peind & IGC_PEIND_LANPORT_PARITY_FATAL)
+ sc->fatal_error_lan_count++;
+ if (peind & IGC_PEIND_MNG_PARITY_FATAL)
+ sc->fatal_error_mng_count++;
+ if (peind & IGC_PEIND_PCIE_PARITY_FATAL)
+ sc->fatal_error_pcie_count++;
+ if (peind & IGC_PEIND_DMA_PARITY_FATAL)
+ sc->fatal_error_dma_count++;
+ if (peind == 0)
+ sc->fatal_error_unknown_count++;
+
+ device_printf(sc->dev,
+ "fatal internal memory error: PEIND %#x, PCIEERRSTS %#x, "
+ "LANPERRSTS %#x, MNGPARSTS %#x\n",
+ peind, sc->fatal_error_pcie, sc->fatal_error_lan,
+ sc->fatal_error_mng);
+ /* Management-memory recovery is owned by management firmware. */
+ if (peind != 0 && (peind & IGC_PEIND_HOST_FATAL_MASK) == 0) {
+ sc->fatal_error_peind = 0;
+ sc->fatal_error_pcie = 0;
+ sc->fatal_error_lan = 0;
+ sc->fatal_error_mng = 0;
+ atomic_store_rel_32(&sc->fatal_error_state,
+ IGC_FATAL_ERROR_NONE);
+ IGC_WRITE_REG(&sc->hw, IGC_IMS, IGC_IMS_FER);
+ IGC_WRITE_FLUSH(&sc->hw);
+ return (true);
+ }
+
+ device_printf(sc->dev, "requesting reset after memory error\n");
+ iflib_request_reset(sc->ctx);
+ /* Re-enter the admin task so it observes the reset request. */
+ iflib_admin_intr_deferred(sc->ctx);
+ return (true);
+}
+
/*********************************************************************
*
* Media Ioctl callback
@@ -1446,6 +1551,9 @@ igc_if_update_admin_status(if_ctx_t ctx)
device_t dev = iflib_get_dev(ctx);
u32 link_check, thstat, ctrl;
+ if (igc_handle_fatal_error_admin(sc))
+ return;
+
link_check = thstat = ctrl = 0;
/* Get the cached link value or read phy for real */
switch (hw->phy.media_type) {
@@ -1501,10 +1609,93 @@ igc_if_stop(if_ctx_t ctx)
INIT_DEBUGOUT("igc_if_stop: begin");
igc_led_restore(sc);
+ igc_prepare_fatal_error_reset(sc);
igc_reset_hw(&sc->hw);
+ igc_finish_fatal_error_reset(sc);
IGC_WRITE_REG(&sc->hw, IGC_WUC, 0);
}
+/*
+ * A PCIe-region parity failure stops PCIe and DMA traffic. Intel requires a
+ * device reset before master disable in this case, unlike the normal reset
+ * path, which disables the bus master first.
+ */
+static void
+igc_prepare_fatal_error_reset(struct igc_softc *sc)
+{
+ struct igc_hw *hw;
+ s32 error;
+ u32 ctrl, pcieerr;
+ int i;
+
+ if (atomic_load_acq_32(&sc->fatal_error_state) ==
+ IGC_FATAL_ERROR_NONE)
+ return;
+
+ hw = &sc->hw;
+ pcieerr = sc->fatal_error_pcie |
+ (IGC_READ_REG(hw, IGC_PCIEERRSTS) & IGC_PCIEERRSTS_FATAL_MASK);
+ if ((sc->fatal_error_peind & IGC_PEIND_PCIE_PARITY_FATAL) == 0 &&
+ pcieerr == 0)
+ return;
+
+ ctrl = IGC_READ_REG(hw, IGC_CTRL);
+ IGC_WRITE_REG(hw, IGC_CTRL, ctrl | IGC_CTRL_DEV_RST);
+ /* Do not access device registers for at least 3 ms after DEV_RST. */
+ msec_delay(3);
+ for (i = 0; i < AUTO_READ_DONE_TIMEOUT; i++) {
+ if ((IGC_READ_REG(hw, IGC_EECD) & IGC_EECD_AUTO_RD) != 0 &&
+ (IGC_READ_REG(hw, IGC_STATUS) & IGC_STATUS_RST_DONE) != 0)
+ break;
+ msec_delay(1);
+ }
+ if (i == AUTO_READ_DONE_TIMEOUT)
+ device_printf(sc->dev,
+ "device reset did not complete during parity recovery\n");
+ error = igc_disable_pcie_master_generic(hw);
+ if (error != IGC_SUCCESS)
+ device_printf(sc->dev,
+ "PCIe master disable failed during parity recovery: %d\n",
+ error);
+ pcieerr |= IGC_READ_REG(hw, IGC_PCIEERRSTS) &
+ IGC_PCIEERRSTS_FATAL_MASK;
+ if (pcieerr != 0)
+ IGC_WRITE_REG(hw, IGC_PCIEERRSTS, pcieerr);
+}
+
+static void
+igc_finish_fatal_error_reset(struct igc_softc *sc)
+{
+ struct igc_hw *hw;
+ u32 lanerr, pcieerr;
+
+ if (atomic_load_acq_32(&sc->fatal_error_state) ==
+ IGC_FATAL_ERROR_NONE)
+ return;
+
+ hw = &sc->hw;
+ pcieerr = sc->fatal_error_pcie |
+ (IGC_READ_REG(hw, IGC_PCIEERRSTS) & IGC_PCIEERRSTS_FATAL_MASK);
+ if (pcieerr != 0)
+ IGC_WRITE_REG(hw, IGC_PCIEERRSTS, pcieerr);
+ lanerr = sc->fatal_error_lan |
+ (IGC_READ_REG(hw, IGC_LANPERRSTS) & IGC_LANPERRSTS_RETX_BUF);
+ if (lanerr != 0)
+ IGC_WRITE_REG(hw, IGC_LANPERRSTS, lanerr);
+ /*
+ * DEV_RST can relatch PEIND from a subordinate status register
+ * before that register is cleared. Drain the recovered indication
+ * before unmasking FER so a later error is not misattributed.
+ */
+ (void)IGC_READ_REG(hw, IGC_PEIND);
+
+ sc->fatal_error_peind = 0;
+ sc->fatal_error_pcie = 0;
+ sc->fatal_error_lan = 0;
+ sc->fatal_error_mng = 0;
+ atomic_store_rel_32(&sc->fatal_error_state, IGC_FATAL_ERROR_NONE);
+}
+
/*
* I225/I226 have three configurable LED outputs. DPDK uses LED1 for
* adapter identification; retain that convention and preserve the OEM's
@@ -2569,9 +2760,13 @@ igc_if_intr_enable(if_ctx_t ctx)
IGC_WRITE_REG(hw, IGC_EIAC, mask);
IGC_WRITE_REG(hw, IGC_EIAM, mask);
IGC_WRITE_REG(hw, IGC_EIMS, mask);
- IGC_WRITE_REG(hw, IGC_IMS, IGC_IMS_LSC);
+ mask = IGC_IMS_LSC;
} else
- IGC_WRITE_REG(hw, IGC_IMS, IMS_ENABLE_MASK);
+ mask = IMS_ENABLE_MASK;
+ if (atomic_load_acq_32(&sc->fatal_error_state) ==
+ IGC_FATAL_ERROR_NONE)
+ mask |= IGC_IMS_FER;
+ IGC_WRITE_REG(hw, IGC_IMS, mask);
IGC_WRITE_FLUSH(hw);
}
@@ -2901,8 +3096,10 @@ igc_add_hw_stats(struct igc_softc *sc)
struct sysctl_oid_list *child = SYSCTL_CHILDREN(tree);
struct igc_hw_stats *stats = &sc->stats;
- struct sysctl_oid *eee_node, *stat_node, *queue_node, *int_node;
- struct sysctl_oid_list *eee_list, *stat_list, *queue_list, *int_list;
+ struct sysctl_oid *eee_node, *memerr_node, *stat_node, *queue_node,
+ *int_node;
+ struct sysctl_oid_list *eee_list, *memerr_list, *stat_list, *queue_list,
+ *int_list;
#define QUEUE_NAME_LEN 32
char namebuf[QUEUE_NAME_LEN];
@@ -2931,6 +3128,25 @@ igc_add_hw_stats(struct igc_softc *sc)
SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "fc_low_water",
CTLFLAG_RD, &sc->hw.fc.low_water, 0,
"Flow Control Low Watermark");
+ memerr_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "memory_errors",
+ CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
+ "Internal memory error indications");
+ memerr_list = SYSCTL_CHILDREN(memerr_node);
+ SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO, "fatal_lan",
+ CTLFLAG_RD, &sc->fatal_error_lan_count,
+ "Fatal LAN-port memory error indications");
+ SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO, "fatal_management",
+ CTLFLAG_RD, &sc->fatal_error_mng_count,
+ "Fatal management-memory error indications");
+ SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO, "fatal_pcie",
+ CTLFLAG_RD, &sc->fatal_error_pcie_count,
+ "Fatal PCIe memory error indications");
+ SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO, "fatal_dma",
+ CTLFLAG_RD, &sc->fatal_error_dma_count,
+ "Fatal DMA memory error indications");
+ SYSCTL_ADD_UQUAD(ctx, memerr_list, OID_AUTO, "fatal_unknown",
+ CTLFLAG_RD, &sc->fatal_error_unknown_count,
+ "Fatal memory errors without a reported region");
eee_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "eee",
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
"Energy Efficient Ethernet statistics");
diff --git a/sys/dev/igc/if_igc.h b/sys/dev/igc/if_igc.h
index c98f17a97ca3..8b35e1f59a60 100644
--- a/sys/dev/igc/if_igc.h
+++ b/sys/dev/igc/if_igc.h
@@ -414,6 +414,16 @@ struct igc_softc {
unsigned long dropped_pkts;
unsigned long link_irq;
unsigned long rx_overruns;
+ u32 fatal_error_state;
+ u32 fatal_error_peind;
+ u32 fatal_error_pcie;
+ u32 fatal_error_lan;
+ u32 fatal_error_mng;
+ uint64_t fatal_error_lan_count;
+ uint64_t fatal_error_mng_count;
+ uint64_t fatal_error_pcie_count;
+ uint64_t fatal_error_dma_count;
+ uint64_t fatal_error_unknown_count;
struct igc_hw_stats stats;
u16 vf_ifp;
};
diff --git a/sys/dev/igc/igc_defines.h b/sys/dev/igc/igc_defines.h
index 885f0799f6a6..1856c86b91a8 100644
--- a/sys/dev/igc/igc_defines.h
+++ b/sys/dev/igc/igc_defines.h
@@ -254,6 +254,7 @@
#define IGC_STATUS_LAN_INIT_DONE 0x00000200 /* Lan Init Compltn by NVM */
#define IGC_STATUS_PHYRA 0x00000400 /* PHY Reset Asserted */
#define IGC_STATUS_GIO_MASTER_ENABLE 0x00080000 /* Master request status */
+#define IGC_STATUS_RST_DONE 0x00200000 /* Device reset complete */
#define IGC_STATUS_2P5_SKU 0x00001000 /* Val of 2.5GBE SKU strap */
#define IGC_STATUS_2P5_SKU_OVER 0x00002000 /* Val of 2.5GBE SKU Over */
#define IGC_STATUS_PCIM_STATE 0x40000000 /* PCIm function state */
@@ -427,11 +428,25 @@
#define IGC_PBS_16K IGC_PBA_16K
-/* Uncorrectable/correctable ECC Error counts and enable bits */
-#define IGC_PBECCSTS_CORR_ERR_CNT_MASK 0x000000FF
-#define IGC_PBECCSTS_UNCORR_ERR_CNT_MASK 0x0000FF00
-#define IGC_PBECCSTS_UNCORR_ERR_CNT_SHIFT 8
-#define IGC_PBECCSTS_ECC_ENABLE 0x00010000
+/* I225/I226 memory error status bits. */
+#define IGC_PEIND_LANPORT_PARITY_FATAL 0x00000001
+#define IGC_PEIND_MNG_PARITY_FATAL 0x00000002
+#define IGC_PEIND_PCIE_PARITY_FATAL 0x00000004
+#define IGC_PEIND_DMA_PARITY_FATAL 0x00000008
+#define IGC_PEIND_FATAL_MASK 0x0000000F
+#define IGC_PEIND_HOST_FATAL_MASK (IGC_PEIND_LANPORT_PARITY_FATAL | \
+ IGC_PEIND_PCIE_PARITY_FATAL | IGC_PEIND_DMA_PARITY_FATAL)
+
+#define IGC_PBECCSTS_ECC_ENABLE 0x00000001
+#define IGC_PBECCSTS_CORR_ERR 0x00000004
+
+#define IGC_PCIEERRSTS_FATAL_MASK 0x00000078
+#define IGC_PCIEECCSTS_TX_WR_DATA 0x00000010
+#define IGC_PCIEECCSTS_RETRY_BUF 0x00000020
+#define IGC_PCIEECCSTS_CORR_MASK 0x00000030
+
+#define IGC_LANPERRSTS_RETX_BUF 0x00000200
+#define IGC_MNGPARSTS_FATAL_MASK 0x00000003
#define IFS_MAX 80
#define IFS_MIN 40
diff --git a/sys/dev/igc/igc_regs.h b/sys/dev/igc/igc_regs.h
index f4690e24c2af..849b13b2adf2 100644
--- a/sys/dev/igc/igc_regs.h
+++ b/sys/dev/igc/igc_regs.h
@@ -376,8 +376,13 @@
#define IGC_FCRTC 0x02170 /* Flow Control Rx high watermark */
#define IGC_PCIEMISC 0x05BB8 /* PCIE misc config register */
-/* PCIe Parity Status Register */
-#define IGC_PCIEERRSTS 0x05BA8
+/* Memory error detection registers */
+#define IGC_PEIND 0x01084 /* Parity and ECC Indication - RC */
+#define IGC_PBECCSTS 0x0245C /* Packet Buffer ECC Status */
+#define IGC_PCIEERRSTS 0x05BA8 /* PCIe Parity Status - RW1C */
+#define IGC_PCIEECCSTS 0x05BAC /* PCIe ECC Status - RW1C */
+#define IGC_LANPERRSTS 0x05F58 /* LAN Port Parity Status - RW1C */
+#define IGC_MNGPARSTS 0x08F24 /* Management Parity Status */
#define IGC_PROXYS 0x5F64 /* Proxying Status */
#define IGC_PROXYFC 0x5F60 /* Proxying Filter Control */