On an I219 (pch_spt+) copper with jumbo MTU enabled, a link partner
reset (e.g. a switch reboot) can leave the PHY's pause advertisement
bits dropped. Nothing re-asserts them on link-up, so every renegotiation
resolves flow control to none. rx_crc_errors climb and traffic stalls until
the interface is manually reset. Observed on I219-LM (8086:15b7) at
MTU 9000 behind a switch that was power-cycled.

In the watchdog, detect a completed autonegotiation that resolved
to no flow control despite pause being requested and the pause bits
missing from the advertisement, and schedule reset_task to rebuild
the advertisement (as ethtool -r does). Remediation is limited to three
attempts, 30s apart, until the next link down.

Remediate via the existing reset path rather than re-asserting the
advertisement on link-up; a root-cause rework can follow in net-next.

Fixes: 79849ebc0e06 ("e1000e: initial support for i219")
Cc: [email protected]
Link: https://github.com/systemd/systemd/issues/25925
Assisted-by: LLM
Signed-off-by: Craig I. Hagan <[email protected]>
---
 drivers/net/ethernet/intel/e1000e/e1000.h  |  11 ++
 drivers/net/ethernet/intel/e1000e/netdev.c | 132 +++++++++++++++++++++
 2 files changed, 143 insertions(+)

diff --git a/drivers/net/ethernet/intel/e1000e/e1000.h 
b/drivers/net/ethernet/intel/e1000e/e1000.h
index 63ebe0037..4353519d4 100644
--- a/drivers/net/ethernet/intel/e1000e/e1000.h
+++ b/drivers/net/ethernet/intel/e1000e/e1000.h
@@ -210,6 +210,17 @@ struct e1000_adapter {
        /* track device up/down/testing state */
        unsigned long state;
 
+       /* Flow-control advertisement-latch auto-recovery.
+        * These MUST persist across the reinit that remediation triggers
+        * (the reinit generates its own carrier-up), so they are NOT reset
+        * on link-up. The holdoff and per-episode attempt budget prevent a
+        * reset/flap storm against a partner that is still booting or that
+        * genuinely never advertises pause.
+        */
+       unsigned long fclatch_last_fix; /* jiffies of last remediation, 0=none 
*/
+       u8 fclatch_attempts;            /* attempts in the current link episode 
*/
+       bool fclatch_gave_up;           /* terminal for episode; re-armed on 
link-down */
+
        /* Interrupt Throttle Rate */
        u32 itr;
        u32 itr_setting;
diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c 
b/drivers/net/ethernet/intel/e1000e/netdev.c
index 844f31ab3..28bd33335 100644
--- a/drivers/net/ethernet/intel/e1000e/netdev.c
+++ b/drivers/net/ethernet/intel/e1000e/netdev.c
@@ -38,6 +38,15 @@ static int debug = -1;
 module_param(debug, int, 0);
 MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)");
 
+/* Flow-control advertisement-latch auto-recovery tunables. Fixed constants
+ * rather than user knobs: the values only need to be safe, not tunable.
+ *   MAX_ATTEMPTS : autoneg-restart attempts per link episode before giving up
+ *   HOLDOFF      : minimum spacing between attempts; longer than a switch's
+ *                  autoneg settle so we do not fight a still-booting partner
+ */
+#define E1000E_FCLATCH_MAX_ATTEMPTS    3       /* autoneg-restarts per link 
episode */
+#define E1000E_FCLATCH_HOLDOFF         (30 * HZ)       /* min spacing between 
attempts */
+
 static const struct e1000_info *e1000_info_tbl[] = {
        [board_82571]           = &e1000_82571_info,
        [board_82572]           = &e1000_82572_info,
@@ -5217,6 +5226,113 @@ static void e1000_watchdog(struct timer_list *t)
        /* TODO: make this use queue_delayed_work() */
 }
 
