Date: Fri, 31 Jul 2026 09:43:51 +0000 From: Kevin Bowling <kbowling@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Michael Adler <madler@tapil.com> Subject: git: 9d30fd353dd3 - main - igc: Disable ASPM L1.2 on I226 to prevent RX stalls Message-ID: <6a6c6e57.32455.6b6ebed8@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by kbowling: URL: https://cgit.FreeBSD.org/src/commit/?id=9d30fd353dd3eacb095ce98cb9c91ec015b9af64 commit 9d30fd353dd3eacb095ce98cb9c91ec015b9af64 Author: Michael Adler <madler@tapil.com> AuthorDate: 2026-07-09 17:02:37 +0000 Commit: Kevin Bowling <kbowling@FreeBSD.org> CommitDate: 2026-07-31 09:33:54 +0000 igc: Disable ASPM L1.2 on I226 to prevent RX stalls I226 parts advertise support for the PCIe L1.2 link substate, but a hardware erratum makes the exit latency from that low-power state longer than the packet buffer can absorb under load. This stalls the inbound packet stream. Disabling ASPM system-wide (BIOS or OS ASPM policy) does not fix it. The L1.2 enable bit must be cleared directly in the device's own PCIe L1 PM extended capability. Add igc_is_device_id_i226() to identify affected parts and igc_disable_broken_aspm_l1_2() to clear the ASPM L1.2 enable bit on attach and after resume, since PCIe config space can be reset across a suspend/resume cycle. Adapted from the Linux igc driver: 0325143b59c6 igc: disable L1.2 PCI-E link substate to avoid performance issue 1468c1f97cf3 igc: fix disabling L1.2 PCI-E link substate on I226 on init Signed-off-by: Michael Adler <madler@tapil.com> PR: 279245 Reviewed by: Jim Thompson MFC after: 1 week Pull-Request: https://github.com/freebsd/freebsd-src/pull/2318 --- sys/dev/igc/if_igc.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ sys/dev/igc/igc_base.c | 21 +++++++++++++++++++++ sys/dev/igc/igc_base.h | 1 + 3 files changed, 66 insertions(+) diff --git a/sys/dev/igc/if_igc.c b/sys/dev/igc/if_igc.c index 6dcb55ed6475..f8de961afeca 100644 --- a/sys/dev/igc/if_igc.c +++ b/sys/dev/igc/if_igc.c @@ -114,6 +114,7 @@ static bool igc_if_needs_restart(if_ctx_t, enum iflib_restart_event); static void igc_identify_hardware(if_ctx_t); static int igc_allocate_pci_resources(if_ctx_t); static void igc_free_pci_resources(if_ctx_t); +static void igc_disable_broken_aspm_l1_2(if_ctx_t); static void igc_reset(if_ctx_t); static int igc_setup_interface(if_ctx_t); static int igc_setup_msix(if_ctx_t); @@ -554,6 +555,14 @@ igc_if_attach_pre(if_ctx_t ctx) /* Determine hardware and mac info */ igc_identify_hardware(ctx); + /* + * I226 parts have an erratum where the PCIe ASPM L1.2 exit + * latency can exceed what the packet buffer can absorb under + * load, stalling the inbound packet stream. Disable ASPM L1.2 + * on the device to work around it. + */ + igc_disable_broken_aspm_l1_2(ctx); + scctx->isc_tx_nsegments = IGC_MAX_SCATTER; scctx->isc_nrxqsets_max = scctx->isc_ntxqsets_max = igc_set_num_queues(ctx); @@ -796,6 +805,12 @@ igc_if_suspend(if_ctx_t ctx) static int igc_if_resume(if_ctx_t ctx) { + /* + * PCIe config space, and with it ASPM L1.2, may have been reset + * across the suspend/resume cycle. + */ + igc_disable_broken_aspm_l1_2(ctx); + igc_if_init(ctx); return(0); @@ -1497,6 +1512,35 @@ igc_identify_hardware(if_ctx_t ctx) } } +/********************************************************************* + * + * I226 devices advertise support for the PCIe L1.2 link substate, but + * due to a hardware erratum the exit latency from that low-power state + * can exceed what the packet buffer can tolerate under load, which + * stalls the inbound packet stream. Disabling ASPM L1.2 on the device + * itself (as opposed to disabling ASPM/power management in the BIOS or + * at the OS level) works around the issue. + * + **********************************************************************/ +static void +igc_disable_broken_aspm_l1_2(if_ctx_t ctx) +{ + device_t dev = iflib_get_dev(ctx); + struct igc_softc *sc = iflib_get_softc(ctx); + int cap; + uint32_t ctl1; + + if (!igc_is_device_id_i226(&sc->hw)) + return; + + if (pci_find_extcap(dev, PCIZ_L1PM, &cap) != 0) + return; + + ctl1 = pci_read_config(dev, cap + PCIR_L1PM_CTL1, 4); + ctl1 &= ~PCIM_L1PM_CTL1_ASPM_L1_2; + pci_write_config(dev, cap + PCIR_L1PM_CTL1, ctl1, 4); +} + static int igc_allocate_pci_resources(if_ctx_t ctx) { diff --git a/sys/dev/igc/igc_base.c b/sys/dev/igc/igc_base.c index de48303a9c7c..181fd7025ba3 100644 --- a/sys/dev/igc/igc_base.c +++ b/sys/dev/igc/igc_base.c @@ -184,3 +184,24 @@ void igc_rx_fifo_flush_base(struct igc_hw *hw) IGC_READ_REG(hw, IGC_RNBC); IGC_READ_REG(hw, IGC_MPC); } + +/** + * igc_is_device_id_i226 - Check whether the device is I226 silicon + * @hw: pointer to the HW structure + * + * I225 and I226 share the same mac.type, so this checks the PCI + * device ID directly to distinguish I226 parts, e.g. for erratum + * workarounds that apply only to that silicon. + **/ +bool igc_is_device_id_i226(struct igc_hw *hw) +{ + switch (hw->device_id) { + case IGC_DEV_ID_I226_LM: + case IGC_DEV_ID_I226_V: + case IGC_DEV_ID_I226_K: + case IGC_DEV_ID_I226_IT: + return true; + default: + return false; + } +} diff --git a/sys/dev/igc/igc_base.h b/sys/dev/igc/igc_base.h index c986ecc840e3..91cb602f809d 100644 --- a/sys/dev/igc/igc_base.h +++ b/sys/dev/igc/igc_base.h @@ -13,6 +13,7 @@ void igc_power_down_phy_copper_base(struct igc_hw *hw); extern void igc_rx_fifo_flush_base(struct igc_hw *hw); s32 igc_acquire_phy_base(struct igc_hw *hw); void igc_release_phy_base(struct igc_hw *hw); +bool igc_is_device_id_i226(struct igc_hw *hw); /* Transmit Descriptor - Advanced */ union igc_adv_tx_desc {home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a6c6e57.32455.6b6ebed8>
