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