git: 1568e6bb3c9d - main - hn: Refresh VF RSS configuration after link recovery

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

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

commit 1568e6bb3c9d4007492d0297c0dfa3834a2ba751
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-09-18 11:25:44 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-18 12:00:35 +0000

    hn: Refresh VF RSS configuration after link recovery
    
    A VF can finish initialization or reset recovery after hn's handoff-time
    RSS query returned ENXIO.  In that case hn suppresses synthetic receive
    hash metadata, but previously left it disabled even after the VF could
    answer the queries again.
    
    Queue an RSS refresh on a VF link-up notification, using the existing VF
    worker in both transparent and non-transparent modes.  Revalidate the
    association, active VF path, administrative state, and carrier under
    hn_lock before querying and reconfiguring synthetic RSS.  Coalesce the
    requests with an atomic flag and retain a request while capability
    forwarding temporarily excludes the worker.  Keep the existing query
    validity checks and unsupported-query fallback unchanged.
    
    This is a one-shot refresh, not a readiness poll.  Recovery without a
    link-up notification, or a query that still fails during the refresh,
    does not trigger another retry by itself.  A later link-up or normal
    handoff can query again.  Consuming the request while the VF path is
    inactive is intentional: the next handoff queries RSS unconditionally.
    
    Continue to reprogram RSS after a successful query even when the cached
    key and types match.  They are updated before host reconfiguration and
    do not prove that the previous programming succeeded.
    
    The event callback only records and schedules work; RSS queries and host
    reconfiguration remain in sleepable context.  No packet-path changes or
    new timer are needed.
    
    With an E835 iavf VF, reproduced an ENABLE_QUEUES timeout and an ENXIO
    RSS query in both transparent and non-transparent modes.  In each mode
    hn restored mbuf_hash after VF recovery, without another interface
    reconfiguration.
    
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
---
 sys/dev/hyperv/netvsc/if_hn.c    | 34 +++++++++++++++++++++++++++-------
 sys/dev/hyperv/netvsc/if_hnvar.h |  1 +
 2 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/sys/dev/hyperv/netvsc/if_hn.c b/sys/dev/hyperv/netvsc/if_hn.c
index d8ac23a4af6e..6d9953dd2217 100644
--- a/sys/dev/hyperv/netvsc/if_hn.c
+++ b/sys/dev/hyperv/netvsc/if_hn.c
@@ -2084,7 +2084,7 @@ hn_xpnt_vf_init_taskfunc(void *xsc, int pending __unused)
 			hn_rxvf_change_locked(sc, sc->hn_vf_ifp, false);
 		hn_rxvf_change_locked(sc, sc->hn_vf_ifp,
 		    (if_getflags(sc->hn_vf_ifp) & IFF_UP) != 0);
-		goto done;
+		goto rss;
 	}
 	if (sc->hn_vf_active_assoc != 0 && sc->hn_vf_active_assoc !=
 	    atomic_load_acq_int(&sc->hn_vf_assoc))
@@ -2096,7 +2096,7 @@ hn_xpnt_vf_init_taskfunc(void *xsc, int pending __unused)
 	}
 	hn_xpnt_vf_sync_vlans(sc, false);
 	if (sc->hn_xvf_flags & HN_XVFFLAG_ENABLED)
-		goto done;
+		goto rss;
 
 	if (if_getdrvflags(sc->hn_ifp) & IFF_DRV_RUNNING) {
 		/*
@@ -2108,6 +2108,20 @@ hn_xpnt_vf_init_taskfunc(void *xsc, int pending __unused)
 		}
 		hn_xpnt_vf_init(sc);
 	}
+rss:
+	/*
+	 * A link-up event can follow recovery from a failed handoff RSS query.
+	 * An inactive path can consume this request: its next handoff queries
+	 * RSS again. This is a one-shot refresh, not a readiness poll.
+	 */
+	if (atomic_readandclear_int(&sc->hn_vf_rss_refresh) != 0 &&
+	    ((sc->hn_flags & HN_FLAG_RXVF) ||
+	    (sc->hn_xvf_flags & HN_XVFFLAG_ENABLED)) &&
+	    (sc->hn_vf_active_assoc & HN_VF_ASSOC_ALLOCATED) != 0 &&
+	    sc->hn_vf_active_assoc == atomic_load_acq_int(&sc->hn_vf_assoc) &&
+	    (if_getflags(sc->hn_vf_ifp) & IFF_UP) != 0 &&
+	    if_getlinkstate(sc->hn_vf_ifp) == LINK_STATE_UP)
+		hn_vf_rss_fixup(sc, true);
 done:
 	HN_UNLOCK(sc);
 }
@@ -2281,11 +2295,18 @@ hn_ifnet_lnkevent(void *xsc, if_t ifp, int link_state)
 
 	/* Publish before a concurrent handoff can restore synthetic carrier. */
 	rm_rlock(&sc->hn_vf_lock, &pt);
-	if (sc->hn_vf_ifp == ifp &&
-	    (sc->hn_xvf_flags & (HN_XVFFLAG_ENABLED | HN_XVFFLAG_SWITCHING)) ==
+	if (sc->hn_vf_ifp != ifp)
+		goto out;
+	if (link_state == LINK_STATE_UP) {
+		/* RSS queries and host reconfiguration require sleepable context. */
+		atomic_store_rel_int(&sc->hn_vf_rss_refresh, 1);
+		taskqueue_enqueue_timeout(sc->hn_vf_taskq, &sc->hn_vf_init, 0);
+	}
+	if ((sc->hn_xvf_flags & (HN_XVFFLAG_ENABLED | HN_XVFFLAG_SWITCHING)) ==
 	    HN_XVFFLAG_ENABLED && sc->hn_vf_active_assoc ==
 	    atomic_load_acq_int(&sc->hn_vf_assoc))
 		if_link_state_change(sc->hn_ifp, link_state);
+out:
 	rm_runlock(&sc->hn_vf_lock, &pt);
 }
 
@@ -2718,10 +2739,9 @@ hn_attach(device_t dev)
 		    hn_ifnet_event, sc, EVENTHANDLER_PRI_ANY);
 		sc->hn_ifaddr_evthand = EVENTHANDLER_REGISTER(ifaddr_event,
 		    hn_ifaddr_event, sc, EVENTHANDLER_PRI_ANY);
-	} else {
-		sc->hn_ifnet_lnkhand = EVENTHANDLER_REGISTER(ifnet_link_event,
-		    hn_ifnet_lnkevent, sc, EVENTHANDLER_PRI_ANY);
 	}
+	sc->hn_ifnet_lnkhand = EVENTHANDLER_REGISTER(ifnet_link_event,
+	    hn_ifnet_lnkevent, sc, EVENTHANDLER_PRI_ANY);
 
 	/*
 	 * NOTE:
diff --git a/sys/dev/hyperv/netvsc/if_hnvar.h b/sys/dev/hyperv/netvsc/if_hnvar.h
index 7fde5c8fe30e..9d19137579e6 100644
--- a/sys/dev/hyperv/netvsc/if_hnvar.h
+++ b/sys/dev/hyperv/netvsc/if_hnvar.h
@@ -278,6 +278,7 @@ struct hn_softc {
 	bool			hn_detaching;	/* hn_lock */
 	u_int			hn_vf_assoc;	/* atomic generation + allocated */
 	u_int			hn_vf_active_assoc; /* confirmed VF generation */
+	u_int			hn_vf_rss_refresh; /* atomic link-up request */
 	struct taskqueue	*hn_vf_taskq;
 	struct timeout_task	hn_vf_init;
 	struct task		hn_vf_vlancap;