xref: /illumos-gate/usr/src/uts/common/io/aggr/aggr_port.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
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  */
25 
26 /*
27  * IEEE 802.3ad Link Aggregation - Link Aggregation MAC ports.
28  *
29  * Implements the functions needed to manage the MAC ports that are
30  * part of Link Aggregation groups.
31  */
32 
33 #include <sys/types.h>
34 #include <sys/sysmacros.h>
35 #include <sys/conf.h>
36 #include <sys/cmn_err.h>
37 #include <sys/id_space.h>
38 #include <sys/list.h>
39 #include <sys/ksynch.h>
40 #include <sys/kmem.h>
41 #include <sys/stream.h>
42 #include <sys/modctl.h>
43 #include <sys/ddi.h>
44 #include <sys/sunddi.h>
45 #include <sys/atomic.h>
46 #include <sys/stat.h>
47 #include <sys/sdt.h>
48 #include <sys/dlpi.h>
49 #include <sys/dls.h>
50 #include <sys/aggr.h>
51 #include <sys/aggr_impl.h>
52 
53 static kmem_cache_t *aggr_port_cache;
54 static id_space_t *aggr_portids;
55 
56 static void aggr_port_notify_cb(void *, mac_notify_type_t);
57 
58 /*ARGSUSED*/
59 static int
60 aggr_port_constructor(void *buf, void *arg, int kmflag)
61 {
62 	bzero(buf, sizeof (aggr_port_t));
63 	return (0);
64 }
65 
66 /*ARGSUSED*/
67 static void
68 aggr_port_destructor(void *buf, void *arg)
69 {
70 	aggr_port_t *port = buf;
71 
72 	ASSERT(port->lp_mnh == NULL);
73 	ASSERT(port->lp_mphp == NULL);
74 	ASSERT(!port->lp_rx_grp_added && !port->lp_tx_grp_added);
75 	ASSERT(port->lp_hwgh == NULL);
76 }
77 
78 void
79 aggr_port_init(void)
80 {
81 	aggr_port_cache = kmem_cache_create("aggr_port_cache",
82 	    sizeof (aggr_port_t), 0, aggr_port_constructor,
83 	    aggr_port_destructor, NULL, NULL, NULL, 0);
84 
85 	/*
86 	 * Allocate a id space to manage port identification. The range of
87 	 * the arena will be from 1 to UINT16_MAX, because the LACP protocol
88 	 * specifies 16-bit unique identification.
89 	 */
90 	aggr_portids = id_space_create("aggr_portids", 1, UINT16_MAX);
91 	ASSERT(aggr_portids != NULL);
92 }
93 
94 void
95 aggr_port_fini(void)
96 {
97 	/*
98 	 * This function is called only after all groups have been
99 	 * freed. This ensures that there are no remaining allocated
100 	 * ports when this function is invoked.
101 	 */
102 	kmem_cache_destroy(aggr_port_cache);
103 	id_space_destroy(aggr_portids);
104 }
105 
106 /* ARGSUSED */
107 void
108 aggr_port_init_callbacks(aggr_port_t *port)
109 {
110 	/* add the port's receive callback */
111 	port->lp_mnh = mac_notify_add(port->lp_mh, aggr_port_notify_cb, port);
112 	/*
113 	 * Hold a reference of the grp and the port and this reference will
114 	 * be released when the thread exits.
115 	 *
116 	 * The reference on the port is used for aggr_port_delete() to
117 	 * continue without waiting for the thread to exit; the reference
118 	 * on the grp is used for aggr_grp_delete() to wait for the thread
119 	 * to exit before calling mac_unregister().
120 	 *
121 	 * Note that these references will be released either in
122 	 * aggr_port_delete() when mac_notify_remove() succeeds, or in
123 	 * the aggr_port_notify_cb() callback when the port is deleted
124 	 * (lp_closing is set).
125 	 */
126 	aggr_grp_port_hold(port);
127 }
128 
129 /* ARGSUSED */
130 int
131 aggr_port_create(aggr_grp_t *grp, const datalink_id_t linkid, boolean_t force,
132     aggr_port_t **pp)
133 {
134 	int err;
135 	mac_handle_t mh;
136 	mac_client_handle_t mch = NULL;
137 	aggr_port_t *port;
138 	uint16_t portid;
139 	uint_t i;
140 	boolean_t no_link_update = B_FALSE;
141 	const mac_info_t *mip;
142 	uint32_t note;
143 	uint32_t margin;
144 	char client_name[MAXNAMELEN];
145 	char aggr_name[MAXNAMELEN];
146 	char port_name[MAXNAMELEN];
147 	mac_diag_t diag;
148 	mac_unicast_handle_t mah;
149 
150 	*pp = NULL;
151 
152 	if ((err = mac_open_by_linkid(linkid, &mh)) != 0)
153 		return (err);
154 
155 	mip = mac_info(mh);
156 	if (mip->mi_media != DL_ETHER || mip->mi_nativemedia != DL_ETHER) {
157 		err = EINVAL;
158 		goto fail;
159 	}
160 
161 	/*
162 	 * If the underlying MAC does not support link update notification, it
163 	 * can only be aggregated if `force' is set.  This is because aggr
164 	 * depends on link notifications to attach ports whose link is up.
165 	 */
166 	note = mac_no_notification(mh);
167 	if ((note & (DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN)) != 0) {
168 		no_link_update = B_TRUE;
169 		if (!force) {
170 			/*
171 			 * We borrow this error code to indicate that link
172 			 * notification is not supported.
173 			 */
174 			err = ENETDOWN;
175 			goto fail;
176 		}
177 	}
178 
179 	if (((err = dls_mgmt_get_linkinfo(grp->lg_linkid,
180 	    aggr_name, NULL, NULL, NULL)) != 0) ||
181 	    ((err = dls_mgmt_get_linkinfo(linkid, port_name,
182 	    NULL, NULL, NULL)) != 0)) {
183 		goto fail;
184 	}
185 
186 	(void) snprintf(client_name, MAXNAMELEN, "%s-%s", aggr_name, port_name);
187 	if ((err = mac_client_open(mh, &mch, client_name,
188 	    MAC_OPEN_FLAGS_IS_AGGR_PORT | MAC_OPEN_FLAGS_EXCLUSIVE)) != 0) {
189 		goto fail;
190 	}
191 
192 	if ((portid = (uint16_t)id_alloc(aggr_portids)) == 0) {
193 		err = ENOMEM;
194 		goto fail;
195 	}
196 
197 	/*
198 	 * As the underlying mac's current margin size is used to determine
199 	 * the margin size of the aggregation itself, request the underlying
200 	 * mac not to change to a smaller size.
201 	 */
202 	if ((err = mac_margin_add(mh, &margin, B_TRUE)) != 0) {
203 		id_free(aggr_portids, portid);
204 		goto fail;
205 	}
206 
207 	if ((err = mac_unicast_add(mch, NULL, MAC_UNICAST_PRIMARY |
208 	    MAC_UNICAST_DISABLE_TX_VID_CHECK, &mah, 0, &diag)) != 0) {
209 		VERIFY(mac_margin_remove(mh, margin) == 0);
210 		id_free(aggr_portids, portid);
211 		goto fail;
212 	}
213 
214 	port = kmem_cache_alloc(aggr_port_cache, KM_SLEEP);
215 
216 	port->lp_refs = 1;
217 	port->lp_next = NULL;
218 	port->lp_mh = mh;
219 	port->lp_mch = mch;
220 	port->lp_mip = mip;
221 	port->lp_linkid = linkid;
222 	port->lp_closing = B_FALSE;
223 	port->lp_mah = mah;
224 
225 	/* get the port's original MAC address */
226 	mac_unicast_primary_get(port->lp_mh, port->lp_addr);
227 
228 	/* initialize state */
229 	port->lp_state = AGGR_PORT_STATE_STANDBY;
230 	port->lp_link_state = LINK_STATE_UNKNOWN;
231 	port->lp_ifspeed = 0;
232 	port->lp_link_duplex = LINK_DUPLEX_UNKNOWN;
233 	port->lp_started = B_FALSE;
234 	port->lp_tx_enabled = B_FALSE;
235 	port->lp_promisc_on = B_FALSE;
236 	port->lp_no_link_update = no_link_update;
237 	port->lp_portid = portid;
238 	port->lp_margin = margin;
239 	port->lp_prom_addr = NULL;
240 
241 	/*
242 	 * Save the current statistics of the port. They will be used
243 	 * later by aggr_m_stats() when aggregating the statistics of
244 	 * the constituent ports.
245 	 */
246 	for (i = 0; i < MAC_NSTAT; i++) {
247 		port->lp_stat[i] =
248 		    aggr_port_stat(port, i + MAC_STAT_MIN);
249 	}
250 	for (i = 0; i < ETHER_NSTAT; i++) {
251 		port->lp_ether_stat[i] =
252 		    aggr_port_stat(port, i + MACTYPE_STAT_MIN);
253 	}
254 
255 	/* LACP related state */
256 	port->lp_collector_enabled = B_FALSE;
257 
258 	*pp = port;
259 	return (0);
260 
261 fail:
262 	if (mch != NULL)
263 		mac_client_close(mch, MAC_CLOSE_FLAGS_EXCLUSIVE);
264 	mac_close(mh);
265 	return (err);
266 }
267 
268 void
269 aggr_port_delete(aggr_port_t *port)
270 {
271 	aggr_lacp_port_t *pl = &port->lp_lacp;
272 
273 	ASSERT(port->lp_mphp == NULL);
274 	ASSERT(!port->lp_promisc_on);
275 
276 	port->lp_closing = B_TRUE;
277 
278 	VERIFY(mac_margin_remove(port->lp_mh, port->lp_margin) == 0);
279 	mac_rx_clear(port->lp_mch);
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 	if (port->lp_mah != NULL)
311 		(void) mac_unicast_remove(port->lp_mch, port->lp_mah);
312 	mac_client_close(port->lp_mch, MAC_CLOSE_FLAGS_EXCLUSIVE);
313 	mac_close(port->lp_mh);
314 	AGGR_PORT_REFRELE(port);
315 }
316 
317 void
318 aggr_port_free(aggr_port_t *port)
319 {
320 	ASSERT(port->lp_refs == 0);
321 	if (port->lp_grp != NULL)
322 		AGGR_GRP_REFRELE(port->lp_grp);
323 	port->lp_grp = NULL;
324 	id_free(aggr_portids, port->lp_portid);
325 	port->lp_portid = 0;
326 	mutex_destroy(&port->lp_lacp.lacp_timer_lock);
327 	cv_destroy(&port->lp_lacp.lacp_timer_cv);
328 	kmem_cache_free(aggr_port_cache, port);
329 }
330 
331 /*
332  * Invoked upon receiving a MAC_NOTE_LINK notification for
333  * one of the constituent ports.
334  */
335 boolean_t
336 aggr_port_notify_link(aggr_grp_t *grp, aggr_port_t *port)
337 {
338 	boolean_t do_attach = B_FALSE;
339 	boolean_t do_detach = B_FALSE;
340 	boolean_t link_state_changed = B_TRUE;
341 	uint64_t ifspeed;
342 	link_state_t link_state;
343 	link_duplex_t link_duplex;
344 	mac_perim_handle_t mph;
345 
346 	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
347 	mac_perim_enter_by_mh(port->lp_mh, &mph);
348 
349 	/*
350 	 * link state change?  For links that do not support link state
351 	 * notification, always assume the link is up.
352 	 */
353 	link_state = port->lp_no_link_update ? LINK_STATE_UP :
354 	    mac_link_get(port->lp_mh);
355 	if (port->lp_link_state != link_state) {
356 		if (link_state == LINK_STATE_UP)
357 			do_attach = (port->lp_link_state != LINK_STATE_UP);
358 		else
359 			do_detach = (port->lp_link_state == LINK_STATE_UP);
360 	}
361 	port->lp_link_state = link_state;
362 
363 	/* link duplex change? */
364 	link_duplex = aggr_port_stat(port, ETHER_STAT_LINK_DUPLEX);
365 	if (port->lp_link_duplex != link_duplex) {
366 		if (link_duplex == LINK_DUPLEX_FULL)
367 			do_attach |= (port->lp_link_duplex != LINK_DUPLEX_FULL);
368 		else
369 			do_detach |= (port->lp_link_duplex == LINK_DUPLEX_FULL);
370 	}
371 	port->lp_link_duplex = link_duplex;
372 
373 	/* link speed changes? */
374 	ifspeed = aggr_port_stat(port, MAC_STAT_IFSPEED);
375 	if (port->lp_ifspeed != ifspeed) {
376 		if (port->lp_state == AGGR_PORT_STATE_ATTACHED)
377 			do_detach |= (ifspeed != grp->lg_ifspeed);
378 		else
379 			do_attach |= (ifspeed == grp->lg_ifspeed);
380 	}
381 	port->lp_ifspeed = ifspeed;
382 
383 	if (do_attach) {
384 		/* attempt to attach the port to the aggregation */
385 		link_state_changed = aggr_grp_attach_port(grp, port);
386 	} else if (do_detach) {
387 		/* detach the port from the aggregation */
388 		link_state_changed = aggr_grp_detach_port(grp, port);
389 	}
390 
391 	mac_perim_exit(mph);
392 	return (link_state_changed);
393 }
394 
395 /*
396  * Invoked upon receiving a MAC_NOTE_UNICST for one of the constituent
397  * ports of a group.
398  */
399 static void
400 aggr_port_notify_unicst(aggr_grp_t *grp, aggr_port_t *port,
401     boolean_t *mac_addr_changedp, boolean_t *link_state_changedp)
402 {
403 	boolean_t mac_addr_changed = B_FALSE;
404 	boolean_t link_state_changed = B_FALSE;
405 	uint8_t mac_addr[ETHERADDRL];
406 	mac_perim_handle_t mph;
407 
408 	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
409 	ASSERT(mac_addr_changedp != NULL);
410 	ASSERT(link_state_changedp != NULL);
411 	mac_perim_enter_by_mh(port->lp_mh, &mph);
412 
413 	/*
414 	 * If it is called when setting the MAC address to the
415 	 * aggregation group MAC address, do nothing.
416 	 */
417 	mac_unicast_primary_get(port->lp_mh, mac_addr);
418 	if (bcmp(mac_addr, grp->lg_addr, ETHERADDRL) == 0) {
419 		mac_perim_exit(mph);
420 		goto done;
421 	}
422 
423 	/* save the new port MAC address */
424 	bcopy(mac_addr, port->lp_addr, ETHERADDRL);
425 
426 	aggr_grp_port_mac_changed(grp, port, &mac_addr_changed,
427 	    &link_state_changed);
428 
429 	mac_perim_exit(mph);
430 
431 	/*
432 	 * If this port was used to determine the MAC address of
433 	 * the group, update the MAC address of the constituent
434 	 * ports.
435 	 */
436 	if (mac_addr_changed && aggr_grp_update_ports_mac(grp))
437 		link_state_changed = B_TRUE;
438 
439 done:
440 	*mac_addr_changedp = mac_addr_changed;
441 	*link_state_changedp = link_state_changed;
442 }
443 
444 /*
445  * Notification callback invoked by the MAC service module for
446  * a particular MAC port.
447  */
448 static void
449 aggr_port_notify_cb(void *arg, mac_notify_type_t type)
450 {
451 	aggr_port_t *port = arg;
452 	aggr_grp_t *grp = port->lp_grp;
453 	boolean_t mac_addr_changed, link_state_changed;
454 	mac_perim_handle_t mph;
455 
456 	mac_perim_enter_by_mh(grp->lg_mh, &mph);
457 	if (port->lp_closing) {
458 		mac_perim_exit(mph);
459 
460 		/*
461 		 * Release the reference so it is safe for aggr to call
462 		 * mac_unregister() now.
463 		 */
464 		aggr_grp_port_rele(port);
465 		return;
466 	}
467 
468 	switch (type) {
469 	case MAC_NOTE_TX:
470 		mac_tx_update(grp->lg_mh);
471 		break;
472 	case MAC_NOTE_LINK:
473 		if (aggr_port_notify_link(grp, port))
474 			mac_link_update(grp->lg_mh, grp->lg_link_state);
475 		break;
476 	case MAC_NOTE_UNICST:
477 		aggr_port_notify_unicst(grp, port, &mac_addr_changed,
478 		    &link_state_changed);
479 		if (mac_addr_changed)
480 			mac_unicst_update(grp->lg_mh, grp->lg_addr);
481 		if (link_state_changed)
482 			mac_link_update(grp->lg_mh, grp->lg_link_state);
483 		break;
484 	default:
485 		break;
486 	}
487 
488 	mac_perim_exit(mph);
489 }
490 
491 int
492 aggr_port_start(aggr_port_t *port)
493 {
494 	ASSERT(MAC_PERIM_HELD(port->lp_mh));
495 
496 	if (port->lp_started)
497 		return (0);
498 
499 	port->lp_started = B_TRUE;
500 	aggr_grp_multicst_port(port, B_TRUE);
501 	return (0);
502 }
503 
504 void
505 aggr_port_stop(aggr_port_t *port)
506 {
507 	ASSERT(MAC_PERIM_HELD(port->lp_mh));
508 
509 	if (!port->lp_started)
510 		return;
511 
512 	aggr_grp_multicst_port(port, B_FALSE);
513 
514 	/* update the port state */
515 	port->lp_started = B_FALSE;
516 }
517 
518 int
519 aggr_port_promisc(aggr_port_t *port, boolean_t on)
520 {
521 	int rc;
522 
523 	ASSERT(MAC_PERIM_HELD(port->lp_mh));
524 
525 	if (on == port->lp_promisc_on)
526 		/* already in desired promiscous mode */
527 		return (0);
528 
529 	if (on) {
530 		mac_rx_clear(port->lp_mch);
531 		rc = mac_promisc_add(port->lp_mch, MAC_CLIENT_PROMISC_ALL,
532 		    aggr_recv_cb, port, &port->lp_mphp,
533 		    MAC_PROMISC_FLAGS_NO_TX_LOOP);
534 		if (rc != 0) {
535 			mac_rx_set(port->lp_mch, aggr_recv_cb, port);
536 			return (rc);
537 		}
538 	} else {
539 		mac_promisc_remove(port->lp_mphp);
540 		port->lp_mphp = NULL;
541 		mac_rx_set(port->lp_mch, aggr_recv_cb, port);
542 	}
543 
544 	port->lp_promisc_on = on;
545 
546 	return (0);
547 }
548 
549 /*
550  * Set the MAC address of a port.
551  */
552 int
553 aggr_port_unicst(aggr_port_t *port)
554 {
555 	aggr_grp_t		*grp = port->lp_grp;
556 
557 	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
558 	ASSERT(MAC_PERIM_HELD(port->lp_mh));
559 
560 	return (mac_unicast_primary_set(port->lp_mh, grp->lg_addr));
561 }
562 
563 /*
564  * Add or remove a multicast address to/from a port.
565  */
566 int
567 aggr_port_multicst(void *arg, boolean_t add, const uint8_t *addrp)
568 {
569 	aggr_port_t *port = arg;
570 
571 	if (add) {
572 		return (mac_multicast_add(port->lp_mch, addrp));
573 	} else {
574 		mac_multicast_remove(port->lp_mch, addrp);
575 		return (0);
576 	}
577 }
578 
579 uint64_t
580 aggr_port_stat(aggr_port_t *port, uint_t stat)
581 {
582 	return (mac_stat_get(port->lp_mh, stat));
583 }
584 
585 /*
586  * Add a non-primary unicast address to the underlying port. If the port
587  * supports HW Rx group, try to add the address into the HW Rx group of
588  * the port first. If that fails, or if the port does not support HW Rx
589  * group, enable the port's promiscous mode.
590  */
591 int
592 aggr_port_addmac(aggr_port_t *port, const uint8_t *mac_addr)
593 {
594 	aggr_unicst_addr_t	*addr, **pprev;
595 	mac_perim_handle_t	pmph;
596 	int			err;
597 
598 	ASSERT(MAC_PERIM_HELD(port->lp_grp->lg_mh));
599 	mac_perim_enter_by_mh(port->lp_mh, &pmph);
600 
601 	/*
602 	 * If the underlying port support HW Rx group, add the mac to its
603 	 * RX group directly.
604 	 */
605 	if ((port->lp_hwgh != NULL) &&
606 	    ((mac_hwgroup_addmac(port->lp_hwgh, mac_addr)) == 0)) {
607 		mac_perim_exit(pmph);
608 		return (0);
609 	}
610 
611 	/*
612 	 * If that fails, or if the port does not support HW Rx group, enable
613 	 * the port's promiscous mode. (Note that we turn on the promiscous
614 	 * mode only if the port is already started.
615 	 */
616 	if (port->lp_started &&
617 	    ((err = aggr_port_promisc(port, B_TRUE)) != 0)) {
618 		mac_perim_exit(pmph);
619 		return (err);
620 	}
621 
622 	/*
623 	 * Walk through the unicast addresses that requires promiscous mode
624 	 * enabled on this port, and add this address to the end of the list.
625 	 */
626 	pprev = &port->lp_prom_addr;
627 	while ((addr = *pprev) != NULL) {
628 		ASSERT(bcmp(mac_addr, addr->aua_addr, ETHERADDRL) != 0);
629 		pprev = &addr->aua_next;
630 	}
631 	addr = kmem_alloc(sizeof (aggr_unicst_addr_t), KM_SLEEP);
632 	bcopy(mac_addr, addr->aua_addr, ETHERADDRL);
633 	addr->aua_next = NULL;
634 	*pprev = addr;
635 	mac_perim_exit(pmph);
636 	return (0);
637 }
638 
639 /*
640  * Remove a non-primary unicast address from the underlying port. This address
641  * must has been added by aggr_port_addmac(). As a result, we probably need to
642  * remove the address from the port's HW Rx group, or to disable the port's
643  * promiscous mode.
644  */
645 void
646 aggr_port_remmac(aggr_port_t *port, const uint8_t *mac_addr)
647 {
648 	aggr_grp_t		*grp = port->lp_grp;
649 	aggr_unicst_addr_t	*addr, **pprev;
650 	mac_perim_handle_t	pmph;
651 
652 	ASSERT(MAC_PERIM_HELD(grp->lg_mh));
653 	mac_perim_enter_by_mh(port->lp_mh, &pmph);
654 
655 	/*
656 	 * See whether this address is in the list of addresses that requires
657 	 * the port being promiscous mode.
658 	 */
659 	pprev = &port->lp_prom_addr;
660 	while ((addr = *pprev) != NULL) {
661 		if (bcmp(mac_addr, addr->aua_addr, ETHERADDRL) == 0)
662 			break;
663 		pprev = &addr->aua_next;
664 	}
665 	if (addr != NULL) {
666 		/*
667 		 * This unicast address put the port into the promiscous mode,
668 		 * delete this address from the lp_prom_addr list. If this is
669 		 * the last address in that list, disable the promiscous mode
670 		 * if the aggregation is not in promiscous mode.
671 		 */
672 		*pprev = addr->aua_next;
673 		kmem_free(addr, sizeof (aggr_unicst_addr_t));
674 		if (port->lp_prom_addr == NULL && !grp->lg_promisc)
675 			(void) aggr_port_promisc(port, B_FALSE);
676 	} else {
677 		ASSERT(port->lp_hwgh != NULL);
678 		(void) mac_hwgroup_remmac(port->lp_hwgh, mac_addr);
679 	}
680 	mac_perim_exit(pmph);
681 }
682