root/usr/src/uts/common/io/dls/dls.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2012, Nexenta Systems, Inc. All rights reserved.
 */

/*
 * Copyright 2019 Joyent, Inc.
 * Copyright 2025 Oxide Computer Company
 */

/*
 * Data-Link Services Module
 */

#include        <sys/stdbit.h>
#include        <sys/strsun.h>
#include        <sys/vlan.h>
#include        <sys/dld_impl.h>
#include        <sys/mac_client_priv.h>

int
dls_open(dls_link_t *dlp, dls_dl_handle_t ddh, dld_str_t *dsp)
{
        zoneid_t        zid = getzoneid();
        boolean_t       local;
        int             err;

        /*
         * Check whether this client belongs to the zone of this dlp. Note that
         * a global zone client is allowed to open a local zone dlp.
         */
        if (zid != GLOBAL_ZONEID && dlp->dl_zid != zid)
                return (ENOENT);

        /*
         * mac_start() is required for non-legacy MACs to show accurate
         * kstats even before the interface is brought up. For legacy
         * drivers, this is not needed. Further, calling mac_start() for
         * legacy drivers would make the shared-lower-stream to stay in
         * the DL_IDLE state, which in turn causes performance regression.
         */
        if (!mac_capab_get(dlp->dl_mh, MAC_CAPAB_LEGACY, NULL) &&
            ((err = mac_start(dlp->dl_mh)) != 0)) {
                return (err);
        }

        local = (zid == dlp->dl_zid);
        dlp->dl_zone_ref += (local ? 1 : 0);

        /*
         * Cache a copy of the MAC interface handle, a pointer to the
         * immutable MAC info.
         */
        dsp->ds_dlp = dlp;
        dsp->ds_mh = dlp->dl_mh;
        dsp->ds_mch = dlp->dl_mch;
        dsp->ds_mip = dlp->dl_mip;
        dsp->ds_ddh = ddh;
        dsp->ds_local = local;

        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));
        return (0);
}

void
dls_close(dld_str_t *dsp)
{
        dls_link_t              *dlp = dsp->ds_dlp;
        dls_multicst_addr_t     *p;
        dls_multicst_addr_t     *nextp;

        ASSERT(dsp->ds_datathr_cnt == 0);
        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));

        if (dsp->ds_local)
                dlp->dl_zone_ref--;
        dsp->ds_local = B_FALSE;

        /*
         * Walk the list of multicast addresses, disabling each at the MAC.
         * Note that we must remove multicast address before
         * mac_unicast_remove() (called by dls_active_clear()) because
         * mac_multicast_remove() relies on the unicast flows on the mac
         * client.
         */
        for (p = dsp->ds_dmap; p != NULL; p = nextp) {
                (void) mac_multicast_remove(dsp->ds_mch, p->dma_addr);
                nextp = p->dma_nextp;
                kmem_free(p, sizeof (dls_multicst_addr_t));
        }
        dsp->ds_dmap = NULL;

        dls_active_clear(dsp, B_TRUE);

        /*
         * If the dld_str_t is bound then unbind it.
         */
        if (dsp->ds_dlstate == DL_IDLE) {
                dls_unbind(dsp);
                dsp->ds_dlstate = DL_UNBOUND;
        }

        /*
         * If the MAC has been set in promiscuous mode then disable it.
         * This needs to be done before resetting ds_rx.
         */
        (void) dls_promisc(dsp, 0);

        /*
         * At this point we have cutoff inbound packet flow from the mac
         * for this 'dsp'. The dls_link_remove above cut off packets meant
         * for us and waited for upcalls to finish. Similarly the dls_promisc
         * reset above waited for promisc callbacks to finish. Now we can
         * safely reset ds_rx to NULL
         */
        dsp->ds_rx = NULL;
        dsp->ds_rx_arg = NULL;

        dsp->ds_dlp = NULL;

        if (!mac_capab_get(dsp->ds_mh, MAC_CAPAB_LEGACY, NULL))
                mac_stop(dsp->ds_mh);

        /*
         * Release our reference to the dls_link_t allowing that to be
         * destroyed if there are no more dls_impl_t.
         */
        dls_link_rele(dlp);
}

