The branch main has been updated by kbowling:

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

commit 0bd6a167c1561f01c227b1c428a3d8adf0e38833
Author:     Kevin Bowling <[email protected]>
AuthorDate: 2026-07-25 21:00:10 +0000
Commit:     Kevin Bowling <[email protected]>
CommitDate: 2026-07-25 21:00:10 +0000

    e1000: Defer link-up notification until after TSO reset
    
    em_automask_tso() changes the enabled TSO capabilities when the link
    moves between 10/100 and 1000 Mb/s.  A running interface must be
    reinitialized to apply the new capability set.  Do not publish
    LINK_STATE_UP until the requested iflib reset has completed.
    
    Replace link_active with an explicit state machine that distinguishes
    the physical link, its publication to iflib, and an outstanding reset
    barrier.  Preserve that barrier across a link flap with
    DOWN_RESET_PENDING, and only publish DOWN if UP was previously
    published.
    
    Only request a reset for a running interface or for an initialization
    while the interface is administratively up.  In other states the next
    initialization will apply the capability changes, avoiding a reset
    request that iflib's admin task could discard.
    
    Reviewed by:    Faraz Vahedi <[email protected]>
    Fixes:  2ddf24f8f525 ("e1000: Automask TSO on lem(4)/em(4) 10/100 Ethernet")
    MFC after:      1 week
---
 sys/dev/e1000/if_em.c | 79 +++++++++++++++++++++++++++++++++++++++------------
 sys/dev/e1000/if_em.h | 10 ++++++-
 2 files changed, 70 insertions(+), 19 deletions(-)

diff --git a/sys/dev/e1000/if_em.c b/sys/dev/e1000/if_em.c
index 3118b27e8122..5547c4ec8614 100644
--- a/sys/dev/e1000/if_em.c
+++ b/sys/dev/e1000/if_em.c
@@ -1562,6 +1562,10 @@ em_if_init(if_ctx_t ctx)
 
        /* Initialize the hardware */
        em_reset(ctx);
