git: 83b308875d25 - main - hn: Relay VLAN membership to transparent VFs
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 17 Sep 2026 00:09:10 UTC
The branch main has been updated by kbowling:
URL: https://cgit.FreeBSD.org/src/commit/?id=83b308875d254a894e04dbe91d5fbdb637eafdf2
commit 83b308875d254a894e04dbe91d5fbdb637eafdf2
Author: Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-09-16 17:13:40 +0000
Commit: Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-17 00:07:32 +0000
hn: Relay VLAN membership to transparent VFs
VLANs configured on the synthetic interface do not otherwise reach the
accelerating VF's VLAN callbacks. Those callbacks can be needed for
hardware filter membership or for interpreting stripped VLAN tags, even
though no vlan interface is attached directly to the VF.
Subscribe to VLAN events only in transparent mode and schedule the
existing VF task. Snapshot the synthetic interface's VLAN topology under
network epoch, then leave epoch before invoking the VF callbacks, which
may sleep. Do not acquire hn_lock or configure the VF from a VLAN event
handler; the worker applies membership outside the VLAN configuration
lock.
Keep an applied-VID bitmap under hn_lock and relay only changes. Replay
VLANs configured before VF arrival, reconcile changes while acceleration
is active, and preserve membership across temporary datapath switches.
This relays guest intent; it does not configure host access VLAN policy
or overcome PF restrictions on tagged traffic.
Block initialization while either interface is detaching. Deregister
VLAN handlers before draining work, remove registrations when hn leaves a
live VF, and discard the applied bitmap when the VF itself departs.
MFC after: 2 weeks
Sponsored by: BBOX.io
---
sys/dev/hyperv/netvsc/if_hn.c | 85 ++++++++++++++++++++++++++++++++++++++--
sys/dev/hyperv/netvsc/if_hnvar.h | 8 ++++
2 files changed, 89 insertions(+), 4 deletions(-)
diff --git a/sys/dev/hyperv/netvsc/if_hn.c b/sys/dev/hyperv/netvsc/if_hn.c
index 72bef946b0d5..73ad8b978c7e 100644
--- a/sys/dev/hyperv/netvsc/if_hn.c
+++ b/sys/dev/hyperv/netvsc/if_hn.c
@@ -97,6 +97,7 @@
#include <net/if_media.h>
#include <net/if_types.h>
#include <net/if_var.h>
+#include <net/if_vlan_var.h>
#include <net/rndis.h>
#include <net/rss_config.h>
@@ -1771,6 +1772,7 @@ hn_xpnt_vf_caninit(struct hn_softc *sc)
HN_LOCK_ASSERT(sc);
return (hn_xpnt_vf && sc->hn_vf_ifp != NULL &&
+ !sc->hn_detaching && !sc->hn_vf_detaching &&
(atomic_load_acq_int(&sc->hn_vf_assoc) & HN_VF_ASSOC_ALLOCATED) &&
(int)(ticks - sc->hn_vf_rdytick) >= 0);
}
@@ -1855,6 +1857,60 @@ hn_xpnt_vf_setdisable(struct hn_softc *sc, bool clear_vf)
sc->hn_rx_ring[i].hn_rx_flags &= ~HN_RX_FLAG_XPNT_VF;
}
+/*
+ * Do not configure the VF from the VLAN event callback. The worker reads
+ * and applies the current VLAN topology outside the VLAN configuration
+ * lock, including VLANs configured before the VF arrives. Only
+ * transparent mode subscribes.
+ */
+static void
+hn_vlan_event(void *xsc, if_t ifp, uint16_t vid __unused)
+{
+ struct hn_softc *sc = xsc;
+
+ if (ifp != sc->hn_ifp)
+ return;
+ taskqueue_enqueue_timeout(sc->hn_vf_taskq, &sc->hn_vf_init, 0);
+}
+
+/* Apply guest VLAN intent, not the host's administrative access VLAN. */
+static void
+hn_xpnt_vf_sync_vlans(struct hn_softc *sc, bool remove)
+{
+ struct epoch_tracker et;
+ u_int desired[HN_VLAN_WORDS] = { 0 }, changed, mask;
+ unsigned int i, bit;
+ uint16_t vid;
+
+ HN_LOCK_ASSERT(sc);
+ KASSERT(sc->hn_vf_ifp != NULL, ("VLAN sync without a VF"));
+ if (!remove) {
+ NET_EPOCH_ENTER(et);
+ for (vid = 1; vid < EVL_VLID_MASK; vid++) {
+ if (VLAN_DEVAT(sc->hn_ifp, vid) != NULL)
+ desired[vid / 32] |= 1U << (vid % 32);
+ }
+ NET_EPOCH_EXIT(et);
+ }
+ /* VF callbacks may sleep; never invoke them inside network epoch. */
+ for (i = 0; i < HN_VLAN_WORDS; i++) {
+ changed = desired[i] ^ sc->hn_vf_vlans[i];
+ while (changed != 0) {
+ bit = ffs(changed) - 1;
+ mask = 1U << bit;
+ vid = i * 32 + bit;
+ if ((desired[i] & mask) != 0)
+ EVENTHANDLER_INVOKE(vlan_config, sc->hn_vf_ifp,
+ vid);
+ else
+ EVENTHANDLER_INVOKE(vlan_unconfig, sc->hn_vf_ifp,
+ vid);
+ changed &= ~mask;
+ }
+ sc->hn_vf_vlans[i] = desired[i];
+ }
+}
+
static void
hn_xpnt_vf_init(struct hn_softc *sc)
{
@@ -1867,6 +1923,7 @@ hn_xpnt_vf_init(struct hn_softc *sc)
("%s: transparent VF was enabled", if_name(sc->hn_ifp)));
if (!hn_xpnt_vf_caninit(sc))
return;
+ hn_xpnt_vf_sync_vlans(sc, false);
assoc = atomic_load_acq_int(&sc->hn_vf_assoc);
rm_wlock(&sc->hn_vf_lock);
sc->hn_xvf_flags |= HN_XVFFLAG_SWITCHING;
@@ -1939,7 +1996,7 @@ hn_xpnt_vf_init_taskfunc(void *xsc, int pending __unused)
if ((sc->hn_flags & HN_FLAG_SYNTH_ATTACHED) == 0)
goto done;
- if (sc->hn_vf_ifp == NULL)
+ if (sc->hn_vf_ifp == NULL || sc->hn_vf_detaching)
goto done;
if (!hn_xpnt_vf) {
if ((sc->hn_flags & HN_FLAG_RXVF) && sc->hn_vf_active_assoc !=
@@ -1952,13 +2009,14 @@ hn_xpnt_vf_init_taskfunc(void *xsc, int pending __unused)
if (sc->hn_vf_active_assoc != 0 && sc->hn_vf_active_assoc !=
atomic_load_acq_int(&sc->hn_vf_assoc))
hn_xpnt_vf_deactivate(sc);
- if (sc->hn_xvf_flags & HN_XVFFLAG_ENABLED)
- goto done;
if ((int)(ticks - sc->hn_vf_rdytick) < 0) {
taskqueue_enqueue_timeout(sc->hn_vf_taskq, &sc->hn_vf_init,
sc->hn_vf_rdytick - ticks);
goto done;
}
+ hn_xpnt_vf_sync_vlans(sc, false);
+ if (sc->hn_xvf_flags & HN_XVFFLAG_ENABLED)
+ goto done;
if (if_getdrvflags(sc->hn_ifp) & IFF_DRV_RUNNING) {
/*
@@ -2033,6 +2091,7 @@ hn_ifnet_attevent(void *xsc, if_t ifp)
("%s: transparent VF was enabled", if_name(sc->hn_ifp)));
sc->hn_vf_ifp = ifp;
rm_wunlock(&sc->hn_vf_lock);
+ sc->hn_vf_detaching = false;
if (hn_xpnt_vf) {
int wait_ticks;
@@ -2073,6 +2132,7 @@ hn_ifnet_detevent(void *xsc, if_t ifp)
goto done;
if (hn_xpnt_vf) {
+ sc->hn_vf_detaching = true;
/*
* Make sure that the delayed initialization is not running.
*
@@ -2094,6 +2154,8 @@ hn_ifnet_detevent(void *xsc, if_t ifp)
hn_xpnt_vf_deactivate(sc);
else
hn_xpnt_vf_restore(sc);
+ /* A departing VF discards its registration state itself. */
+ bzero(sc->hn_vf_vlans, sizeof(sc->hn_vf_vlans));
if_setinputfn(ifp, sc->hn_vf_input);
sc->hn_vf_input = NULL;
} else if (sc->hn_flags & HN_FLAG_SYNTH_ATTACHED) {
@@ -2540,6 +2602,13 @@ hn_attach(device_t dev)
if_sethwtsomaxsegsize(ifp, PAGE_SIZE);
}
+ /* Schedule reconciliation when the synthetic interface's VLANs change. */
+ if (hn_xpnt_vf) {
+ sc->hn_vlan_atthand = EVENTHANDLER_REGISTER(vlan_config,
+ hn_vlan_event, sc, EVENTHANDLER_PRI_ANY);
+ sc->hn_vlan_dethand = EVENTHANDLER_REGISTER(vlan_unconfig,
+ hn_vlan_event, sc, EVENTHANDLER_PRI_ANY);
+ }
ether_ifattach(ifp, eaddr);
if ((if_getcapabilities(ifp) & (IFCAP_TSO6 | IFCAP_TSO4)) && bootverbose) {
@@ -2606,6 +2675,10 @@ hn_detach(device_t dev)
HN_LOCK(sc);
sc->hn_detaching = true;
HN_UNLOCK(sc);
+ if (sc->hn_vlan_atthand != NULL)
+ EVENTHANDLER_DEREGISTER(vlan_config, sc->hn_vlan_atthand);
+ if (sc->hn_vlan_dethand != NULL)
+ EVENTHANDLER_DEREGISTER(vlan_unconfig, sc->hn_vlan_dethand);
taskqueue_drain_timeout(sc->hn_vf_taskq, &sc->hn_vf_init);
if (sc->hn_ifaddr_evthand != NULL)
@@ -2623,8 +2696,12 @@ hn_detach(device_t dev)
if (sc->hn_ifnet_lnkhand != NULL)
EVENTHANDLER_DEREGISTER(ifnet_link_event, sc->hn_ifnet_lnkhand);
+ HN_LOCK(sc);
vf_ifp = sc->hn_vf_ifp;
- __compiler_membar();
+ /* hn is leaving; remove its registrations from the live VF. */
+ if (vf_ifp != NULL && hn_xpnt_vf)
+ hn_xpnt_vf_sync_vlans(sc, true);
+ HN_UNLOCK(sc);
if (vf_ifp != NULL)
hn_ifnet_detevent(sc, vf_ifp);
diff --git a/sys/dev/hyperv/netvsc/if_hnvar.h b/sys/dev/hyperv/netvsc/if_hnvar.h
index fb545b8b2bc8..4be884d5522c 100644
--- a/sys/dev/hyperv/netvsc/if_hnvar.h
+++ b/sys/dev/hyperv/netvsc/if_hnvar.h
@@ -46,6 +46,8 @@
#define HN_GPACNT_MAX 32
+#define HN_VLAN_WORDS 128
+
struct hn_txdesc;
#ifndef HN_USE_TXDESC_BUFRING
SLIST_HEAD(hn_txdesc_list, hn_txdesc);
@@ -260,6 +262,12 @@ struct hn_softc {
eventhandler_tag hn_ifnet_atthand;
eventhandler_tag hn_ifnet_dethand;
eventhandler_tag hn_ifnet_lnkhand;
+ eventhandler_tag hn_vlan_atthand;
+ eventhandler_tag hn_vlan_dethand;
+
+ /* Applied VLANs and VF lifetime use hn_lock. */
+ u_int hn_vf_vlans[HN_VLAN_WORDS];
+ bool hn_vf_detaching;
/*
* Transparent VF delayed initialization.