int
dls_bind(dld_str_t *dsp, uint32_t sap)
{
        uint32_t        dls_sap;

        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));

        /*
         * Check to see the value is legal for the media type.
         */
        if (!mac_sap_verify(dsp->ds_mh, sap, &dls_sap))
                return (EINVAL);

        if (dsp->ds_promisc & DLS_PROMISC_SAP)
                dls_sap = DLS_SAP_PROMISC;

        /*
         * Set up the dld_str_t to mark it as able to receive packets.
         */
        dsp->ds_sap = sap;

        /*
         * The MAC layer does the VLAN demultiplexing and will only pass up
         * untagged packets to non-promiscuous primary MAC clients. In order to
         * support binding to the VLAN SAP, which is required by DLPI, DLS
         * needs to get a copy of all tagged packets when the client binds to
         * the VLAN SAP. We do this by registering a separate promiscuous
         * callback for each DLS client binding to that SAP.
         *
         * Note: even though there are two promiscuous handles in dld_str_t,
         * ds_mph is for the regular promiscuous mode, ds_vlan_mph is the handle
         * to receive VLAN traffic when promiscuous mode is not on. Only one of
         * them can be non-NULL at the same time, to avoid receiving duplicate
         * copies of packets.
         */
        if (sap == ETHERTYPE_VLAN && dsp->ds_promisc == 0) {
                int err;

                if (dsp->ds_vlan_mph != NULL)
                        return (EINVAL);
                err = mac_promisc_add(dsp->ds_mch,
                    MAC_CLIENT_PROMISC_ALL, dls_rx_vlan_promisc, dsp,
                    &dsp->ds_vlan_mph, MAC_PROMISC_FLAGS_NO_PHYS);

                if (err == 0 && dsp->ds_nonip &&
                    dsp->ds_dlp->dl_nonip_cnt++ == 0)
                        mac_rx_bypass_disable(dsp->ds_mch);

                return (err);
        }

        /*
         * Now bind the dld_str_t by adding it into the hash table in the
         * dls_link_t.
         */
        dls_link_add(dsp->ds_dlp, dls_sap, dsp);
        if (dsp->ds_nonip && dsp->ds_dlp->dl_nonip_cnt++ == 0)
                mac_rx_bypass_disable(dsp->ds_mch);

        return (0);
}

void
dls_unbind(dld_str_t *dsp)
{
        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));

        if (dsp->ds_nonip && --dsp->ds_dlp->dl_nonip_cnt == 0)
                mac_rx_bypass_enable(dsp->ds_mch);

        /*
         * A VLAN SAP does not actually add itself to the STREAM head today.
         * While we initially set up a VLAN handle below, it's possible that
         * something else will have come in and clobbered it.
         */
        if (dsp->ds_sap == ETHERTYPE_VLAN) {
                if (dsp->ds_vlan_mph != NULL) {
                        mac_promisc_remove(dsp->ds_vlan_mph);
                        dsp->ds_vlan_mph = NULL;
                }
                return;
        }

        /*
         * Unbind the dld_str_t by removing it from the hash table in the
         * dls_link_t.
         */
        dls_link_remove(dsp->ds_dlp, dsp);
        dsp->ds_sap = 0;
}

/*
 * In order to prevent promiscuous-mode processing with dsp->ds_promisc
 * set to inaccurate values, this function sets dsp->ds_promisc with new
 * flags.  For enabling (mac_promisc_add), the flags are set prior to the
 * actual enabling.  For disabling (mac_promisc_remove), the flags are set
 * after the actual disabling.
 */