+       /* Re-arm a link-up transition deferred for this reset. */
+       if (sc->link_state == EM_LINK_STATE_DOWN_RESET_PENDING ||
+           sc->link_state == EM_LINK_STATE_UP_RESET_PENDING)
+               sc->link_state = EM_LINK_STATE_DOWN;
        em_if_update_admin_status(ctx);
 
        for (i = 0, tx_que = sc->tx_queues; i < sc->tx_num_queues;
@@ -1999,7 +2003,8 @@ em_if_media_status(if_ctx_t ctx, struct ifmediareq *ifmr)
        ifmr->ifm_status = IFM_AVALID;
        ifmr->ifm_active = IFM_ETHER;
 
-       if (!sc->link_active) {
+       if (sc->link_state == EM_LINK_STATE_DOWN ||
+           sc->link_state == EM_LINK_STATE_DOWN_RESET_PENDING) {
                return;
        }
 
@@ -2219,7 +2224,7 @@ em_if_update_admin_status(if_ctx_t ctx)
        struct e1000_hw *hw = &sc->hw;
        device_t dev = iflib_get_dev(ctx);
        u32 link_check, thstat, ctrl;
-       bool automasked = false;
+       bool reset_requested = false;
 
        link_check = thstat = ctrl = 0;
        /* Get the cached link value or read phy for real */
@@ -2262,7 +2267,13 @@ em_if_update_admin_status(if_ctx_t ctx)
        }
 
        /* Now check for a transition */
-       if (link_check && (sc->link_active == 0)) {
+       if (link_check &&
+           (sc->link_state == EM_LINK_STATE_DOWN ||
+           sc->link_state == EM_LINK_STATE_DOWN_RESET_PENDING)) {
+               bool reset_pending;
+
+               reset_pending =
+                   sc->link_state == EM_LINK_STATE_DOWN_RESET_PENDING;
                e1000_get_speed_and_duplex(hw, &sc->link_speed,
                    &sc->link_duplex);
                /* Check if we must disable SPEED_MODE bit on PCI-E */
@@ -2279,7 +2290,7 @@ em_if_update_admin_status(if_ctx_t ctx)
                            sc->link_speed,
                            ((sc->link_duplex == FULL_DUPLEX) ?
                            "Full Duplex" : "Half Duplex"));
-               sc->link_active = 1;
+               sc->link_state = EM_LINK_STATE_UP;
                sc->smartspeed = 0;
                if ((ctrl & E1000_CTRL_EXT_LINK_MODE_MASK) ==
                    E1000_CTRL_EXT_LINK_MODE_GMII &&
@@ -2299,17 +2310,33 @@ em_if_update_admin_status(if_ctx_t ctx)
                }
                /* Only do TSO on gigabit for older chips due to errata */
                if (hw->mac.type < igb_mac_min)
-                       automasked = em_automask_tso(ctx);
+                       reset_requested = em_automask_tso(ctx);
 
-               /* Automasking resets the interface so don't mark it up yet */
-               if (!automasked)
+               if (reset_pending || reset_requested) {
+                       /*
+                        * The PHY is up, but publish it only after the TSO
+                        * capability-change reset.
+                        */
+                       sc->link_state = EM_LINK_STATE_UP_RESET_PENDING;
+               } else {
                        iflib_link_state_change(ctx, LINK_STATE_UP,
                            IF_Mbps(sc->link_speed));
-       } else if (!link_check && (sc->link_active == 1)) {
+               }
+       } else if (!link_check &&
+           (sc->link_state == EM_LINK_STATE_UP ||
+           sc->link_state == EM_LINK_STATE_UP_RESET_PENDING)) {
+               bool link_was_published;
+               bool reset_pending;
+
+               link_was_published = sc->link_state == EM_LINK_STATE_UP;
+               reset_pending =
+                   sc->link_state == EM_LINK_STATE_UP_RESET_PENDING;
                sc->link_speed = 0;
                sc->link_duplex = 0;
-               sc->link_active = 0;
-               iflib_link_state_change(ctx, LINK_STATE_DOWN, 0);
+               sc->link_state = reset_pending ?
+                   EM_LINK_STATE_DOWN_RESET_PENDING : EM_LINK_STATE_DOWN;
+               if (link_was_published)
+                       iflib_link_state_change(ctx, LINK_STATE_DOWN, 0);
        }
        em_update_stats_counters(sc);
 
@@ -2758,7 +2785,9 @@ lem_smartspeed(struct e1000_softc *sc)
 {
        u16 phy_tmp;
 
-       if (sc->link_active || (sc->hw.phy.type != e1000_phy_igp) ||
+       if (sc->link_state == EM_LINK_STATE_UP ||
+           sc->link_state == EM_LINK_STATE_UP_RESET_PENDING ||
+           (sc->hw.phy.type != e1000_phy_igp) ||
            sc->hw.mac.autoneg == 0 ||
            (sc->hw.phy.autoneg_advertised & ADVERTISE_1000_FULL) == 0)
                return;
@@ -4342,6 +4371,8 @@ em_automask_tso(if_ctx_t ctx)
        struct e1000_softc *sc = iflib_get_softc(ctx);
        if_softc_ctx_t scctx = iflib_get_softc_ctx(ctx);
        if_t ifp = iflib_get_ifp(ctx);
+       bool reset_needed;
+       int drvflags;
 
        if (!em_unsupported_tso && sc->link_speed &&
            sc->link_speed != SPEED_1000 &&
@@ -4351,20 +4382,32 @@ em_automask_tso(if_ctx_t ctx)
                sc->tso_automasked = scctx->isc_capenable & IFCAP_TSO;
                scctx->isc_capenable &= ~IFCAP_TSO;
                if_setcapenablebit(ifp, 0, IFCAP_TSO);
-               /* iflib_init_locked handles ifnet hwassistbits */
-               iflib_request_reset(ctx);
-               return true;
        } else if (sc->link_speed == SPEED_1000 && sc->tso_automasked) {
                device_printf(sc->dev, "Re-enabling TSO for GbE.\n");
                scctx->isc_capenable |= sc->tso_automasked;
                if_setcapenablebit(ifp, sc->tso_automasked, 0);
                sc->tso_automasked = 0;
-               /* iflib_init_locked handles ifnet hwassistbits */
-               iflib_request_reset(ctx);
-               return true;
+       } else {
+               return (false);
        }
 
-       return false;
+       /*
+        * Reset a running interface, or one being initialized while
+        * administratively up.  OACTIVE remains set after iflib_stop(), so
+        * it alone cannot distinguish initialization from an interface that
+        * is down.  In other states, the next initialization will apply the
+        * updated capabilities.
+        */
+       drvflags = if_getdrvflags(ifp);
+       reset_needed = (drvflags & IFF_DRV_RUNNING) != 0 ||
+           ((drvflags & IFF_DRV_OACTIVE) != 0 &&
+           (if_getflags(ifp) & IFF_UP) != 0);
+       if (!reset_needed)
+               return (false);
+
+       /* iflib_init_locked handles ifnet hwassistbits */
+       iflib_request_reset(ctx);
+       return (true);
 }
 
 /*
diff --git a/sys/dev/e1000/if_em.h b/sys/dev/e1000/if_em.h
index 4c80c7696952..56c28597fa22 100644
--- a/sys/dev/e1000/if_em.h
+++ b/sys/dev/e1000/if_em.h
@@ -469,6 +469,14 @@ struct em_rx_queue {
        struct if_irq           que_irq;
 };  
 
+/* Driver-observed link state and its publication barrier. */
+enum em_link_state {
+       EM_LINK_STATE_DOWN = 0,
+       EM_LINK_STATE_DOWN_RESET_PENDING,
+       EM_LINK_STATE_UP,
+       EM_LINK_STATE_UP_RESET_PENDING,
+};
+
 /* Our softc structure */
 struct e1000_softc {
        struct e1000_hw         hw;
@@ -530,7 +538,7 @@ struct e1000_softc {
        u32                     shadow_vfta[EM_VFTA_SIZE];
 
        /* Info about the interface */
-       u16                     link_active;
+       enum em_link_state      link_state;
        u16                     fc;
        u16                     link_speed;
        u16                     link_duplex;

Reply via email to