Module Name:    src
Committed By:   martin
Date:           Thu Oct  3 11:53:46 UTC 2024

Modified Files:
        src/sys/net/lagg [netbsd-10]: if_lagg.c if_lagg_lacp.c if_lagg_lacp.h
            if_laggproto.c if_laggproto.h
        src/tests/net/if_lagg [netbsd-10]: t_lagg.sh

Log Message:
Pull up following revision(s) (requested by rin in ticket #916):

        sys/net/lagg/if_laggproto.c: revision 1.15
        sys/net/lagg/if_lagg_lacp.c: revision 1.36
        sys/net/lagg/if_laggproto.c: revision 1.16
        sys/net/lagg/if_lagg_lacp.c: revision 1.37
        sys/net/lagg/if_lagg_lacp.c: revision 1.38
        sys/net/lagg/if_lagg_lacp.c: revision 1.39
        sys/net/lagg/if_lagg.c: revision 1.54
        sys/net/lagg/if_lagg.c: revision 1.55
        sys/net/lagg/if_lagg.c: revision 1.59
        sys/net/lagg/if_lagg.c: revision 1.70
        sys/net/lagg/if_laggproto.h: revision 1.19
        sys/net/lagg/if_lagg_lacp.c: revision 1.28
        sys/net/lagg/if_lagg_lacp.c: revision 1.29
        sys/net/lagg/if_laggproto.c: revision 1.7
        sys/net/lagg/if_lagg_lacp.h: revision 1.5
        sys/net/lagg/if_laggproto.c: revision 1.8
        sys/net/lagg/if_laggproto.c: revision 1.9
        sys/net/lagg/if_lagg_lacp.c: revision 1.40
        sys/net/lagg/if_lagg_lacp.c: revision 1.41
        sys/net/lagg/if_lagg_lacp.c: revision 1.42
        sys/net/lagg/if_lagg_lacp.c: revision 1.43
        tests/net/if_lagg/t_lagg.sh: revision 1.11
        sys/net/lagg/if_lagg.c: revision 1.60
        sys/net/lagg/if_lagg.c: revision 1.62
        sys/net/lagg/if_lagg.c: revision 1.63
        sys/net/lagg/if_lagg.c: revision 1.64
        sys/net/lagg/if_laggproto.h: revision 1.20
        sys/net/lagg/if_lagg.c: revision 1.65
        sys/net/lagg/if_lagg.c: revision 1.66
        sys/net/lagg/if_lagg.c: revision 1.67
        sys/net/lagg/if_lagg_lacp.c: revision 1.30
        sys/net/lagg/if_lagg.c: revision 1.68
        sys/net/lagg/if_laggproto.c: revision 1.10
        sys/net/lagg/if_lagg_lacp.c: revision 1.31
        sys/net/lagg/if_lagg.c: revision 1.69
        sys/net/lagg/if_laggproto.c: revision 1.11
        sys/net/lagg/if_lagg_lacp.c: revision 1.32
        sys/net/lagg/if_laggproto.c: revision 1.12
        sys/net/lagg/if_lagg_lacp.c: revision 1.33
        sys/net/lagg/if_laggproto.c: revision 1.13
        sys/net/lagg/if_lagg_lacp.c: revision 1.34
        sys/net/lagg/if_laggproto.c: revision 1.14
        sys/net/lagg/if_lagg_lacp.c: revision 1.35

Set the fastest linkspeed in each physical interface to lagg(4)

lagg(4): Added logs about LACP processing

lagg(4): Fix missing IFNET_LOCK acquirement

lagg(4): update link speed when a physical interface is removed

lagg(4): fix missing update of the number of active ports

lagg(4): Added 0 length check

lagg(4): Added LACP_READY state for logging
when a port turns SELECTED or UNSELECTED

lagg(4): added log on detaching a port from SELECTED state to STANDBY
acquire LAGG_PROTO_LOCK instead of pserialize read section

lagg(4): Remove unnecessary LAGG_LOCK holding while lagg_proto_detach()
to avoid deadlock in workqueue_wait due to LAGG_LOCK holding
lagg_proto_detach dose not need to hold LAGG_LOCK because only one
context can access to a detaching protocol after sc->sc_var is updated.

But it was held without any reason. And it had caused a deadlock by
holding LAGG_LOCK in caller of workqueue_wait
and waiting for the lock in worker.
added missing LAGG_UNLOCK()

lagg(4): move comment about IFF_PROMISC
pointed out by ozaki-r@, thanks.

lagg(4): added NULL check for pfil_run_hooks
pointed out by ozaki-r@, thanks.

lagg(4): change errno
suggested by ozaki-r@, thanks.

lagg(4): increase output packets and bytes only if no error occurred
pointed out by ozaki-r@, thanks.

lagg(4): replace NULL check with KASSERT because lp_softc is always non-NULL

lagg(4): Use CTASSERT
Added KASSERT for LACP_LOCK

lagg(4): move allocate memory before ioctl
Added comments to lagg(4)

lagg(4): added __predict_true

lagg(4): added missing pserialize_read_enter
fix missing LACP_LOCK

lagg(4): added check of LACP running state for safety

When LACP stops, the handler of callout do nothing
because all port is already detached from lacp.

Therefore, the added checks are just for safety.
added missing workq_wait for lacp_tick_work()

lagg(4): set suppress at the same time with distribution state

lagg(4): remove unnecessary masking
pointed out by ozaki-r@, thanks.

lagg(4): move reply limitation to recive processing

lagg(4): release lock before pserialize_perform() if possible

lagg(4): Added vlan check

lagg(4): Fix missing destroy for list and entry

lagg(4) test: Fix typo and old comment

lagg: fill name of workqueue correctly
Found by KASSERT failure for DIAGNOSTIC kernel.
Authored by ozaki-r@.


To generate a diff of this commit:
cvs rdiff -u -r1.48.4.3 -r1.48.4.4 src/sys/net/lagg/if_lagg.c
cvs rdiff -u -r1.25.4.1 -r1.25.4.2 src/sys/net/lagg/if_lagg_lacp.c
cvs rdiff -u -r1.4 -r1.4.4.1 src/sys/net/lagg/if_lagg_lacp.h
cvs rdiff -u -r1.6 -r1.6.4.1 src/sys/net/lagg/if_laggproto.c
cvs rdiff -u -r1.18 -r1.18.4.1 src/sys/net/lagg/if_laggproto.h
cvs rdiff -u -r1.8.2.1 -r1.8.2.2 src/tests/net/if_lagg/t_lagg.sh

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/net/lagg/if_lagg.c
diff -u src/sys/net/lagg/if_lagg.c:1.48.4.3 src/sys/net/lagg/if_lagg.c:1.48.4.4
--- src/sys/net/lagg/if_lagg.c:1.48.4.3	Tue Dec 12 16:45:00 2023
+++ src/sys/net/lagg/if_lagg.c	Thu Oct  3 11:53:46 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_lagg.c,v 1.48.4.3 2023/12/12 16:45:00 martin Exp $	*/
+/*	$NetBSD: if_lagg.c,v 1.48.4.4 2024/10/03 11:53:46 martin Exp $	*/
 
 /*
  * Copyright (c) 2005, 2006 Reyk Floeter <r...@openbsd.org>
@@ -20,7 +20,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_lagg.c,v 1.48.4.3 2023/12/12 16:45:00 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_lagg.c,v 1.48.4.4 2024/10/03 11:53:46 martin Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -114,7 +114,7 @@ static const struct lagg_proto lagg_prot
 		.pr_startport = lagg_common_startport,
 		.pr_stopport = lagg_common_stopport,
 		.pr_portstat = lagg_fail_portstat,
-		.pr_linkstate = lagg_common_linkstate,
+		.pr_linkstate = lagg_common_linkstate_ifnet_locked,
 		.pr_ioctl = lagg_fail_ioctl,
 	},
 	[LAGG_PROTO_LOADBALANCE] = {
@@ -128,7 +128,7 @@ static const struct lagg_proto lagg_prot
 		.pr_startport = lagg_lb_startport,
 		.pr_stopport = lagg_lb_stopport,
 		.pr_portstat = lagg_lb_portstat,
-		.pr_linkstate = lagg_common_linkstate,
+		.pr_linkstate = lagg_common_linkstate_ifnet_locked,
 	},
 };
 
@@ -1024,7 +1024,7 @@ lagg_tx_common(struct ifnet *ifp, struct
 	} else {
 		m_freem(m);
 		if_statinc(ifp, if_oerrors);
-		error = ENOBUFS;
+		error = EIO;
 	}
 
 	lagg_variant_putref(var, &psref);
@@ -1065,22 +1065,26 @@ lagg_output(struct lagg_softc *sc, struc
 	mflags = m->m_flags;
 
 	error = pfil_run_hooks(ifp->if_pfil, &m, ifp, PFIL_OUT);
-	if (error != 0)
+	if (error != 0) {
+		if (m != NULL) {
+			m_freem(m);
+		}
 		return;
+	}
 	bpf_mtap(ifp, m, BPF_D_OUT);
 
 	error = lagg_port_xmit(lp, m);
 	if (error) {
 		/* mbuf is already freed */
 		if_statinc(ifp, if_oerrors);
+	} else {
+		net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
+		if_statinc_ref(nsr, if_opackets);
+		if_statadd_ref(nsr, if_obytes, len);
+		if (mflags & M_MCAST)
+			if_statinc_ref(nsr, if_omcasts);
+		IF_STAT_PUTREF(ifp);
 	}