int
dls_promisc(dld_str_t *dsp, uint32_t new_flags)
{
        int err = 0;
        uint32_t old_flags = dsp->ds_promisc;
        const uint32_t option_flags = DLS_PROMISC_RX_ONLY |
            DLS_PROMISC_INCOMING | DLS_PROMISC_OUTGOING;
        uint32_t old_type = old_flags & ~option_flags;
        uint32_t new_type = new_flags & ~option_flags;
        mac_client_promisc_type_t mptype = MAC_CLIENT_PROMISC_ALL;
        uint16_t mac_flags = 0;

        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));
        ASSERT(!(new_flags & ~(DLS_PROMISC_SAP | DLS_PROMISC_MULTI |
            DLS_PROMISC_PHYS | option_flags)));

        /*
         * If the user has only requested DLS_PROMISC_MULTI then we need to make
         * sure that they don't see all packets.
         */
        if (new_type == DLS_PROMISC_MULTI)
                mptype = MAC_CLIENT_PROMISC_MULTI;

        /* Can choose only one of Rx-only or incoming/outoing. */
        if (stdc_count_ones_ui(new_flags & option_flags) > 1)
                return (EINVAL);

        /*
         * Look at new flags and figure out the correct mac promisc flags.
         * If we've only requested DLS_PROMISC_SAP and not _MULTI or _PHYS,
         * don't turn on physical promisc mode.
         *
         * Note that there are two MAC flags, MAC_PROMISC_FLAGS_NO_TX_LOOP and
         * MAC_PROMISC_FLAGS_RX_ONLY, which are similar but subtly different.
         * NO_TX_LOOP stops outgoing packets transmitted by a MAC client from
         * being delivered to a promsic handler on the same client. Whereas
         * RX_ONLY suppresses outgoing packets across all MAC clients. For
         * backwards compatibility, we map DLS_PROMISC_RX_ONLY to
         * MAC_PROMISC_FLAGS_NO_TX_LOOP and DLS_PROMISC_INCOMING/OUTGOING to
         * MAC_PROMISC_FLAGS_RX_ONLY/MAC_PROMISC_FLAGS_TX_ONLY.
         */
        if (new_flags & DLS_PROMISC_RX_ONLY)
                mac_flags |= MAC_PROMISC_FLAGS_NO_TX_LOOP;
        if (new_flags & DLS_PROMISC_INCOMING)
                mac_flags |= MAC_PROMISC_FLAGS_RX_ONLY;
        if (new_flags & DLS_PROMISC_OUTGOING)
                mac_flags |= MAC_PROMISC_FLAGS_TX_ONLY;
        if (new_type == DLS_PROMISC_SAP)
                mac_flags |= MAC_PROMISC_FLAGS_NO_PHYS;

        /*
         * There are three cases we care about here with respect to MAC. Going
         * from nothing to something, something to nothing, something to
         * something where we need to change how we're getting stuff from mac.
         * In the last case, as long as they're not equal, we need to assume
         * something has changed and do something about it.
         */
        if (old_type == 0 && new_type == 0) {
                /*
                 * If there are only option flags in the old and new flags
                 * then there is no need to talk to MAC. Just update the flags.
                 */
                dsp->ds_promisc = new_flags;
        } else if (old_type == 0 && new_type != 0) {
                dsp->ds_promisc = new_flags;
                err = mac_promisc_add(dsp->ds_mch, mptype,
                    dls_rx_promisc, dsp, &dsp->ds_mph, mac_flags);
                if (err != 0) {
                        dsp->ds_promisc = old_flags;
                        return (err);
                }

                /* Remove vlan promisc handle to avoid sending dup copy up */
                if (dsp->ds_vlan_mph != NULL) {
                        mac_promisc_remove(dsp->ds_vlan_mph);
                        dsp->ds_vlan_mph = NULL;
                }
        } else if (old_type != 0 && new_type == 0) {
                ASSERT(dsp->ds_mph != NULL);

                mac_promisc_remove(dsp->ds_mph);
                dsp->ds_promisc = new_flags;
                dsp->ds_mph = NULL;

                if (dsp->ds_sap == ETHERTYPE_VLAN &&
                    dsp->ds_dlstate != DL_UNBOUND) {
                        if (dsp->ds_vlan_mph != NULL)
                                return (EINVAL);
                        err = mac_promisc_add(dsp->ds_mch,
                            MAC_CLIENT_PROMISC_ALL, dls_rx_vlan_promisc, dsp,
                            &dsp->ds_vlan_mph, MAC_PROMISC_FLAGS_NO_PHYS);
                }
        } else if (new_flags != old_flags) {
                ASSERT(dsp->ds_mph != NULL);
                mac_promisc_remove(dsp->ds_mph);
                /* Honors both after-remove and before-add semantics! */
                dsp->ds_promisc = new_flags;
                err = mac_promisc_add(dsp->ds_mch, mptype,
                    dls_rx_promisc, dsp, &dsp->ds_mph, mac_flags);
                if (err != 0)
                        dsp->ds_promisc = old_flags;
        }

        return (err);
}

