git: 45dc4a13654c - main - ixgbe: Pause firmware recovery polling in low power

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Fri, 11 Sep 2026 20:16:18 UTC
The branch main has been updated by kbowling:

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

commit 45dc4a13654c5e90bcf5f757e46f1353b30848ee
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-09-04 06:37:25 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-11 20:15:55 +0000

    ixgbe: Pause firmware recovery polling in low power
    
    The firmware recovery callout continues polling controller registers
    after a power transition.  An inaccessible E610 GL_MNG_FWSM register
    reads as all ones in D3, which looks like firmware recovery mode and
    queues an iflib reset while the device is suspended.  A later D0 poll
    then reports recovery complete and queues another reset.
    
    Pause and drain the callout before terminal stop policy is applied,
    prevent an in-flight callback from rearming it, and restart polling
    only after resume has cleared the wake state.  Track callout
    initialization so partial attach cleanup does not drain an uninitialized
    callout.
    
    The false transition was reproduced on a dual-port E610 with direct D3
    and system S3.  Validate the guard with wake-disabled and wake-armed D3,
    three repeated D3 cycles per port, and an S3 magic-packet wake.  Both
    ports returned to D0 without a false recovery transition.
    
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
---
 sys/dev/ixgbe/if_ix.c | 38 +++++++++++++++++++++++++++++++++-----
 sys/dev/ixgbe/ixgbe.h |  2 ++
 2 files changed, 35 insertions(+), 5 deletions(-)

diff --git a/sys/dev/ixgbe/if_ix.c b/sys/dev/ixgbe/if_ix.c
index f734db0f79f0..b2a133c80b55 100644
--- a/sys/dev/ixgbe/if_ix.c
+++ b/sys/dev/ixgbe/if_ix.c
@@ -254,6 +254,8 @@ static void ixgbe_update_stats_counters(struct ixgbe_softc *);
 static void ixgbe_config_link(if_ctx_t);
 static void ixgbe_get_slot_info(struct ixgbe_softc *);
 static void ixgbe_fw_mode_timer(void *);
+static void ixgbe_fw_mode_timer_pause(struct ixgbe_softc *);
+static void ixgbe_fw_mode_timer_resume(struct ixgbe_softc *);
 static void ixgbe_configure_wakeup(if_ctx_t);
 static void ixgbe_configure_wakeup_mta(if_ctx_t);
 static void ixgbe_prepare_wakeup(if_ctx_t, bool);
@@ -1408,6 +1410,7 @@ ixgbe_if_attach_post(if_ctx_t ctx)
 
 		/* Set up the timer callout */
 		callout_init(&sc->fw_mode_timer, true);
+		sc->fw_mode_timer_initialized = true;
 
 		/* Start the task */
 		callout_reset(&sc->fw_mode_timer, hz, ixgbe_fw_mode_timer, sc);
@@ -3916,6 +3919,7 @@ ixgbe_if_detach(if_ctx_t ctx)
 	INIT_DEBUGOUT("ixgbe_detach: begin");
 
 	sc->iov_recovery_stop = true;
+	ixgbe_fw_mode_timer_pause(sc);
 
 	ixgbe_setup_low_power_mode(ctx);
 
@@ -3924,8 +3928,6 @@ ixgbe_if_detach(if_ctx_t ctx)
 	ctrl_ext &= ~IXGBE_CTRL_EXT_DRV_LOAD;
 	IXGBE_WRITE_REG(&sc->hw, IXGBE_CTRL_EXT, ctrl_ext);
 
-	callout_drain(&sc->fw_mode_timer);
-
 	if (sc->hw.mac.type == ixgbe_mac_E610) {
 		ixgbe_disable_lse(sc);
 		ixgbe_shutdown_aci(&sc->hw);
@@ -4004,7 +4006,9 @@ ixgbe_configure_wakeup_mta(if_ctx_t ctx)
 static int
 ixgbe_if_power_prepare(if_ctx_t ctx, enum iflib_power_event event)
 {
+	struct ixgbe_softc *sc = iflib_get_softc(ctx);
 
+	ixgbe_fw_mode_timer_pause(sc);
 	ixgbe_prepare_wakeup(ctx, event != IFLIB_POWER_DETACH);
 	return (0);
 }
@@ -4154,6 +4158,7 @@ ixgbe_if_resume(if_ctx_t ctx)
 	pci_clear_pme(dev);
 	hw->wol_enabled = false;
 	sc->wol_filters = 0;
+	ixgbe_fw_mode_timer_resume(sc);
 
 	return (0);
 } /* ixgbe_if_resume */
@@ -4796,6 +4801,9 @@ ixgbe_fw_mode_timer(void *arg)
 	struct ixgbe_softc *sc = arg;
 	struct ixgbe_hw *hw = &sc->hw;
 
+	if (atomic_load_acq_int(&sc->fw_mode_timer_paused) != 0)
+		return;
+
 	if (ixgbe_fw_recovery_mode(hw)) {
 		if (atomic_cmpset_acq_int(&sc->recovery_mode, 0, 1)) {
 			/* Firmware error detected, entering recovery mode */
@@ -4815,11 +4823,31 @@ ixgbe_fw_mode_timer(void *arg)
 		iflib_admin_intr_deferred(sc->ctx);
 	}
 
-
-	callout_reset(&sc->fw_mode_timer, hz,
-	    ixgbe_fw_mode_timer, sc);
+	if (atomic_load_acq_int(&sc->fw_mode_timer_paused) == 0)
+		callout_reset(&sc->fw_mode_timer, hz,
+		    ixgbe_fw_mode_timer, sc);
 } /* ixgbe_fw_mode_timer */
 
+static void
+ixgbe_fw_mode_timer_pause(struct ixgbe_softc *sc)
+{
+
+	if (!sc->fw_mode_timer_initialized ||
+	    atomic_swap_int(&sc->fw_mode_timer_paused, 1) != 0)
+		return;
+	callout_drain(&sc->fw_mode_timer);
+}
+
+static void
+ixgbe_fw_mode_timer_resume(struct ixgbe_softc *sc)
+{
+
+	if (!sc->fw_mode_timer_initialized ||
+	    atomic_swap_int(&sc->fw_mode_timer_paused, 0) == 0)
+		return;
+	callout_reset(&sc->fw_mode_timer, hz, ixgbe_fw_mode_timer, sc);
+}
+
 /************************************************************************
  * ixgbe_sfp_probe
  *
diff --git a/sys/dev/ixgbe/ixgbe.h b/sys/dev/ixgbe/ixgbe.h
index ba8cfb94915a..fb0ac15f5008 100644
--- a/sys/dev/ixgbe/ixgbe.h
+++ b/sys/dev/ixgbe/ixgbe.h
@@ -505,6 +505,8 @@ struct ixgbe_softc {
 
 	/* Firmware error check */
 	int			recovery_mode;
+	u_int			fw_mode_timer_paused;
+	bool			fw_mode_timer_initialized;
 	bool			overtemp_shutdown_pending;
 	struct callout		fw_mode_timer;