git: c1a574ef9a35 - stable/14 - igc: Disable ASPM L1.2 on I226 to prevent RX stalls

Kevin Bowling <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a767bce.3eb58.174243a7__34885.53783843$1786149867$gmane$org@gitrepo.freebsd.org>
The branch stable/14 has been updated by kbowling:

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

commit c1a574ef9a35cf277ae3ecc5238586ca7763d4f5
Author:     Michael Adler <[email protected]>
AuthorDate: 2026-07-09 17:02:37 +0000
Commit:     Kevin Bowling <[email protected]>
CommitDate: 2026-08-08 00:40:07 +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 <[email protected]>
    
    PR:             279245
    Reviewed by:    Jim Thompson
    Pull-Request:   https://github.com/freebsd/freebsd-src/pull/2318
    
    (cherry picked from commit 9d30fd353dd3eacb095ce98cb9c91ec015b9af64)
---
 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 df82d5a4e073..c015bf1f3c52 100644
--- a/sys/dev/igc/if_igc.c
+++ b/sys/dev/igc/if_igc.c
@@ -115,6 +115,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);
@@ -555,6 +556,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);
@@ -797,6 +806,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);
@@ -1498,6 +1513,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 {
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.