-
-	net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
-	if_statinc_ref(nsr, if_opackets);
-	if_statadd_ref(nsr, if_obytes, len);
-	if (mflags & M_MCAST)
-		if_statinc_ref(nsr, if_omcasts);
-	IF_STAT_PUTREF(ifp);
 }
 
 static struct mbuf *
@@ -1136,11 +1140,6 @@ lagg_input_ethernet(struct ifnet *ifp_po
 
 	ifp = &lp->lp_softc->sc_if;
 
-	/*
-	 * Drop promiscuously received packets
-	 * if we are not in promiscuous mode.
-	 */
-
 	if (__predict_false(m->m_len < (int)sizeof(*eh))) {
 		if ((m = m_pullup(m, sizeof(*eh))) == NULL) {
 			if_statinc(ifp, if_ierrors);
@@ -1163,6 +1162,10 @@ lagg_input_ethernet(struct ifnet *ifp_po
 
 		if_statinc(ifp_port, if_imcasts);
 	} else {
+		/*
+		 * Drop promiscuously received packets
+		 * if we are not in promiscuous mode.
+		 */
 		if ((ifp->if_flags & IFF_PROMISC) == 0 &&
 		    (ifp_port->if_flags & IFF_PROMISC) != 0 &&
 		    memcmp(CLLADDR(ifp->if_sadl), eh->ether_dhost,
@@ -1173,8 +1176,13 @@ lagg_input_ethernet(struct ifnet *ifp_po
 	if_statadd(ifp_port, if_ibytes, m->m_pkthdr.len);
 
 	if (pfil_run_hooks(ifp_port->if_pfil, &m,
-	    ifp_port, PFIL_IN) != 0)
+	    ifp_port, PFIL_IN) != 0) {
+		if (m != NULL) {
+			m_freem(m);
+			m = NULL;
+		}
 		goto out;
+	}
 
 	m = lagg_proto_input(lp->lp_softc, lp, m);
 	if (m != NULL) {
@@ -1216,6 +1224,9 @@ lagg_media_status(struct ifnet *ifp, str
 	imr->ifm_active = IFM_ETHER | IFM_AUTO;
 
 	LAGG_LOCK(sc);
+
+	imr->ifm_active |= sc->sc_media_active;
+
 	LAGG_PORTS_FOREACH(sc, lp) {
 		if (lagg_portactive(lp))
 			imr->ifm_status |= IFM_ACTIVE;
@@ -1223,6 +1234,52 @@ lagg_media_status(struct ifnet *ifp, str
 	LAGG_UNLOCK(sc);
 }
 
+static uint64_t
+lagg_search_media_type(uint64_t linkspeed)
+{
+
+	if (linkspeed == IF_Gbps(40))
+		return IFM_40G_T | IFM_FDX;
+
+	if (linkspeed == IF_Gbps(25))
+		return IFM_25G_T | IFM_FDX;
+
+	if (linkspeed == IF_Gbps(10))
+		return IFM_10G_T | IFM_FDX;
+
+	if (linkspeed == IF_Gbps(5))
+		return IFM_5000_T | IFM_FDX;
+
+	if (linkspeed == IF_Mbps(2500))
+		return IFM_2500_T | IFM_FDX;
+
+	if (linkspeed == IF_Gbps(1))
+		return IFM_1000_T | IFM_FDX;
+
+	if (linkspeed == IF_Mbps(100))
+		return IFM_100_TX | IFM_FDX;
+
+	if (linkspeed == IF_Mbps(10))
+		return IFM_10_T | IFM_FDX;
+
+	return 0;
+}
+
+void
+lagg_set_linkspeed(struct lagg_softc *sc, uint64_t linkspeed)
+{
+	struct ifnet *ifp;
+
+	ifp = &sc->sc_if;
+
+	KASSERT(LAGG_LOCKED(sc));
+
+	ifp->if_baudrate = linkspeed;
+
+	sc->sc_media_active =
+	    lagg_search_media_type(linkspeed);
+}
+
 static int
 lagg_port_vlan_cb(struct lagg_port *lp,
     struct lagg_vlantag *lvt, bool set)
@@ -1600,11 +1657,9 @@ lagg_pr_attach(struct lagg_softc *sc, la
 {
 	struct lagg_variant *newvar, *oldvar;
 	struct lagg_proto_softc *psc;
-	bool cleanup_oldvar;
 	int error;
 
 	error = 0;
-	cleanup_oldvar = false;
 	newvar = kmem_alloc(sizeof(*newvar), KM_SLEEP);
 
 	LAGG_LOCK(sc);
@@ -1612,30 +1667,29 @@ lagg_pr_attach(struct lagg_softc *sc, la
 
 	if (oldvar != NULL && oldvar->lv_proto == pr) {
 		error = 0;
-		goto done;
+		goto failed;
 	}
 
 	error = lagg_proto_attach(sc, pr, &psc);
 	if (error != 0)
-		goto done;
+		goto failed;
 
 	newvar->lv_proto = pr;
 	newvar->lv_psc = psc;
-
 	lagg_variant_update(sc, newvar);
-	newvar = NULL;
+	lagg_set_linkspeed(sc, 0);
+	LAGG_UNLOCK(sc);
 
 	if (oldvar != NULL) {
 		lagg_proto_detach(oldvar);
-		cleanup_oldvar = true;
+		kmem_free(oldvar, sizeof(*oldvar));
 	}
-done:
-	LAGG_UNLOCK(sc);
 
-	if (newvar != NULL)
-		kmem_free(newvar, sizeof(*newvar));
-	if (cleanup_oldvar)
-		kmem_free(oldvar, sizeof(*oldvar));
+	return 0;
+
+failed:
+	LAGG_UNLOCK(sc);
+	kmem_free(newvar, sizeof(*newvar));
 
 	return error;
 }
@@ -1646,15 +1700,14 @@ lagg_pr_detach(struct lagg_softc *sc)
 	struct lagg_variant *var;
 
 	LAGG_LOCK(sc);
-
 	var = sc->sc_var;
 	atomic_store_release(&sc->sc_var, NULL);
+	LAGG_UNLOCK(sc);
 	pserialize_perform(sc->sc_psz);
 
 	if (var != NULL)
 		lagg_proto_detach(var);
 
-	LAGG_UNLOCK(sc);
 
 	if (var != NULL)
 		kmem_free(var, sizeof(*var));
@@ -2109,6 +2162,7 @@ lagg_port_setsadl(struct lagg_port *lp, 
 
 		if (ifp_port->if_init != NULL) {
 			error = 0;
+			/* Apply updated ifp_port->if_sadl to the device */
 			if (ISSET(ifp_port->if_flags, IFF_RUNNING))
 				error = if_init(ifp_port);
 
@@ -2210,6 +2264,9 @@ lagg_port_setup(struct lagg_softc *sc,
 	switch (ifp_port->if_type) {
 	case IFT_ETHER:
 	case IFT_L2TP:
+		if (VLAN_ATTACHED((struct ethercom *)ifp_port))
+			return EBUSY;
+
 		if_type = IFT_IEEE8023ADLAG;
 		break;
 	default:
@@ -2655,10 +2712,11 @@ lagg_port_ioctl(struct ifnet *ifp, u_lon
 	int error = 0;
 	u_int ifflags;
 
-	if ((lp = ifp->if_lagg) == NULL ||
-	    (sc = lp->lp_softc) == NULL) {
+	if ((lp = ifp->if_lagg) == NULL)
 		goto fallback;
-	}
+
+	sc = lp->lp_softc;
+	KASSERT(sc != NULL);
 
 	KASSERT(IFNET_LOCKED(lp->lp_ifp));
 
@@ -2730,12 +2788,9 @@ lagg_ifdetach(void *xifp_port)
 	if (lp == NULL) {
 		pserialize_read_exit(s);
 		return;
-	}
-
-	sc = lp->lp_softc;
-	if (sc == NULL) {
-		pserialize_read_exit(s);
-		return;
+	} else {
+		sc = lp->lp_softc;
+		KASSERT(sc != NULL);
 	}
 	pserialize_read_exit(s);
 
@@ -2875,7 +2930,9 @@ lagg_chg_sadl(struct ifnet *ifp, const u
 	error = 0;
 	ifa_lla = NULL;
 
+	/* Renew all AF_LINK address to update sdl_type */
 	while (1) {
+		/* find a Link-Level address that has the previous sdl_type */
 		s = pserialize_read_enter();
 		IFADDR_READER_FOREACH(ifa_cur, ifp) {
 			sdl = satocsdl(ifa_cur->ifa_addr);
@@ -2891,7 +2948,10 @@ lagg_chg_sadl(struct ifnet *ifp, const u
 
 		if (ifa_cur == NULL)
 			break;
-
+		/*
+		 * create a new address that has new sdl_type,
+		 * and copy address from the previous.
+		 */
 		ifa_next = if_dl_create(ifp, &nsdl);
 		if (ifa_next == NULL) {
 			error = ENOMEM;
@@ -2903,6 +2963,7 @@ lagg_chg_sadl(struct ifnet *ifp, const u
 		    CLLADDR(sdl), ifp->if_addrlen);
 		ifa_insert(ifp, ifa_next);
 
+		/* the next Link-Level address is already set */
 		if (ifa_lla == NULL &&
 		    memcmp(CLLADDR(sdl), lla, lla_len) == 0) {
 			ifa_lla = ifa_next;
@@ -2918,6 +2979,7 @@ lagg_chg_sadl(struct ifnet *ifp, const u
 			ifafree(ifa_cur);
 		}
 
+		/* remove the old address */
 		ifaref(ifa_cur);
 		ifa_release(ifa_cur, &psref_cur);
 		ifa_remove(ifp, ifa_cur);
@@ -2927,6 +2989,7 @@ lagg_chg_sadl(struct ifnet *ifp, const u
 		ifa_release(ifa_next, &psref_next);
 	}
 
+	/* acquire or create the next Link-Level address */
 	if (ifa_lla != NULL) {
 		ifa_next = ifa_lla;
 
@@ -2946,13 +3009,19 @@ lagg_chg_sadl(struct ifnet *ifp, const u
 		ifa_insert(ifp, ifa_next);
 	}
 
-	if (ifa_next != ifp->if_dl) {
+	/* Activate the next Link-Level address */
+	if (__predict_true(ifa_next != ifp->if_dl)) {
+		/* save the current address */
 		ifa_cur = ifp->if_dl;
 		if (ifa_cur != NULL)
 			ifa_acquire(ifa_cur, &psref_cur);
 
 		if_activate_sadl(ifp, ifa_next, nsdl);
 
+		/*
+		 * free the saved address after switching,
+		 * if the address is not if_hwdl.
+		 */
 		if (ifa_cur != NULL) {
 			if (ifa_cur != ifp->if_hwdl) {
 				ifaref(ifa_cur);

Index: src/sys/net/lagg/if_lagg_lacp.c
diff -u src/sys/net/lagg/if_lagg_lacp.c:1.25.4.1 src/sys/net/lagg/if_lagg_lacp.c:1.25.4.2
--- src/sys/net/lagg/if_lagg_lacp.c:1.25.4.1	Mon Nov 27 20:05:57 2023
+++ src/sys/net/lagg/if_lagg_lacp.c	Thu Oct  3 11:53:46 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_lagg_lacp.c,v 1.25.4.1 2023/11/27 20:05:57 martin Exp $	*/
+/*	$NetBSD: if_lagg_lacp.c,v 1.25.4.2 2024/10/03 11:53:46 martin Exp $	*/
 
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-NetBSD
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_lagg_lacp.c,v 1.25.4.1 2023/11/27 20:05:57 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_lagg_lacp.c,v 1.25.4.2 2024/10/03 11:53:46 martin Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_lagg.h"
@@ -75,6 +75,7 @@ enum {
 
 enum lacp_selected {
 	LACP_UNSELECTED,
+	LACP_READY,
 	LACP_STANDBY,
 	LACP_SELECTED,
 };
@@ -126,7 +127,7 @@ struct lacp_port {
 	struct lacp_aggregator	*lp_aggregator;
 	struct lacp_aggregator_systemid
 				 lp_aggregator_sidbuf;
-	uint32_t		 lp_media;
+	uint64_t		 lp_linkspeed;
 	int			 lp_pending;
 	LIST_ENTRY(lacp_port)	 lp_entry_la;
 	struct timeval		 lp_last_lacpdu;
@@ -165,6 +166,7 @@ struct lacp_softc {
 	struct workqueue	*lsc_workq;
 	struct lagg_work	 lsc_work_tick;
 	struct lagg_work	 lsc_work_rcvdu;
+	struct lagg_work	 lsc_work_linkspeed;
 	callout_t		 lsc_tick;
 	pcq_t			*lsc_du_q;
 
@@ -247,7 +249,6 @@ static void	lacp_dprintf(const struct la
 static void	lacp_tick(void *);
 static void	lacp_tick_work(struct lagg_work *, void *);
 static void	lacp_linkstate(struct lagg_proto_softc *, struct lagg_port *);
-static uint32_t	lacp_ifmedia2lacpmedia(u_int);
 static void	lacp_port_disable(struct lacp_softc *, struct lacp_port *);
 static void	lacp_port_enable(struct lacp_softc *, struct lacp_port *);
 static void	lacp_peerinfo_actor(struct lacp_softc *, struct lacp_port *,
@@ -285,6 +286,7 @@ static void	lacp_sm_ptx_update_timeout(s
 
 static void	lacp_rcvdu_work(struct lagg_work *, void *);
 static void	lacp_marker_work(struct lagg_work *, void *);
+static void	lacp_linkspeed_work(struct lagg_work *, void *);
 static void	lacp_dump_lacpdutlv(const struct lacpdu_peerinfo *,
 		    const struct lacpdu_peerinfo *,
 		    const struct lacpdu_collectorinfo *);
@@ -378,7 +380,7 @@ lacp_mcastaddr(struct ifreq *ifr, const 
 	ifr->ifr_addr.sa_len = sizeof(ifr->ifr_addr);
 	ifr->ifr_addr.sa_family = AF_UNSPEC;
 
-	KASSERT(sizeof(ifr->ifr_addr) >= sizeof(addr));
+	CTASSERT(sizeof(ifr->ifr_addr) >= sizeof(addr));
 	memcpy(&ifr->ifr_addr.sa_data, addr, sizeof(addr));
 }
 
@@ -413,9 +415,9 @@ lacp_port_priority_max(struct lacp_port 
 	if (pri_b < pri_a)
 		return b;
 
-	if (a->lp_media > b->lp_media)
+	if (a->lp_linkspeed > b->lp_linkspeed)
 		return a;
-	if (b->lp_media > a->lp_media)
+	if (b->lp_linkspeed > a->lp_linkspeed)
 		return b;
 
 	return a;
@@ -487,6 +489,8 @@ lacp_attach(struct lagg_softc *sc, struc
 
 	lagg_work_set(&lsc->lsc_work_tick, lacp_tick_work, lsc);
 	lagg_work_set(&lsc->lsc_work_rcvdu, lacp_rcvdu_work, lsc);
+	lagg_work_set(&lsc->lsc_work_linkspeed,
+	    lacp_linkspeed_work, lsc);
 
 	snprintf(xnamebuf, sizeof(xnamebuf), "%s.lacp",
 	    sc->sc_if.if_xname);
@@ -532,13 +536,15 @@ lacp_detach(struct lagg_proto_softc *xls
 	struct lacp_softc *lsc = (struct lacp_softc *)xlsc;
 	struct lagg_softc *sc __diagused = lsc->lsc_softc;
 
-	KASSERT(LAGG_LOCKED(lsc->lsc_softc));
 	KASSERT(TAILQ_EMPTY(&lsc->lsc_aggregators));
 	KASSERT(SIMPLEQ_EMPTY(&sc->sc_ports));
 
+	LAGG_LOCK(lsc->lsc_softc);
 	lacp_down(xlsc);
+	LAGG_UNLOCK(lsc->lsc_softc);
 
 	lagg_workq_wait(lsc->lsc_workq, &lsc->lsc_work_rcvdu);
+	lagg_workq_wait(lsc->lsc_workq, &lsc->lsc_work_tick);
 	evcnt_detach(&lsc->lsc_mgethdr_failed);
 	evcnt_detach(&lsc->lsc_mpullup_failed);
 	evcnt_detach(&lsc->lsc_badlacpdu);
@@ -635,7 +641,7 @@ lacp_transmit(struct lagg_proto_softc *x
 	if (__predict_false(lsc->lsc_suppress_distributing)) {
 		LACP_DPRINTF((lsc, NULL, "waiting transit\n"));
 		m_freem(m);
-		return ENOBUFS;
+		return EBUSY;
 	}
 
 	lp = lacp_select_tx_port(lsc, m, &psref);
@@ -669,6 +675,10 @@ lacp_allocport(struct lagg_proto_softc *
 	KASSERT(LAGG_LOCKED(sc));
 	KASSERT(IFNET_LOCKED(lp->lp_ifp));
 
+	lacpp = kmem_zalloc(sizeof(*lacpp), KM_NOSLEEP);
+	if (lacpp == NULL)
+		return ENOMEM;
+
 	lacp_mcastaddr(&ifr, lp->lp_ifp->if_xname);
 	error = lp->lp_ioctl(lp->lp_ifp, SIOCADDMULTI, (void *)&ifr);
 
@@ -682,13 +692,10 @@ lacp_allocport(struct lagg_proto_softc *
 	default:
 		LAGG_LOG(sc, LOG_ERR, "SIOCADDMULTI failed on %s\n",
 		    lp->lp_ifp->if_xname);
+		kmem_free(lacpp, sizeof(*lacpp));
 		return error;
 	}
 
-	lacpp = kmem_zalloc(sizeof(*lacpp), KM_NOSLEEP);
-	if (lacpp == NULL)
-		return ENOMEM;
-
 	lacpp->lp_added_multi = added_multi;
 	lagg_work_set(&lacpp->lp_work_smtx, lacp_sm_tx_work, lsc);
 	lagg_work_set(&lacpp->lp_work_marker, lacp_marker_work, lsc);
@@ -852,7 +859,7 @@ lacp_linkstate_ifnet_locked(struct lagg_
 	struct ifmediareq ifmr;
 	struct ifnet *ifp_port;
 	uint8_t old_state;
-	uint32_t media, old_media;
+	uint64_t old_linkspeed, new_linkspeed;
 	int error;
 
 	KASSERT(IFNET_LOCKED(lp->lp_ifp));
@@ -861,14 +868,24 @@ lacp_linkstate_ifnet_locked(struct lagg_
 
 	ifp_port = lp->lp_ifp;
 	lacpp = lp->lp_proto_ctx;
-	media = LACP_MEDIA_DEFAULT;
 
 	memset(&ifmr, 0, sizeof(ifmr));
 	ifmr.ifm_count = 0;
 	error = if_ioctl(ifp_port, SIOCGIFMEDIA, (void *)&ifmr);
 	if (error == 0) {
-		media = lacp_ifmedia2lacpmedia(ifmr.ifm_active);
-	} else if (error != ENOTTY){
+		new_linkspeed = ifmedia_baudrate(ifmr.ifm_active);
+#ifdef LACP_NOFDX
+		/*
+		 * some driver that has no media, e.g. vioif(4),
+		 * returns (IFM_ETHER | IFM_AUTO)
+		 */
+		if ((ifmr.ifm_active & ~(IFM_ETHER | IFM_AUTO)) == 0)
+			ifmr.ifm_active |= IFM_FDX;
+#endif
+	} else if (error == ENOTTY) {
+		ifmr.ifm_active = IFM_FDX | IF_Gbps(0);
+		new_linkspeed = 0;
+	} else {
 		LACP_DPRINTF((lsc, lacpp,
 		    "SIOCGIFMEDIA failed (%d)\n", error));
 		return;
@@ -876,34 +893,31 @@ lacp_linkstate_ifnet_locked(struct lagg_
 
 	LACP_LOCK(lsc);
 	if (lsc->lsc_running) {
-		old_media = lacpp->lp_media;
+		old_linkspeed = lacpp->lp_linkspeed;
 		old_state = lacpp->lp_actor.lpi_state;
 
-		if (lacpp->lp_media != media) {
+		if (new_linkspeed != old_linkspeed) {
 			LACP_DPRINTF((lsc, lacpp,
-			    "media changed 0x%"PRIx32"->0x%"PRIx32", "
-			    "ether = %d, fdx = %d, link = %d, running = %d\n",
-			    lacpp->lp_media, media,
-			    ISSET(media, LACP_MEDIA_ETHER) != 0,
-			    ISSET(media, LACP_MEDIA_FDX) != 0,
-			    ifp_port->if_link_state != LINK_STATE_DOWN,
-			    ISSET(ifp_port->if_flags, IFF_RUNNING) != 0));
-			lacpp->lp_media = media;
+			    "linkspeed changed %"PRIu64" -> %"PRIu64"\n",
+			    old_linkspeed, new_linkspeed));
+			lacpp->lp_linkspeed = new_linkspeed;
 		}
 
-		if (ISSET(media, LACP_MEDIA_ETHER) &&
-#ifndef LACP_NOFDX
-		    ISSET(media, LACP_MEDIA_FDX) &&
-#endif
-		    ifp_port->if_link_state != LINK_STATE_DOWN &&
-		    ISSET(ifp_port->if_flags, IFF_RUNNING)) {
+		if (ISSET(ifmr.ifm_active, IFM_FDX) &&
+		    ISSET(ifp_port->if_flags, IFF_RUNNING) &&
+		    ifp_port->if_link_state != LINK_STATE_DOWN) {
 			lacp_port_enable(lsc, lacpp);
 		} else {
+			LACP_DPRINTF((lsc, lacpp,
+			    "FDX=%d, RUNNING=%d, link=%d\n",
+			    ISSET(ifmr.ifm_active, IFM_FDX) != 0,
+			    ISSET(ifp_port->if_flags, IFF_RUNNING) != 0,
+			    ifp_port->if_link_state != LINK_STATE_DOWN));
 			lacp_port_disable(lsc, lacpp);
 		}
 
 		if (old_state != lacpp->lp_actor.lpi_state ||
-		    old_media != media) {
+		    old_linkspeed != new_linkspeed) {
 			LACP_DPRINTF((lsc, lacpp,
 			    "state changed to UNSELECTED\n"));
 			lacpp->lp_selected = LACP_UNSELECTED;
@@ -912,7 +926,6 @@ lacp_linkstate_ifnet_locked(struct lagg_
 		LACP_DPRINTF((lsc, lacpp,
 		    "LACP is inactive, skip linkstate\n"));
 	}
-
 	LACP_UNLOCK(lsc);
 }
 
@@ -1278,7 +1291,7 @@ lacp_rcvdu_work(struct lagg_work *lw __u
 	struct lagg_port *lp;
 	struct mbuf *m;
 	uint8_t subtype;
-	int bound, s;
+	int bound, s0, s1;
 
 	bound = curlwp_bind();
 
@@ -1287,23 +1300,26 @@ lacp_rcvdu_work(struct lagg_work *lw __u
 		if (m == NULL)
 			break;
 
-		ifp = m_get_rcvif(m, &s);
+		ifp = m_get_rcvif(m, &s0);
 		if (ifp == NULL) {
 			m_freem(m);
 			lsc->lsc_norcvif.ev_count++;
 			continue;
 		}
 
+		s1 = pserialize_read_enter();
 		lp = atomic_load_consume(&ifp->if_lagg);
 		if (lp == NULL) {
-			m_put_rcvif(ifp, &s);
+			pserialize_read_exit(s1);
+			m_put_rcvif(ifp, &s0);
 			m_freem(m);
 			lsc->lsc_norcvif.ev_count++;
 			continue;
 		}
 
 		lagg_port_getref(lp, &psref_lp);
-		m_put_rcvif(ifp, &s);
+		pserialize_read_exit(s1);
+		m_put_rcvif(ifp, &s0);
 
 		m_copydata(m, sizeof(struct ether_header),
 		    sizeof(subtype), &subtype);
@@ -1344,10 +1360,6 @@ lacp_port_need_to_tell(struct lacp_port 
 	if (!ISSET(lacpp->lp_flags, LACP_PORT_NTT))
 		return false;
 
-	if (ppsratecheck(&lacpp->lp_last_lacpdu, &lacpp->lp_lacpdu_sent,
-	    (LACP_SENDDU_PPS / LACP_FAST_PERIODIC_TIME)) == 0)
-		return false;
-
 	return true;
 }
 
@@ -1429,10 +1441,13 @@ lacp_sm_tx_work(struct lagg_work *lw, vo
 	lsc = xlsc;
 	lacpp = container_of(lw, struct lacp_port, lp_work_smtx);
 
-	if (lsc->lsc_stop_lacpdu)
+	LACP_LOCK(lsc);
+
+	if (lsc->lsc_stop_lacpdu) {
+		LACP_UNLOCK(lsc);
 		return;
+	}
 
-	LACP_LOCK(lsc);
 	m = lacp_lacpdu_mbuf(lsc, lacpp);
 	if (m == NULL) {
 		LACP_UNLOCK(lsc);
@@ -1473,10 +1488,16 @@ lacp_tick(void *xlsc)
 
 	lsc = xlsc;
 
-	lagg_workq_add(lsc->lsc_workq, &lsc->lsc_work_tick);
-
 	LACP_LOCK(lsc);
+
+	if (!lsc->lsc_running) {
+		LACP_UNLOCK(lsc);
+		return;
+	}
+
+	lagg_workq_add(lsc->lsc_workq, &lsc->lsc_work_tick);
 	callout_schedule(&lsc->lsc_tick, hz);
+
 	LACP_UNLOCK(lsc);
 }
 
@@ -1485,6 +1506,8 @@ lacp_run_timers(struct lacp_softc *lsc, 
 {
 	size_t i;
 
+	KASSERT(LACP_LOCKED(lsc));
+
 	for (i = 0; i < LACP_NTIMER; i++) {
 		KASSERT(lacpp->lp_timer[i] >= 0);
 
@@ -1528,6 +1551,11 @@ lacp_tick_work(struct lagg_work *lw __un
 	sc = lsc->lsc_softc;
 
 	LACP_LOCK(lsc);
+	if (!lsc->lsc_running) {
+		LACP_UNLOCK(lsc);
+		return;
+	}
+
 	lacp_run_prototimers(lsc);
 	LACP_UNLOCK(lsc);
 
@@ -1803,6 +1831,8 @@ static void
 lacp_port_disable(struct lacp_softc *lsc, struct lacp_port *lacpp)
 {
 
+	KASSERT(LACP_LOCKED(lsc));
+
 	if (ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_AGGREGATION))
 		LACP_DPRINTF((lsc, lacpp, "enable -> disable\n"));
 
@@ -1818,6 +1848,8 @@ lacp_port_enable(struct lacp_softc *lsc 
     struct lacp_port *lacpp)
 {
 
+	KASSERT(LACP_LOCKED(lsc));
+
 	if (!ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_AGGREGATION))
 		LACP_DPRINTF((lsc, lacpp, "disable -> enable\n"));
 
@@ -1829,6 +1861,8 @@ static void
 lacp_sm_rx_timer(struct lacp_softc *lsc, struct lacp_port *lacpp)
 {
 
+	KASSERT(LACP_LOCKED(lsc));
+
 	if (!ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_EXPIRED)) {
 		/* CURRENT -> EXPIRED */
 		LACP_DPRINTF((lsc, lacpp, "CURRENT -> EXPIRED\n"));
@@ -1844,6 +1878,7 @@ static void
 lacp_sm_ptx_timer(struct lacp_softc *lsc __unused, struct lacp_port *lacpp)
 {
 
+	KASSERT(LACP_LOCKED(lsc));
 	lacp_sm_assert_ntt(lacpp);
 }
 
@@ -1891,6 +1926,7 @@ lacp_sm_mux_timer(struct lacp_softc *lsc
 {
 	char buf[LACP_SYSTEMIDSTR_LEN] __LACPDEBUGUSED;
 
+	KASSERT(LACP_LOCKED(lsc));
 	KASSERT(lacpp->lp_pending > 0);
 
 	LACP_AGGREGATOR_STR(lacpp->lp_aggregator, buf, sizeof(buf));
@@ -1948,8 +1984,14 @@ lacp_sm_rx_update_ntt(struct lacp_softc 
 	if (lacp_compare_peerinfo(&actor, my_pi) != 0 ||
 	    !LACP_STATE_EQ(lacpp->lp_actor.lpi_state, my_pi->lpi_state,
 	    LACP_STATE_ACTIVITY | LACP_STATE_SYNC | LACP_STATE_AGGREGATION)) {
-		LACP_DPRINTF((lsc, lacpp, "assert ntt\n"));
-		lacp_sm_assert_ntt(lacpp);
+		if (ppsratecheck(&lacpp->lp_last_lacpdu, &lacpp->lp_lacpdu_sent,
+		    (LACP_SENDDU_PPS / LACP_FAST_PERIODIC_TIME)) == 0) {
+			LACP_DPRINTF((lsc, lacpp,
+			    "skip ntt due to rate limit"));
+		} else {
+			LACP_DPRINTF((lsc, lacpp, "assert ntt\n"));
+			lacp_sm_assert_ntt(lacpp);
+		}
 	}
 }
 
@@ -2051,6 +2093,8 @@ lacp_update_portmap(struct lacp_softc *l
 		if_link_state_change(&sc->sc_if, link);
 	}
 
+	lagg_workq_add(lsc->lsc_workq,  &lsc->lsc_work_linkspeed);
+
 	/* cleanup */
 	pm_act->pm_count = 0;
 	memset(pm_act->pm_ports, 0, sizeof(pm_act->pm_ports));
@@ -2066,8 +2110,11 @@ lacp_disable_distributing(struct lacp_so
 
 	KASSERT(LACP_LOCKED(lsc));
 
-	LACP_DPRINTF((lsc, lacpp, "distributing disabled\n"));
-	CLR(lacpp->lp_actor.lpi_state, LACP_STATE_DISTRIBUTING);
+	if (ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_DISTRIBUTING)) {
+		LAGG_LOG(lsc->lsc_softc, LOG_INFO,
+		    "disable distributing on %s\n", LACP_PORT_XNAME(lacpp));
+		CLR(lacpp->lp_actor.lpi_state, LACP_STATE_DISTRIBUTING);
+	}
 
 	s = pserialize_read_enter();
 	act = LACP_PORTMAP_ACTIVE(lsc);
@@ -2094,7 +2141,8 @@ lacp_enable_distributing(struct lacp_sof
 
 	KASSERT(lacp_isactive(lsc, lacpp));
 
-	LACP_DPRINTF((lsc, lacpp, "distributing enabled\n"));
+	LAGG_LOG(lsc->lsc_softc, LOG_INFO,
+	    "enable distributing on %s\n", LACP_PORT_XNAME(lacpp));
 	SET(lacpp->lp_actor.lpi_state, LACP_STATE_DISTRIBUTING);
 	lacp_suppress_distributing(lsc);
 	lacp_update_portmap(lsc);
@@ -2301,6 +2349,10 @@ lacp_sm_mux(struct lacp_softc *lsc, stru
 			break;
 		case LACP_MUX_ATTACHED:
 			if (selected != LACP_SELECTED) {
+				if (selected == LACP_STANDBY)
+					LAGG_LOG(lsc->lsc_softc, LOG_INFO,
+					    "detaching %s\n",
+					    LACP_PORT_XNAME(lacpp));
 				next_state = LACP_MUX_DETACHED;
 			} else if (lacp_isactive(lsc, lacpp) && p_sync) {
 				next_state = LACP_MUX_COLLECTING;
@@ -2354,8 +2406,10 @@ static void
 lacp_selected_update(struct lacp_softc *lsc, struct lacp_aggregator *la)
 {
 	struct lacp_port *lacpp;
+	enum lacp_selected next_selected;
+	const char *msg;
 	size_t nselected;
-	uint32_t media;
+	uint64_t linkspeed;
 
 	KASSERT(LACP_LOCKED(lsc));
 
@@ -2363,27 +2417,34 @@ lacp_selected_update(struct lacp_softc *
 	if (lacpp == NULL)
 		return;
 
-	media = lacpp->lp_media;
+	linkspeed = lacpp->lp_linkspeed;
 	nselected = 0;
 	LIST_FOREACH(lacpp, &la->la_ports, lp_entry_la) {
-		if (nselected >= lsc->lsc_max_ports ||
-		    (!lsc->lsc_multi_linkspeed && media != lacpp->lp_media)) {
-			if (lacpp->lp_selected == LACP_SELECTED)
-				lacpp->lp_selected = LACP_STANDBY;
+		if (lacpp->lp_selected == LACP_UNSELECTED)
 			continue;
+
+		next_selected = LACP_SELECTED;
+		msg = " is selected";
+
+		if (nselected >= lsc->lsc_max_ports) {
+			next_selected = LACP_STANDBY;
+			msg = " is standby because of too many active ports";
 		}
 
-		switch (lacpp->lp_selected) {
-		case LACP_STANDBY:
-			lacpp->lp_selected = LACP_SELECTED;
-			/* fall through */
-		case LACP_SELECTED:
-			nselected++;
-			break;
-		default:
-			/* do nothing */
-			break;
+		if (!lsc->lsc_multi_linkspeed &&
+		    linkspeed != lacpp->lp_linkspeed) {
+			next_selected = LACP_STANDBY;
+			msg = " is standby because of link speed mismatch";
+		}
+
+		if (lacpp->lp_selected != next_selected) {
+			lacpp->lp_selected = next_selected;
+			LAGG_LOG(lsc->lsc_softc, LOG_INFO,
+			    "%s%s\n", LACP_PORT_XNAME(lacpp), msg);
 		}
+
+		if (lacpp->lp_selected == LACP_SELECTED)
+			nselected++;
 	}
 }
 
@@ -2433,7 +2494,7 @@ lacp_select(struct lacp_softc *lsc, stru
 	LACP_DPRINTF((lsc, lacpp, "aggregator lagid=%s\n", buf));
 
 	lacpp->lp_aggregator = la;
-	lacpp->lp_selected = LACP_STANDBY;
+	lacpp->lp_selected = LACP_READY;
 
 	LIST_FOREACH(lacpp0, &la->la_ports, lp_entry_la) {
 		if (lacp_port_priority_max(lacpp0, lacpp) == lacpp) {
@@ -2501,19 +2562,23 @@ lacp_suppress_distributing(struct lacp_s
 {
 	struct lacp_aggregator *la;
 	struct lacp_port *lacpp;
+	bool marker_scheduled;
 
 	KASSERT(LACP_LOCKED(lsc));
 
 	la = lsc->lsc_aggregator;
+	marker_scheduled = false;
 
 	LIST_FOREACH(lacpp, &la->la_ports, lp_entry_la) {
 		if (ISSET(lacpp->lp_actor.lpi_state,
 		    LACP_STATE_DISTRIBUTING)) {
 			lagg_workq_add(lsc->lsc_workq,
 			    &lacpp->lp_work_marker);
+			marker_scheduled = true;
 		}
 	}
 
+	lsc->lsc_suppress_distributing = marker_scheduled;
 	LACP_PTIMER_ARM(lsc, LACP_PTIMER_DISTRIBUTING,
 	    LACP_TRANSIT_DELAY);
 }
@@ -2600,7 +2665,6 @@ lacp_marker_work(struct lagg_work *lw, v
 		return;
 	}
 	SET(lacpp->lp_flags, LACP_PORT_MARK);
-	lsc->lsc_suppress_distributing = true;
 	lp = lacpp->lp_laggport;
 	bound = curlwp_bind();
 	lagg_port_getref(lp, &psref);
@@ -2677,61 +2741,11 @@ lacp_dump_markertlv(const struct markerd
 	}
 }
 
-static uint32_t
-lacp_ifmedia2lacpmedia(u_int ifmedia)
-{
-	uint32_t rv;
-
-	switch (IFM_SUBTYPE(ifmedia)) {
-	case IFM_10_T:
-	case IFM_10_2:
-	case IFM_10_5:
-	case IFM_10_STP:
-	case IFM_10_FL:
-		rv = LACP_LINKSPEED_10;
-		break;
-	case IFM_100_TX:
-	case IFM_100_FX:
-	case IFM_100_T4:
-	case IFM_100_VG:
-	case IFM_100_T2:
-		rv = LACP_LINKSPEED_100;
-		break;
-	case IFM_1000_SX:
-	case IFM_1000_LX:
-	case IFM_1000_CX:
-	case IFM_1000_T:
-	case IFM_1000_BX10:
-	case IFM_1000_KX:
-		rv = LACP_LINKSPEED_1000;
-		break;
-	case IFM_2500_SX:
-	case IFM_2500_KX:
-		rv = LACP_LINKSPEED_2500;
-		break;
-	case IFM_5000_T:
-		rv = LACP_LINKSPEED_5000;
-		break;
-	case IFM_10G_LR:
-	case IFM_10G_SR:
-	case IFM_10G_CX4:
-	case IFM_10G_TWINAX:
-	case IFM_10G_TWINAX_LONG:
-	case IFM_10G_LRM:
-	case IFM_10G_T:
-		rv = LACP_LINKSPEED_10G;
-		break;
-	default:
-		rv = LACP_LINKSPEED_UNKNOWN;
-	}
-
-	if (IFM_TYPE(ifmedia) == IFM_ETHER)
-		SET(rv, LACP_MEDIA_ETHER);
-	if ((ifmedia & IFM_FDX) != 0)
-		SET(rv, LACP_MEDIA_FDX);
-
-	return rv;
-}
+/*
+ * lacp_linkstate:
+ *   callback on link state changed.
+ *   enable, disable or reset LACP processing on the physical port.
+ */
 
 static void
 lacp_linkstate(struct lagg_proto_softc *xlsc, struct lagg_port *lp)
@@ -2743,3 +2757,31 @@ lacp_linkstate(struct lagg_proto_softc *
 	lacp_linkstate_ifnet_locked(xlsc, lp);
 	IFNET_UNLOCK(lp->lp_ifp);
 }
+
+static void
+lacp_linkspeed_work(struct lagg_work *lw __unused, void *xlsc)
+{
+	struct lacp_softc *lsc = (struct lacp_softc *)xlsc;
+	struct lagg_softc *sc = lsc->lsc_softc;
+	struct lacp_portmap *pm;
+	struct lagg_port *lp;
+	struct lacp_port *lacpp;
+	uint64_t linkspeed;
+	size_t act, i;
+
+	linkspeed = 0;
+
+	LACP_LOCK(lsc);
+	act = LACP_PORTMAP_ACTIVE(lsc);
+	pm = &lsc->lsc_portmaps[act];
+	for (i = 0; i < pm->pm_count; i++) {
+		lp = pm->pm_ports[i];
+		lacpp = lp->lp_proto_ctx;
+		linkspeed = MAX(linkspeed, lacpp->lp_linkspeed);
+	}
+	LACP_UNLOCK(lsc);
+
+	LAGG_LOCK(sc);
+	lagg_set_linkspeed(sc, linkspeed);
+	LAGG_UNLOCK(sc);
+}

Index: src/sys/net/lagg/if_lagg_lacp.h
diff -u src/sys/net/lagg/if_lagg_lacp.h:1.4 src/sys/net/lagg/if_lagg_lacp.h:1.4.4.1
--- src/sys/net/lagg/if_lagg_lacp.h:1.4	Thu Mar 31 02:04:50 2022
+++ src/sys/net/lagg/if_lagg_lacp.h	Thu Oct  3 11:53:46 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_lagg_lacp.h,v 1.4 2022/03/31 02:04:50 yamaguchi Exp $	*/
+/*	$NetBSD: if_lagg_lacp.h,v 1.4.4.1 2024/10/03 11:53:46 martin Exp $	*/
 
 /*
  * Copyright (c) 2021 Internet Initiative Japan Inc.
@@ -129,35 +129,4 @@ struct markerdu {
 	struct tlvhdr		 mdu_tlv_term;
 	uint8_t			 mdu_resv[90];
 } __packed;
-
-/*
- * lacp media:
- *   1byte
- * +-------+-------+-------+-------+
- * | media |                 speed |
- * +-------+-------+-------+-------+
- */
-
-enum lacp_linkspeed {
-	LACP_LINKSPEED_UNKNOWN = 0,
-	LACP_LINKSPEED_10,
-	LACP_LINKSPEED_100,
-	LACP_LINKSPEED_1000,
-	LACP_LINKSPEED_2500,
-	LACP_LINKSPEED_5000,
-	LACP_LINKSPEED_10G,
-	LACP_LINKSPEED_25G,
-	LACP_LINKSPEED_40G,
-	LACP_LINKSPEED_50G,
-	LACP_LINKSPEED_56G,
-	LACP_LINKSPEED_100G,
-	LACP_LINKSPEED_200G,
-};
-
-#define LACP_MEDIA_OFFSET	24
-#define LACP_MEDIA_MASK		0xff000000U
-#define LACP_MEDIA_ETHER	(__BIT(0) << LACP_MEDIA_OFFSET)
-#define LACP_MEDIA_FDX		(__BIT(1) << LACP_MEDIA_OFFSET)
-#define LACP_MEDIA_DEFAULT	(LACP_LINKSPEED_UNKNOWN |	\
-				 LACP_MEDIA_ETHER | LACP_MEDIA_FDX)
 #endif

Index: src/sys/net/lagg/if_laggproto.c
diff -u src/sys/net/lagg/if_laggproto.c:1.6 src/sys/net/lagg/if_laggproto.c:1.6.4.1
--- src/sys/net/lagg/if_laggproto.c:1.6	Thu Mar 31 07:59:05 2022
+++ src/sys/net/lagg/if_laggproto.c	Thu Oct  3 11:53:46 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_laggproto.c,v 1.6 2022/03/31 07:59:05 yamaguchi Exp $	*/
+/*	$NetBSD: if_laggproto.c,v 1.6.4.1 2024/10/03 11:53:46 martin Exp $	*/
 
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-NetBSD
@@ -29,7 +29,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_laggproto.c,v 1.6 2022/03/31 07:59:05 yamaguchi Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_laggproto.c,v 1.6.4.1 2024/10/03 11:53:46 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -57,15 +57,16 @@ struct lagg_proto_softc {
 	size_t			 psc_ctxsiz;
 	void			*psc_ctx;
 	size_t			 psc_nactports;
+	struct workqueue	*psc_workq;
+	struct lagg_work	 psc_work_linkspeed;
 };
 
 /*
  * Locking notes:
  * - Items of struct lagg_proto_softc is protected by
  *   psc_lock (an adaptive mutex)
- * - psc_ports is protected by pserialize (psc_psz)
- *   - Updates of psc_ports is serialized by sc_lock in
- *     struct lagg_softc
+ * - psc_ports is protected by pselialize (psc_psz) and
+ *   it updates exclusively by LAGG_PROTO_LOCK.
  * - Other locking notes are described in if_laggproto.h
  */
 
@@ -90,7 +91,9 @@ struct lagg_lb {
 struct lagg_proto_port {
 	struct pslist_entry	 lpp_entry;
 	struct lagg_port	*lpp_laggport;
+	uint64_t		 lpp_linkspeed;
 	bool			 lpp_active;
+	bool			 lpp_running;
 };
 
 #define LAGG_PROTO_LOCK(_psc)	mutex_enter(&(_psc)->psc_lock)
@@ -107,6 +110,11 @@ static void	lagg_proto_remove_port(struc
 static struct lagg_port *
 		lagg_link_active(struct lagg_proto_softc *psc,
 		    struct lagg_proto_port *, struct psref *);
+static void	lagg_fail_linkspeed_work(struct lagg_work *, void *);
+static void	lagg_lb_linkspeed_work(struct lagg_work*,
+		    void *);
+static void	lagg_common_linkstate(struct lagg_proto_softc *,
+		    struct lagg_port *);
 
 static inline struct lagg_portmap *
 lagg_portmap_active(struct lagg_portmaps *maps)
@@ -124,7 +132,6 @@ lagg_portmap_next(struct lagg_portmaps *
 	size_t i;
 
 	i = atomic_load_consume(&maps->maps_activepmap);
-	i &= 0x1;
 	i ^= 0x1;
 
 	return &maps->maps_pmap[i];
@@ -146,6 +153,7 @@ static struct lagg_proto_softc *
 lagg_proto_alloc(lagg_proto pr, struct lagg_softc *sc)
 {
 	struct lagg_proto_softc *psc;
+	char xnamebuf[MAXCOMLEN];
 	size_t ctxsiz;
 
 	switch (pr) {
@@ -163,9 +171,20 @@ lagg_proto_alloc(lagg_proto pr, struct l
 	if (psc == NULL)
 		return NULL;
 
+	snprintf(xnamebuf, sizeof(xnamebuf), "%s.proto",
+	    sc->sc_if.if_xname);
+	psc->psc_workq = lagg_workq_create(xnamebuf,
+		    PRI_SOFTNET, IPL_SOFTNET, WQ_MPSAFE);
+	if (psc->psc_workq == NULL) {
+		LAGG_LOG(sc, LOG_ERR, "workqueue create failed\n");
+		kmem_free(psc, sizeof(*psc));
+		return NULL;
+	}
+
 	if (ctxsiz > 0) {
 		psc->psc_ctx = kmem_zalloc(ctxsiz, KM_NOSLEEP);
 		if (psc->psc_ctx == NULL) {
+			lagg_workq_destroy(psc->psc_workq);
 			kmem_free(psc, sizeof(*psc));
 			return NULL;
 		}
@@ -185,8 +204,11 @@ static void
 lagg_proto_free(struct lagg_proto_softc *psc)
 {
 
+	lagg_workq_wait(psc->psc_workq, &psc->psc_work_linkspeed);
 	pserialize_destroy(psc->psc_psz);
 	mutex_destroy(&psc->psc_lock);
+	lagg_workq_destroy(psc->psc_workq);
+	PSLIST_DESTROY(&psc->psc_ports);
 
 	if (psc->psc_ctxsiz > 0)
 		kmem_free(psc->psc_ctx, psc->psc_ctxsiz);
@@ -251,6 +273,7 @@ lagg_common_freeport(struct lagg_proto_s
 	struct lagg_proto_port *pport;
 
 	pport = lp->lp_proto_ctx;
+	KASSERT(!pport->lpp_running);
 	lp->lp_proto_ctx = NULL;
 
 	kmem_free(pport, sizeof(*pport));
@@ -299,8 +322,12 @@ lagg_proto_remove_port(struct lagg_proto
 
 	LAGG_PROTO_LOCK(psc);
 	PSLIST_WRITER_REMOVE(pport, lpp_entry);
-	pserialize_perform(psc->psc_psz);
 	LAGG_PROTO_UNLOCK(psc);
+	pserialize_perform(psc->psc_psz);
+
+	/* re-initialize for reuse */
+	PSLIST_ENTRY_DESTROY(pport, lpp_entry);
+	PSLIST_ENTRY_INIT(pport, lpp_entry);
 }
 
 void
@@ -311,6 +338,10 @@ lagg_common_startport(struct lagg_proto_
 	pport = lp->lp_proto_ctx;
 	lagg_proto_insert_port(psc, pport);
 
+	LAGG_PROTO_LOCK(psc);
+	pport->lpp_running = true;
+	LAGG_PROTO_UNLOCK(psc);
+
 	lagg_common_linkstate(psc, lp);
 }
 
@@ -321,6 +352,11 @@ lagg_common_stopport(struct lagg_proto_s
 	struct ifnet *ifp;
 
 	pport = lp->lp_proto_ctx;
+
+	LAGG_PROTO_LOCK(psc);
+	pport->lpp_running = false;
+	LAGG_PROTO_UNLOCK(psc);
+
 	lagg_proto_remove_port(psc, pport);
 
 	if (pport->lpp_active) {
@@ -334,22 +370,46 @@ lagg_common_stopport(struct lagg_proto_s
 
 		pport->lpp_active = false;
 	}
+
+	lagg_workq_add(psc->psc_workq, &psc->psc_work_linkspeed);
+}
+static void
+lagg_common_linkstate(struct lagg_proto_softc *psc, struct lagg_port *lp)
+{
+
+	IFNET_ASSERT_UNLOCKED(lp->lp_ifp);
+
+	IFNET_LOCK(lp->lp_ifp);
+	lagg_common_linkstate_ifnet_locked(psc, lp);
+	IFNET_UNLOCK(lp->lp_ifp);
 }
 
 void
-lagg_common_linkstate(struct lagg_proto_softc *psc, struct lagg_port *lp)
+lagg_common_linkstate_ifnet_locked(struct lagg_proto_softc *psc, struct lagg_port *lp)
 {
 	struct lagg_proto_port *pport;
-	struct ifnet *ifp;
+	struct ifnet *ifp, *ifp_port;
+	struct ifmediareq ifmr;
+	uint64_t linkspeed;
 	bool is_active;
+	int error;
 
 	pport = lp->lp_proto_ctx;
 	is_active = lagg_portactive(lp);
+	ifp_port = lp->lp_ifp;
+
+	KASSERT(IFNET_LOCKED(ifp_port));
 
-	if (pport->lpp_active == is_active)
+	LAGG_PROTO_LOCK(psc);
+	if (!pport->lpp_running ||
+	    pport->lpp_active == is_active) {
+		LAGG_PROTO_UNLOCK(psc);
 		return;
+	}
 
 	ifp = &psc->psc_softc->sc_if;
+	pport->lpp_active = is_active;
+
 	if (is_active) {
 		psc->psc_nactports++;
 		if (psc->psc_nactports == 1)
@@ -361,8 +421,20 @@ lagg_common_linkstate(struct lagg_proto_
 		if (psc->psc_nactports == 0)
 			if_link_state_change(ifp, LINK_STATE_DOWN);
 	}
+	LAGG_PROTO_UNLOCK(psc);
+
+	memset(&ifmr, 0, sizeof(ifmr));
+	error = if_ioctl(ifp_port, SIOCGIFMEDIA, (void *)&ifmr);
+	if (error == 0) {
+		linkspeed = ifmedia_baudrate(ifmr.ifm_active);
+	} else {
+		linkspeed = 0;
+	}
 
-	atomic_store_relaxed(&pport->lpp_active, is_active);
+	LAGG_PROTO_LOCK(psc);
+	pport->lpp_linkspeed = linkspeed;
+	LAGG_PROTO_UNLOCK(psc);
+	lagg_workq_add(psc->psc_workq, &psc->psc_work_linkspeed);
 }
 
 void
@@ -392,6 +464,8 @@ lagg_fail_attach(struct lagg_softc *sc, 
 
 	fovr = psc->psc_ctx;
 	fovr->fo_rx_all = true;
+	lagg_work_set(&psc->psc_work_linkspeed,
+	    lagg_fail_linkspeed_work, psc);
 
 	*xpsc = psc;
 	return 0;
@@ -510,6 +584,33 @@ lagg_fail_ioctl(struct lagg_proto_softc 
 	return error;
 }
 
+void
+lagg_fail_linkspeed_work(struct lagg_work *_lw __unused, void *xpsc)
+{
+	struct lagg_proto_softc *psc = xpsc;
+	struct lagg_proto_port *pport;
+	struct lagg_port *lp;
+	struct psref psref;
+	uint64_t linkspeed;
+
+	kpreempt_disable();
+	lp = lagg_link_active(psc, NULL, &psref);
+	if (lp != NULL) {
+		pport = lp->lp_proto_ctx;
+		LAGG_PROTO_LOCK(psc);
+		linkspeed = pport->lpp_linkspeed;
+		LAGG_PROTO_UNLOCK(psc);
+		lagg_port_putref(lp, &psref);
+	} else {
+		linkspeed = 0;
+	}
+	kpreempt_enable();
+
+	LAGG_LOCK(psc->psc_softc);
+	lagg_set_linkspeed(psc->psc_softc, linkspeed);
+	LAGG_UNLOCK(psc->psc_softc);
+}
+
 int
 lagg_lb_attach(struct lagg_softc *sc, struct lagg_proto_softc **xpsc)
 {
@@ -522,6 +623,8 @@ lagg_lb_attach(struct lagg_softc *sc, st
 
 	lb = psc->psc_ctx;
 	lb->lb_pmaps.maps_activepmap = 0;
+	lagg_work_set(&psc->psc_work_linkspeed,
+	    lagg_lb_linkspeed_work, psc);
 
 	*xpsc = psc;
 	return 0;
@@ -550,8 +653,8 @@ lagg_lb_startport(struct lagg_proto_soft
 	pm_next->pm_nports = n;
 
 	lagg_portmap_switch(&lb->lb_pmaps);
-	pserialize_perform(psc->psc_psz);
 	LAGG_PROTO_UNLOCK(psc);
+	pserialize_perform(psc->psc_psz);
 }
 
 void
@@ -576,9 +679,11 @@ lagg_lb_stopport(struct lagg_proto_softc
 		n++;
 	}
 
+	pm_next->pm_nports = n;
+
 	lagg_portmap_switch(&lb->lb_pmaps);
-	pserialize_perform(psc->psc_psz);
 	LAGG_PROTO_UNLOCK(psc);
+	pserialize_perform(psc->psc_psz);
 
 	lagg_common_stopport(psc, lp);
 }
@@ -595,14 +700,18 @@ lagg_lb_transmit(struct lagg_proto_softc
 	int s;
 
 	lb = psc->psc_ctx;
-	hash  = lagg_hashmbuf(psc->psc_softc, m);
+	hash = lagg_hashmbuf(psc->psc_softc, m);
 
 	s = pserialize_read_enter();
 
 	pm = lagg_portmap_active(&lb->lb_pmaps);
-	hash %= pm->pm_nports;
-	lp0 = pm->pm_ports[hash];
-	lp = lagg_link_active(psc, lp0->lp_proto_ctx, &psref);
+	if (__predict_true(pm->pm_nports != 0)) {
+		hash %= pm->pm_nports;
+		lp0 = pm->pm_ports[hash];
+		lp = lagg_link_active(psc, lp0->lp_proto_ctx, &psref);
+	} else {
+		lp = NULL;
+	}
 
 	pserialize_read_exit(s);
 
@@ -640,3 +749,27 @@ lagg_lb_portstat(struct lagg_proto_softc
 		    LAGG_PORT_COLLECTING | LAGG_PORT_DISTRIBUTING);
 	}
 }
+
+static void
+lagg_lb_linkspeed_work(struct lagg_work *_lw __unused, void *xpsc)
+{
+	struct lagg_proto_softc *psc = xpsc;
+	struct lagg_proto_port *pport;
+	uint64_t linkspeed, l;
+
+	linkspeed = 0;
+
+	LAGG_PROTO_LOCK(psc); /* acquired to refer lpp_linkspeed */
+	PSLIST_READER_FOREACH(pport, &psc->psc_ports,
+	    struct lagg_proto_port, lpp_entry) {
+		if (pport->lpp_active) {
+			l = pport->lpp_linkspeed;
+			linkspeed = MAX(linkspeed, l);
+		}
+	}
+	LAGG_PROTO_UNLOCK(psc);
+
+	LAGG_LOCK(psc->psc_softc);
+	lagg_set_linkspeed(psc->psc_softc, linkspeed);
+	LAGG_UNLOCK(psc->psc_softc);
+}

Index: src/sys/net/lagg/if_laggproto.h
diff -u src/sys/net/lagg/if_laggproto.h:1.18 src/sys/net/lagg/if_laggproto.h:1.18.4.1
--- src/sys/net/lagg/if_laggproto.h:1.18	Sun Jun 26 17:55:24 2022
+++ src/sys/net/lagg/if_laggproto.h	Thu Oct  3 11:53:46 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_laggproto.h,v 1.18 2022/06/26 17:55:24 riastradh Exp $	*/
+/*	$NetBSD: if_laggproto.h,v 1.18.4.1 2024/10/03 11:53:46 martin Exp $	*/
 
 /*
  * Copyright (c) 2021 Internet Initiative Japan Inc.
@@ -146,6 +146,7 @@ struct lagg_vlantag {
 struct lagg_softc {
 	kmutex_t		 sc_lock;
 	struct ifmedia		 sc_media;
+	uint64_t		 sc_media_active;
 	u_char			 sc_iftype;
 
 	/* interface link-layer address */
@@ -286,6 +287,7 @@ void		lagg_port_putref(struct lagg_port 
 void		lagg_output(struct lagg_softc *,
 		    struct lagg_port *, struct mbuf *);
 uint32_t	lagg_hashmbuf(struct lagg_softc *, struct mbuf *);
+void		lagg_set_linkspeed(struct lagg_softc *, uint64_t);
 
 void		lagg_common_detach(struct lagg_proto_softc *);
 int		lagg_common_allocport(struct lagg_proto_softc *,
@@ -296,7 +298,7 @@ void		lagg_common_startport(struct lagg_
 		    struct lagg_port *);
 void		lagg_common_stopport(struct lagg_proto_softc *,
 		    struct lagg_port *);
-void		lagg_common_linkstate(struct lagg_proto_softc *,
+void		lagg_common_linkstate_ifnet_locked(struct lagg_proto_softc *,
 		    struct lagg_port *);
 
 int		lagg_none_attach(struct lagg_softc *,

Index: src/tests/net/if_lagg/t_lagg.sh
diff -u src/tests/net/if_lagg/t_lagg.sh:1.8.2.1 src/tests/net/if_lagg/t_lagg.sh:1.8.2.2
--- src/tests/net/if_lagg/t_lagg.sh:1.8.2.1	Thu Oct 19 07:23:50 2023
+++ src/tests/net/if_lagg/t_lagg.sh	Thu Oct  3 11:53:46 2024
@@ -1,4 +1,4 @@
-#	$NetBSD: t_lagg.sh,v 1.8.2.1 2023/10/19 07:23:50 martin Exp $
+#	$NetBSD: t_lagg.sh,v 1.8.2.2 2024/10/03 11:53:46 martin Exp $
 #
 # Copyright (c) 2021 Internet Initiative Japan Inc.
 # All rights reserved.
@@ -214,7 +214,7 @@ lagg_ifconfig_body()
 
 lagg_ifconfig_cleanup()
 {
-	$DEBG && dump
+	$DEBUG && dump
 	cleanup
 }
 
@@ -392,7 +392,7 @@ lagg_mtu_body()
 	atf_check -s exit:0 -o match:"mtu *$mtu_big" rump.ifconfig shmif1
 	atf_check -s exit:0 -o match:"mtu *$mtu_small" rump.ifconfig shmif2
 
-	# copy MTU from 1st port
+	# copy MTU to 1st added port
 	$atf_ifconfig lagg0 laggport shmif0
 	atf_check -s exit:0 -o match:"mtu *$mtu_lagg" rump.ifconfig lagg0
 	atf_check -s exit:0 -o match:"mtu *$mtu_lagg" rump.ifconfig shmif0

Reply via email to