int
dls_multicst_add(dld_str_t *dsp, const uint8_t *addr)
{
        int                     err;
        dls_multicst_addr_t     **pp;
        dls_multicst_addr_t     *p;
        uint_t                  addr_length;

        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));

        /*
         * Check whether the address is in the list of enabled addresses for
         * this dld_str_t.
         */
        addr_length = dsp->ds_mip->mi_addr_length;

        /*
         * Protect against concurrent access of ds_dmap by data threads using
         * ds_rw_lock. The mac perimeter serializes the dls_multicst_add and
         * remove operations. Dropping the ds_rw_lock across mac calls is thus
         * ok and is also required by the locking protocol.
         */
        rw_enter(&dsp->ds_rw_lock, RW_WRITER);
        for (pp = &(dsp->ds_dmap); (p = *pp) != NULL; pp = &(p->dma_nextp)) {
                if (bcmp(addr, p->dma_addr, addr_length) == 0) {
                        /*
                         * It is there so there's nothing to do.
                         */
                        err = 0;
                        goto done;
                }
        }

        /*
         * Allocate a new list item and add it to the list.
         */
        p = kmem_zalloc(sizeof (dls_multicst_addr_t), KM_SLEEP);
        bcopy(addr, p->dma_addr, addr_length);
        *pp = p;
        rw_exit(&dsp->ds_rw_lock);

        /*
         * Enable the address at the MAC.
         */
        err = mac_multicast_add(dsp->ds_mch, addr);
        if (err == 0)
                return (0);

        /* Undo the operation as it has failed */
        rw_enter(&dsp->ds_rw_lock, RW_WRITER);
        ASSERT(*pp == p && p->dma_nextp == NULL);
        *pp = NULL;
        kmem_free(p, sizeof (dls_multicst_addr_t));
done:
        rw_exit(&dsp->ds_rw_lock);
        return (err);
}

int
dls_multicst_remove(dld_str_t *dsp, const uint8_t *addr)
{
        dls_multicst_addr_t     **pp;
        dls_multicst_addr_t     *p;
        uint_t                  addr_length;

        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));

        /*
         * Find the address in the list of enabled addresses for this
         * dld_str_t.
         */
        addr_length = dsp->ds_mip->mi_addr_length;

        /*
         * Protect against concurrent access to ds_dmap by data threads using
         * ds_rw_lock. The mac perimeter serializes the dls_multicst_add and
         * remove operations. Dropping the ds_rw_lock across mac calls is thus
         * ok and is also required by the locking protocol.
         */
        rw_enter(&dsp->ds_rw_lock, RW_WRITER);
        for (pp = &(dsp->ds_dmap); (p = *pp) != NULL; pp = &(p->dma_nextp)) {
                if (bcmp(addr, p->dma_addr, addr_length) == 0)
                        break;
        }

        /*
         * If we walked to the end of the list then the given address is
         * not currently enabled for this dld_str_t.
         */
        if (p == NULL) {
                rw_exit(&dsp->ds_rw_lock);
                return (ENOENT);
        }

        /*
         * Remove the address from the list.
         */
        *pp = p->dma_nextp;
        rw_exit(&dsp->ds_rw_lock);

        /*
         * Disable the address at the MAC.
         */
        mac_multicast_remove(dsp->ds_mch, addr);
        kmem_free(p, sizeof (dls_multicst_addr_t));
        return (0);
}

