xref: /illumos-gate/usr/src/uts/common/io/aggr/aggr_grp.c (revision 560f878bce5cdf0661659001415019ca5c8a01b4)
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 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * IEEE 802.3ad Link Aggregation -- Link Aggregation Groups.
30  *
31  * An instance of the structure aggr_grp_t is allocated for each
32  * link aggregation group. When created, aggr_grp_t objects are
33  * entered into the aggr_grp_hash hash table maintained by the modhash
34  * module. The hash key is the port number associated with the link
35  * aggregation group. The port number associated with a group corresponds
36  * the key associated with the group.
37  *
38  * A set of MAC ports are associated with each association group.
39  */
40 
41 #include <sys/types.h>
42 #include <sys/sysmacros.h>
43 #include <sys/conf.h>
44 #include <sys/cmn_err.h>
45 #include <sys/list.h>
46 #include <sys/ksynch.h>
47 #include <sys/kmem.h>
48 #include <sys/stream.h>
49 #include <sys/modctl.h>
50 #include <sys/ddi.h>
51 #include <sys/sunddi.h>
52 #include <sys/atomic.h>
53 #include <sys/stat.h>
54 #include <sys/modhash.h>
55 #include <sys/strsun.h>
56 #include <sys/dlpi.h>
57 
58 #include <sys/aggr.h>
59 #include <sys/aggr_impl.h>
60 
61 static void aggr_m_info(void *, mac_info_t *);
62 static int aggr_m_start(void *);
63 static void aggr_m_stop(void *);
64 static int aggr_m_promisc(void *, boolean_t);
65 static int aggr_m_multicst(void *, boolean_t, const uint8_t *);
66 static int aggr_m_unicst(void *, const uint8_t *);
67 static uint64_t aggr_m_stat(void *, enum mac_stat);
68 static void aggr_m_resources(void *);
69 static void aggr_m_ioctl(void *, queue_t *, mblk_t *);
70 
71 static aggr_port_t *aggr_grp_port_lookup(aggr_grp_t *, const char *, uint32_t);
72 static int aggr_grp_rem_port(aggr_grp_t *, aggr_port_t *, boolean_t *,
73     boolean_t *);
74 static void aggr_stats_op(enum mac_stat, uint64_t *, uint64_t *, boolean_t);
75 static void aggr_grp_capab_set(aggr_grp_t *);
76 static boolean_t aggr_grp_capab_check(aggr_grp_t *, aggr_port_t *);
77 
78 static kmem_cache_t	*aggr_grp_cache;
79 static mod_hash_t	*aggr_grp_hash;
80 static krwlock_t	aggr_grp_lock;
81 static uint_t		aggr_grp_cnt;
82 
83 #define	GRP_HASHSZ		64
84 #define	GRP_HASH_KEY(key)	((mod_hash_key_t)(uintptr_t)key)
85 
86 static uchar_t aggr_zero_mac[] = {0, 0, 0, 0, 0, 0};
87 static uchar_t aggr_brdcst_mac[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
88 
89 /* used by grp_info_walker */
90 typedef struct aggr_grp_info_state {
91 	uint32_t	ls_group_key;
92 	boolean_t	ls_group_found;
93 	aggr_grp_info_new_grp_fn_t ls_new_grp_fn;
94 	aggr_grp_info_new_port_fn_t ls_new_port_fn;
95 	void		*ls_fn_arg;
96 	int		ls_rc;
97 } aggr_grp_info_state_t;
98 
99 /*ARGSUSED*/
100 static int
101 aggr_grp_constructor(void *buf, void *arg, int kmflag)
102 {
103 	aggr_grp_t *grp = buf;
104 
105 	bzero(grp, sizeof (*grp));
106 	rw_init(&grp->lg_lock, NULL, RW_DRIVER, NULL);
107 	mutex_init(&grp->aggr.gl_lock, NULL, MUTEX_DEFAULT, NULL);
108 
109 	grp->lg_link_state = LINK_STATE_UNKNOWN;
110 
111 	return (0);
112 }
113 
114 /*ARGSUSED*/
115 static void
116 aggr_grp_destructor(void *buf, void *arg)
117 {
118 	aggr_grp_t *grp = buf;
119 
120 	if (grp->lg_tx_ports != NULL) {
121 		kmem_free(grp->lg_tx_ports,
122 		    grp->lg_tx_ports_size * sizeof (aggr_port_t *));
123 	}
124 
125 	mutex_destroy(&grp->aggr.gl_lock);
126 	rw_destroy(&grp->lg_lock);
127 }
128 
129 void
130 aggr_grp_init(void)
131 {
132 	aggr_grp_cache = kmem_cache_create("aggr_grp_cache",
133 	    sizeof (aggr_grp_t), 0, aggr_grp_constructor,
134 	    aggr_grp_destructor, NULL, NULL, NULL, 0);
135 
136 	aggr_grp_hash = mod_hash_create_idhash("aggr_grp_hash",
137 	    GRP_HASHSZ, mod_hash_null_valdtor);
138 	rw_init(&aggr_grp_lock, NULL, RW_DEFAULT, NULL);
139 	aggr_grp_cnt = 0;
140 }
141 
142 void
143 aggr_grp_fini(void)
144 {
145 	rw_destroy(&aggr_grp_lock);
146 	mod_hash_destroy_idhash(aggr_grp_hash);
147 	kmem_cache_destroy(aggr_grp_cache);
148 }
149 
150 uint_t
151 aggr_grp_count(void)
152 {
153 	uint_t	count;
154 
155 	rw_enter(&aggr_grp_lock, RW_READER);
156 	count = aggr_grp_cnt;
157 	rw_exit(&aggr_grp_lock);
158 	return (count);
159 }
160 
161 /*
162  * Attach a port to a link aggregation group.
163  *
164  * A port is attached to a link aggregation group once its speed
165  * and link state have been verified.
166  *
167  * Returns B_TRUE if the group link state or speed has changed. If
168  * it's the case, the caller must notify the MAC layer via a call
169  * to mac_link().
170  */
171 boolean_t
172 aggr_grp_attach_port(aggr_grp_t *grp, aggr_port_t *port)
173 {
174 	boolean_t link_state_changed = B_FALSE;
175 
176 	ASSERT(AGGR_LACP_LOCK_HELD(grp));
177 	ASSERT(RW_WRITE_HELD(&grp->lg_lock));
178 	ASSERT(RW_WRITE_HELD(&port->lp_lock));
179 
180 	if (port->lp_state == AGGR_PORT_STATE_ATTACHED)
181 		return (B_FALSE);
182 
183 	/*
184 	 * Validate the MAC port link speed and update the group
185 	 * link speed if needed.
186 	 */
187 	if (port->lp_ifspeed == 0 ||
188 	    port->lp_link_state != LINK_STATE_UP ||
189 	    port->lp_link_duplex != LINK_DUPLEX_FULL) {
190 		/*
191 		 * Can't attach a MAC port with unknown link speed,
192 		 * down link, or not in full duplex mode.
193 		 */
194 		return (B_FALSE);
195 	}
196 
197 	if (grp->lg_ifspeed == 0) {
198 		/*
199 		 * The group inherits the speed of the first link being
200 		 * attached.
201 		 */
202 		grp->lg_ifspeed = port->lp_ifspeed;
203 		link_state_changed = B_TRUE;
204 	} else if (grp->lg_ifspeed != port->lp_ifspeed) {
205 		/*
206 		 * The link speed of the MAC port must be the same as
207 		 * the group link speed, as per 802.3ad. Since it is
208 		 * not, the attach is cancelled.
209 		 */
210 		return (B_FALSE);
211 	}
212 
213 	grp->lg_nattached_ports++;
214 
215 	/*
216 	 * Update the group link state.
217 	 */
218 	if (grp->lg_link_state != LINK_STATE_UP) {
219 		grp->lg_link_state = LINK_STATE_UP;
220 		grp->lg_link_duplex = LINK_DUPLEX_FULL;
221 		link_state_changed = B_TRUE;
222 	}
223 
224 	aggr_grp_multicst_port(port, B_TRUE);
225 
226 	/*
227 	 * Update port's state.
228 	 */
229 	port->lp_state = AGGR_PORT_STATE_ATTACHED;
230 
231 	/*
232 	 * Set port's receive callback
233 	 */
234 	port->lp_mrh = mac_rx_add(port->lp_mh, aggr_recv_cb, (void *)port);
235 
236 	/*
237 	 * If LACP is OFF, the port can be used to send data as soon
238 	 * as its link is up and verified to be compatible with the
239 	 * aggregation.
240 	 *
241 	 * If LACP is active or passive, notify the LACP subsystem, which
242 	 * will enable sending on the port following the LACP protocol.
243 	 */
244 	if (grp->lg_lacp_mode == AGGR_LACP_OFF)
245 		aggr_send_port_enable(port);
246 	else
247 		aggr_lacp_port_attached(port);
248 
249 	return (link_state_changed);
250 }
251 
252 boolean_t
253 aggr_grp_detach_port(aggr_grp_t *grp, aggr_port_t *port)
254 {
255 	boolean_t link_state_changed = B_FALSE;
256 
257 	ASSERT(RW_WRITE_HELD(&grp->lg_lock));
258 	ASSERT(RW_WRITE_HELD(&port->lp_lock));
259 	ASSERT(AGGR_LACP_LOCK_HELD(grp));
260 
261 	/* update state */
262 	if (port->lp_state != AGGR_PORT_STATE_ATTACHED)
263 		return (B_FALSE);
264 
265 	mac_rx_remove(port->lp_mh, port->lp_mrh);
266 	port->lp_state = AGGR_PORT_STATE_STANDBY;
267 
268 	aggr_grp_multicst_port(port, B_FALSE);
269 
270 	if (grp->lg_lacp_mode == AGGR_LACP_OFF)
271 		aggr_send_port_disable(port);
272 	else
273 		aggr_lacp_port_detached(port);
274 
275 	grp->lg_nattached_ports--;
276 	if (grp->lg_nattached_ports == 0) {
277 		/* the last attached MAC port of the group is being detached */
278 		grp->lg_ifspeed = 0;
279 		grp->lg_link_state = LINK_STATE_DOWN;
280 		grp->lg_link_duplex = LINK_DUPLEX_UNKNOWN;
281 		link_state_changed = B_TRUE;
282 	}
283 
284 	return (link_state_changed);
285 }
286 
287 /*
288  * Update the MAC addresses of the constituent ports of the specified
289  * group. This function is invoked:
290  * - after creating a new aggregation group.
291  * - after adding new ports to an aggregation group.
292  * - after removing a port from a group when the MAC address of
293  *   that port was used for the MAC address of the group.
294  * - after the MAC address of a port changed when the MAC address
295  *   of that port was used for the MAC address of the group.
296  *
297  * Return true if the link state of the aggregation changed, for example
298  * as a result of a failure changing the MAC address of one of the
299  * constituent ports.
300  */
301 boolean_t
302 aggr_grp_update_ports_mac(aggr_grp_t *grp)
303 {
304 	aggr_port_t *cport;
305 	boolean_t link_state_changed = B_FALSE;
306 
307 	ASSERT(RW_WRITE_HELD(&grp->lg_lock));
308 
309 	if (grp->lg_closing)
310 		return (link_state_changed);
311 
312 	for (cport = grp->lg_ports; cport != NULL;
313 	    cport = cport->lp_next) {
314 		rw_enter(&cport->lp_lock, RW_WRITER);
315 		if (aggr_port_unicst(cport, grp->lg_addr) != 0) {
316 			link_state_changed = link_state_changed ||
317 			    aggr_grp_detach_port(grp, cport);
318 		} else {
319 			/*
320 			 * If a port was detached because of a previous
321 			 * failure changing the MAC address, the port is
322 			 * reattached when it successfully changes the MAC
323 			 * address now, and this might cause the link state
324 			 * of the aggregation to change.
325 			 */
326 			link_state_changed = link_state_changed ||
327 			    aggr_grp_attach_port(grp, cport);
328 		}
329 		rw_exit(&cport->lp_lock);
330 	}
331 	return (link_state_changed);
332 }
333 
334 /*
335  * Invoked when the MAC address of a port has changed. If the port's
336  * MAC address was used for the group MAC address, set mac_addr_changedp
337  * to B_TRUE to indicate to the caller that it should send a MAC_NOTE_UNICST
338  * notification. If the link state changes due to detach/attach of
339  * the constituent port, set link_state_changedp to B_TRUE to indicate
340  * to the caller that it should send a MAC_NOTE_LINK notification. In both
341  * cases, it is the responsibility of the caller to invoke notification
342  * functions after releasing the the port lock.
343  */
344 void
345 aggr_grp_port_mac_changed(aggr_grp_t *grp, aggr_port_t *port,
346     boolean_t *mac_addr_changedp, boolean_t *link_state_changedp)
347 {
348 	ASSERT(AGGR_LACP_LOCK_HELD(grp));
349 	ASSERT(RW_WRITE_HELD(&grp->lg_lock));
350 	ASSERT(RW_WRITE_HELD(&port->lp_lock));
351 	ASSERT(mac_addr_changedp != NULL);
352 	ASSERT(link_state_changedp != NULL);
353 
354 	*mac_addr_changedp = B_FALSE;
355 	*link_state_changedp = B_FALSE;
356 
357 	if (grp->lg_addr_fixed) {
358 		/*
359 		 * The group is using a fixed MAC address or an automatic
360 		 * MAC address has not been set.
361 		 */
362 		return;
363 	}
364 
365 	if (grp->lg_mac_addr_port == port) {
366 		/*
367 		 * The MAC address of the port was assigned to the group
368 		 * MAC address. Update the group MAC address.
369 		 */
370 		bcopy(port->lp_addr, grp->lg_addr, ETHERADDRL);
371 		*mac_addr_changedp = B_TRUE;
372 	} else {
373 		/*
374 		 * Update the actual port MAC address to the MAC address
375 		 * of the group.
376 		 */
377 		if (aggr_port_unicst(port, grp->lg_addr) != 0) {
378 			*link_state_changedp = aggr_grp_detach_port(grp, port);
379 		} else {
380 			/*
381 			 * If a port was detached because of a previous
382 			 * failure changing the MAC address, the port is
383 			 * reattached when it successfully changes the MAC
384 			 * address now, and this might cause the link state
385 			 * of the aggregation to change.
386 			 */
387 			*link_state_changedp = aggr_grp_attach_port(grp, port);
388 		}
389 	}
390 }
391 
392 /*
393  * Add a port to a link aggregation group.
394  */
395 static int
396 aggr_grp_add_port(aggr_grp_t *grp, const char *name, uint_t portnum,
397     aggr_port_t **pp)
398 {
399 	aggr_port_t *port, **cport;
400 	int err;
401 
402 	ASSERT(AGGR_LACP_LOCK_HELD(grp));
403 	ASSERT(RW_WRITE_HELD(&grp->lg_lock));
404 
405 	/* create new port */
406 	err = aggr_port_create(name, portnum, &port);
407 	if (err != 0)
408 		return (err);
409 
410 	rw_enter(&port->lp_lock, RW_WRITER);
411 
412 	/* add port to list of group constituent ports */
413 	cport = &grp->lg_ports;
414 	while (*cport != NULL)
415 		cport = &((*cport)->lp_next);
416 	*cport = port;
417 
418 	/*
419 	 * Back reference to the group it is member of. A port always
420 	 * holds a reference to its group to ensure that the back
421 	 * reference is always valid.
422 	 */
423 	port->lp_grp = grp;
424 	AGGR_GRP_REFHOLD(grp);
425 	grp->lg_nports++;
426 
427 	aggr_lacp_init_port(port);
428 
429 	/*
430 	 * Initialize the callback functions for this port. Note that this
431 	 * can only be done after the lp_grp field is set.
432 	 */
433 	aggr_port_init_callbacks(port);
434 
435 	rw_exit(&port->lp_lock);
436 
437 	if (pp != NULL)
438 		*pp = port;
439 
440 	return (0);
441 }
442 
443 /*
444  * Add one or more ports to an existing link aggregation group.
445  */
446 int
447 aggr_grp_add_ports(uint32_t key, uint_t nports, laioc_port_t *ports)
448 {
449 	int rc, i, nadded = 0;
450 	aggr_grp_t *grp = NULL;
451 	aggr_port_t *port;
452 	boolean_t link_state_changed = B_FALSE;
453 
454 	/* get group corresponding to key */
455 	rw_enter(&aggr_grp_lock, RW_READER);
456 	if (mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key),
457 	    (mod_hash_val_t *)&grp) != 0) {
458 		rw_exit(&aggr_grp_lock);
459 		return (ENOENT);
460 	}
461 	AGGR_GRP_REFHOLD(grp);
462 	rw_exit(&aggr_grp_lock);
463 
464 	AGGR_LACP_LOCK(grp);
465 	rw_enter(&grp->lg_lock, RW_WRITER);
466 
467 	/* add the specified ports to group */
468 	for (i = 0; i < nports; i++) {
469 		/* add port to group */
470 		if ((rc = aggr_grp_add_port(grp, ports[i].lp_devname,
471 		    ports[i].lp_port, &port)) != 0)
472 			goto bail;
473 		ASSERT(port != NULL);
474 		nadded++;
475 
476 		/* check capabilities */
477 		if (!aggr_grp_capab_check(grp, port)) {
478 			rc = ENOTSUP;
479 			goto bail;
480 		}
481 
482 		/* start port if group has already been started */
483 		if (grp->lg_started) {
484 			rw_enter(&port->lp_lock, RW_WRITER);
485 			rc = aggr_port_start(port);
486 			if (rc != 0) {
487 				rw_exit(&port->lp_lock);
488 				goto bail;
489 			}
490 
491 			/* set port promiscuous mode */
492 			rc = aggr_port_promisc(port, grp->lg_promisc);
493 			if (rc != 0) {
494 				rw_exit(&port->lp_lock);
495 				goto bail;
496 			}
497 			rw_exit(&port->lp_lock);
498 		}
499 
500 		/*
501 		 * Attach each port if necessary.
502 		 */
503 		link_state_changed = link_state_changed ||
504 		    aggr_port_notify_link(grp, port, B_FALSE);
505 	}
506 
507 	/* update the MAC address of the constituent ports */
508 	link_state_changed = link_state_changed ||
509 	    aggr_grp_update_ports_mac(grp);
510 
511 	if (link_state_changed)
512 		mac_link_update(&grp->lg_mac, grp->lg_link_state);
513 
514 bail:
515 	if (rc != 0) {
516 		/* stop and remove ports that have been added */
517 		for (i = 0; i < nadded && !grp->lg_closing; i++) {
518 			port = aggr_grp_port_lookup(grp, ports[i].lp_devname,
519 			    ports[i].lp_port);
520 			ASSERT(port != NULL);
521 			if (grp->lg_started) {
522 				rw_enter(&port->lp_lock, RW_WRITER);
523 				aggr_port_stop(port);
524 				rw_exit(&port->lp_lock);
525 			}
526 			(void) aggr_grp_rem_port(grp, port, NULL, NULL);
527 		}
528 	}
529 
530 	rw_exit(&grp->lg_lock);
531 	AGGR_LACP_UNLOCK(grp);
532 	if (rc == 0 && !grp->lg_closing)
533 		mac_resource_update(&grp->lg_mac);
534 	AGGR_GRP_REFRELE(grp);
535 	return (rc);
536 }
537 
538 /*
539  * Update properties of an existing link aggregation group.
540  */
541 int
542 aggr_grp_modify(uint32_t key, aggr_grp_t *grp_arg, uint8_t update_mask,
543     uint32_t policy, boolean_t mac_fixed, const uchar_t *mac_addr,
544     aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer)
545 {
546 	int rc = 0;
547 	aggr_grp_t *grp = NULL;
548 	boolean_t mac_addr_changed = B_FALSE;
549 	boolean_t link_state_changed = B_FALSE;
550 
551 	if (grp_arg == NULL) {
552 		/* get group corresponding to key */
553 		rw_enter(&aggr_grp_lock, RW_READER);
554 		if (mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key),
555 		    (mod_hash_val_t *)&grp) != 0) {
556 			rc = ENOENT;
557 			goto bail;
558 		}
559 		AGGR_LACP_LOCK(grp);
560 		rw_enter(&grp->lg_lock, RW_WRITER);
561 	} else {
562 		grp = grp_arg;
563 		ASSERT(AGGR_LACP_LOCK_HELD(grp));
564 		ASSERT(RW_WRITE_HELD(&grp->lg_lock));
565 	}
566 
567 	ASSERT(RW_WRITE_HELD(&grp->lg_lock) || RW_READ_HELD(&grp->lg_lock));
568 	AGGR_GRP_REFHOLD(grp);
569 
570 	/* validate fixed address if specified */
571 	if ((update_mask & AGGR_MODIFY_MAC) && mac_fixed &&
572 	    ((bcmp(aggr_zero_mac, mac_addr, ETHERADDRL) == 0) ||
573 	    (mac_addr[0] & 0x01))) {
574 		rc = EINVAL;
575 		goto bail;
576 	}
577 
578 	/* update policy if requested */
579 	if (update_mask & AGGR_MODIFY_POLICY)
580 		aggr_send_update_policy(grp, policy);
581 
582 	/* update unicast MAC address if requested */
583 	if (update_mask & AGGR_MODIFY_MAC) {
584 		if (mac_fixed) {
585 			/* user-supplied MAC address */
586 			grp->lg_mac_addr_port = NULL;
587 			if (bcmp(mac_addr, grp->lg_addr, ETHERADDRL) != 0) {
588 				bcopy(mac_addr, grp->lg_addr, ETHERADDRL);
589 				mac_addr_changed = B_TRUE;
590 			}
591 		} else if (grp->lg_addr_fixed) {
592 			/* switch from user-supplied to automatic */
593 			aggr_port_t *port = grp->lg_ports;
594 
595 			rw_enter(&port->lp_lock, RW_WRITER);
596 			bcopy(port->lp_addr, grp->lg_addr, ETHERADDRL);
597 			grp->lg_mac_addr_port = port;
598 			mac_addr_changed = B_TRUE;
599 			rw_exit(&port->lp_lock);
600 		}
601 		grp->lg_addr_fixed = mac_fixed;
602 	}
603 
604 	if (mac_addr_changed)
605 		link_state_changed = aggr_grp_update_ports_mac(grp);
606 
607 	if (update_mask & AGGR_MODIFY_LACP_MODE)
608 		aggr_lacp_update_mode(grp, lacp_mode);
609 
610 	if ((update_mask & AGGR_MODIFY_LACP_TIMER) && !grp->lg_closing)
611 		aggr_lacp_update_timer(grp, lacp_timer);
612 
613 bail:
614 	if (grp != NULL && !grp->lg_closing) {
615 		/*
616 		 * If grp_arg is non-NULL, this function is called from
617 		 * mac_unicst_set(), and the MAC_NOTE_UNICST notification
618 		 * will be sent there.
619 		 */
620 		if ((grp_arg == NULL) && mac_addr_changed)
621 			mac_unicst_update(&grp->lg_mac, grp->lg_addr);
622 
623 		if (link_state_changed)
624 			mac_link_update(&grp->lg_mac, grp->lg_link_state);
625 
626 	}
627 
628 	if (grp_arg == NULL) {
629 		if (grp != NULL) {
630 			rw_exit(&grp->lg_lock);
631 			AGGR_LACP_UNLOCK(grp);
632 		}
633 		rw_exit(&aggr_grp_lock);
634 	}
635 
636 	if (grp != NULL)
637 		AGGR_GRP_REFRELE(grp);
638 
639 	return (rc);
640 }
641 
642 /*
643  * Create a new link aggregation group upon request from administrator.
644  * Returns 0 on success, an errno on failure.
645  */
646 int
647 aggr_grp_create(uint32_t key, uint_t nports, laioc_port_t *ports,
648     uint32_t policy, boolean_t mac_fixed, uchar_t *mac_addr,
649     aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer)
650 {
651 	aggr_grp_t *grp = NULL;
652 	aggr_port_t *port;
653 	mac_t *mac;
654 	mac_info_t *mip;
655 	boolean_t link_state_changed;
656 	int err;
657 	int i;
658 
659 	/* need at least one port */
660 	if (nports == 0)
661 		return (EINVAL);
662 
663 	rw_enter(&aggr_grp_lock, RW_WRITER);
664 
665 	/* does a group with the same key already exist? */
666 	err = mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key),
667 	    (mod_hash_val_t *)&grp);
668 	if (err == 0) {
669 		rw_exit(&aggr_grp_lock);
670 		return (EEXIST);
671 	}
672 
673 	grp = kmem_cache_alloc(aggr_grp_cache, KM_SLEEP);
674 
675 	AGGR_LACP_LOCK(grp);
676 	rw_enter(&grp->lg_lock, RW_WRITER);
677 
678 	grp->lg_refs = 1;
679 	grp->lg_closing = B_FALSE;
680 	grp->lg_key = key;
681 
682 	grp->lg_ifspeed = 0;
683 	grp->lg_link_state = LINK_STATE_UNKNOWN;
684 	grp->lg_link_duplex = LINK_DUPLEX_UNKNOWN;
685 	grp->lg_started = B_FALSE;
686 	grp->lg_promisc = B_FALSE;
687 	aggr_lacp_init_grp(grp);
688 
689 	/* add MAC ports to group */
690 	grp->lg_ports = NULL;
691 	grp->lg_nports = 0;
692 	grp->lg_nattached_ports = 0;
693 	grp->lg_ntx_ports = 0;
694 
695 	for (i = 0; i < nports; i++) {
696 		err = aggr_grp_add_port(grp, ports[i].lp_devname,
697 		    ports[i].lp_port, NULL);
698 		if (err != 0)
699 			goto bail;
700 	}
701 
702 	/*
703 	 * If no explicit MAC address was specified by the administrator,
704 	 * set it to the MAC address of the first port.
705 	 */
706 	grp->lg_addr_fixed = mac_fixed;
707 	if (grp->lg_addr_fixed) {
708 		/* validate specified address */
709 		if (bcmp(aggr_zero_mac, mac_addr, ETHERADDRL) == 0) {
710 			err = EINVAL;
711 			goto bail;
712 		}
713 		bcopy(mac_addr, grp->lg_addr, ETHERADDRL);
714 	} else {
715 		bcopy(grp->lg_ports->lp_addr, grp->lg_addr, ETHERADDRL);
716 		grp->lg_mac_addr_port = grp->lg_ports;
717 	}
718 
719 	/*
720 	 * Update the MAC address of the constituent ports.
721 	 * None of the port is attached at this time, the link state of the
722 	 * aggregation will not change.
723 	 */
724 	link_state_changed = aggr_grp_update_ports_mac(grp);
725 	ASSERT(!link_state_changed);
726 
727 	/* update outbound load balancing policy */
728 	aggr_send_update_policy(grp, policy);
729 
730 	/* register with the MAC module */
731 	mac = &grp->lg_mac;
732 	bzero(mac, sizeof (*mac));
733 
734 	mac->m_ident = MAC_IDENT;
735 
736 	mac->m_driver = grp;
737 	mac->m_dip = aggr_dip;
738 	mac->m_port = key;
739 
740 	mip = &(mac->m_info);
741 	mip->mi_media = DL_ETHER;
742 	mip->mi_sdu_min = 0;
743 	mip->mi_sdu_max = ETHERMTU;
744 
745 	MAC_STAT_MIB(mip->mi_stat);
746 	MAC_STAT_ETHER(mip->mi_stat);
747 	mip->mi_stat[MAC_STAT_LINK_DUPLEX] = B_TRUE;
748 
749 	mip->mi_addr_length = ETHERADDRL;
750 	bcopy(aggr_brdcst_mac, mip->mi_brdcst_addr, ETHERADDRL);
751 	bcopy(grp->lg_addr, mip->mi_unicst_addr, ETHERADDRL);
752 
753 	mac->m_stat = aggr_m_stat;
754 	mac->m_start = aggr_m_start;
755 	mac->m_stop = aggr_m_stop;
756 	mac->m_promisc = aggr_m_promisc;
757 	mac->m_multicst = aggr_m_multicst;
758 	mac->m_unicst = aggr_m_unicst;
759 	mac->m_tx = aggr_m_tx;
760 	mac->m_resources = aggr_m_resources;
761 	mac->m_ioctl = aggr_m_ioctl;
762 
763 	/* set the initial group capabilities */
764 	aggr_grp_capab_set(grp);
765 
766 	if ((err = mac_register(mac)) != 0)
767 		goto bail;
768 
769 	/* set LACP mode */
770 	aggr_lacp_set_mode(grp, lacp_mode, lacp_timer);
771 
772 	/*
773 	 * Attach each port if necessary.
774 	 */
775 	for (port = grp->lg_ports; port != NULL; port = port->lp_next)
776 		(void) aggr_port_notify_link(grp, port, B_FALSE);
777 
778 	/* add new group to hash table */
779 	err = mod_hash_insert(aggr_grp_hash, GRP_HASH_KEY(key),
780 	    (mod_hash_val_t)grp);
781 	ASSERT(err == 0);
782 	aggr_grp_cnt++;
783 
784 	rw_exit(&grp->lg_lock);
785 	AGGR_LACP_UNLOCK(grp);
786 	rw_exit(&aggr_grp_lock);
787 	return (0);
788 
789 bail:
790 	if (grp != NULL) {
791 		aggr_port_t *cport;
792 
793 		grp->lg_closing = B_TRUE;
794 
795 		port = grp->lg_ports;
796 		while (port != NULL) {
797 			cport = port->lp_next;
798 			aggr_port_delete(port);
799 			port = cport;
800 		}
801 
802 		rw_exit(&grp->lg_lock);
803 		AGGR_LACP_UNLOCK(grp);
804 
805 		kmem_cache_free(aggr_grp_cache, grp);
806 	}
807 
808 	rw_exit(&aggr_grp_lock);
809 	return (err);
810 }
811 
812 /*
813  * Return a pointer to the member of a group with specified device name
814  * and port number.
815  */
816 static aggr_port_t *
817 aggr_grp_port_lookup(aggr_grp_t *grp, const char *devname, uint32_t portnum)
818 {
819 	aggr_port_t *port;
820 
821 	ASSERT(RW_WRITE_HELD(&grp->lg_lock) || RW_READ_HELD(&grp->lg_lock));
822 
823 	for (port = grp->lg_ports; port != NULL; port = port->lp_next) {
824 		if ((strcmp(port->lp_devname, devname) == 0) &&
825 		    (port->lp_port == portnum))
826 			break;
827 	}
828 
829 	return (port);
830 }
831 
832 /*
833  * Stop, detach and remove a port from a link aggregation group.
834  */
835 static int
836 aggr_grp_rem_port(aggr_grp_t *grp, aggr_port_t *port,
837     boolean_t *mac_addr_changedp, boolean_t *link_state_changedp)
838 {
839 	int rc = 0;
840 	aggr_port_t **pport;
841 	boolean_t mac_addr_changed = B_FALSE;
842 	boolean_t link_state_changed = B_FALSE;
843 	uint64_t val;
844 	uint_t i;
845 
846 	ASSERT(AGGR_LACP_LOCK_HELD(grp));
847 	ASSERT(RW_WRITE_HELD(&grp->lg_lock));
848 	ASSERT(grp->lg_nports > 1);
849 	ASSERT(!grp->lg_closing);
850 
851 	/* unlink port */
852 	for (pport = &grp->lg_ports; *pport != port;
853 	    pport = &(*pport)->lp_next) {
854 		if (*pport == NULL) {
855 			rc = ENOENT;
856 			goto done;
857 		}
858 	}
859 	*pport = port->lp_next;
860 
861 	atomic_add_32(&port->lp_closing, 1);
862 
863 	rw_enter(&port->lp_lock, RW_WRITER);
864 
865 	/*
866 	 * If the MAC address of the port being removed was assigned
867 	 * to the group, update the group MAC address
868 	 * using the MAC address of a different port.
869 	 */
870 	if (!grp->lg_addr_fixed && grp->lg_mac_addr_port == port) {
871 		/*
872 		 * Set the MAC address of the group to the
873 		 * MAC address of its first port.
874 		 */
875 		bcopy(grp->lg_ports->lp_addr, grp->lg_addr, ETHERADDRL);
876 		grp->lg_mac_addr_port = grp->lg_ports;
877 		mac_addr_changed = B_TRUE;
878 	}
879 
880 	link_state_changed = aggr_grp_detach_port(grp, port);
881 
882 	/*
883 	 * Add the statistics of the ports while it was aggregated
884 	 * to the group's residual statistics.
885 	 */
886 	for (i = 0; i < MAC_NSTAT && !grp->lg_closing; i++) {
887 		/* avoid stats that are not counters */
888 		if (i == MAC_STAT_IFSPEED || i == MAC_STAT_LINK_DUPLEX)
889 			continue;
890 
891 		/* get current value */
892 		val = aggr_port_stat(port, i);
893 		/* subtract value at the point of aggregation */
894 		val -= port->lp_stat[i];
895 		/* add to the residual stat */
896 		grp->lg_stat[i] += val;
897 	}
898 
899 	grp->lg_nports--;
900 
901 	rw_exit(&port->lp_lock);
902 
903 	aggr_port_delete(port);
904 
905 	/*
906 	 * If the group MAC address has changed, update the MAC address of
907 	 * the remaining consistuent ports according to the new MAC
908 	 * address of the group.
909 	 */
910 	if (mac_addr_changed)
911 		link_state_changed = link_state_changed ||
912 		    aggr_grp_update_ports_mac(grp);
913 
914 done:
915 	if (mac_addr_changedp != NULL)
916 		*mac_addr_changedp = mac_addr_changed;
917 	if (link_state_changedp != NULL)
918 		*link_state_changedp = link_state_changed;
919 
920 	return (rc);
921 }
922 
923 /*
924  * Remove one or more ports from an existing link aggregation group.
925  */
926 int
927 aggr_grp_rem_ports(uint32_t key, uint_t nports, laioc_port_t *ports)
928 {
929 	int rc = 0, i;
930 	aggr_grp_t *grp = NULL;
931 	aggr_port_t *port;
932 	boolean_t mac_addr_update = B_FALSE, mac_addr_changed;
933 	boolean_t link_state_update = B_FALSE, link_state_changed;
934 
935 	/* get group corresponding to key */
936 	rw_enter(&aggr_grp_lock, RW_READER);
937 	if (mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key),
938 	    (mod_hash_val_t *)&grp) != 0) {
939 		rw_exit(&aggr_grp_lock);
940 		return (ENOENT);
941 	}
942 	AGGR_GRP_REFHOLD(grp);
943 	rw_exit(&aggr_grp_lock);
944 
945 	AGGR_LACP_LOCK(grp);
946 	rw_enter(&grp->lg_lock, RW_WRITER);
947 
948 	/* we need to keep at least one port per group */
949 	if (nports >= grp->lg_nports) {
950 		rc = EINVAL;
951 		goto bail;
952 	}
953 
954 	/* first verify that all the groups are valid */
955 	for (i = 0; i < nports; i++) {
956 		if (aggr_grp_port_lookup(grp, ports[i].lp_devname,
957 		    ports[i].lp_port) == NULL) {
958 			/* port not found */
959 			rc = ENOENT;
960 			goto bail;
961 		}
962 	}
963 
964 	/* remove the specified ports from group */
965 	for (i = 0; i < nports && !grp->lg_closing; i++) {
966 		/* lookup port */
967 		port = aggr_grp_port_lookup(grp, ports[i].lp_devname,
968 		    ports[i].lp_port);
969 		ASSERT(port != NULL);
970 
971 		/* stop port if group has already been started */
972 		if (grp->lg_started) {
973 			rw_enter(&port->lp_lock, RW_WRITER);
974 			aggr_port_stop(port);
975 			rw_exit(&port->lp_lock);
976 		}
977 
978 		/* remove port from group */
979 		rc = aggr_grp_rem_port(grp, port, &mac_addr_changed,
980 		    &link_state_changed);
981 		ASSERT(rc == 0);
982 		mac_addr_update = mac_addr_update || mac_addr_changed;
983 		link_state_update = link_state_update || link_state_changed;
984 	}
985 
986 bail:
987 	rw_exit(&grp->lg_lock);
988 	AGGR_LACP_UNLOCK(grp);
989 	if (mac_addr_update)
990 		mac_unicst_update(&grp->lg_mac, grp->lg_addr);
991 	if (link_state_update)
992 		mac_link_update(&grp->lg_mac, grp->lg_link_state);
993 	if (rc == 0)
994 		mac_resource_update(&grp->lg_mac);
995 	AGGR_GRP_REFRELE(grp);
996 
997 	return (rc);
998 }
999 
1000 int
1001 aggr_grp_delete(uint32_t key)
1002 {
1003 	aggr_grp_t *grp = NULL;
1004 	aggr_port_t *port, *cport;
1005 	mod_hash_val_t val;
1006 
1007 	rw_enter(&aggr_grp_lock, RW_WRITER);
1008 
1009 	if (mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key),
1010 	    (mod_hash_val_t *)&grp) != 0) {
1011 		rw_exit(&aggr_grp_lock);
1012 		return (ENOENT);
1013 	}
1014 
1015 	AGGR_LACP_LOCK(grp);
1016 	rw_enter(&grp->lg_lock, RW_WRITER);
1017 
1018 	grp->lg_closing = B_TRUE;
1019 
1020 	/*
1021 	 * Unregister from the MAC service module. Since this can
1022 	 * fail if a client hasn't closed the MAC port, we gracefully
1023 	 * fail the operation.
1024 	 */
1025 	if (mac_unregister(&grp->lg_mac)) {
1026 		rw_exit(&grp->lg_lock);
1027 		AGGR_LACP_UNLOCK(grp);
1028 		rw_exit(&aggr_grp_lock);
1029 		return (EBUSY);
1030 	}
1031 
1032 	/* detach and free MAC ports associated with group */
1033 	port = grp->lg_ports;
1034 	while (port != NULL) {
1035 		cport = port->lp_next;
1036 		rw_enter(&port->lp_lock, RW_WRITER);
1037 		if (grp->lg_started)
1038 			aggr_port_stop(port);
1039 		(void) aggr_grp_detach_port(grp, port);
1040 		rw_exit(&port->lp_lock);
1041 		aggr_port_delete(port);
1042 		port = cport;
1043 	}
1044 
1045 	rw_exit(&grp->lg_lock);
1046 	AGGR_LACP_UNLOCK(grp);
1047 
1048 	(void) mod_hash_remove(aggr_grp_hash, GRP_HASH_KEY(key), &val);
1049 	ASSERT(grp == (aggr_grp_t *)val);
1050 
1051 	ASSERT(aggr_grp_cnt > 0);
1052 	aggr_grp_cnt--;
1053 
1054 	rw_exit(&aggr_grp_lock);
1055 	AGGR_GRP_REFRELE(grp);
1056 
1057 	return (0);
1058 }
1059 
1060 void
1061 aggr_grp_free(aggr_grp_t *grp)
1062 {
1063 	ASSERT(grp->lg_refs == 0);
1064 	kmem_cache_free(aggr_grp_cache, grp);
1065 }
1066 
1067 /*
1068  * Walker invoked when building the list of configured groups and
1069  * their ports that must be passed up to user-space.
1070  */
1071 
1072 /*ARGSUSED*/
1073 static uint_t
1074 aggr_grp_info_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
1075 {
1076 	aggr_grp_t *grp;
1077 	aggr_port_t *port;
1078 	aggr_grp_info_state_t *state = arg;
1079 
1080 	if (state->ls_rc != 0)
1081 		return (MH_WALK_TERMINATE);	/* terminate walk */
1082 
1083 	grp = (aggr_grp_t *)val;
1084 
1085 	rw_enter(&grp->lg_lock, RW_READER);
1086 
1087 	if (state->ls_group_key != 0 && grp->lg_key != state->ls_group_key)
1088 		goto bail;
1089 
1090 	state->ls_group_found = B_TRUE;
1091 
1092 	state->ls_rc = state->ls_new_grp_fn(state->ls_fn_arg, grp->lg_key,
1093 	    grp->lg_addr, grp->lg_addr_fixed, grp->lg_tx_policy,
1094 	    grp->lg_nports, grp->lg_lacp_mode, grp->aggr.PeriodicTimer);
1095 
1096 	if (state->ls_rc != 0)
1097 		goto bail;
1098 
1099 	for (port = grp->lg_ports; port != NULL; port = port->lp_next) {
1100 
1101 		rw_enter(&port->lp_lock, RW_READER);
1102 
1103 		state->ls_rc = state->ls_new_port_fn(state->ls_fn_arg,
1104 		    port->lp_devname, port->lp_port, port->lp_addr,
1105 		    port->lp_state, &port->lp_lacp.ActorOperPortState);
1106 
1107 		rw_exit(&port->lp_lock);
1108 
1109 		if (state->ls_rc != 0)
1110 			goto bail;
1111 	}
1112 
1113 bail:
1114 	rw_exit(&grp->lg_lock);
1115 	return ((state->ls_rc == 0) ? MH_WALK_CONTINUE : MH_WALK_TERMINATE);
1116 }
1117 
1118 int
1119 aggr_grp_info(uint_t *ngroups, uint32_t group_key, void *fn_arg,
1120     aggr_grp_info_new_grp_fn_t new_grp_fn,
1121     aggr_grp_info_new_port_fn_t new_port_fn)
1122 {
1123 	aggr_grp_info_state_t state;
1124 	int rc = 0;
1125 
1126 	rw_enter(&aggr_grp_lock, RW_READER);
1127 
1128 	*ngroups = aggr_grp_cnt;
1129 
1130 	bzero(&state, sizeof (state));
1131 	state.ls_group_key = group_key;
1132 	state.ls_new_grp_fn = new_grp_fn;
1133 	state.ls_new_port_fn = new_port_fn;
1134 	state.ls_fn_arg = fn_arg;
1135 
1136 	mod_hash_walk(aggr_grp_hash, aggr_grp_info_walker, &state);
1137 
1138 	if ((rc = state.ls_rc) == 0 && group_key != 0 &&
1139 	    !state.ls_group_found)
1140 		rc = ENOENT;
1141 
1142 	rw_exit(&aggr_grp_lock);
1143 	return (rc);
1144 }
1145 
1146 static void
1147 aggr_m_resources(void *arg)
1148 {
1149 	aggr_grp_t *grp = arg;
1150 	aggr_port_t *port;
1151 
1152 	/* Call each port's m_resources function */
1153 	for (port = grp->lg_ports; port != NULL; port = port->lp_next)
1154 		mac_resources(port->lp_mh);
1155 }
1156 
1157 /*ARGSUSED*/
1158 static void
1159 aggr_m_ioctl(void *arg, queue_t *q, mblk_t *mp)
1160 {
1161 	miocnak(q, mp, 0, ENOTSUP);
1162 }
1163 
1164 static uint64_t
1165 aggr_m_stat(void *arg, enum mac_stat stat)
1166 {
1167 	aggr_grp_t *grp = arg;
1168 	aggr_port_t *port;
1169 	uint64_t val;
1170 
1171 	rw_enter(&grp->lg_lock, RW_READER);
1172 
1173 	switch (stat) {
1174 	case MAC_STAT_IFSPEED:
1175 		val = grp->lg_ifspeed;
1176 		break;
1177 	case MAC_STAT_LINK_DUPLEX:
1178 		val = grp->lg_link_duplex;
1179 		break;
1180 	default:
1181 		/*
1182 		 * The remaining statistics are counters. They are computed
1183 		 * by aggregating the counters of the members MACs while they
1184 		 * were aggregated, plus the residual counter of the group
1185 		 * itself, which is updated each time a MAC is removed from
1186 		 * the group.
1187 		 */
1188 		val = 0;
1189 		for (port = grp->lg_ports; port != NULL; port = port->lp_next) {
1190 			/* actual port statistic */
1191 			val += aggr_port_stat(port, stat);
1192 			/* minus the port stat when it was added */
1193 			val -= port->lp_stat[stat];
1194 			/* plus any residual amount for the group */
1195 			val += grp->lg_stat[stat];
1196 		}
1197 	}
1198 
1199 	rw_exit(&grp->lg_lock);
1200 	return (val);
1201 }
1202 
1203 static int
1204 aggr_m_start(void *arg)
1205 {
1206 	aggr_grp_t *grp = arg;
1207 	aggr_port_t *port;
1208 
1209 	AGGR_LACP_LOCK(grp);
1210 	rw_enter(&grp->lg_lock, RW_WRITER);
1211 
1212 	/*
1213 	 * Attempts to start all configured members of the group.
1214 	 * Group members will be attached when their link-up notification
1215 	 * is received.
1216 	 */
1217 	for (port = grp->lg_ports; port != NULL; port = port->lp_next) {
1218 		rw_enter(&port->lp_lock, RW_WRITER);
1219 		if (aggr_port_start(port) != 0) {
1220 			rw_exit(&port->lp_lock);
1221 			continue;
1222 		}
1223 
1224 		/* set port promiscuous mode */
1225 		if (aggr_port_promisc(port, grp->lg_promisc) != 0)
1226 			aggr_port_stop(port);
1227 		rw_exit(&port->lp_lock);
1228 	}
1229 
1230 	grp->lg_started = B_TRUE;
1231 
1232 	rw_exit(&grp->lg_lock);
1233 	AGGR_LACP_UNLOCK(grp);
1234 
1235 	return (0);
1236 }
1237 
1238 static void
1239 aggr_m_stop(void *arg)
1240 {
1241 	aggr_grp_t *grp = arg;
1242 	aggr_port_t *port;
1243 
1244 	rw_enter(&grp->lg_lock, RW_WRITER);
1245 
1246 	for (port = grp->lg_ports; port != NULL; port = port->lp_next) {
1247 		rw_enter(&port->lp_lock, RW_WRITER);
1248 		aggr_port_stop(port);
1249 		rw_exit(&port->lp_lock);
1250 	}
1251 
1252 	grp->lg_started = B_FALSE;
1253 
1254 	rw_exit(&grp->lg_lock);
1255 }
1256 
1257 static int
1258 aggr_m_promisc(void *arg, boolean_t on)
1259 {
1260 	aggr_grp_t *grp = arg;
1261 	aggr_port_t *port;
1262 	boolean_t link_state_changed = B_FALSE;
1263 
1264 	AGGR_LACP_LOCK(grp);
1265 	rw_enter(&grp->lg_lock, RW_WRITER);
1266 	AGGR_GRP_REFHOLD(grp);
1267 
1268 	ASSERT(!grp->lg_closing);
1269 
1270 	if (on == grp->lg_promisc)
1271 		goto bail;
1272 
1273 	for (port = grp->lg_ports; port != NULL; port = port->lp_next) {
1274 		rw_enter(&port->lp_lock, RW_WRITER);
1275 		AGGR_PORT_REFHOLD(port);
1276 		if (port->lp_started) {
1277 			if (aggr_port_promisc(port, on) != 0) {
1278 				link_state_changed = link_state_changed ||
1279 				    aggr_grp_detach_port(grp, port);
1280 			} else {
1281 				/*
1282 				 * If a port was detached because of a previous
1283 				 * failure changing the promiscuity, the port
1284 				 * is reattached when it successfully changes
1285 				 * the promiscuity now, and this might cause
1286 				 * the link state of the aggregation to change.
1287 				 */
1288 				link_state_changed = link_state_changed ||
1289 				    aggr_grp_attach_port(grp, port);
1290 			}
1291 		}
1292 		rw_exit(&port->lp_lock);
1293 		AGGR_PORT_REFRELE(port);
1294 	}
1295 
1296 	grp->lg_promisc = on;
1297 
1298 	if (link_state_changed)
1299 		mac_link_update(&grp->lg_mac, grp->lg_link_state);
1300 
1301 bail:
1302 	rw_exit(&grp->lg_lock);
1303 	AGGR_LACP_UNLOCK(grp);
1304 	AGGR_GRP_REFRELE(grp);
1305 
1306 	return (0);
1307 }
1308 
1309 /*
1310  * Add or remove the multicast addresses that are defined for the group
1311  * to or from the specified port.
1312  * This function is called before stopping a port, before a port
1313  * is detached from a group, and when attaching a port to a group.
1314  */
1315 void
1316 aggr_grp_multicst_port(aggr_port_t *port, boolean_t add)
1317 {
1318 	aggr_grp_t *grp = port->lp_grp;
1319 
1320 	ASSERT(RW_WRITE_HELD(&port->lp_lock));
1321 	ASSERT(RW_WRITE_HELD(&grp->lg_lock) || RW_READ_HELD(&grp->lg_lock));
1322 
1323 	if (!port->lp_started)
1324 		return;
1325 
1326 	mac_multicst_refresh(&grp->lg_mac, aggr_port_multicst, port,
1327 	    add);
1328 }
1329 
1330 static int
1331 aggr_m_multicst(void *arg, boolean_t add, const uint8_t *addrp)
1332 {
1333 	aggr_grp_t *grp = arg;
1334 	aggr_port_t *port = NULL;
1335 	int err = 0, cerr;
1336 
1337 	rw_enter(&grp->lg_lock, RW_WRITER);
1338 	for (port = grp->lg_ports; port != NULL; port = port->lp_next) {
1339 		if (port->lp_state != AGGR_PORT_STATE_ATTACHED)
1340 			continue;
1341 		cerr = aggr_port_multicst(port, add, addrp);
1342 		if (cerr != 0 && err == 0)
1343 			err = cerr;
1344 	}
1345 	rw_exit(&grp->lg_lock);
1346 	return (err);
1347 }
1348 
1349 static int
1350 aggr_m_unicst(void *arg, const uint8_t *macaddr)
1351 {
1352 	aggr_grp_t *grp = arg;
1353 	int rc;
1354 
1355 	AGGR_LACP_LOCK(grp);
1356 	rw_enter(&grp->lg_lock, RW_WRITER);
1357 	rc = aggr_grp_modify(0, grp, AGGR_MODIFY_MAC, 0, B_TRUE, macaddr,
1358 	    0, 0);
1359 	rw_exit(&grp->lg_lock);
1360 	AGGR_LACP_UNLOCK(grp);
1361 
1362 	return (rc);
1363 }
1364 
1365 /*
1366  * Initialize the capabilities that are advertised for the group
1367  * according to the capabilities of the constituent ports.
1368  */
1369 static void
1370 aggr_grp_capab_set(aggr_grp_t *grp)
1371 {
1372 	uint32_t cksum = (uint32_t)-1;
1373 	uint32_t poll = DL_CAPAB_POLL;
1374 	aggr_port_t *port;
1375 	const mac_info_t *port_mi;
1376 
1377 	ASSERT(RW_WRITE_HELD(&grp->lg_lock));
1378 
1379 	ASSERT(grp->lg_ports != NULL);
1380 	for (port = grp->lg_ports; port != NULL; port = port->lp_next) {
1381 		port_mi = mac_info(port->lp_mh);
1382 		cksum &= port_mi->mi_cksum;
1383 		poll &= port_mi->mi_poll;
1384 	}
1385 
1386 	grp->lg_mac.m_info.mi_cksum = cksum;
1387 	grp->lg_mac.m_info.mi_poll = poll;
1388 }
1389 
1390 /*
1391  * Checks whether the capabilities of the ports being added are compatible
1392  * with the current capabilities of the aggregation.
1393  */
1394 static boolean_t
1395 aggr_grp_capab_check(aggr_grp_t *grp, aggr_port_t *port)
1396 {
1397 	const mac_info_t *port_mi = mac_info(port->lp_mh);
1398 	uint32_t grp_cksum = grp->lg_mac.m_info.mi_cksum;
1399 
1400 	ASSERT(grp->lg_ports != NULL);
1401 
1402 	return (((grp_cksum & port_mi->mi_cksum) == grp_cksum) &&
1403 	    (grp->lg_mac.m_info.mi_poll == port_mi->mi_poll));
1404 }
1405