1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 * Copyright 2012 OmniTI Computer Consulting, Inc  All rights reserved.
25 * Copyright 2020 Joyent, Inc.
26 */
27
28/*
29 * IEEE 802.3ad Link Aggregation - Link Aggregation MAC ports.
30 *
31 * Implements the functions needed to manage the MAC ports that are
32 * part of Link Aggregation groups.
33 */
34
35#include <sys/types.h>
36#include <sys/sysmacros.h>
37#include <sys/conf.h>
38#include <sys/cmn_err.h>
39#include <sys/id_space.h>
40#include <sys/list.h>
41#include <sys/ksynch.h>
42#include <sys/kmem.h>
43#include <sys/stream.h>
44#include <sys/modctl.h>
45#include <sys/ddi.h>
46#include <sys/sunddi.h>
47#include <sys/atomic.h>
48#include <sys/stat.h>
49#include <sys/sdt.h>
50#include <sys/dlpi.h>
51#include <sys/dls.h>
52#include <sys/aggr.h>
53#include <sys/aggr_impl.h>
54
55static kmem_cache_t *aggr_port_cache;
56static id_space_t *aggr_portids;
57
58static void aggr_port_notify_cb(void *, mac_notify_type_t);
59
60/*ARGSUSED*/
61static int
62aggr_port_constructor(void *buf, void *arg, int kmflag)
63{
64	bzero(buf, sizeof (aggr_port_t));
65	return (0);
66}
67
68/*ARGSUSED*/
69static void
70aggr_port_destructor(void *buf, void *arg)
71{
72	aggr_port_t *port = buf;
73
74	ASSERT3P(port->lp_mnh, ==, NULL);
75	ASSERT(!port->lp_tx_grp_added);
76	for (uint_t i = 0; i < MAX_GROUPS_PER_PORT; i++)
77		ASSERT3P(port->lp_hwghs[i], ==, NULL);
78}
79
80void
81aggr_port_init(void)
82{
83	aggr_port_cache = kmem_cache_create("aggr_port_cache",
84	    sizeof (aggr_port_t), 0, aggr_port_constructor,
85	    aggr_port_destructor, NULL, NULL, NULL, 0);
86
87	/*
88	 * Allocate a id space to manage port identification. The range of
89	 * the arena will be from 1 to UINT16_MAX, because the LACP protocol
90	 * specifies 16-bit unique identification.
91	 */
92	aggr_portids = id_space_create("aggr_portids", 1, UINT16_MAX);
93	ASSERT(aggr_portids != NULL);
94}
95
96void
97aggr_port_fini(void)
98{
99	/*
100	 * This function is called only after all groups have been
101	 * freed. This ensures that there are no remaining allocated
102	 * ports when this function is invoked.
103	 */
104	kmem_cache_destroy(aggr_port_cache);
105	id_space_destroy(aggr_portids);
106}
107
108/* ARGSUSED */
109void
110aggr_port_init_callbacks(aggr_port_t *port)
111{
112	/* add the port's receive callback */
113	port->lp_mnh = mac_notify_add(port->lp_mh, aggr_port_notify_cb, port);
114	/*
115	 * Hold a reference of the grp and the port and this reference will
116	 * be released when the thread exits.
117	 *
118	 * The reference on the port is used for aggr_port_delete() to
119	 * continue without waiting for the thread to exit; the reference
120	 * on the grp is used for aggr_grp_delete() to wait for the thread
121	 * to exit before calling mac_unregister().
122	 *
123	 * Note that these references will be released either in
124	 * aggr_port_delete() when mac_notify_remove() succeeds, or in
125	 * the aggr_port_notify_cb() callback when the port is deleted
126	 * (lp_closing is set).
127	 */
128	aggr_grp_port_hold(port);
129}
130
131int
132aggr_port_create(aggr_grp_t *grp, const datalink_id_t linkid, boolean_t force,
133    aggr_port_t **pp)
134{
135	int err;
136	mac_handle_t mh;
137	mac_client_handle_t mch = NULL;
138	aggr_port_t *port;
139	uint16_t portid;
140	uint_t i;
141	boolean_t no_link_update = B_FALSE;
142	const mac_info_t *mip;
143	uint32_t note;
144	uint32_t margin;
145	char client_name[MAXNAMELEN];
146	char aggr_name[MAXNAMELEN];
147	char port_name[MAXNAMELEN];
148	mac_diag_t diag;
149	mac_unicast_handle_t mah;
150
151	*pp = NULL;
152
153	if ((err = mac_open_by_linkid(linkid, &mh)) != 0)
154		return (err);
155
156	mip = mac_info(mh);
157	if (mip->mi_media != DL_ETHER || mip->mi_nativemedia != DL_ETHER) {
158		err = EINVAL;
159		goto fail;
160	}
161
162	/*
163	 * If the underlying MAC does not support link update notification, it
164	 * can only be aggregated if `force' is set.  This is because aggr
165	 * depends on link notifications to attach ports whose link is up.
166	 */
167	note = mac_no_notification(mh);
168	if ((note & (DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN)) != 0) {
169		no_link_update = B_TRUE;
170		if (!force) {
171			/*
172			 * We borrow this error code to indicate that link
173			 * notification is not supported.
174			 */
175			err = ENETDOWN;
176			goto fail;
177		}
178	}
179
180	if (((err = dls_mgmt_get_linkinfo(grp->lg_linkid,
181	    aggr_name, NULL, NULL, NULL)) != 0) ||
182	    ((err = dls_mgmt_get_linkinfo(linkid, port_name,
183	    NULL, NULL, NULL)) != 0)) {
184		goto fail;
185	}
186
187	(void) snprintf(client_name, MAXNAMELEN, "%s-%s", aggr_name, port_name);
188	if ((err = mac_client_open(mh, &mch, client_name,
189	    MAC_OPEN_FLAGS_IS_AGGR_PORT | MAC_OPEN_FLAGS_EXCLUSIVE)) != 0) {
190		goto fail;
191	}
192
193	if ((portid = (uint16_t)id_alloc(aggr_portids)) == 0) {
194		err = ENOMEM;
195		goto fail;
196	}
197
198	/*
199	 * As the underlying MAC's current margin size is used to determine
200	 * the margin size of the aggregation itself, request the underlying
201	 * MAC not to change to a smaller size.
202	 */
203	if ((err = mac_margin_add(mh, &margin, B_TRUE)) != 0) {
204		id_free(aggr_portids, portid);
205		goto fail;
206	}
207
208	if ((err = mac_unicast_add(mch, NULL, MAC_UNICAST_PRIMARY |
209	    MAC_UNICAST_DISABLE_TX_VID_CHECK, &mah, 0, &diag)) != 0) {
210		VERIFY3S(mac_margin_remove(mh, margin), ==, 0);
211		id_free(aggr_portids, portid);
212		goto fail;
213	}
214
215	port = kmem_cache_alloc(aggr_port_cache, KM_SLEEP);
216
217	port->lp_refs = 1;
218	port->lp_next = NULL;
219	port->lp_mh = mh;
220	port->lp_mch = mch;
221	port->lp_mip = mip;
222	port->lp_linkid = linkid;
223	port->lp_closing = B_FALSE;
224	port->lp_mah = mah;
225
226	/* get the port's original MAC address */
227	mac_unicast_primary_get(port->lp_mh, port->lp_addr);
228
229	/* initialize state */
230	port->lp_state = AGGR_PORT_STATE_STANDBY;
231	port->lp_link_state = LINK_STATE_UNKNOWN;
232	port->lp_ifspeed = 0;
233	port->lp_link_duplex = LINK_DUPLEX_UNKNOWN;
234	port->lp_started = B_FALSE;
235	port->lp_tx_enabled = B_FALSE;
236	port->lp_promisc_on = B_FALSE;
237	port->lp_no_link_update = no_link_update;
238	port->lp_portid = portid;
239	port->lp_margin = margin;
240	port->lp_prom_addr = NULL;
241
242	/*
243	 * Save the current statistics of the port. They will be used
244	 * later by aggr_m_stats() when aggregating the statistics of
245	 * the constituent ports.
246	 */
247	for (i = 0; i < MAC_NSTAT; i++) {
248		port->lp_stat[i] =
249		    aggr_port_stat(port, i + MAC_STAT_MIN);
250	}
251	for (i = 0; i < ETHER_NSTAT; i++) {
252		port->lp_ether_stat[i] =
253		    aggr_port_stat(port, i + MACTYPE_STAT_MIN);
254	}
255
256	/* LACP related state */
257	port->lp_collector_enabled = B_FALSE;
258
259	*pp = port;
260	return (0);
261
262fail:
263	if (mch != NULL)
264		mac_client_close(mch, MAC_CLOSE_FLAGS_EXCLUSIVE);
265
266	mac_close(mh);
267	return (err);
268}
269
270void
271aggr_port_delete(aggr_port_t *port)
272{
273	aggr_lacp_port_t *pl = &port->lp_lacp;
274
275	ASSERT(!port->lp_promisc_on);
276	port->lp_closing = B_TRUE;
277	VERIFY0(mac_margin_remove(port->lp_mh, port->lp_margin));
278	mac_client_clear_flow_cb(port->lp_mch);
279
280	/*
281	 * If the notification callback is already in process and waiting for
282	 * the aggr grp's mac perimeter, don't wait (otherwise there would be
283	 * deadlock). Otherwise, if mac_notify_remove() succeeds, we can
284	 * release the reference held when mac_notify_add() is called.
285	 */
286	if ((port->lp_mnh != NULL) &&
287	    (mac_notify_remove(port->lp_mnh, B_FALSE) == 0)) {
288		aggr_grp_port_rele(port);
289	}
290	port->lp_mnh = NULL;
291
292	/*
293	 * Inform the the port lacp timer thread to exit. Note that waiting
294	 * for the thread to exit may cause deadlock since that thread may
295	 * need to enter into the mac perimeter which we are currently in.
296	 * It is fine to continue without waiting though since that thread
297	 * is holding a reference of the port.
298	 */
299	mutex_enter(&pl->lacp_timer_lock);
300	pl->lacp_timer_bits |= LACP_THREAD_EXIT;
301	cv_broadcast(&pl->lacp_timer_cv);
302	mutex_exit(&pl->lacp_timer_lock);
303
304	/*
305	 * Restore the port MAC address. Note it is called after the
306	 * port's notification callback being removed. This prevent
307	 * port's MAC_NOTE_UNICST notify callback function being called.
308	 */
309	(void) mac_unicast_primary_set(port->lp_mh, port->lp_addr);
310
311	if (port->lp_mah != NULL)
312		(void) mac_unicast_remove(port->lp_mch, port->lp_mah);
313
314	mac_client_close(port->lp_mch, MAC_CLOSE_FLAGS_EXCLUSIVE);
315	mac_close(port->lp_mh);
316	AGGR_PORT_REFRELE(port);
317}
318
319void
320aggr_port_free(aggr_port_t *port)
321{
322	ASSERT(port->lp_refs == 0);
323	if (port->lp_grp != NULL)
324		AGGR_GRP_REFRELE(port->lp_grp);
325	port->lp_grp = NULL;
326	id_free(aggr_portids, port->lp_portid);
327	port->lp_portid = 0;
328	mutex_destroy(&port->lp_lacp.lacp_timer_lock);
329	cv_destroy(&port->lp_lacp.lacp_timer_cv);
330	kmem_cache_free(aggr_port_cache, port);
331}
332
333/*
334 * Invoked upon receiving a MAC_NOTE_LINK notification for
335 * one of the constituent ports.
336 */
337boolean_t
338aggr_port_notify_link(aggr_grp_t *grp, aggr_port_t *port)
339{
340	boolean_t do_attach = B_FALSE;
341	boolean_t do_detach = B_FALSE;
342	boolean_t link_state_changed = B_TRUE;
343	uint64_t ifspeed;
344	link_state_t link_state;
345	link_duplex_t link_duplex;
346	mac_perim_handle_t mph;
347
348	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
349	mac_perim_enter_by_mh(port->lp_mh, &mph);
350
351	/*
352	 * link state change?  For links that do not support link state
353	 * notification, always assume the link is up.
354	 */
355	link_state = port->lp_no_link_update ? LINK_STATE_UP :
356	    mac_link_get(port->lp_mh);
357	if (port->lp_link_state != link_state) {
358		if (link_state == LINK_STATE_UP)
359			do_attach = (port->lp_link_state != LINK_STATE_UP);
360		else
361			do_detach = (port->lp_link_state == LINK_STATE_UP);
362	}
363	port->lp_link_state = link_state;
364
365	/* link duplex change? */
366	link_duplex = aggr_port_stat(port, ETHER_STAT_LINK_DUPLEX);
367	if (port->lp_link_duplex != link_duplex) {
368		if (link_duplex == LINK_DUPLEX_FULL)
369			do_attach |= (port->lp_link_duplex != LINK_DUPLEX_FULL);
370		else
371			do_detach |= (port->lp_link_duplex == LINK_DUPLEX_FULL);
372	}
373	port->lp_link_duplex = link_duplex;
374
375	/* link speed changes? */
376	ifspeed = aggr_port_stat(port, MAC_STAT_IFSPEED);
377	if (port->lp_ifspeed != ifspeed) {
378		mutex_enter(&grp->lg_stat_lock);
379
380		if (port->lp_state == AGGR_PORT_STATE_ATTACHED)
381			do_detach |= (ifspeed != grp->lg_ifspeed);
382		else
383			do_attach |= (ifspeed == grp->lg_ifspeed);
384
385		mutex_exit(&grp->lg_stat_lock);
386	}
387	port->lp_ifspeed = ifspeed;
388
389	if (do_attach) {
390		/* attempt to attach the port to the aggregation */
391		link_state_changed = aggr_grp_attach_port(grp, port);
392	} else if (do_detach) {
393		/* detach the port from the aggregation */
394		link_state_changed = aggr_grp_detach_port(grp, port);
395	}
396
397	mac_perim_exit(mph);
398	return (link_state_changed);
399}
400
401/*
402 * Invoked upon receiving a MAC_NOTE_UNICST for one of the constituent
403 * ports of a group.
404 */
405static void
406aggr_port_notify_unicst(aggr_grp_t *grp, aggr_port_t *port,
407    boolean_t *mac_addr_changedp, boolean_t *link_state_changedp)
408{
409	boolean_t mac_addr_changed = B_FALSE;
410	boolean_t link_state_changed = B_FALSE;
411	uint8_t mac_addr[ETHERADDRL];
412	mac_perim_handle_t mph;
413
414	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
415	ASSERT(mac_addr_changedp != NULL);
416	ASSERT(link_state_changedp != NULL);
417	mac_perim_enter_by_mh(port->lp_mh, &mph);
418
419	/*
420	 * If it is called when setting the MAC address to the
421	 * aggregation group MAC address, do nothing.
422	 */
423	mac_unicast_primary_get(port->lp_mh, mac_addr);
424	if (bcmp(mac_addr, grp->lg_addr, ETHERADDRL) == 0) {
425		mac_perim_exit(mph);
426		goto done;
427	}
428
429	/* save the new port MAC address */
430	bcopy(mac_addr, port->lp_addr, ETHERADDRL);
431
432	aggr_grp_port_mac_changed(grp, port, &mac_addr_changed,
433	    &link_state_changed);
434
435	mac_perim_exit(mph);
436
437	/*
438	 * If this port was used to determine the MAC address of
439	 * the group, update the MAC address of the constituent
440	 * ports.
441	 */
442	if (mac_addr_changed && aggr_grp_update_ports_mac(grp))
443		link_state_changed = B_TRUE;
444
445done:
446	*mac_addr_changedp = mac_addr_changed;
447	*link_state_changedp = link_state_changed;
448}
449
450/*
451 * Notification callback invoked by the MAC service module for
452 * a particular MAC port.
453 */
454static void
455aggr_port_notify_cb(void *arg, mac_notify_type_t type)
456{
457	aggr_port_t *port = arg;
458	aggr_grp_t *grp = port->lp_grp;
459	boolean_t mac_addr_changed, link_state_changed;
460	mac_perim_handle_t mph;
461
462	mac_perim_enter_by_mh(grp->lg_mh, &mph);
463	if (port->lp_closing) {
464		mac_perim_exit(mph);
465
466		/*
467		 * Release the reference so it is safe for aggr to call
468		 * mac_unregister() now.
469		 */
470		aggr_grp_port_rele(port);
471		return;
472	}
473
474	switch (type) {
475	case MAC_NOTE_TX:
476		mac_tx_update(grp->lg_mh);
477		break;
478	case MAC_NOTE_LINK:
479		if (aggr_port_notify_link(grp, port))
480			mac_link_update(grp->lg_mh, grp->lg_link_state);
481		break;
482	case MAC_NOTE_UNICST:
483		aggr_port_notify_unicst(grp, port, &mac_addr_changed,
484		    &link_state_changed);
485		if (mac_addr_changed)
486			mac_unicst_update(grp->lg_mh, grp->lg_addr);
487		if (link_state_changed)
488			mac_link_update(grp->lg_mh, grp->lg_link_state);
489		break;
490	default:
491		break;
492	}
493
494	mac_perim_exit(mph);
495}
496
497int
498aggr_port_start(aggr_port_t *port)
499{
500	ASSERT(MAC_PERIM_HELD(port->lp_mh));
501
502	if (port->lp_started)
503		return (0);
504
505	port->lp_started = B_TRUE;
506	aggr_grp_multicst_port(port, B_TRUE);
507	return (0);
508}
509
510void
511aggr_port_stop(aggr_port_t *port)
512{
513	ASSERT(MAC_PERIM_HELD(port->lp_mh));
514
515	if (!port->lp_started)
516		return;
517
518	aggr_grp_multicst_port(port, B_FALSE);
519
520	/* update the port state */
521	port->lp_started = B_FALSE;
522}
523
524/*
525 * Set the promisc mode of the port. If the port is already in the
526 * requested mode then do nothing.
527 */
528int
529aggr_port_promisc(aggr_port_t *port, boolean_t on)
530{
531	int rc;
532
533	ASSERT(MAC_PERIM_HELD(port->lp_mh));
534
535	if (on == port->lp_promisc_on)
536		return (0);
537
538	rc = mac_set_promisc(port->lp_mh, on);
539
540	if (rc == 0)
541		port->lp_promisc_on = on;
542
543	return (rc);
544}
545
546/*
547 * Set the MAC address of a port.
548 */
549int
550aggr_port_unicst(aggr_port_t *port)
551{
552	aggr_grp_t		*grp = port->lp_grp;
553
554	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
555	ASSERT(MAC_PERIM_HELD(port->lp_mh));
556
557	return (mac_unicast_primary_set(port->lp_mh, grp->lg_addr));
558}
559
560/*
561 * Add or remove a multicast address to/from a port.
562 */
563int
564aggr_port_multicst(void *arg, boolean_t add, const uint8_t *addrp)
565{
566	aggr_port_t *port = arg;
567
568	if (add) {
569		return (mac_multicast_add(port->lp_mch, addrp));
570	} else {
571		mac_multicast_remove(port->lp_mch, addrp);
572		return (0);
573	}
574}
575
576uint64_t
577aggr_port_stat(aggr_port_t *port, uint_t stat)
578{
579	return (mac_stat_get(port->lp_mh, stat));
580}
581
582/*
583 * Add a non-primary unicast address to the underlying port. If the
584 * port supports HW Rx groups, then try to add the address filter to
585 * the HW group first. If that fails, or if the port does not support
586 * RINGS capab, then enable the port's promiscous mode.
587 */
588int
589aggr_port_addmac(aggr_port_t *port, uint_t idx, const uint8_t *mac_addr)
590{
591	aggr_unicst_addr_t	*addr, **pprev;
592	mac_perim_handle_t	pmph;
593	int			err;
594
595	ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh));
596	ASSERT3U(idx, <, MAX_GROUPS_PER_PORT);
597	mac_perim_enter_by_mh(port->lp_mh, &pmph);
598
599	/*
600	 * If the port doesn't have a HW group to back the aggr's
601	 * pseudo group, then try using the port's default group and
602	 * let the aggr SW classify its traffic. This scenario happens
603	 * when mixing ports with a different number of HW groups.
604	 */
605	if (port->lp_hwghs[idx] == NULL)
606		idx = 0;
607
608	/*
609	 * If there is an underlying HW Rx group, then try adding this
610	 * unicast address to it.
611	 */
612	if ((port->lp_hwghs[idx] != NULL) &&
613	    ((mac_hwgroup_addmac(port->lp_hwghs[idx], mac_addr)) == 0)) {
614		mac_perim_exit(pmph);
615		return (0);
616	}
617
618	/*
619	 * If the port doesn't have HW groups, or we failed to add the
620	 * HW filter, then enable the port's promiscuous mode. We
621	 * enable promiscuous mode only if the port is already started.
622	 */
623	if (port->lp_started &&
624	    ((err = aggr_port_promisc(port, B_TRUE)) != 0)) {
625		mac_perim_exit(pmph);
626		return (err);
627	}
628
629	/*
630	 * Walk through the unicast addresses that requires promiscous mode
631	 * enabled on this port, and add this address to the end of the list.
632	 */
633	pprev = &port->lp_prom_addr;
634	while ((addr = *pprev) != NULL) {
635		ASSERT(bcmp(mac_addr, addr->aua_addr, ETHERADDRL) != 0);
636		pprev = &addr->aua_next;
637	}
638	addr = kmem_alloc(sizeof (aggr_unicst_addr_t), KM_SLEEP);
639	bcopy(mac_addr, addr->aua_addr, ETHERADDRL);
640	addr->aua_next = NULL;
641	*pprev = addr;
642	mac_perim_exit(pmph);
643	return (0);
644}
645
646/*
647 * Remove a non-primary unicast address from the underlying port. This address
648 * must has been added by aggr_port_addmac(). As a result, we probably need to
649 * remove the address from the port's HW Rx group, or to disable the port's
650 * promiscous mode.
651 */
652void
653aggr_port_remmac(aggr_port_t *port, uint_t idx, const uint8_t *mac_addr)
654{
655	aggr_grp_t		*grp = port->lp_grp;
656	aggr_unicst_addr_t	*addr, **pprev;
657	mac_perim_handle_t	pmph;
658
659	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
660	ASSERT3U(idx, <, MAX_GROUPS_PER_PORT);
661	mac_perim_enter_by_mh(port->lp_mh, &pmph);
662
663	/*
664	 * See whether this address is in the list of addresses that requires
665	 * the port being promiscous mode.
666	 */
667	pprev = &port->lp_prom_addr;
668	while ((addr = *pprev) != NULL) {
669		if (bcmp(mac_addr, addr->aua_addr, ETHERADDRL) == 0)
670			break;
671		pprev = &addr->aua_next;
672	}
673
674	if (addr != NULL) {
675		/*
676		 * This unicast address put the port into the promiscous mode,
677		 * delete this address from the lp_prom_addr list. If this is
678		 * the last address in that list, disable the promiscous mode
679		 * if the aggregation is not in promiscous mode.
680		 */
681		*pprev = addr->aua_next;
682		kmem_free(addr, sizeof (aggr_unicst_addr_t));
683		if (port->lp_prom_addr == NULL && !grp->lg_promisc)
684			(void) aggr_port_promisc(port, B_FALSE);
685	} else {
686		/* See comment in aggr_port_addmac(). */
687		if (port->lp_hwghs[idx] == NULL)
688			idx = 0;
689
690		ASSERT3P(port->lp_hwghs[idx], !=, NULL);
691		(void) mac_hwgroup_remmac(port->lp_hwghs[idx], mac_addr);
692	}
693
694	mac_perim_exit(pmph);
695}
696
697int
698aggr_port_addvlan(aggr_port_t *port, uint_t idx, uint16_t vid)
699{
700	mac_perim_handle_t	pmph;
701	int			err;
702
703	ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh));
704	ASSERT3U(idx, <, MAX_GROUPS_PER_PORT);
705	mac_perim_enter_by_mh(port->lp_mh, &pmph);
706
707	/* See comment in aggr_port_addmac(). */
708	if (port->lp_hwghs[idx] == NULL)
709		idx = 0;
710
711	/*
712	 * Add the VLAN filter to the HW group if the port has a HW
713	 * group. If the port doesn't have a HW group, then it will
714	 * implicitly allow tagged traffic to pass and there is
715	 * nothing to do.
716	 */
717	if (port->lp_hwghs[idx] == NULL)
718		err = 0;
719	else
720		err = mac_hwgroup_addvlan(port->lp_hwghs[idx], vid);
721
722	mac_perim_exit(pmph);
723	return (err);
724}
725
726int
727aggr_port_remvlan(aggr_port_t *port, uint_t idx, uint16_t vid)
728{
729	mac_perim_handle_t	pmph;
730	int			err;
731
732	ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh));
733	ASSERT3U(idx, <, MAX_GROUPS_PER_PORT);
734	mac_perim_enter_by_mh(port->lp_mh, &pmph);
735
736	/* See comment in aggr_port_addmac(). */
737	if (port->lp_hwghs[idx] == NULL)
738		idx = 0;
739
740	if (port->lp_hwghs[idx] == NULL)
741		err = 0;
742	else
743		err = mac_hwgroup_remvlan(port->lp_hwghs[idx], vid);
744
745	mac_perim_exit(pmph);
746	return (err);
747}
748