mblk_t *
dls_header(dld_str_t *dsp, const uint8_t *addr, uint16_t sap, uint_t pri,
    mblk_t **payloadp)
{
        uint16_t vid;
        size_t extra_len;
        uint16_t mac_sap;
        mblk_t *mp, *payload;
        boolean_t is_ethernet = (dsp->ds_mip->mi_media == DL_ETHER);
        struct ether_vlan_header *evhp;

        vid = mac_client_vid(dsp->ds_mch);
        payload = (payloadp == NULL) ? NULL : (*payloadp);

        /*
         * In the case of Ethernet, we need to tell mac_header() if we need
         * extra room beyond the Ethernet header for a VLAN header.  We'll
         * need to add a VLAN header if this isn't an ETHERTYPE_VLAN listener
         * (because such streams will be handling VLAN headers on their own)
         * and one of the following conditions is satisfied:
         *
         * - This is a VLAN stream
         * - This is a physical stream, the priority is not 0, and user
         *   priority tagging is allowed.
         */
        if (is_ethernet && sap != ETHERTYPE_VLAN &&
            (vid != VLAN_ID_NONE ||
            (pri != 0 && dsp->ds_dlp->dl_tagmode != LINK_TAGMODE_VLANONLY))) {
                extra_len = sizeof (struct ether_vlan_header) -
                    sizeof (struct ether_header);
                mac_sap = ETHERTYPE_VLAN;
        } else {
                extra_len = 0;
                mac_sap = sap;
        }

        mp = mac_header(dsp->ds_mh, addr, mac_sap, payload, extra_len);
        if (mp == NULL)
                return (NULL);

        if ((vid == VLAN_ID_NONE && (pri == 0 ||
            dsp->ds_dlp->dl_tagmode == LINK_TAGMODE_VLANONLY)) || !is_ethernet)
                return (mp);

        /*
         * Fill in the tag information.
         */
        ASSERT(MBLKL(mp) == sizeof (struct ether_header));
        if (extra_len != 0) {
                mp->b_wptr += extra_len;
                evhp = (struct ether_vlan_header *)mp->b_rptr;
                evhp->ether_tci = htons(VLAN_TCI(pri, ETHER_CFI, vid));
                evhp->ether_type = htons(sap);
        } else {
                /*
                 * The stream is ETHERTYPE_VLAN listener, so its VLAN tag is
                 * in the payload. Update the priority.
                 */
                struct ether_vlan_extinfo *extinfo;
                size_t len = sizeof (struct ether_vlan_extinfo);

                ASSERT(sap == ETHERTYPE_VLAN);
                ASSERT(payload != NULL);

                if ((DB_REF(payload) > 1) || (MBLKL(payload) < len)) {
                        mblk_t *newmp;

                        /*
                         * Because some DLS consumers only check the db_ref
                         * count of the first mblk, we pullup 'payload' into
                         * a single mblk.
                         */
                        newmp = msgpullup(payload, -1);
                        if ((newmp == NULL) || (MBLKL(newmp) < len)) {
                                freemsg(newmp);
                                freemsg(mp);
                                return (NULL);
                        } else {
                                freemsg(payload);
                                *payloadp = payload = newmp;
                        }
                }

                extinfo = (struct ether_vlan_extinfo *)payload->b_rptr;
                extinfo->ether_tci = htons(VLAN_TCI(pri, ETHER_CFI,
                    VLAN_ID(ntohs(extinfo->ether_tci))));
        }
        return (mp);
}

void
dls_rx_set(dld_str_t *dsp, dls_rx_t rx, void *arg)
{
        mutex_enter(&dsp->ds_lock);
        dsp->ds_rx = rx;
        dsp->ds_rx_arg = arg;
        mutex_exit(&dsp->ds_lock);
}

static boolean_t
dls_accept_common(dld_str_t *dsp, mac_header_info_t *mhip, dls_rx_t *ds_rx,
    void **ds_rx_arg, boolean_t promisc, boolean_t promisc_loopback)
{
        dls_multicst_addr_t     *dmap;
        size_t                  addr_length = dsp->ds_mip->mi_addr_length;

        /*
         * We must not accept packets if the dld_str_t is not marked as bound
         * or is being removed.
         */
        if (dsp->ds_dlstate != DL_IDLE)
                goto refuse;

        if (dsp->ds_promisc != 0) {
                /*
                 * Filter out packets that arrived from the data path
                 * (i_dls_link_rx) when promisc mode is on. We need to correlate
                 * the ds_promisc flags with the mac header destination type. If
                 * only DLS_PROMISC_MULTI is enabled, we need to only reject
                 * multicast packets as those are the only ones which filter up
                 * the promiscuous path. If we have DLS_PROMISC_PHYS or
                 * DLS_PROMISC_SAP set, then we know that we'll be seeing
                 * everything, so we should drop it now.
                 */
                if (!promisc && !(dsp->ds_promisc == DLS_PROMISC_MULTI &&
                    mhip->mhi_dsttype != MAC_ADDRTYPE_MULTICAST))
                        goto refuse;
                /*
                 * If the dls_impl_t is in 'all physical' mode then
                 * always accept.
                 */
                if (dsp->ds_promisc & DLS_PROMISC_PHYS)
                        goto accept;

                /*
                 * Loopback packets i.e. packets sent out by DLS on a given
                 * mac end point, will be accepted back by DLS on loopback
                 * from the mac, only in the 'all physical' mode which has been
                 * covered by the previous check above
                 */
                if (promisc_loopback)
                        goto refuse;
        }

        switch (mhip->mhi_dsttype) {
        case MAC_ADDRTYPE_UNICAST:
        case MAC_ADDRTYPE_BROADCAST:
                /*
                 * We can accept unicast and broadcast packets because
                 * filtering is already done by the mac layer.
                 */
                goto accept;
        case MAC_ADDRTYPE_MULTICAST:
                /*
                 * Additional filtering is needed for multicast addresses
                 * because different streams may be interested in different
                 * addresses.
                 */
                if (dsp->ds_promisc & DLS_PROMISC_MULTI)
                        goto accept;

                rw_enter(&dsp->ds_rw_lock, RW_READER);
                for (dmap = dsp->ds_dmap; dmap != NULL;
                    dmap = dmap->dma_nextp) {
                        if (memcmp(mhip->mhi_daddr, dmap->dma_addr,
                            addr_length) == 0) {
                                rw_exit(&dsp->ds_rw_lock);
                                goto accept;
                        }
                }
                rw_exit(&dsp->ds_rw_lock);
                break;
        }

refuse:
        return (B_FALSE);

accept:
        /*
         * the returned ds_rx and ds_rx_arg will always be in sync.
         */
        mutex_enter(&dsp->ds_lock);
        *ds_rx = dsp->ds_rx;
        *ds_rx_arg = dsp->ds_rx_arg;
        mutex_exit(&dsp->ds_lock);

        return (B_TRUE);
}

