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