The branch main has been updated by kbowling:

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

commit bbf93227fe9ee1f38b7db5477398c3564e5c84bc
Author:     Kevin Bowling <[email protected]>
AuthorDate: 2026-08-12 04:12:49 +0000
Commit:     Kevin Bowling <[email protected]>
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 */

Reply via email to