/* ARGSUSED */
boolean_t
dls_accept(dld_str_t *dsp, mac_header_info_t *mhip, dls_rx_t *ds_rx,
    void **ds_rx_arg)
{
        return (dls_accept_common(dsp, mhip, ds_rx, ds_rx_arg, B_FALSE,
            B_FALSE));
}

boolean_t
dls_accept_promisc(dld_str_t *dsp, mac_header_info_t *mhip, dls_rx_t *ds_rx,
    void **ds_rx_arg, boolean_t loopback)
{
        return (dls_accept_common(dsp, mhip, ds_rx, ds_rx_arg, B_TRUE,
            loopback));
}

int
dls_mac_active_set(dls_link_t *dlp)
{
        int err = 0;

        /*
         * First client; add the primary unicast address.
         */
        if (dlp->dl_nactive == 0) {
                /*
                 * First client; add the primary unicast address.
                 */
                mac_diag_t diag;

                /* request the primary MAC address */
                if ((err = mac_unicast_add(dlp->dl_mch, NULL,
                    MAC_UNICAST_PRIMARY | MAC_UNICAST_TAG_DISABLE |
                    MAC_UNICAST_DISABLE_TX_VID_CHECK, &dlp->dl_mah,
                    VLAN_ID_NONE, &diag)) != 0) {
                        return (err);
                }

                /*
                 * Set the function to start receiving packets.
                 */
                mac_rx_set(dlp->dl_mch, i_dls_link_rx, dlp);
        }
        dlp->dl_nactive++;
        return (0);
}

void
dls_mac_active_clear(dls_link_t *dlp)
{
        if (--dlp->dl_nactive == 0) {
                ASSERT(dlp->dl_mah != NULL);
                (void) mac_unicast_remove(dlp->dl_mch, dlp->dl_mah);
                dlp->dl_mah = NULL;
                mac_rx_clear(dlp->dl_mch);
        }
}

int
dls_active_set(dld_str_t *dsp)
{
        int err = 0;

        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));

        if (dsp->ds_passivestate == DLD_PASSIVE)
                return (0);

        /* If we're already active, then there's nothing more to do. */
        if ((dsp->ds_nactive == 0) &&
            ((err = dls_mac_active_set(dsp->ds_dlp)) != 0)) {
                /* except for ENXIO all other errors are mapped to EBUSY */
                if (err != ENXIO)
                        return (EBUSY);
                return (err);
        }

        dsp->ds_passivestate = DLD_ACTIVE;
        dsp->ds_nactive++;
        return (0);
}

/*
 * Note that dls_active_set() is called whenever an active operation
 * (DL_BIND_REQ, DL_ENABMULTI_REQ ...) is processed and
 * dls_active_clear(dsp, B_FALSE) is called whenever the active operation
 * is being undone (DL_UNBIND_REQ, DL_DISABMULTI_REQ ...). In some cases,
 * a stream is closed without every active operation being undone and we
 * need to clear all the "active" states by calling
 * dls_active_clear(dsp, B_TRUE).
 */
void
dls_active_clear(dld_str_t *dsp, boolean_t all)
{
        ASSERT(MAC_PERIM_HELD(dsp->ds_mh));

        if (dsp->ds_passivestate == DLD_PASSIVE)
                return;

        if (all && dsp->ds_nactive == 0)
                return;

        ASSERT(dsp->ds_nactive > 0);

        dsp->ds_nactive -= (all ? dsp->ds_nactive : 1);
        if (dsp->ds_nactive != 0)
                return;

        ASSERT(dsp->ds_passivestate == DLD_ACTIVE);
        dls_mac_active_clear(dsp->ds_dlp);
        dsp->ds_passivestate = DLD_UNINITIALIZED;
}