xref: /illumos-gate/usr/src/uts/common/io/aggr/aggr_port.c (revision 84de666edc7f7d835057ae4807a387447c086bcf)
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 
55 static kmem_cache_t *aggr_port_cache;
56 static id_space_t *aggr_portids;
57 
58 static void aggr_port_notify_cb(void *, mac_notify_type_t);
59 
60 /*ARGSUSED*/
61 static int
62 aggr_port_constructor(void *buf, void *arg, int kmflag)
63 {
64 	bzero(buf, sizeof (aggr_port_t));
65 	return (0);
66 }
67 
68 /*ARGSUSED*/
69 static void
70 aggr_port_destructor(void *buf, void *arg)
71 {
72 	aggr_port_t *port = buf;
73 
74 	ASSERT(port->lp_mnh == NULL);
75 	ASSERT(port->lp_mphp == NULL);
76 	ASSERT(!port->lp_rx_grp_added && !port->lp_tx_grp_added);
77 	ASSERT(port->lp_hwgh == NULL);
78 }
79 
80 void
81 aggr_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 
96 void
97 aggr_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 */
109 void
110 aggr_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 
131 /* ARGSUSED */
132 int
133 aggr_port_create(aggr_grp_t *grp, const datalink_id_t linkid, boolean_t force,
134     aggr_port_t **pp)
135 {
136 	int err;
137 	mac_handle_t mh;
138 	mac_client_handle_t mch = NULL;
139 	aggr_port_t *port;
140 	uint16_t portid;
141 	uint_t i;
142 	boolean_t no_link_update = B_FALSE;
143 	const mac_info_t *mip;
144 	uint32_t note;
145 	uint32_t margin;
146 	char client_name[MAXNAMELEN];
147 	char aggr_name[MAXNAMELEN];
148 	char port_name[MAXNAMELEN];
149 	mac_diag_t diag;
150 	mac_unicast_handle_t mah;
151 
152 	*pp = NULL;
153 
154 	if ((err = mac_open_by_linkid(linkid, &mh)) != 0)
155 		return (err);
156 
157 	mip = mac_info(mh);
158 	if (mip->mi_media != DL_ETHER || mip->mi_nativemedia != DL_ETHER) {
159 		err = EINVAL;
160 		goto fail;
161 	}
162 
163 	/*
164 	 * If the underlying MAC does not support link update notification, it
165 	 * can only be aggregated if `force' is set.  This is because aggr
166 	 * depends on link notifications to attach ports whose link is up.
167 	 */
168 	note = mac_no_notification(mh);
169 	if ((note & (DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN)) != 0) {
170 		no_link_update = B_TRUE;
171 		if (!force) {
172 			/*
173 			 * We borrow this error code to indicate that link
174 			 * notification is not supported.
175 			 */
176 			err = ENETDOWN;
177 			goto fail;
178 		}
179 	}
180 
181 	if (((err = dls_mgmt_get_linkinfo(grp->lg_linkid,
182 	    aggr_name, NULL, NULL, NULL)) != 0) ||
183 	    ((err = dls_mgmt_get_linkinfo(linkid, port_name,
184 	    NULL, NULL, NULL)) != 0)) {
185 		goto fail;
186 	}
187 
188 	(void) snprintf(client_name, MAXNAMELEN, "%s-%s", aggr_name, port_name);
189 	if ((err = mac_client_open(mh, &mch, client_name,
190 	    MAC_OPEN_FLAGS_IS_AGGR_PORT | MAC_OPEN_FLAGS_EXCLUSIVE)) != 0) {
191 		goto fail;
192 	}
193 
194 	if ((portid = (uint16_t)id_alloc(aggr_portids)) == 0) {
195 		err = ENOMEM;
196 		goto fail;
197 	}
198 
199 	/*
200 	 * As the underlying mac's current margin size is used to determine
201 	 * the margin size of the aggregation itself, request the underlying
202 	 * mac not to change to a smaller size.
203 	 */
204 	if ((err = mac_margin_add(mh, &margin, B_TRUE)) != 0) {
205 		id_free(aggr_portids, portid);
206 		goto fail;
207 	}
208 
209 	if ((err = mac_unicast_add(mch, NULL, MAC_UNICAST_PRIMARY |
210 	    MAC_UNICAST_DISABLE_TX_VID_CHECK, &mah, 0, &diag)) != 0) {
211 		VERIFY(mac_margin_remove(mh, margin) == 0);
212 		id_free(aggr_portids, portid);
213 		goto fail;
214 	}
215 
216 	port = kmem_cache_alloc(aggr_port_cache, KM_SLEEP);
217 
218 	port->lp_refs = 1;
219 	port->lp_next = NULL;
220 	port->lp_mh = mh;
221 	port->lp_mch = mch;
222 	port->lp_mip = mip;
223 	port->lp_linkid = linkid;
224 	port->lp_closing = B_FALSE;
225 	port->lp_mah = mah;
226 
227 	/* get the port's original MAC address */
228 	mac_unicast_primary_get(port->lp_mh, port->lp_addr);
229 
230 	/* initialize state */
231 	port->lp_state = AGGR_PORT_STATE_STANDBY;
232 	port->lp_link_state = LINK_STATE_UNKNOWN;
233 	port->lp_ifspeed = 0;
234 	port->lp_link_duplex = LINK_DUPLEX_UNKNOWN;
235 	port->lp_started = B_FALSE;
236 	port->lp_tx_enabled = B_FALSE;
237 	port->lp_promisc_on = B_FALSE;
238 	port->lp_no_link_update = no_link_update;
239 	port->lp_portid = portid;
240 	port->lp_margin = margin;
241 	port->lp_prom_addr = NULL;
242 
243 	/*
244 	 * Save the current statistics of the port. They will be used
245 	 * later by aggr_m_stats() when aggregating the statistics of
246 	 * the constituent ports.
247 	 */
248 	for (i = 0; i < MAC_NSTAT; i++) {
249 		port->lp_stat[i] =
250 		    aggr_port_stat(port, i + MAC_STAT_MIN);
251 	}
252 	for (i = 0; i < ETHER_NSTAT; i++) {
253 		port->lp_ether_stat[i] =
254 		    aggr_port_stat(port, i + MACTYPE_STAT_MIN);
255 	}
256 
257 	/* LACP related state */
258 	port->lp_collector_enabled = B_FALSE;
259 
260 	*pp = port;
261 	return (0);
262 
263 fail:
264 	if (mch != NULL)
265 		mac_client_close(mch, MAC_CLOSE_FLAGS_EXCLUSIVE);
266 	mac_close(mh);
267 	return (err);
268 }
269 
270 void
271 aggr_port_delete(aggr_port_t *port)
272 {
273 	aggr_lacp_port_t *pl = &port->lp_lacp;
274 
275 	ASSERT(port->lp_mphp == NULL);
276 	ASSERT(!port->lp_promisc_on);
277 
278 	port->lp_closing = B_TRUE;
279 
280 	VERIFY(mac_margin_remove(port->lp_mh, port->lp_margin) == 0);
281 	mac_rx_clear(port->lp_mch);
282 	/*
283 	 * If the notification callback is already in process and waiting for
284 	 * the aggr grp's mac perimeter, don't wait (otherwise there would be
285 	 * deadlock). Otherwise, if mac_notify_remove() succeeds, we can
286 	 * release the reference held when mac_notify_add() is called.
287 	 */
288 	if ((port->lp_mnh != NULL) &&
289 	    (mac_notify_remove(port->lp_mnh, B_FALSE) == 0)) {
290 		aggr_grp_port_rele(port);
291 	}
292 	port->lp_mnh = NULL;
293 
294 	/*
295 	 * Inform the the port lacp timer thread to exit. Note that waiting
296 	 * for the thread to exit may cause deadlock since that thread may
297 	 * need to enter into the mac perimeter which we are currently in.
298 	 * It is fine to continue without waiting though since that thread
299 	 * is holding a reference of the port.
300 	 */
301 	mutex_enter(&pl->lacp_timer_lock);
302 	pl->lacp_timer_bits |= LACP_THREAD_EXIT;
303 	cv_broadcast(&pl->lacp_timer_cv);
304 	mutex_exit(&pl->lacp_timer_lock);
305 
306 	/*
307 	 * Restore the port MAC address. Note it is called after the
308 	 * port's notification callback being removed. This prevent
309 	 * port's MAC_NOTE_UNICST notify callback function being called.
310 	 */
311 	(void) mac_unicast_primary_set(port->lp_mh, port->lp_addr);
312 	if (port->lp_mah != NULL)
313 		(void) mac_unicast_remove(port->lp_mch, port->lp_mah);
314 	mac_client_close(port->lp_mch, MAC_CLOSE_FLAGS_EXCLUSIVE);
315 	mac_close(port->lp_mh);
316 	AGGR_PORT_REFRELE(port);
317 }
318 
319 void
320 aggr_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  */
337 boolean_t
338 aggr_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  */
405 static void
406 aggr_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 
445 done:
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  */
454 static void
455 aggr_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 
497 int
498 aggr_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 
510 void
511 aggr_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 int
525 aggr_port_promisc(aggr_port_t *port, boolean_t on)
526 {
527 	int rc;
528 
529 	ASSERT(MAC_PERIM_HELD(port->lp_mh));
530 
531 	if (on == port->lp_promisc_on)
532 		/* already in desired promiscous mode */
533 		return (0);
534 
535 	if (on) {
536 		mac_rx_clear(port->lp_mch);
537 
538 		/*
539 		 * We use the promisc callback because without hardware
540 		 * rings, we deliver through flows that will cause duplicate
541 		 * delivery of packets when we've flipped into this mode
542 		 * to compensate for the lack of hardware MAC matching
543 		 */
544 		rc = mac_promisc_add(port->lp_mch, MAC_CLIENT_PROMISC_ALL,
545 		    aggr_recv_promisc_cb, port, &port->lp_mphp,
546 		    MAC_PROMISC_FLAGS_NO_TX_LOOP);
547 		if (rc != 0) {
548 			mac_rx_set(port->lp_mch, aggr_recv_cb, port);
549 			return (rc);
550 		}
551 	} else {
552 		mac_promisc_remove(port->lp_mphp);
553 		port->lp_mphp = NULL;
554 		mac_rx_set(port->lp_mch, aggr_recv_cb, port);
555 	}
556 
557 	port->lp_promisc_on = on;
558 
559 	return (0);
560 }
561 
562 /*
563  * Set the MAC address of a port.
564  */
565 int
566 aggr_port_unicst(aggr_port_t *port)
567 {
568 	aggr_grp_t		*grp = port->lp_grp;
569 
570 	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
571 	ASSERT(MAC_PERIM_HELD(port->lp_mh));
572 
573 	return (mac_unicast_primary_set(port->lp_mh, grp->lg_addr));
574 }
575 
576 /*
577  * Add or remove a multicast address to/from a port.
578  */
579 int
580 aggr_port_multicst(void *arg, boolean_t add, const uint8_t *addrp)
581 {
582 	aggr_port_t *port = arg;
583 
584 	if (add) {
585 		return (mac_multicast_add(port->lp_mch, addrp));
586 	} else {
587 		mac_multicast_remove(port->lp_mch, addrp);
588 		return (0);
589 	}
590 }
591 
592 uint64_t
593 aggr_port_stat(aggr_port_t *port, uint_t stat)
594 {
595 	return (mac_stat_get(port->lp_mh, stat));
596 }
597 
598 /*
599  * Add a non-primary unicast address to the underlying port. If the port
600  * supports HW Rx group, try to add the address into the HW Rx group of
601  * the port first. If that fails, or if the port does not support HW Rx
602  * group, enable the port's promiscous mode.
603  */
604 int
605 aggr_port_addmac(aggr_port_t *port, const uint8_t *mac_addr)
606 {
607 	aggr_unicst_addr_t	*addr, **pprev;
608 	mac_perim_handle_t	pmph;
609 	int			err;
610 
611 	ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh));
612 	mac_perim_enter_by_mh(port->lp_mh, &pmph);
613 
614 	/*
615 	 * If the underlying port support HW Rx group, add the mac to its
616 	 * RX group directly.
617 	 */
618 	if ((port->lp_hwgh != NULL) &&
619 	    ((mac_hwgroup_addmac(port->lp_hwgh, mac_addr)) == 0)) {
620 		mac_perim_exit(pmph);
621 		return (0);
622 	}
623 
624 	/*
625 	 * If that fails, or if the port does not support HW Rx group, enable
626 	 * the port's promiscous mode. (Note that we turn on the promiscous
627 	 * mode only if the port is already started.
628 	 */
629 	if (port->lp_started &&
630 	    ((err = aggr_port_promisc(port, B_TRUE)) != 0)) {
631 		mac_perim_exit(pmph);
632 		return (err);
633 	}
634 
635 	/*
636 	 * Walk through the unicast addresses that requires promiscous mode
637 	 * enabled on this port, and add this address to the end of the list.
638 	 */
639 	pprev = &port->lp_prom_addr;
640 	while ((addr = *pprev) != NULL) {
641 		ASSERT(bcmp(mac_addr, addr->aua_addr, ETHERADDRL) != 0);
642 		pprev = &addr->aua_next;
643 	}
644 	addr = kmem_alloc(sizeof (aggr_unicst_addr_t), KM_SLEEP);
645 	bcopy(mac_addr, addr->aua_addr, ETHERADDRL);
646 	addr->aua_next = NULL;
647 	*pprev = addr;
648 	mac_perim_exit(pmph);
649 	return (0);
650 }
651 
652 /*
653  * Remove a non-primary unicast address from the underlying port. This address
654  * must has been added by aggr_port_addmac(). As a result, we probably need to
655  * remove the address from the port's HW Rx group, or to disable the port's
656  * promiscous mode.
657  */
658 void
659 aggr_port_remmac(aggr_port_t *port, const uint8_t *mac_addr)
660 {
661 	aggr_grp_t		*grp = port->lp_grp;
662 	aggr_unicst_addr_t	*addr, **pprev;
663 	mac_perim_handle_t	pmph;
664 
665 	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
666 	mac_perim_enter_by_mh(port->lp_mh, &pmph);
667 
668 	/*
669 	 * See whether this address is in the list of addresses that requires
670 	 * the port being promiscous mode.
671 	 */
672 	pprev = &port->lp_prom_addr;
673 	while ((addr = *pprev) != NULL) {
674 		if (bcmp(mac_addr, addr->aua_addr, ETHERADDRL) == 0)
675 			break;
676 		pprev = &addr->aua_next;
677 	}
678 	if (addr != NULL) {
679 		/*
680 		 * This unicast address put the port into the promiscous mode,
681 		 * delete this address from the lp_prom_addr list. If this is
682 		 * the last address in that list, disable the promiscous mode
683 		 * if the aggregation is not in promiscous mode.
684 		 */
685 		*pprev = addr->aua_next;
686 		kmem_free(addr, sizeof (aggr_unicst_addr_t));
687 		if (port->lp_prom_addr == NULL && !grp->lg_promisc)
688 			(void) aggr_port_promisc(port, B_FALSE);
689 	} else {
690 		ASSERT(port->lp_hwgh != NULL);
691 		(void) mac_hwgroup_remmac(port->lp_hwgh, mac_addr);
692 	}
693 	mac_perim_exit(pmph);
694 }
695 
696 int
697 aggr_port_addvlan(aggr_port_t *port, uint16_t vid)
698 {
699 	mac_perim_handle_t	pmph;
700 	int			err;
701 
702 	ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh));
703 	mac_perim_enter_by_mh(port->lp_mh, &pmph);
704 
705 	/*
706 	 * Add the VLAN filter to the HW group if the port has a HW
707 	 * group. If the port doesn't have a HW group, then it will
708 	 * implicitly allow tagged traffic to pass and there is
709 	 * nothing to do.
710 	 */
711 	if (port->lp_hwgh == NULL) {
712 		mac_perim_exit(pmph);
713 		return (0);
714 	}
715 
716 	err = mac_hwgroup_addvlan(port->lp_hwgh, vid);
717 	mac_perim_exit(pmph);
718 	return (err);
719 }
720 
721 int
722 aggr_port_remvlan(aggr_port_t *port, uint16_t vid)
723 {
724 	mac_perim_handle_t	pmph;
725 	int			err;
726 
727 	ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh));
728 	mac_perim_enter_by_mh(port->lp_mh, &pmph);
729 
730 	if (port->lp_hwgh == NULL) {
731 		mac_perim_exit(pmph);
732 		return (0);
733 	}
734 
735 	err = mac_hwgroup_remvlan(port->lp_hwgh, vid);
736 	mac_perim_exit(pmph);
737 	return (err);
738 }
739