+/**
+ * e1000e_check_fc_latch - re-assert a dropped pause advertisement
+ * @adapter: board private structure
+ *
+ * On the I219/PCH family at jumbo MTU, an upstream switch reboot can bring the
+ * link back with the PHY's pause-advertisement bits dropped and flow control
+ * resolved to none, even when a pause mode was requested. The driver does not
+ * re-assert the advertisement on a plain link-up (e1000_phy_setup_autoneg()
+ * runs only during a full setup pass), so every later renegotiation resolves
+ * to none and the link does not recover on its own.
+ *
+ * Detect that state from a fresh PHY read and schedule a full reconfigure (the
+ * reset_task path, as ethtool -r uses) to rebuild the advertisement. 
reset_task
+ * bounces the link and re-enters this check, so the attempt count is kept
+ * across the bounce rather than reset on link-up. Remediation stops after 
three
+ * tries per link episode; the give-up state is re-armed only on a real
+ * link-down (peer/cable change), so a partner that never advertises pause is
+ * reset three times and then left alone until the link is re-established --
+ * it cannot flap indefinitely.
+ */
+static void e1000e_check_fc_latch(struct e1000_adapter *adapter)
+{
+       struct e1000_hw *hw = &adapter->hw;
+       struct net_device *netdev = adapter->netdev;
+       u16 bmsr = 0, adv = 0, expected;
+       unsigned long now;
+       int rv;
+
+       /* Skip while down/resetting or with a reset already pending. */
+       if (!netif_carrier_ok(netdev) ||
+           test_bit(__E1000_DOWN, &adapter->state) ||
+           test_bit(__E1000_RESETTING, &adapter->state) ||
+           (adapter->flags & FLAG_RESTART_NOW))
+               return;
+
+       /* Limited to I219/PCH copper at jumbo MTU, where the behavior is seen
+        * and the RX-side impact matters. Act only when a pause mode was
+        * requested but autoneg resolved none. Pause is only meaningful on a
+        * full-duplex link, so a half-duplex result legitimately carries no
+        * pause bits and must not be treated as the fault.
+        */
+       if (hw->phy.media_type != e1000_media_type_copper ||
+           hw->mac.type < e1000_pch_spt ||             /* pch_spt+ (I219 and 
later) */
+           netdev->mtu <= ETH_DATA_LEN ||              /* jumbo only */
+           adapter->link_duplex != FULL_DUPLEX ||
+           !hw->mac.autoneg || !adapter->fc_autoneg ||
+           hw->fc.current_mode != e1000_fc_none)
+               return;
+       if (hw->fc.requested_mode != e1000_fc_full &&
+           hw->fc.requested_mode != e1000_fc_rx_pause &&
+           hw->fc.requested_mode != e1000_fc_tx_pause)
+               return;
+
+       /* Fresh, self-contained reads; act only on a good, autoneg-complete 
read. */
+       rv = e1e_rphy(hw, MII_BMSR, &bmsr);
+       if (!rv)
+               rv = e1e_rphy(hw, MII_ADVERTISE, &adv);
+       if (rv || !(bmsr & BMSR_ANEGCOMPLETE))
+               return;
+
+       /* Pause bits we should be advertising for the requested mode (mirrors
+        * e1000_phy_setup_autoneg): full/rx use CAP|ASYM, tx uses ASYM only.
+        */
+       if (hw->fc.requested_mode == e1000_fc_tx_pause)
+               expected = ADVERTISE_PAUSE_ASYM;
+       else
+               expected = ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
+
+       /* The advertisement should carry these bits; missing them while a pause
+        * mode was requested is the failure this recovers from.
+        */
+       if ((adv & expected) == expected)
+               return;
+
+       now = jiffies;
+
+       /* Give-up is terminal for this link episode; it is re-armed only on a
+        * real link-down (see the watchdog link-down path), i.e. when the peer
+        * or cabling actually changes -- not on a timer, which cannot tell a
+        * recovered switch from the same never-pause partner 60s later.
+        */
+       if (adapter->fclatch_gave_up)
+               return;
+       if (adapter->fclatch_last_fix &&
+           time_before(now, adapter->fclatch_last_fix + 
E1000E_FCLATCH_HOLDOFF))
+               return;                         /* within holdoff */
+
+       if (adapter->fclatch_attempts >= E1000E_FCLATCH_MAX_ATTEMPTS) {
+               adapter->fclatch_gave_up = true;
+               e_warn("flow-control advertisement latch persists after %u 
autoneg-restart attempts (adv=0x%04x, requested fc=%u); giving up until the 
link goes down and back up\n",
+                      adapter->fclatch_attempts, adv, hw->fc.requested_mode);
+               return;
+       }
+
+       adapter->fclatch_attempts++;
+       adapter->fclatch_last_fix = now;
+       e_warn("flow-control advertisement latch detected (adv=0x%04x missing 
pause for requested fc=%u); forcing autoneg restart (attempt %u/%d)\n",
+              adv, hw->fc.requested_mode, adapter->fclatch_attempts,
+              E1000E_FCLATCH_MAX_ATTEMPTS);
+       /* Request the reset via FLAG_RESTART_NOW; the watchdog schedules
+        * reset_task on this flag just below the call site. The flag also
+        * tells reset_task this is intentional, suppressing its "Reset
+        * adapter unexpectedly" warning + register dump.
+        */
+       adapter->flags |= FLAG_RESTART_NOW;
+}
+
 static void e1000_watchdog_task(struct work_struct *work)
 {
        struct e1000_adapter *adapter = container_of(work,
@@ -5353,6 +5469,17 @@ static void e1000_watchdog_task(struct work_struct *work)
                        netdev_info(netdev, "NIC Link is Down\n");
                        netif_carrier_off(netdev);
                        netif_stop_queue(netdev);
+
+                       /* A real link-down ends the current fc-latch episode:
+                        * re-arm remediation so the next link-up (e.g. the
+                        * switch finishing its reboot) gets a fresh set of
+                        * attempts. Our own reset_task bounce does not reach
+                        * here (it goes through __E1000_DOWN/RESETTING), so
+                        * this does not defeat the per-episode attempt cap.
+                        */
+                       adapter->fclatch_attempts = 0;
+                       adapter->fclatch_gave_up = false;
+                       adapter->fclatch_last_fix = 0;
                        if (!test_bit(__E1000_DOWN, &adapter->state))
                                mod_timer(&adapter->phy_info_timer,
                                          round_jiffies(jiffies + 2 * HZ));
@@ -5384,6 +5511,11 @@ static void e1000_watchdog_task(struct work_struct *work)
        adapter->gotc_old = adapter->stats.gotc;
        spin_unlock(&adapter->stats64_lock);
 
+       /* Auto-recover from a latched pause advertisement (I219/PCH,
+        * jumbo) after an upstream switch reboot; see e1000e_check_fc_latch().
+        */
+       e1000e_check_fc_latch(adapter);
+
        /* If the link is lost the controller stops DMA, but
         * if there is queued Tx work it cannot be done.  So
         * reset the controller to flush the Tx packet buffers.
-- 
2.47.3

Reply via email to