git: 5e56a1fb09cd - main - e1000: Recover from 82580 memory errors
Kevin Bowling <[email protected]>
| Newsgroups | gmane.os.freebsd.devel.cvs.src |
|---|---|
| Message-ID | <6a8a68ef.18824.29649956__7404.42615221895$1787455748$gmane$org@gitrepo.freebsd.org> |
The branch main has been updated by kbowling: URL: https://cgit.FreeBSD.org/src/commit/?id=5e56a1fb09cd3cc2081978dd6508ff5bc7c59eca commit 5e56a1fb09cd3cc2081978dd6508ff5bc7c59eca Author: Kevin Bowling <[email protected]> AuthorDate: 2026-08-16 05:48:58 +0000 Commit: Kevin Bowling <[email protected]> CommitDate: 2026-08-23 03:28:33 +0000 e1000: Recover from 82580 memory errors 82580 reports fatal parity and uncorrectable ECC errors through ICR.FER and its four region PEIND hierarchy. Region specific status registers identify PCIe, DMA transmit, DMA receive, DMA host, and LAN port memories that can leave traffic stopped. Enable the documented DMA, PCIe, packet-buffer, and host-owned LAN parity and ECC checks only after initializing queue and filter tables. Leave the flexible filter parity controls under management firmware ownership. Capture read-clear and RW1C status in the interrupt filter and keep FER masked until the admin task resolves the event. Reset for a host-owned region or an unknown FER source. Leave management-only recovery to firmware. Use CTRL.RST before master disable because fatal 82580 memory errors can stop PCIe traffic. Do not use CTRL.DEV_RST: specification update item 9 declares that bit reserved and says it must always be written as zero. Wait for EEPROM auto read completion; STATUS bit 21 is reserved on 82580, not PF_RST_DONE. Validated on an Intel I340-T2 (82580, revision 1). A one queue port programmed LANPERRCTL as 0x6e00 while a four-queue port programmed 0x7e00, avoiding the RSS checker until RETA is initialized. Both ports programmed PEINDM 0xf, DTPARC 0x1555, DRPARC and DDPARC 0x55, PCIEERRCTL 0x5555, and PCIEECCCTL 0x11. Three down/up cycles left all status and counters clear. A synthetic ICS.FER event on each function caused exactly one unknown source reset without advancing the sibling function counters. Controls were restored, the linked port recovered carrier, and bidirectional traffic after recovery. Enabling flexible filter parity checkers before programming their memories produced genuine LAN region faults with LANPERRSTS bits 0 and 1. Each fault advanced fatal_lan and fatal_resets exactly once, left the sibling function unchanged, and recovered the port proving hardware events will trigger the intended recovery. MFC after: 2 weeks Sponsored by: BBOX.io --- sys/dev/e1000/e1000_defines.h | 12 +++ sys/dev/e1000/e1000_regs.h | 9 ++ sys/dev/e1000/if_em.c | 205 +++++++++++++++++++++++++++++++++++++----- sys/dev/e1000/if_em.h | 2 + 4 files changed, 207 insertions(+), 21 deletions(-) diff --git a/sys/dev/e1000/e1000_defines.h b/sys/dev/e1000/e1000_defines.h index 033eab0ae370..56c9418b2419 100644 --- a/sys/dev/e1000/e1000_defines.h +++ b/sys/dev/e1000/e1000_defines.h @@ -542,6 +542,18 @@ #define E1000_PEIND_DMA_PARITY_FATAL 0x00000008 #define E1000_PEIND_FATAL_MASK 0x0000000F +/* 82580 parity and ECC detection controls. */ +#define E1000_DTPARC_82580_ENABLE_MASK 0x00001555 +#define E1000_DRPARC_82580_ENABLE_MASK 0x00000055 +#define E1000_DDPARC_82580_ENABLE_MASK 0x00000055 +#define E1000_PCIEERRCTL_82580_ENABLE_MASK 0x00005555 +#define E1000_PCIEECCCTL_82580_ENABLE_MASK 0x00000011 +#define E1000_LANPERRCTL_82580_HOST_MASK 0x00007E00 +#define E1000_LANPERRCTL_82580_RSS_ENABLE 0x00001000 +#define E1000_PCIEECCSTS_82580_ERROR_MASK 0x00000003 +#define E1000_LANPERRSTS_82580_ERROR_MASK 0x00007FFF +#define E1000_PBECCSTS_82580_ECC_ENABLE 0x00010000 + /* 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 diff --git a/sys/dev/e1000/e1000_regs.h b/sys/dev/e1000/e1000_regs.h index 3cfa1a05f297..a2c724247014 100644 --- a/sys/dev/e1000/e1000_regs.h +++ b/sys/dev/e1000/e1000_regs.h @@ -707,11 +707,20 @@ /* Memory error status registers */ #define E1000_RPBECCSTS 0x0245C /* Rx Packet Buffer ECC Status - RW */ #define E1000_TPBECCSTS 0x0345C /* Tx Packet Buffer ECC Status - RW */ +#define E1000_DTPARC_82580 0x03500 /* 82580 DMA Tx Parity Control - RW */ +#define E1000_DRPARC_82580 0x03504 /* 82580 DMA Rx Parity Control - RW */ +#define E1000_DDPARC_82580 0x03508 /* 82580 DMA Host Parity Control - RW */ +#define E1000_DTPARS_82580 0x03510 /* 82580 DMA Tx Parity Status - RW1C */ +#define E1000_DRPARS_82580 0x03514 /* 82580 DMA Rx Parity Status - RW1C */ +#define E1000_DDPARS_82580 0x03518 /* 82580 DMA Host Parity Status - RW1C */ #define E1000_DTPARS 0x03F10 /* DMA Tx Parity and ECC Status - RW1C */ #define E1000_DRPARS 0x03F14 /* DMA Rx Parity and ECC Status - RW1C */ #define E1000_DDECCS 0x03F18 /* DMA Host ECC Status - RW1C */ +#define E1000_PCIEERRCTL_82580 0x05BA0 /* 82580 PCIe Parity Control - RW */ +#define E1000_PCIEECCCTL_82580 0x05BA4 /* 82580 PCIe ECC Control - RW */ #define E1000_PCIEERRSTS 0x05BA8 /* PCIe Parity Status - RW1C */ #define E1000_PCIEECCSTS 0x05BAC /* PCIe ECC Status - RW1C */ +#define E1000_LANPERRCTL_82580 0x05F54 /* 82580 LAN Parity Control - RW */ #define E1000_LANPERRSTS 0x05F58 /* LAN Port Parity Status - RW1C */ #define E1000_PROXYS 0x5F64 /* Proxying Status */ diff --git a/sys/dev/e1000/if_em.c b/sys/dev/e1000/if_em.c index 7e312399b132..95f50c6694a6 100644 --- a/sys/dev/e1000/if_em.c +++ b/sys/dev/e1000/if_em.c @@ -461,6 +461,7 @@ static void em_prepare_fatal_error_reset(struct e1000_softc *); static void em_finish_fatal_error_reset(struct e1000_softc *); static void em_configure_peind_memory_errors(struct e1000_softc *); static void em_configure_82575_memory_errors(struct e1000_softc *); +static void em_configure_82580_memory_errors(struct e1000_softc *); static void em_if_multi_set(if_ctx_t); static void em_if_update_admin_status(if_ctx_t); static void em_if_debug(if_ctx_t); @@ -1998,6 +1999,7 @@ em_if_init(if_ctx_t ctx) } em_configure_peind_memory_errors(sc); em_configure_82575_memory_errors(sc); + em_configure_82580_memory_errors(sc); if (sc->vf_ifp) sc->vf_reset_pending = false; } @@ -2268,6 +2270,75 @@ em_configure_82576_memory_errors(struct e1000_softc *sc) E1000_WRITE_FLUSH(hw); } +static bool +em_has_82580_memory_errors(const struct e1000_hw *hw) +{ + + return (hw->mac.type == e1000_82580); +} + +static void +em_clear_82580_memory_error_status(struct e1000_hw *hw, u32 reg) +{ + u32 status; + + status = E1000_READ_REG(hw, reg); + if (status != 0) + E1000_WRITE_REG(hw, reg, status); +} + +static void +em_configure_82580_memory_errors(struct e1000_softc *sc) +{ + struct e1000_hw *hw; + u32 reg; + + hw = &sc->hw; + if (!em_has_82580_memory_errors(hw)) + return; + + /* Clear status left before the driver completed its memory tables. */ + (void)E1000_READ_REG(hw, E1000_PEIND); + em_clear_82580_memory_error_status(hw, E1000_DTPARS_82580); + em_clear_82580_memory_error_status(hw, E1000_DRPARS_82580); + em_clear_82580_memory_error_status(hw, E1000_DDPARS_82580); + em_clear_82580_memory_error_status(hw, E1000_PCIEERRSTS); + em_clear_82580_memory_error_status(hw, E1000_PCIEECCSTS); + (void)E1000_READ_REG(hw, E1000_LANPERRSTS); + (void)E1000_READ_REG(hw, E1000_RPBECCSTS); + (void)E1000_READ_REG(hw, E1000_TPBECCSTS); + E1000_WRITE_REG(hw, E1000_RPBECCSTS, + E1000_PBECCSTS_82580_ECC_ENABLE); + E1000_WRITE_REG(hw, E1000_TPBECCSTS, + E1000_PBECCSTS_82580_ECC_ENABLE); + + reg = E1000_READ_REG(hw, E1000_DTPARC_82580); + E1000_WRITE_REG(hw, E1000_DTPARC_82580, + reg | E1000_DTPARC_82580_ENABLE_MASK); + reg = E1000_READ_REG(hw, E1000_DRPARC_82580); + E1000_WRITE_REG(hw, E1000_DRPARC_82580, + reg | E1000_DRPARC_82580_ENABLE_MASK); + reg = E1000_READ_REG(hw, E1000_DDPARC_82580); + E1000_WRITE_REG(hw, E1000_DDPARC_82580, + reg | E1000_DDPARC_82580_ENABLE_MASK); + reg = E1000_READ_REG(hw, E1000_PCIEERRCTL_82580); + E1000_WRITE_REG(hw, E1000_PCIEERRCTL_82580, + reg | E1000_PCIEERRCTL_82580_ENABLE_MASK); + reg = E1000_READ_REG(hw, E1000_PCIEECCCTL_82580); + E1000_WRITE_REG(hw, E1000_PCIEECCCTL_82580, + reg | E1000_PCIEECCCTL_82580_ENABLE_MASK); + reg = E1000_READ_REG(hw, E1000_LANPERRCTL_82580); + reg |= E1000_LANPERRCTL_82580_HOST_MASK; + /* The RSS memory is initialized only for a multiqueue layout. */ + if (sc->rx_num_queues <= 1) + reg &= ~E1000_LANPERRCTL_82580_RSS_ENABLE; + E1000_WRITE_REG(hw, E1000_LANPERRCTL_82580, reg); + reg = E1000_READ_REG(hw, E1000_PEINDM); + E1000_WRITE_REG(hw, E1000_PEINDM, + reg | E1000_PEIND_FATAL_MASK); + E1000_WRITE_FLUSH(hw); +} + static bool em_has_i210_memory_errors(const struct e1000_hw *hw) { @@ -2304,10 +2375,11 @@ em_configure_peind_memory_errors(struct e1000_softc *sc) } static bool -em_has_i210_i350_memory_errors(const struct e1000_hw *hw) +em_has_peind_memory_errors(const struct e1000_hw *hw) { - return (em_has_i350_memory_errors(hw) || + return (em_has_82580_memory_errors(hw) || + em_has_i350_memory_errors(hw) || em_has_i210_memory_errors(hw)); } @@ -2315,6 +2387,8 @@ static u32 em_pcie_fatal_error_mask(const struct e1000_hw *hw) { + if (em_has_82580_memory_errors(hw)) + return (~0U); if (em_has_i350_memory_errors(hw)) return (E1000_PCIEERRSTS_I350_FATAL_MASK); if (em_has_i210_memory_errors(hw)) @@ -2330,7 +2404,7 @@ em_memory_error_intr_mask(const struct e1000_hw *hw) return (E1000_IMS_82575_MEMORY_ERROR_MASK); if (em_has_82576_memory_errors(hw)) return (E1000_IMS_FER | E1000_IMS_NFER); - if (em_has_pch_ecc(hw) || em_has_i210_i350_memory_errors(hw)) + if (em_has_pch_ecc(hw) || em_has_peind_memory_errors(hw)) return (E1000_IMS_FER); return (0); } @@ -2551,7 +2625,8 @@ static void em_handle_fatal_error_intr(struct e1000_softc *sc, u32 icr) { struct e1000_hw *hw; - u32 dma_rx, dma_tx, error_mask, lanerr, pcieerr, peind; + u32 dma_host, dma_rx, dma_tx, error_mask, lanerr, pcieecc; + u32 pcieerr, peind; error_mask = em_memory_error_intr_mask(&sc->hw); if (!em_has_memory_errors(&sc->hw) || @@ -2582,7 +2657,25 @@ em_handle_fatal_error_intr(struct e1000_softc *sc, u32 icr) E1000_PEIND_FATAL_MASK; pcieerr = E1000_READ_REG(hw, E1000_PCIEERRSTS) & em_pcie_fatal_error_mask(hw); - if (em_has_i350_memory_errors(hw)) { + pcieecc = 0; + dma_host = 0; + if (em_has_82580_memory_errors(hw)) { + /* + * PEIND is visible through every function. Retain the + * management indication, which has no subordinate status, + * but attribute host-owned regions from this function's + * status registers. + */ + peind &= E1000_PEIND_MNG_PARITY_FATAL; + pcieecc = E1000_READ_REG(hw, E1000_PCIEECCSTS) & + E1000_PCIEECCSTS_82580_ERROR_MASK; + dma_tx = E1000_READ_REG(hw, E1000_DTPARS_82580); + dma_rx = E1000_READ_REG(hw, E1000_DRPARS_82580); + dma_host = E1000_READ_REG(hw, + E1000_DDPARS_82580); + lanerr = E1000_READ_REG(hw, E1000_LANPERRSTS) & + E1000_LANPERRSTS_82580_ERROR_MASK; + } else 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) & @@ -2595,17 +2688,19 @@ em_handle_fatal_error_intr(struct e1000_softc *sc, u32 icr) lanerr = E1000_READ_REG(hw, E1000_LANPERRSTS) & E1000_LANPERRSTS_RETX_BUF; } - if (pcieerr != 0) + if (pcieerr != 0 || pcieecc != 0) peind |= E1000_PEIND_PCIE_PARITY_FATAL; if (lanerr != 0) peind |= E1000_PEIND_LANPORT_PARITY_FATAL; - if (dma_tx != 0 || dma_rx != 0) + if (dma_tx != 0 || dma_rx != 0 || dma_host != 0) peind |= E1000_PEIND_DMA_PARITY_FATAL; sc->fatal_error_peind = peind; sc->fatal_error_pcie = pcieerr; + sc->fatal_error_pcie_ecc = pcieecc; sc->fatal_error_lan = lanerr; sc->fatal_error_dma_tx = dma_tx; sc->fatal_error_dma_rx = dma_rx; + sc->fatal_error_dma_host = dma_host; } atomic_store_rel_32(&sc->fatal_error_state, EM_FATAL_ERROR_DETECTED); @@ -2676,12 +2771,28 @@ em_handle_fatal_error_admin(struct e1000_softc *sc) 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, 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); + if (em_has_82580_memory_errors(&sc->hw)) { + device_printf(sc->dev, + "fatal internal memory error: PEIND %#x, " + "PCIEERRSTS %#x, PCIEECCSTS %#x, " + "DTPARS %#x, DRPARS %#x, DDPARS %#x, " + "LANPERRSTS %#x\n", peind, + sc->fatal_error_pcie, + sc->fatal_error_pcie_ecc, + sc->fatal_error_dma_tx, + sc->fatal_error_dma_rx, + sc->fatal_error_dma_host, + sc->fatal_error_lan); + } else { + device_printf(sc->dev, + "fatal internal memory error: PEIND %#x, " + "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 | @@ -2704,9 +2815,11 @@ em_handle_fatal_error_admin(struct e1000_softc *sc) E1000_LANPERRSTS_I350_NO_RESET_MASK); sc->fatal_error_peind = 0; sc->fatal_error_pcie = 0; + sc->fatal_error_pcie_ecc = 0; sc->fatal_error_lan = 0; sc->fatal_error_dma_tx = 0; sc->fatal_error_dma_rx = 0; + sc->fatal_error_dma_host = 0; atomic_store_rel_32(&sc->fatal_error_state, EM_FATAL_ERROR_NONE); E1000_WRITE_REG(&sc->hw, E1000_IMS, @@ -2726,18 +2839,20 @@ em_handle_fatal_error_admin(struct e1000_softc *sc) /* * 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. + * I211 require a port reset before master disable in this case. 82580 stops + * PCIe traffic for a fatal error in any host-owned region, so use the same + * order for every 82580 recovery. This differs from the normal reset path, + * which disables the bus master first. */ static void em_prepare_fatal_error_reset(struct e1000_softc *sc) { struct e1000_hw *hw; s32 error; - u32 ctrl, pcieerr; + u32 ctrl, pcieecc, pcieerr; int i; - if (!em_has_i210_i350_memory_errors(&sc->hw) || + if (!em_has_peind_memory_errors(&sc->hw) || atomic_load_acq_32(&sc->fatal_error_state) != EM_FATAL_ERROR_RESET_REQUESTED) return; @@ -2745,7 +2860,9 @@ em_prepare_fatal_error_reset(struct e1000_softc *sc) pcieerr = sc->fatal_error_pcie | (E1000_READ_REG(&sc->hw, E1000_PCIEERRSTS) & em_pcie_fatal_error_mask(&sc->hw)); - if ((sc->fatal_error_peind & E1000_PEIND_PCIE_PARITY_FATAL) == 0 && + pcieecc = sc->fatal_error_pcie_ecc; + if (!em_has_82580_memory_errors(&sc->hw) && + (sc->fatal_error_peind & E1000_PEIND_PCIE_PARITY_FATAL) == 0 && pcieerr == 0) return; @@ -2757,8 +2874,9 @@ em_prepare_fatal_error_reset(struct e1000_softc *sc) for (i = 0; i < AUTO_READ_DONE_TIMEOUT; i++) { if ((E1000_READ_REG(hw, E1000_EECD) & E1000_EECD_AUTO_RD) != 0 && + (em_has_82580_memory_errors(hw) || (E1000_READ_REG(hw, E1000_STATUS) & - E1000_STATUS_RST_DONE) != 0) + E1000_STATUS_RST_DONE) != 0)) break; msec_delay(1); } @@ -2774,6 +2892,12 @@ em_prepare_fatal_error_reset(struct e1000_softc *sc) em_pcie_fatal_error_mask(hw); if (pcieerr != 0) E1000_WRITE_REG(hw, E1000_PCIEERRSTS, pcieerr); + if (em_has_82580_memory_errors(hw)) { + pcieecc |= E1000_READ_REG(hw, E1000_PCIEECCSTS) & + E1000_PCIEECCSTS_82580_ERROR_MASK; + if (pcieecc != 0) + E1000_WRITE_REG(hw, E1000_PCIEECCSTS, pcieecc); + } atomic_store_rel_32(&sc->fatal_error_state, EM_FATAL_ERROR_RESET_PREPARED); } @@ -2782,7 +2906,7 @@ static void em_finish_fatal_error_reset(struct e1000_softc *sc) { struct e1000_hw *hw; - u32 dma_rx, dma_tx, lanerr, pcieerr; + u32 dma_host, dma_rx, dma_tx, lanerr, pcieecc, pcieerr; u32 state; state = atomic_load_acq_32(&sc->fatal_error_state); @@ -2798,7 +2922,44 @@ em_finish_fatal_error_reset(struct e1000_softc *sc) /* 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)) { + } else if (em_has_82580_memory_errors(hw)) { + pcieerr = sc->fatal_error_pcie | + E1000_READ_REG(hw, E1000_PCIEERRSTS); + if (pcieerr != 0) + E1000_WRITE_REG(hw, E1000_PCIEERRSTS, pcieerr); + pcieecc = sc->fatal_error_pcie_ecc | + (E1000_READ_REG(hw, E1000_PCIEECCSTS) & + E1000_PCIEECCSTS_82580_ERROR_MASK); + if (pcieecc != 0) + E1000_WRITE_REG(hw, E1000_PCIEECCSTS, pcieecc); + dma_tx = sc->fatal_error_dma_tx | + E1000_READ_REG(hw, E1000_DTPARS_82580); + if (dma_tx != 0) + E1000_WRITE_REG(hw, E1000_DTPARS_82580, dma_tx); + dma_rx = sc->fatal_error_dma_rx | + E1000_READ_REG(hw, E1000_DRPARS_82580); + if (dma_rx != 0) + E1000_WRITE_REG(hw, E1000_DRPARS_82580, dma_rx); + dma_host = sc->fatal_error_dma_host | + E1000_READ_REG(hw, E1000_DDPARS_82580); + if (dma_host != 0) + E1000_WRITE_REG(hw, E1000_DDPARS_82580, dma_host); + /* LANPERRSTS is read-only and is cleared by the port reset. */ + lanerr = E1000_READ_REG(hw, E1000_LANPERRSTS) & + E1000_LANPERRSTS_82580_ERROR_MASK; + if (lanerr != 0) + device_printf(sc->dev, + "LAN parity status remained set after reset: %#x\n", + lanerr); + (void)E1000_READ_REG(hw, E1000_PEIND); + sc->fatal_error_peind = 0; + sc->fatal_error_pcie = 0; + sc->fatal_error_pcie_ecc = 0; + sc->fatal_error_lan = 0; + sc->fatal_error_dma_tx = 0; + sc->fatal_error_dma_rx = 0; + sc->fatal_error_dma_host = 0; + } else if (em_has_peind_memory_errors(hw)) { pcieerr = sc->fatal_error_pcie | (E1000_READ_REG(hw, E1000_PCIEERRSTS) & em_pcie_fatal_error_mask(hw)); @@ -2833,9 +2994,11 @@ em_finish_fatal_error_reset(struct e1000_softc *sc) (void)E1000_READ_REG(hw, E1000_PEIND); sc->fatal_error_peind = 0; sc->fatal_error_pcie = 0; + sc->fatal_error_pcie_ecc = 0; sc->fatal_error_lan = 0; sc->fatal_error_dma_tx = 0; sc->fatal_error_dma_rx = 0; + sc->fatal_error_dma_host = 0; } sc->fatal_error_icr = 0; sc->fatal_error_pbeccsts = 0; diff --git a/sys/dev/e1000/if_em.h b/sys/dev/e1000/if_em.h index e5b0fec8b087..0901c1ce5780 100644 --- a/sys/dev/e1000/if_em.h +++ b/sys/dev/e1000/if_em.h @@ -630,9 +630,11 @@ struct e1000_softc { u32 fatal_error_pbeccsts; u32 fatal_error_peind; u32 fatal_error_pcie; + u32 fatal_error_pcie_ecc; u32 fatal_error_lan; u32 fatal_error_dma_tx; u32 fatal_error_dma_rx; + u32 fatal_error_dma_host; u64 fatal_error_reset_count; u64 fatal_error_lan_count; u64 fatal_error_mng_count;