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 rw_exit(&port->lp_lock); 430 431 if (pp != NULL) 432 *pp = port; 433 434 return (0); 435 } 436 437 /* 438 * Add one or more ports to an existing link aggregation group. 439 */ 440 int 441 aggr_grp_add_ports(uint32_t key, uint_t nports, laioc_port_t *ports) 442 { 443 int rc, i, nadded = 0; 444 aggr_grp_t *grp = NULL; 445 aggr_port_t *port; 446 447 /* get group corresponding to key */ 448 rw_enter(&aggr_grp_lock, RW_READER); 449 if (mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key), 450 (mod_hash_val_t *)&grp) != 0) { 451 rw_exit(&aggr_grp_lock); 452 return (ENOENT); 453 } 454 AGGR_GRP_REFHOLD(grp); 455 rw_exit(&aggr_grp_lock); 456 457 AGGR_LACP_LOCK(grp); 458 rw_enter(&grp->lg_lock, RW_WRITER); 459 460 /* add the specified ports to group */ 461 for (i = 0; i < nports; i++) { 462 /* add port to group */ 463 if ((rc = aggr_grp_add_port(grp, ports[i].lp_devname, 464 ports[i].lp_port, &port)) != 0) 465 goto bail; 466 ASSERT(port != NULL); 467 nadded++; 468 469 /* check capabilities */ 470 if (!aggr_grp_capab_check(grp, port)) { 471 rc = ENOTSUP; 472 goto bail; 473 } 474 475 /* start port if group has already been started */ 476 if (grp->lg_started) { 477 rw_enter(&port->lp_lock, RW_WRITER); 478 rc = aggr_port_start(port); 479 if (rc != 0) { 480 rw_exit(&port->lp_lock); 481 goto bail; 482 } 483 484 /* set port promiscuous mode */ 485 rc = aggr_port_promisc(port, grp->lg_promisc); 486 if (rc != 0) { 487 rw_exit(&port->lp_lock); 488 goto bail; 489 } 490 rw_exit(&port->lp_lock); 491 } 492 } 493 494 /* update the MAC address of the constituent ports */ 495 if (aggr_grp_update_ports_mac(grp)) 496 mac_link_update(&grp->lg_mac, grp->lg_link_state); 497 498 bail: 499 if (rc != 0) { 500 /* stop and remove ports that have been added */ 501 for (i = 0; i < nadded && !grp->lg_closing; i++) { 502 port = aggr_grp_port_lookup(grp, ports[i].lp_devname, 503 ports[i].lp_port); 504 ASSERT(port != NULL); 505 if (grp->lg_started) { 506 rw_enter(&port->lp_lock, RW_WRITER); 507 aggr_port_stop(port); 508 rw_exit(&port->lp_lock); 509 } 510 (void) aggr_grp_rem_port(grp, port, NULL, NULL); 511 } 512 } 513 514 rw_exit(&grp->lg_lock); 515 AGGR_LACP_UNLOCK(grp); 516 if (rc == 0 && !grp->lg_closing) 517 mac_resource_update(&grp->lg_mac); 518 AGGR_GRP_REFRELE(grp); 519 return (rc); 520 } 521 522 /* 523 * Update properties of an existing link aggregation group. 524 */ 525 int 526 aggr_grp_modify(uint32_t key, aggr_grp_t *grp_arg, uint8_t update_mask, 527 uint32_t policy, boolean_t mac_fixed, const uchar_t *mac_addr, 528 aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer) 529 { 530 int rc = 0; 531 aggr_grp_t *grp = NULL; 532 boolean_t mac_addr_changed = B_FALSE; 533 boolean_t link_state_changed = B_FALSE; 534 535 if (grp_arg == NULL) { 536 /* get group corresponding to key */ 537 rw_enter(&aggr_grp_lock, RW_READER); 538 if (mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key), 539 (mod_hash_val_t *)&grp) != 0) { 540 rc = ENOENT; 541 goto bail; 542 } 543 AGGR_LACP_LOCK(grp); 544 rw_enter(&grp->lg_lock, RW_WRITER); 545 } else { 546 grp = grp_arg; 547 ASSERT(AGGR_LACP_LOCK_HELD(grp)); 548 ASSERT(RW_WRITE_HELD(&grp->lg_lock)); 549 } 550 551 ASSERT(RW_WRITE_HELD(&grp->lg_lock) || RW_READ_HELD(&grp->lg_lock)); 552 AGGR_GRP_REFHOLD(grp); 553 554 /* validate fixed address if specified */ 555 if ((update_mask & AGGR_MODIFY_MAC) && mac_fixed && 556 ((bcmp(aggr_zero_mac, mac_addr, ETHERADDRL) == 0) || 557 (mac_addr[0] & 0x01))) { 558 rc = EINVAL; 559 goto bail; 560 } 561 562 /* update policy if requested */ 563 if (update_mask & AGGR_MODIFY_POLICY) 564 aggr_send_update_policy(grp, policy); 565 566 /* update unicast MAC address if requested */ 567 if (update_mask & AGGR_MODIFY_MAC) { 568 if (mac_fixed) { 569 /* user-supplied MAC address */ 570 grp->lg_mac_addr_port = NULL; 571 if (bcmp(mac_addr, grp->lg_addr, ETHERADDRL) != 0) { 572 bcopy(mac_addr, grp->lg_addr, ETHERADDRL); 573 mac_addr_changed = B_TRUE; 574 } 575 } else if (grp->lg_addr_fixed) { 576 /* switch from user-supplied to automatic */ 577 aggr_port_t *port = grp->lg_ports; 578 579 rw_enter(&port->lp_lock, RW_WRITER); 580 bcopy(port->lp_addr, grp->lg_addr, ETHERADDRL); 581 grp->lg_mac_addr_port = port; 582 mac_addr_changed = B_TRUE; 583 rw_exit(&port->lp_lock); 584 } 585 grp->lg_addr_fixed = mac_fixed; 586 } 587 588 if (mac_addr_changed) 589 link_state_changed = aggr_grp_update_ports_mac(grp); 590 591 if (update_mask & AGGR_MODIFY_LACP_MODE) 592 aggr_lacp_update_mode(grp, lacp_mode); 593 594 if ((update_mask & AGGR_MODIFY_LACP_TIMER) && !grp->lg_closing) 595 aggr_lacp_update_timer(grp, lacp_timer); 596 597 bail: 598 if (grp != NULL && !grp->lg_closing) { 599 /* 600 * If grp_arg is non-NULL, this function is called from 601 * mac_unicst_set(), and the MAC_NOTE_UNICST notification 602 * will be sent there. 603 */ 604 if ((grp_arg == NULL) && mac_addr_changed) 605 mac_unicst_update(&grp->lg_mac, grp->lg_addr); 606 607 if (link_state_changed) 608 mac_link_update(&grp->lg_mac, grp->lg_link_state); 609 610 } 611 612 if (grp_arg == NULL) { 613 if (grp != NULL) { 614 rw_exit(&grp->lg_lock); 615 AGGR_LACP_UNLOCK(grp); 616 } 617 rw_exit(&aggr_grp_lock); 618 } 619 620 if (grp != NULL) 621 AGGR_GRP_REFRELE(grp); 622 623 return (rc); 624 } 625 626 /* 627 * Create a new link aggregation group upon request from administrator. 628 * Returns 0 on success, an errno on failure. 629 */ 630 int 631 aggr_grp_create(uint32_t key, uint_t nports, laioc_port_t *ports, 632 uint32_t policy, boolean_t mac_fixed, uchar_t *mac_addr, 633 aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer) 634 { 635 aggr_grp_t *grp = NULL; 636 aggr_port_t *port; 637 mac_t *mac; 638 mac_info_t *mip; 639 boolean_t link_state_changed; 640 int err; 641 int i; 642 643 /* need at least one port */ 644 if (nports == 0) 645 return (EINVAL); 646 647 rw_enter(&aggr_grp_lock, RW_WRITER); 648 649 /* does a group with the same key already exist? */ 650 err = mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key), 651 (mod_hash_val_t *)&grp); 652 if (err == 0) { 653 rw_exit(&aggr_grp_lock); 654 return (EEXIST); 655 } 656 657 grp = kmem_cache_alloc(aggr_grp_cache, KM_SLEEP); 658 659 AGGR_LACP_LOCK(grp); 660 rw_enter(&grp->lg_lock, RW_WRITER); 661 662 grp->lg_refs = 1; 663 grp->lg_closing = B_FALSE; 664 grp->lg_key = key; 665 666 grp->lg_ifspeed = 0; 667 grp->lg_link_state = LINK_STATE_UNKNOWN; 668 grp->lg_link_duplex = LINK_DUPLEX_UNKNOWN; 669 grp->lg_started = B_FALSE; 670 grp->lg_promisc = B_FALSE; 671 aggr_lacp_init_grp(grp); 672 673 /* add MAC ports to group */ 674 grp->lg_ports = NULL; 675 grp->lg_nports = 0; 676 grp->lg_nattached_ports = 0; 677 grp->lg_ntx_ports = 0; 678 679 for (i = 0; i < nports; i++) { 680 err = aggr_grp_add_port(grp, ports[i].lp_devname, 681 ports[i].lp_port, NULL); 682 if (err != 0) 683 goto bail; 684 } 685 686 /* 687 * If no explicit MAC address was specified by the administrator, 688 * set it to the MAC address of the first port. 689 */ 690 grp->lg_addr_fixed = mac_fixed; 691 if (grp->lg_addr_fixed) { 692 /* validate specified address */ 693 if (bcmp(aggr_zero_mac, mac_addr, ETHERADDRL) == 0) { 694 err = EINVAL; 695 goto bail; 696 } 697 bcopy(mac_addr, grp->lg_addr, ETHERADDRL); 698 } else { 699 bcopy(grp->lg_ports->lp_addr, grp->lg_addr, ETHERADDRL); 700 grp->lg_mac_addr_port = grp->lg_ports; 701 } 702 703 /* 704 * Update the MAC address of the constituent ports. 705 * None of the port is attached at this time, the link state of the 706 * aggregation will not change. 707 */ 708 link_state_changed = aggr_grp_update_ports_mac(grp); 709 ASSERT(!link_state_changed); 710 711 /* update outbound load balancing policy */ 712 aggr_send_update_policy(grp, policy); 713 714 /* register with the MAC module */ 715 mac = &grp->lg_mac; 716 bzero(mac, sizeof (*mac)); 717 718 mac->m_ident = MAC_IDENT; 719 720 mac->m_driver = grp; 721 mac->m_dip = aggr_dip; 722 mac->m_port = key; 723 724 mip = &(mac->m_info); 725 mip->mi_media = DL_ETHER; 726 mip->mi_sdu_min = 0; 727 mip->mi_sdu_max = ETHERMTU; 728 729 MAC_STAT_MIB(mip->mi_stat); 730 MAC_STAT_ETHER(mip->mi_stat); 731 mip->mi_stat[MAC_STAT_LINK_DUPLEX] = B_TRUE; 732 733 mip->mi_addr_length = ETHERADDRL; 734 bcopy(aggr_brdcst_mac, mip->mi_brdcst_addr, ETHERADDRL); 735 bcopy(grp->lg_addr, mip->mi_unicst_addr, ETHERADDRL); 736 737 mac->m_stat = aggr_m_stat; 738 mac->m_start = aggr_m_start; 739 mac->m_stop = aggr_m_stop; 740 mac->m_promisc = aggr_m_promisc; 741 mac->m_multicst = aggr_m_multicst; 742 mac->m_unicst = aggr_m_unicst; 743 mac->m_tx = aggr_m_tx; 744 mac->m_resources = aggr_m_resources; 745 mac->m_ioctl = aggr_m_ioctl; 746 747 /* set the initial group capabilities */ 748 aggr_grp_capab_set(grp); 749 750 if ((err = mac_register(mac)) != 0) 751 goto bail; 752 753 /* set LACP mode */ 754 aggr_lacp_set_mode(grp, lacp_mode, lacp_timer); 755 756 /* add new group to hash table */ 757 err = mod_hash_insert(aggr_grp_hash, GRP_HASH_KEY(key), 758 (mod_hash_val_t)grp); 759 ASSERT(err == 0); 760 aggr_grp_cnt++; 761 762 rw_exit(&grp->lg_lock); 763 AGGR_LACP_UNLOCK(grp); 764 rw_exit(&aggr_grp_lock); 765 return (0); 766 767 bail: 768 if (grp != NULL) { 769 aggr_port_t *cport; 770 771 grp->lg_closing = B_TRUE; 772 773 port = grp->lg_ports; 774 while (port != NULL) { 775 cport = port->lp_next; 776 aggr_port_delete(port); 777 port = cport; 778 } 779 780 rw_exit(&grp->lg_lock); 781 AGGR_LACP_UNLOCK(grp); 782 783 kmem_cache_free(aggr_grp_cache, grp); 784 } 785 786 rw_exit(&aggr_grp_lock); 787 return (err); 788 } 789 790 /* 791 * Return a pointer to the member of a group with specified device name 792 * and port number. 793 */ 794 static aggr_port_t * 795 aggr_grp_port_lookup(aggr_grp_t *grp, const char *devname, uint32_t portnum) 796 { 797 aggr_port_t *port; 798 799 ASSERT(RW_WRITE_HELD(&grp->lg_lock) || RW_READ_HELD(&grp->lg_lock)); 800 801 for (port = grp->lg_ports; port != NULL; port = port->lp_next) { 802 if ((strcmp(port->lp_devname, devname) == 0) && 803 (port->lp_port == portnum)) 804 break; 805 } 806 807 return (port); 808 } 809 810 /* 811 * Stop, detach and remove a port from a link aggregation group. 812 */ 813 static int 814 aggr_grp_rem_port(aggr_grp_t *grp, aggr_port_t *port, 815 boolean_t *mac_addr_changedp, boolean_t *link_state_changedp) 816 { 817 int rc = 0; 818 aggr_port_t **pport; 819 boolean_t mac_addr_changed = B_FALSE; 820 boolean_t link_state_changed = B_FALSE; 821 uint64_t val; 822 uint_t i; 823 824 ASSERT(AGGR_LACP_LOCK_HELD(grp)); 825 ASSERT(RW_WRITE_HELD(&grp->lg_lock)); 826 ASSERT(grp->lg_nports > 1); 827 ASSERT(!grp->lg_closing); 828 829 /* unlink port */ 830 for (pport = &grp->lg_ports; *pport != port; 831 pport = &(*pport)->lp_next) { 832 if (*pport == NULL) { 833 rc = ENOENT; 834 goto done; 835 } 836 } 837 *pport = port->lp_next; 838 839 atomic_add_32(&port->lp_closing, 1); 840 841 rw_enter(&port->lp_lock, RW_WRITER); 842 843 /* 844 * If the MAC address of the port being removed was assigned 845 * to the group, update the group MAC address 846 * using the MAC address of a different port. 847 */ 848 if (!grp->lg_addr_fixed && grp->lg_mac_addr_port == port) { 849 /* 850 * Set the MAC address of the group to the 851 * MAC address of its first port. 852 */ 853 bcopy(grp->lg_ports->lp_addr, grp->lg_addr, ETHERADDRL); 854 grp->lg_mac_addr_port = grp->lg_ports; 855 mac_addr_changed = B_TRUE; 856 } 857 858 link_state_changed = aggr_grp_detach_port(grp, port); 859 860 /* 861 * Add the statistics of the ports while it was aggregated 862 * to the group's residual statistics. 863 */ 864 for (i = 0; i < MAC_NSTAT && !grp->lg_closing; i++) { 865 /* avoid stats that are not counters */ 866 if (i == MAC_STAT_IFSPEED || i == MAC_STAT_LINK_DUPLEX) 867 continue; 868 869 /* get current value */ 870 val = aggr_port_stat(port, i); 871 /* subtract value at the point of aggregation */ 872 val -= port->lp_stat[i]; 873 /* add to the residual stat */ 874 grp->lg_stat[i] += val; 875 } 876 877 grp->lg_nports--; 878 879 rw_exit(&port->lp_lock); 880 881 aggr_port_delete(port); 882 883 /* 884 * If the group MAC address has changed, update the MAC address of 885 * the remaining consistuent ports according to the new MAC 886 * address of the group. 887 */ 888 if (mac_addr_changed) 889 link_state_changed = link_state_changed || 890 aggr_grp_update_ports_mac(grp); 891 892 done: 893 if (mac_addr_changedp != NULL) 894 *mac_addr_changedp = mac_addr_changed; 895 if (link_state_changedp != NULL) 896 *link_state_changedp = link_state_changed; 897 898 return (rc); 899 } 900 901 /* 902 * Remove one or more ports from an existing link aggregation group. 903 */ 904 int 905 aggr_grp_rem_ports(uint32_t key, uint_t nports, laioc_port_t *ports) 906 { 907 int rc = 0, i; 908 aggr_grp_t *grp = NULL; 909 aggr_port_t *port; 910 boolean_t mac_addr_update = B_FALSE, mac_addr_changed; 911 boolean_t link_state_update = B_FALSE, link_state_changed; 912 913 /* get group corresponding to key */ 914 rw_enter(&aggr_grp_lock, RW_READER); 915 if (mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key), 916 (mod_hash_val_t *)&grp) != 0) { 917 rw_exit(&aggr_grp_lock); 918 return (ENOENT); 919 } 920 AGGR_GRP_REFHOLD(grp); 921 rw_exit(&aggr_grp_lock); 922 923 AGGR_LACP_LOCK(grp); 924 rw_enter(&grp->lg_lock, RW_WRITER); 925 926 /* we need to keep at least one port per group */ 927 if (nports >= grp->lg_nports) { 928 rc = EINVAL; 929 goto bail; 930 } 931 932 /* first verify that all the groups are valid */ 933 for (i = 0; i < nports; i++) { 934 if (aggr_grp_port_lookup(grp, ports[i].lp_devname, 935 ports[i].lp_port) == NULL) { 936 /* port not found */ 937 rc = ENOENT; 938 goto bail; 939 } 940 } 941 942 /* remove the specified ports from group */ 943 for (i = 0; i < nports && !grp->lg_closing; i++) { 944 /* lookup port */ 945 port = aggr_grp_port_lookup(grp, ports[i].lp_devname, 946 ports[i].lp_port); 947 ASSERT(port != NULL); 948 949 /* stop port if group has already been started */ 950 if (grp->lg_started) { 951 rw_enter(&port->lp_lock, RW_WRITER); 952 aggr_port_stop(port); 953 rw_exit(&port->lp_lock); 954 } 955 956 /* remove port from group */ 957 rc = aggr_grp_rem_port(grp, port, &mac_addr_changed, 958 &link_state_changed); 959 ASSERT(rc == 0); 960 mac_addr_update = mac_addr_update || mac_addr_changed; 961 link_state_update = link_state_update || link_state_changed; 962 } 963 964 bail: 965 rw_exit(&grp->lg_lock); 966 AGGR_LACP_UNLOCK(grp); 967 if (mac_addr_update) 968 mac_unicst_update(&grp->lg_mac, grp->lg_addr); 969 if (link_state_update) 970 mac_link_update(&grp->lg_mac, grp->lg_link_state); 971 if (rc == 0) 972 mac_resource_update(&grp->lg_mac); 973 AGGR_GRP_REFRELE(grp); 974 975 return (rc); 976 } 977 978 int 979 aggr_grp_delete(uint32_t key) 980 { 981 aggr_grp_t *grp = NULL; 982 aggr_port_t *port, *cport; 983 mod_hash_val_t val; 984 985 rw_enter(&aggr_grp_lock, RW_WRITER); 986 987 if (mod_hash_find(aggr_grp_hash, GRP_HASH_KEY(key), 988 (mod_hash_val_t *)&grp) != 0) { 989 rw_exit(&aggr_grp_lock); 990 return (ENOENT); 991 } 992 993 AGGR_LACP_LOCK(grp); 994 rw_enter(&grp->lg_lock, RW_WRITER); 995 996 grp->lg_closing = B_TRUE; 997 998 /* 999 * Unregister from the MAC service module. Since this can 1000 * fail if a client hasn't closed the MAC port, we gracefully 1001 * fail the operation. 1002 */ 1003 if (mac_unregister(&grp->lg_mac)) { 1004 rw_exit(&grp->lg_lock); 1005 AGGR_LACP_UNLOCK(grp); 1006 rw_exit(&aggr_grp_lock); 1007 return (EBUSY); 1008 } 1009 1010 /* detach and free MAC ports associated with group */ 1011 port = grp->lg_ports; 1012 while (port != NULL) { 1013 cport = port->lp_next; 1014 rw_enter(&port->lp_lock, RW_WRITER); 1015 if (grp->lg_started) 1016 aggr_port_stop(port); 1017 (void) aggr_grp_detach_port(grp, port); 1018 rw_exit(&port->lp_lock); 1019 aggr_port_delete(port); 1020 port = cport; 1021 } 1022 1023 rw_exit(&grp->lg_lock); 1024 AGGR_LACP_UNLOCK(grp); 1025 1026 (void) mod_hash_remove(aggr_grp_hash, GRP_HASH_KEY(key), &val); 1027 ASSERT(grp == (aggr_grp_t *)val); 1028 1029 ASSERT(aggr_grp_cnt > 0); 1030 aggr_grp_cnt--; 1031 1032 rw_exit(&aggr_grp_lock); 1033 AGGR_GRP_REFRELE(grp); 1034 1035 return (0); 1036 } 1037 1038 void 1039 aggr_grp_free(aggr_grp_t *grp) 1040 { 1041 ASSERT(grp->lg_refs == 0); 1042 kmem_cache_free(aggr_grp_cache, grp); 1043 } 1044 1045 /* 1046 * Walker invoked when building the list of configured groups and 1047 * their ports that must be passed up to user-space. 1048 */ 1049 1050 /*ARGSUSED*/ 1051 static uint_t 1052 aggr_grp_info_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg) 1053 { 1054 aggr_grp_t *grp; 1055 aggr_port_t *port; 1056 aggr_grp_info_state_t *state = arg; 1057 1058 if (state->ls_rc != 0) 1059 return (MH_WALK_TERMINATE); /* terminate walk */ 1060 1061 grp = (aggr_grp_t *)val; 1062 1063 rw_enter(&grp->lg_lock, RW_READER); 1064 1065 if (state->ls_group_key != 0 && grp->lg_key != state->ls_group_key) 1066 goto bail; 1067 1068 state->ls_group_found = B_TRUE; 1069 1070 state->ls_rc = state->ls_new_grp_fn(state->ls_fn_arg, grp->lg_key, 1071 grp->lg_addr, grp->lg_addr_fixed, grp->lg_tx_policy, 1072 grp->lg_nports, grp->lg_lacp_mode, grp->aggr.PeriodicTimer); 1073 1074 if (state->ls_rc != 0) 1075 goto bail; 1076 1077 for (port = grp->lg_ports; port != NULL; port = port->lp_next) { 1078 1079 rw_enter(&port->lp_lock, RW_READER); 1080 1081 state->ls_rc = state->ls_new_port_fn(state->ls_fn_arg, 1082 port->lp_devname, port->lp_port, port->lp_addr, 1083 port->lp_state, &port->lp_lacp.ActorOperPortState); 1084 1085 rw_exit(&port->lp_lock); 1086 1087 if (state->ls_rc != 0) 1088 goto bail; 1089 } 1090 1091 bail: 1092 rw_exit(&grp->lg_lock); 1093 return ((state->ls_rc == 0) ? MH_WALK_CONTINUE : MH_WALK_TERMINATE); 1094 } 1095 1096 int 1097 aggr_grp_info(uint_t *ngroups, uint32_t group_key, void *fn_arg, 1098 aggr_grp_info_new_grp_fn_t new_grp_fn, 1099 aggr_grp_info_new_port_fn_t new_port_fn) 1100 { 1101 aggr_grp_info_state_t state; 1102 int rc = 0; 1103 1104 rw_enter(&aggr_grp_lock, RW_READER); 1105 1106 *ngroups = aggr_grp_cnt; 1107 1108 bzero(&state, sizeof (state)); 1109 state.ls_group_key = group_key; 1110 state.ls_new_grp_fn = new_grp_fn; 1111 state.ls_new_port_fn = new_port_fn; 1112 state.ls_fn_arg = fn_arg; 1113 1114 mod_hash_walk(aggr_grp_hash, aggr_grp_info_walker, &state); 1115 1116 if ((rc = state.ls_rc) == 0 && group_key != 0 && 1117 !state.ls_group_found) 1118 rc = ENOENT; 1119 1120 rw_exit(&aggr_grp_lock); 1121 return (rc); 1122 } 1123 1124 static void 1125 aggr_m_resources(void *arg) 1126 { 1127 aggr_grp_t *grp = arg; 1128 aggr_port_t *port; 1129 1130 /* Call each port's m_resources function */ 1131 for (port = grp->lg_ports; port != NULL; port = port->lp_next) 1132 mac_resources(port->lp_mh); 1133 } 1134 1135 /*ARGSUSED*/ 1136 static void 1137 aggr_m_ioctl(void *arg, queue_t *q, mblk_t *mp) 1138 { 1139 miocnak(q, mp, 0, ENOTSUP); 1140 } 1141 1142 static uint64_t 1143 aggr_m_stat(void *arg, enum mac_stat stat) 1144 { 1145 aggr_grp_t *grp = arg; 1146 aggr_port_t *port; 1147 uint64_t val; 1148 1149 rw_enter(&grp->lg_lock, RW_READER); 1150 1151 switch (stat) { 1152 case MAC_STAT_IFSPEED: 1153 val = grp->lg_ifspeed; 1154 break; 1155 case MAC_STAT_LINK_DUPLEX: 1156 val = grp->lg_link_duplex; 1157 break; 1158 default: 1159 /* 1160 * The remaining statistics are counters. They are computed 1161 * by aggregating the counters of the members MACs while they 1162 * were aggregated, plus the residual counter of the group 1163 * itself, which is updated each time a MAC is removed from 1164 * the group. 1165 */ 1166 val = 0; 1167 for (port = grp->lg_ports; port != NULL; port = port->lp_next) { 1168 /* actual port statistic */ 1169 val += aggr_port_stat(port, stat); 1170 /* minus the port stat when it was added */ 1171 val -= port->lp_stat[stat]; 1172 /* plus any residual amount for the group */ 1173 val += grp->lg_stat[stat]; 1174 } 1175 } 1176 1177 rw_exit(&grp->lg_lock); 1178 return (val); 1179 } 1180 1181 static int 1182 aggr_m_start(void *arg) 1183 { 1184 aggr_grp_t *grp = arg; 1185 aggr_port_t *port; 1186 1187 AGGR_LACP_LOCK(grp); 1188 rw_enter(&grp->lg_lock, RW_WRITER); 1189 1190 /* 1191 * Attempts to start all configured members of the group. 1192 * Group members will be attached when their link-up notification 1193 * is received. 1194 */ 1195 for (port = grp->lg_ports; port != NULL; port = port->lp_next) { 1196 rw_enter(&port->lp_lock, RW_WRITER); 1197 if (aggr_port_start(port) != 0) { 1198 rw_exit(&port->lp_lock); 1199 continue; 1200 } 1201 1202 /* set port promiscuous mode */ 1203 if (aggr_port_promisc(port, grp->lg_promisc) != 0) 1204 aggr_port_stop(port); 1205 rw_exit(&port->lp_lock); 1206 } 1207 1208 grp->lg_started = B_TRUE; 1209 1210 rw_exit(&grp->lg_lock); 1211 AGGR_LACP_UNLOCK(grp); 1212 1213 return (0); 1214 } 1215 1216 static void 1217 aggr_m_stop(void *arg) 1218 { 1219 aggr_grp_t *grp = arg; 1220 aggr_port_t *port; 1221 1222 rw_enter(&grp->lg_lock, RW_WRITER); 1223 1224 for (port = grp->lg_ports; port != NULL; port = port->lp_next) { 1225 rw_enter(&port->lp_lock, RW_WRITER); 1226 aggr_port_stop(port); 1227 rw_exit(&port->lp_lock); 1228 } 1229 1230 grp->lg_started = B_FALSE; 1231 1232 rw_exit(&grp->lg_lock); 1233 } 1234 1235 static int 1236 aggr_m_promisc(void *arg, boolean_t on) 1237 { 1238 aggr_grp_t *grp = arg; 1239 aggr_port_t *port; 1240 boolean_t link_state_changed = B_FALSE; 1241 1242 AGGR_LACP_LOCK(grp); 1243 rw_enter(&grp->lg_lock, RW_WRITER); 1244 AGGR_GRP_REFHOLD(grp); 1245 1246 ASSERT(!grp->lg_closing); 1247 1248 if (on == grp->lg_promisc) 1249 goto bail; 1250 1251 for (port = grp->lg_ports; port != NULL; port = port->lp_next) { 1252 rw_enter(&port->lp_lock, RW_WRITER); 1253 AGGR_PORT_REFHOLD(port); 1254 if (port->lp_started) { 1255 if (aggr_port_promisc(port, on) != 0) { 1256 link_state_changed = link_state_changed || 1257 aggr_grp_detach_port(grp, port); 1258 } else { 1259 /* 1260 * If a port was detached because of a previous 1261 * failure changing the promiscuity, the port 1262 * is reattached when it successfully changes 1263 * the promiscuity now, and this might cause 1264 * the link state of the aggregation to change. 1265 */ 1266 link_state_changed = link_state_changed || 1267 aggr_grp_attach_port(grp, port); 1268 } 1269 } 1270 rw_exit(&port->lp_lock); 1271 AGGR_PORT_REFRELE(port); 1272 } 1273 1274 grp->lg_promisc = on; 1275 1276 if (link_state_changed) 1277 mac_link_update(&grp->lg_mac, grp->lg_link_state); 1278 1279 bail: 1280 rw_exit(&grp->lg_lock); 1281 AGGR_LACP_UNLOCK(grp); 1282 AGGR_GRP_REFRELE(grp); 1283 1284 return (0); 1285 } 1286 1287 /* 1288 * Add or remove the multicast addresses that are defined for the group 1289 * to or from the specified port. 1290 * This function is called before stopping a port, before a port 1291 * is detached from a group, and when attaching a port to a group. 1292 */ 1293 void 1294 aggr_grp_multicst_port(aggr_port_t *port, boolean_t add) 1295 { 1296 aggr_grp_t *grp = port->lp_grp; 1297 1298 ASSERT(RW_WRITE_HELD(&port->lp_lock)); 1299 ASSERT(RW_WRITE_HELD(&grp->lg_lock) || RW_READ_HELD(&grp->lg_lock)); 1300 1301 if (!port->lp_started) 1302 return; 1303 1304 mac_multicst_refresh(&grp->lg_mac, aggr_port_multicst, port, 1305 add); 1306 } 1307 1308 static int 1309 aggr_m_multicst(void *arg, boolean_t add, const uint8_t *addrp) 1310 { 1311 aggr_grp_t *grp = arg; 1312 aggr_port_t *port = NULL; 1313 int err = 0, cerr; 1314 1315 rw_enter(&grp->lg_lock, RW_WRITER); 1316 for (port = grp->lg_ports; port != NULL; port = port->lp_next) { 1317 if (port->lp_state != AGGR_PORT_STATE_ATTACHED) 1318 continue; 1319 cerr = aggr_port_multicst(port, add, addrp); 1320 if (cerr != 0 && err == 0) 1321 err = cerr; 1322 } 1323 rw_exit(&grp->lg_lock); 1324 return (err); 1325 } 1326 1327 static int 1328 aggr_m_unicst(void *arg, const uint8_t *macaddr) 1329 { 1330 aggr_grp_t *grp = arg; 1331 int rc; 1332 1333 AGGR_LACP_LOCK(grp); 1334 rw_enter(&grp->lg_lock, RW_WRITER); 1335 rc = aggr_grp_modify(0, grp, AGGR_MODIFY_MAC, 0, B_TRUE, macaddr, 1336 0, 0); 1337 rw_exit(&grp->lg_lock); 1338 AGGR_LACP_UNLOCK(grp); 1339 1340 return (rc); 1341 } 1342 1343 /* 1344 * Initialize the capabilities that are advertised for the group 1345 * according to the capabilities of the constituent ports. 1346 */ 1347 static void 1348 aggr_grp_capab_set(aggr_grp_t *grp) 1349 { 1350 uint32_t cksum = (uint32_t)-1; 1351 uint32_t poll = DL_CAPAB_POLL; 1352 aggr_port_t *port; 1353 const mac_info_t *port_mi; 1354 1355 ASSERT(RW_WRITE_HELD(&grp->lg_lock)); 1356 1357 ASSERT(grp->lg_ports != NULL); 1358 for (port = grp->lg_ports; port != NULL; port = port->lp_next) { 1359 port_mi = mac_info(port->lp_mh); 1360 cksum &= port_mi->mi_cksum; 1361 poll &= port_mi->mi_poll; 1362 } 1363 1364 grp->lg_mac.m_info.mi_cksum = cksum; 1365 grp->lg_mac.m_info.mi_poll = poll; 1366 } 1367 1368 /* 1369 * Checks whether the capabilities of the ports being added are compatible 1370 * with the current capabilities of the aggregation. 1371 */ 1372 static boolean_t 1373 aggr_grp_capab_check(aggr_grp_t *grp, aggr_port_t *port) 1374 { 1375 const mac_info_t *port_mi = mac_info(port->lp_mh); 1376 uint32_t grp_cksum = grp->lg_mac.m_info.mi_cksum; 1377 1378 ASSERT(grp->lg_ports != NULL); 1379 1380 return (((grp_cksum & port_mi->mi_cksum) == grp_cksum) && 1381 (grp->lg_mac.m_info.mi_poll == port_mi->mi_poll)); 1382 } 1383