git: 9d30fd353dd3 - main - igc: Disable ASPM L1.2 on I226 to prevent RX stalls
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 31 Jul 2026 09:43:51 UTC
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 {