root/usr/src/uts/common/io/i40e/i40e_gld.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2015 OmniTI Computer Consulting, Inc. All rights reserved.
 * Copyright 2022 Joyent, Inc.
 * Copyright 2017 Tegile Systems, Inc.  All rights reserved.
 * Copyright 2020 Ryan Zezeski
 * Copyright 2020 RackTop Systems, Inc.
 * Copyright 2025 Oxide Computer Company
 */

/*
 * For more information, please see the big theory statement in i40e_main.c.
 */

#include "i40e_sw.h"

#define I40E_PROP_RX_DMA_THRESH "_rx_dma_threshold"
#define I40E_PROP_TX_DMA_THRESH "_tx_dma_threshold"
#define I40E_PROP_RX_ITR        "_rx_intr_throttle"
#define I40E_PROP_TX_ITR        "_tx_intr_throttle"
#define I40E_PROP_OTHER_ITR     "_other_intr_throttle"

char *i40e_priv_props[] = {
        I40E_PROP_RX_DMA_THRESH,
        I40E_PROP_TX_DMA_THRESH,
        I40E_PROP_RX_ITR,
        I40E_PROP_TX_ITR,
        I40E_PROP_OTHER_ITR,
        NULL
};

static int
i40e_group_remove_mac(void *arg, const uint8_t *mac_addr)
{
        i40e_rx_group_t *rxg = arg;
        i40e_t *i40e = rxg->irg_i40e;
        struct i40e_aqc_remove_macvlan_element_data filt;
        struct i40e_hw *hw = &i40e->i40e_hw_space;
        int ret, i, last;
        i40e_uaddr_t *iua;

        if (I40E_IS_MULTICAST(mac_addr))
                return (EINVAL);

        mutex_enter(&i40e->i40e_general_lock);

        if (i40e->i40e_state & I40E_SUSPENDED) {
                ret = ECANCELED;
                goto done;
        }

        for (i = 0; i < i40e->i40e_resources.ifr_nmacfilt_used; i++) {
                if (bcmp(mac_addr, i40e->i40e_uaddrs[i].iua_mac,
                    ETHERADDRL) == 0)
                        break;
        }

        if (i == i40e->i40e_resources.ifr_nmacfilt_used) {
                ret = ENOENT;
                goto done;
        }

        iua = &i40e->i40e_uaddrs[i];
        ASSERT(i40e->i40e_resources.ifr_nmacfilt_used > 0);

        bzero(&filt, sizeof (filt));
        bcopy(mac_addr, filt.mac_addr, ETHERADDRL);
        filt.flags = I40E_AQC_MACVLAN_DEL_PERFECT_MATCH |
            I40E_AQC_MACVLAN_DEL_IGNORE_VLAN;

        if (i40e_aq_remove_macvlan(hw, iua->iua_vsi, &filt, 1, NULL) !=
            I40E_SUCCESS) {
                i40e_error(i40e, "failed to remove mac address "
                    "%02x:%02x:%02x:%02x:%02x:%02x from unicast filter: %d",
                    mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3],
                    mac_addr[4], mac_addr[5], filt.error_code);
                ret = EIO;
                goto done;
        }

        last = i40e->i40e_resources.ifr_nmacfilt_used - 1;
        if (i != last) {
                i40e_uaddr_t *src = &i40e->i40e_uaddrs[last];
                bcopy(src, iua, sizeof (i40e_uaddr_t));
        }

        /*
         * Set the multicast bit in the last one to indicate to ourselves that
         * it's invalid.
         */
        bzero(&i40e->i40e_uaddrs[last], sizeof (i40e_uaddr_t));
        i40e->i40e_uaddrs[last].iua_mac[0] = 0x01;
        i40e->i40e_resources.ifr_nmacfilt_used--;
        ret = 0;
done:
        mutex_exit(&i40e->i40e_general_lock);

        return (ret);
}

static int
i40e_group_add_mac(void *arg, const uint8_t *mac_addr)
{
        i40e_rx_group_t *rxg = arg;
        i40e_t          *i40e = rxg->irg_i40e;
        struct i40e_hw  *hw = &i40e->i40e_hw_space;
        int             i, ret;
        i40e_uaddr_t    *iua;
        struct i40e_aqc_add_macvlan_element_data filt;

        if (I40E_IS_MULTICAST(mac_addr))
                return (EINVAL);

        mutex_enter(&i40e->i40e_general_lock);
        if (i40e->i40e_state & I40E_SUSPENDED) {
                ret = ECANCELED;
                goto done;
        }

        if (i40e->i40e_resources.ifr_nmacfilt ==
            i40e->i40e_resources.ifr_nmacfilt_used) {
                ret = ENOSPC;
                goto done;
        }

        for (i = 0; i < i40e->i40e_resources.ifr_nmacfilt_used; i++) {
                if (bcmp(mac_addr, i40e->i40e_uaddrs[i].iua_mac,
                    ETHERADDRL) == 0) {
                        ret = EEXIST;
                        goto done;
                }
        }

        bzero(&filt, sizeof (filt));
        bcopy(mac_addr, filt.mac_addr, ETHERADDRL);
        filt.flags = I40E_AQC_MACVLAN_ADD_PERFECT_MATCH |
            I40E_AQC_MACVLAN_ADD_IGNORE_VLAN;

        if ((ret = i40e_aq_add_macvlan(hw, rxg->irg_vsi_seid, &filt, 1,
            NULL)) != I40E_SUCCESS) {
                i40e_error(i40e, "failed to add mac address "
                    "%02x:%02x:%02x:%02x:%02x:%02x to unicast filter: %d",
                    mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3],
                    mac_addr[4], mac_addr[5], ret);
                ret = EIO;
                goto done;
        }

        iua = &i40e->i40e_uaddrs[i40e->i40e_resources.ifr_nmacfilt_used];
        bcopy(mac_addr, iua->iua_mac, ETHERADDRL);
        iua->iua_vsi = rxg->irg_vsi_seid;
        i40e->i40e_resources.ifr_nmacfilt_used++;
        ASSERT(i40e->i40e_resources.ifr_nmacfilt_used <=
            i40e->i40e_resources.ifr_nmacfilt);
        ret = 0;
done:
        mutex_exit(&i40e->i40e_general_lock);
        return (ret);
}

static int
i40e_m_start(void *arg)
{
        i40e_t *i40e = arg;
        int rc = 0;

        mutex_enter(&i40e->i40e_general_lock);
        if (i40e->i40e_state & I40E_SUSPENDED) {
                rc = ECANCELED;
                goto done;
        }

        if (!i40e_start(i40e)) {
                rc = EIO;
                goto done;
        }

        atomic_or_32(&i40e->i40e_state, I40E_STARTED);
done:
        mutex_exit(&i40e->i40e_general_lock);

        return (rc);
}

static void
i40e_m_stop(void *arg)
{
        i40e_t *i40e = arg;

        mutex_enter(&i40e->i40e_general_lock);

        if (i40e->i40e_state & I40E_SUSPENDED)
                goto done;

        atomic_and_32(&i40e->i40e_state, ~I40E_STARTED);
        i40e_stop(i40e);
done:
        mutex_exit(&i40e->i40e_general_lock);
}

/*
 * Enable and disable promiscuous mode as requested. We have to toggle both
 * unicast and multicast. Note that multicast may already be enabled due to the
 * i40e_m_multicast may toggle it itself. See i40e_main.c for more information
 * on this.
 */
static int
i40e_m_promisc(void *arg, boolean_t on)
{
        i40e_t *i40e = arg;
        struct i40e_hw *hw = &i40e->i40e_hw_space;
        int ret = 0, err = 0;

        mutex_enter(&i40e->i40e_general_lock);
        if (i40e->i40e_state & I40E_SUSPENDED) {
                ret = ECANCELED;
                goto done;
        }


        ret = i40e_aq_set_vsi_unicast_promiscuous(hw, I40E_DEF_VSI_SEID(i40e),
            on ? true : false, NULL, false);
        if (ret != I40E_SUCCESS) {
                i40e_error(i40e, "failed to %s unicast promiscuity on "
                    "the default VSI: %d", on == B_TRUE ? "enable" : "disable",
                    ret);
                err = EIO;
                goto done;
        }

        /*
         * If we have a non-zero mcast_promisc_count, then it has already been
         * enabled or we need to leave it that way and not touch it.
         */
        if (i40e->i40e_mcast_promisc_count > 0) {
                i40e->i40e_promisc_on = on;
                goto done;
        }

        ret = i40e_aq_set_vsi_multicast_promiscuous(hw, I40E_DEF_VSI_SEID(i40e),
            on, NULL);
        if (ret != I40E_SUCCESS) {
                i40e_error(i40e, "failed to %s multicast promiscuity on "
                    "the default VSI: %d", on == B_TRUE ? "enable" : "disable",
                    ret);

                /*
                 * Try our best to put us back into a state that MAC expects us
                 * to be in.
                 */
                ret = i40e_aq_set_vsi_unicast_promiscuous(hw,
                    I40E_DEF_VSI_SEID(i40e), !on, NULL, false);
                if (ret != I40E_SUCCESS) {
                        i40e_error(i40e, "failed to %s unicast promiscuity on "
                            "the default VSI after toggling multicast failed: "
                            "%d", on == B_TRUE ? "disable" : "enable", ret);
                }

                err = EIO;
                goto done;
        } else {
                i40e->i40e_promisc_on = on;
        }

done:
        mutex_exit(&i40e->i40e_general_lock);
        return (err);
}

/*
 * See the big theory statement in i40e_main.c for multicast address management.
 */
static int
i40e_multicast_add(i40e_t *i40e, const uint8_t *multicast_address)
{
        struct i40e_hw *hw = &i40e->i40e_hw_space;
        struct i40e_aqc_add_macvlan_element_data filt;
        i40e_maddr_t *mc;
        int ret;

        ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));

        if (i40e->i40e_resources.ifr_nmcastfilt_used ==
            i40e->i40e_resources.ifr_nmcastfilt) {
                if (i40e->i40e_mcast_promisc_count == 0 &&
                    i40e->i40e_promisc_on == B_FALSE) {
                        ret = i40e_aq_set_vsi_multicast_promiscuous(hw,
                            I40E_DEF_VSI_SEID(i40e), true, NULL);
                        if (ret != I40E_SUCCESS) {
                                i40e_error(i40e, "failed to enable multicast "
                                    "promiscuous mode on VSI %d: %d",
                                    I40E_DEF_VSI_SEID(i40e), ret);
                                return (EIO);
                        }
                }
                i40e->i40e_mcast_promisc_count++;
                return (0);
        }

        mc = &i40e->i40e_maddrs[i40e->i40e_resources.ifr_nmcastfilt_used];
        bzero(&filt, sizeof (filt));
        bcopy(multicast_address, filt.mac_addr, ETHERADDRL);
        filt.flags = I40E_AQC_MACVLAN_ADD_HASH_MATCH |
            I40E_AQC_MACVLAN_ADD_IGNORE_VLAN;

        if ((ret = i40e_aq_add_macvlan(hw, I40E_DEF_VSI_SEID(i40e), &filt, 1,
            NULL)) != I40E_SUCCESS) {
                i40e_error(i40e, "failed to add mac address "
                    "%02x:%02x:%02x:%02x:%02x:%02x to multicast filter: %d",
                    multicast_address[0], multicast_address[1],
                    multicast_address[2], multicast_address[3],
                    multicast_address[4], multicast_address[5],
                    ret);
                return (EIO);
        }

        bcopy(multicast_address, mc->ima_mac, ETHERADDRL);
        i40e->i40e_resources.ifr_nmcastfilt_used++;
        return (0);
}

/*
 * See the big theory statement in i40e_main.c for multicast address management.
 */
static int
i40e_multicast_remove(i40e_t *i40e, const uint8_t *multicast_address)
{
        int i, ret;
        struct i40e_hw *hw = &i40e->i40e_hw_space;

        ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));

        for (i = 0; i < i40e->i40e_resources.ifr_nmcastfilt_used; i++) {
                struct i40e_aqc_remove_macvlan_element_data filt;
                int last;

                if (bcmp(multicast_address, i40e->i40e_maddrs[i].ima_mac,
                    ETHERADDRL) != 0) {
                        continue;
                }

                bzero(&filt, sizeof (filt));
                bcopy(multicast_address, filt.mac_addr, ETHERADDRL);
                filt.flags = I40E_AQC_MACVLAN_DEL_HASH_MATCH |
                    I40E_AQC_MACVLAN_DEL_IGNORE_VLAN;

                if (i40e_aq_remove_macvlan(hw, I40E_DEF_VSI_SEID(i40e), &filt,
                    1, NULL) != I40E_SUCCESS) {
                        i40e_error(i40e, "failed to remove mac address "
                            "%02x:%02x:%02x:%02x:%02x:%02x from multicast "
                            "filter: %d",
                            multicast_address[0], multicast_address[1],
                            multicast_address[2], multicast_address[3],
                            multicast_address[4], multicast_address[5],
                            filt.error_code);
                        return (EIO);
                }

                last = i40e->i40e_resources.ifr_nmcastfilt_used - 1;
                if (i != last) {
                        bcopy(&i40e->i40e_maddrs[last], &i40e->i40e_maddrs[i],
                            sizeof (i40e_maddr_t));
                        bzero(&i40e->i40e_maddrs[last], sizeof (i40e_maddr_t));
                }

                ASSERT(i40e->i40e_resources.ifr_nmcastfilt_used > 0);
                i40e->i40e_resources.ifr_nmcastfilt_used--;
                return (0);
        }

        if (i40e->i40e_mcast_promisc_count > 0) {
                if (i40e->i40e_mcast_promisc_count == 1 &&
                    i40e->i40e_promisc_on == B_FALSE) {
                        ret = i40e_aq_set_vsi_multicast_promiscuous(hw,
                            I40E_DEF_VSI_SEID(i40e), false, NULL);
                        if (ret != I40E_SUCCESS) {
                                i40e_error(i40e, "failed to disable "
                                    "multicast promiscuous mode on VSI %d: %d",
                                    I40E_DEF_VSI_SEID(i40e), ret);
                                return (EIO);
                        }
                }
                i40e->i40e_mcast_promisc_count--;

                return (0);
        }

        return (ENOENT);
}

static int
i40e_m_multicast(void *arg, boolean_t add, const uint8_t *multicast_address)
{
        i40e_t *i40e = arg;
        int rc;

        mutex_enter(&i40e->i40e_general_lock);

        if (i40e->i40e_state & I40E_SUSPENDED) {
                mutex_exit(&i40e->i40e_general_lock);
                return (ECANCELED);
        }

        if (add == B_TRUE) {
                rc = i40e_multicast_add(i40e, multicast_address);
        } else {
                rc = i40e_multicast_remove(i40e, multicast_address);
        }

        mutex_exit(&i40e->i40e_general_lock);
        return (rc);
}

/* ARGSUSED */
static void
i40e_m_ioctl(void *arg, queue_t *q, mblk_t *mp)
{
        /*
         * At this time, we don't support toggling i40e into loopback mode. It's
         * questionable how much value this has when there's no clear way to
         * toggle this behavior from a supported way in userland.
         */
        miocnak(q, mp, 0, EINVAL);
}

static int
i40e_ring_start(mac_ring_driver_t rh, uint64_t gen_num)
{
        i40e_trqpair_t *itrq = (i40e_trqpair_t *)rh;
        int rv;

        if ((rv = i40e_setup_ring(itrq)) != 0)
                return (rv);

        /*
         * GLDv3 requires we keep track of a generation number, as it uses
         * that number to keep track of whether or not a ring is active.
         */
        mutex_enter(&itrq->itrq_rx_lock);
        itrq->itrq_rxgen = gen_num;
        mutex_exit(&itrq->itrq_rx_lock);
        return (0);
}

static void
i40e_ring_stop(mac_ring_driver_t rh)
{
        i40e_trqpair_t *itrq = (i40e_trqpair_t *)rh;

        if (!i40e_shutdown_ring(itrq)) {
                i40e_t *i40e = itrq->itrq_i40e;

                ddi_fm_service_impact(i40e->i40e_dip, DDI_SERVICE_LOST);
                i40e_error(i40e, "Failed to stop ring %u", itrq->itrq_index);
        }
}

/* ARGSUSED */
static int
i40e_rx_ring_intr_enable(mac_intr_handle_t intrh)
{
        i40e_trqpair_t *itrq = (i40e_trqpair_t *)intrh;

        mutex_enter(&itrq->itrq_rx_lock);
        ASSERT(itrq->itrq_intr_poll == B_TRUE);
        i40e_intr_rx_queue_enable(itrq);
        itrq->itrq_intr_poll = B_FALSE;
        mutex_exit(&itrq->itrq_rx_lock);

        return (0);
}

/* ARGSUSED */
static int
i40e_rx_ring_intr_disable(mac_intr_handle_t intrh)
{
        i40e_trqpair_t *itrq = (i40e_trqpair_t *)intrh;

        mutex_enter(&itrq->itrq_rx_lock);
        i40e_intr_rx_queue_disable(itrq);
        itrq->itrq_intr_poll = B_TRUE;
        mutex_exit(&itrq->itrq_rx_lock);

        return (0);
}

/* ARGSUSED */
static void
i40e_fill_tx_ring(void *arg, mac_ring_type_t rtype, const int group_index,
    const int ring_index, mac_ring_info_t *infop, mac_ring_handle_t rh)
{
        i40e_t *i40e = arg;
        mac_intr_t *mintr = &infop->mri_intr;
        i40e_trqpair_t *itrq = &(i40e->i40e_trqpairs[ring_index]);

        /*
         * Note the group index here is expected to be -1 due to the fact that
         * we're not actually grouping things tx-wise at this time.
         */
        ASSERT(group_index == -1);
        ASSERT(ring_index < i40e->i40e_num_trqpairs_per_vsi);

        itrq->itrq_mactxring = rh;
        infop->mri_driver = (mac_ring_driver_t)itrq;
        infop->mri_start = NULL;
        infop->mri_stop = NULL;
        infop->mri_tx = i40e_ring_tx;
        infop->mri_stat = i40e_tx_ring_stat;

        /*
         * We only provide the handle in cases where we have MSI-X interrupts,
         * to indicate that we'd actually support retargetting.
         */
        if (i40e->i40e_intr_type & DDI_INTR_TYPE_MSIX) {
                mintr->mi_ddi_handle =
                    i40e->i40e_intr_handles[itrq->itrq_tx_intrvec];
        }
}

/* ARGSUSED */
static void
i40e_fill_rx_ring(void *arg, mac_ring_type_t rtype, const int group_index,
    const int ring_index, mac_ring_info_t *infop, mac_ring_handle_t rh)
{
        i40e_t *i40e = arg;
        mac_intr_t *mintr = &infop->mri_intr;
        uint_t trqpair_index;
        i40e_trqpair_t *itrq;

        /* This assumes static groups. */
        ASSERT3S(group_index, >=, 0);
        ASSERT3S(ring_index, >=, 0);
        trqpair_index = (group_index * i40e->i40e_num_trqpairs_per_vsi) +
            ring_index;
        ASSERT3U(trqpair_index, <, i40e->i40e_num_trqpairs);
        itrq = &i40e->i40e_trqpairs[trqpair_index];

        itrq->itrq_macrxring = rh;
        infop->mri_driver = (mac_ring_driver_t)itrq;
        infop->mri_start = i40e_ring_start;
        infop->mri_stop = i40e_ring_stop;
        infop->mri_poll = i40e_ring_rx_poll;
        infop->mri_stat = i40e_rx_ring_stat;
        mintr->mi_handle = (mac_intr_handle_t)itrq;
        mintr->mi_enable = i40e_rx_ring_intr_enable;
        mintr->mi_disable = i40e_rx_ring_intr_disable;

        /*
         * We only provide the handle in cases where we have MSI-X interrupts,
         * to indicate that we'd actually support retargetting.
         */
        if (i40e->i40e_intr_type & DDI_INTR_TYPE_MSIX) {
                mintr->mi_ddi_handle =
                    i40e->i40e_intr_handles[itrq->itrq_rx_intrvec];
        }
}

/* ARGSUSED */
static void
i40e_fill_rx_group(void *arg, mac_ring_type_t rtype, const int index,
    mac_group_info_t *infop, mac_group_handle_t gh)
{
        i40e_t *i40e = arg;
        i40e_rx_group_t *rxg;

        if (rtype != MAC_RING_TYPE_RX)
                return;

        rxg = &i40e->i40e_rx_groups[index];
        rxg->irg_grp_hdl = gh;

        infop->mgi_driver = (mac_group_driver_t)rxg;
        infop->mgi_start = NULL;
        infop->mgi_stop = NULL;
        infop->mgi_addmac = i40e_group_add_mac;
        infop->mgi_remmac = i40e_group_remove_mac;

        ASSERT3U(i40e->i40e_num_rx_groups, <=, I40E_MAX_NUM_RX_GROUPS);
        infop->mgi_count = i40e->i40e_num_trqpairs_per_vsi;
}

static int
i40e_transceiver_info(void *arg, uint_t id, mac_transceiver_info_t *infop)
{
        boolean_t present, usable;
        i40e_t *i40e = arg;

        if (id != 0 || infop == NULL)
                return (EINVAL);

        mutex_enter(&i40e->i40e_general_lock);
        switch (i40e->i40e_hw_space.phy.link_info.module_type[0]) {
        case I40E_MODULE_TYPE_SFP:
        case I40E_MODULE_TYPE_QSFP:
                break;
        default:
                mutex_exit(&i40e->i40e_general_lock);
                return (ENOTSUP);
        }

        present = !!(i40e->i40e_hw_space.phy.link_info.link_info &
            I40E_AQ_MEDIA_AVAILABLE);
        if (present) {
                usable = !!(i40e->i40e_hw_space.phy.link_info.an_info &
                    I40E_AQ_QUALIFIED_MODULE);
        } else {
                usable = B_FALSE;
        }
        mutex_exit(&i40e->i40e_general_lock);

        mac_transceiver_info_set_usable(infop, usable);
        mac_transceiver_info_set_present(infop, present);

        return (0);
}

static int
i40e_transceiver_read(void *arg, uint_t id, uint_t page, void *buf,
    size_t nbytes, off_t offset, size_t *nread)
{
        i40e_t *i40e = arg;
        struct i40e_hw *hw = &i40e->i40e_hw_space;
        uint8_t *buf8 = buf;
        size_t i;

        if (id != 0 || buf == NULL || nbytes == 0 || nread == NULL ||
            (page != 0xa0 && page != 0xa2) || offset < 0)
                return (EINVAL);

        /*
         * Both supported pages have a length of 256 bytes, ensure nothing asks
         * us to go beyond that.
         */
        if (nbytes > 256 || offset >= 256 || (offset + nbytes > 256)) {
                return (EINVAL);
        }

        mutex_enter(&i40e->i40e_general_lock);
        switch (i40e->i40e_hw_space.phy.link_info.module_type[0]) {
        case I40E_MODULE_TYPE_SFP:
        case I40E_MODULE_TYPE_QSFP:
                break;
        default:
                mutex_exit(&i40e->i40e_general_lock);
                return (ENOTSUP);
        }

        /*
         * Make sure we have a sufficiently new firmware version to run this
         * command. This was introduced in firmware API 1.7. This is apparently
         * only supported on the XL710 MAC, not the XL722.
         */
        if (hw->mac.type != I40E_MAC_XL710 || hw->aq.api_maj_ver != 1 ||
            hw->aq.api_min_ver < 7) {
                mutex_exit(&i40e->i40e_general_lock);
                return (ENOTSUP);
        }

        for (i = 0; i < nbytes; i++, offset++) {
                enum i40e_status_code status;
                uint32_t val;

                status = i40e_aq_get_phy_register(hw,
                    I40E_AQ_PHY_REG_ACCESS_EXTERNAL_MODULE, page, TRUE, offset,
                    &val, NULL);
                if (status != I40E_SUCCESS) {
                        mutex_exit(&i40e->i40e_general_lock);
                        return (EIO);
                }

                buf8[i] = (uint8_t)val;
        }

        mutex_exit(&i40e->i40e_general_lock);
        *nread = nbytes;

        return (0);
}

static int
i40e_gld_led_set(void *arg, mac_led_mode_t mode, uint_t flags)
{
        i40e_t *i40e = arg;
        struct i40e_hw *hw = &i40e->i40e_hw_space;

        if (flags != 0)
                return (EINVAL);

        if (mode != MAC_LED_DEFAULT &&
            mode != MAC_LED_IDENT &&
            mode != MAC_LED_OFF &&
            mode != MAC_LED_ON)
                return (ENOTSUP);

        if (mode != MAC_LED_DEFAULT && !i40e->i40e_led_saved) {
                i40e->i40e_led_status = i40e_led_get(hw);
                i40e->i40e_led_saved = B_TRUE;
        }

        switch (mode) {
        case MAC_LED_DEFAULT:
                if (i40e->i40e_led_saved) {
                        i40e_led_set(hw, i40e->i40e_led_status, B_FALSE);
                        i40e->i40e_led_status = 0;
                        i40e->i40e_led_saved = B_FALSE;
                }
                break;
        case MAC_LED_IDENT:
                i40e_led_set(hw, 0xf, true);
                break;
        case MAC_LED_OFF:
                i40e_led_set(hw, 0x0, false);
                break;
        case MAC_LED_ON:
                i40e_led_set(hw, 0xf, false);
                break;
        default:
                return (ENOTSUP);
        }

        return (0);
}

static boolean_t
i40e_m_getcapab(void *arg, mac_capab_t cap, void *cap_data)
{
        i40e_t *i40e = arg;
        mac_capab_rings_t *cap_rings;
        mac_capab_transceiver_t *mct;
        mac_capab_led_t *mcl;

        switch (cap) {
        case MAC_CAPAB_HCKSUM: {
                uint32_t *txflags = cap_data;

                *txflags = 0;
                if (i40e->i40e_tx_hcksum_enable == B_TRUE)
                        *txflags = HCKSUM_INET_PARTIAL | HCKSUM_IPHDRCKSUM;
                break;
        }

        case MAC_CAPAB_LSO: {
                mac_capab_lso_t *cap_lso = cap_data;

                if (i40e->i40e_tx_lso_enable == B_TRUE) {
                        cap_lso->lso_flags = LSO_TX_BASIC_TCP_IPV4 |
                            LSO_TX_BASIC_TCP_IPV6;
                        cap_lso->lso_basic_tcp_ipv4.lso_max = I40E_LSO_MAXLEN;
                        cap_lso->lso_basic_tcp_ipv6.lso_max = I40E_LSO_MAXLEN;
                } else {
                        return (B_FALSE);
                }
                break;
        }

        case MAC_CAPAB_RINGS:
                cap_rings = cap_data;
                cap_rings->mr_group_type = MAC_GROUP_TYPE_STATIC;
                switch (cap_rings->mr_type) {
                case MAC_RING_TYPE_TX:
                        /*
                         * Note, saying we have no groups, but some
                         * number of rings indicates to MAC that it
                         * should create psuedo-groups with one for
                         * each TX ring. This may not be the long term
                         * behavior we want, but it'll work for now.
                         */
                        cap_rings->mr_gnum = 0;
                        cap_rings->mr_rnum = i40e->i40e_num_trqpairs_per_vsi;
                        cap_rings->mr_rget = i40e_fill_tx_ring;
                        cap_rings->mr_gget = NULL;
                        cap_rings->mr_gaddring = NULL;
                        cap_rings->mr_gremring = NULL;
                        break;
                case MAC_RING_TYPE_RX:
                        cap_rings->mr_rnum = i40e->i40e_num_trqpairs;
                        cap_rings->mr_rget = i40e_fill_rx_ring;
                        cap_rings->mr_gnum = i40e->i40e_num_rx_groups;
                        cap_rings->mr_gget = i40e_fill_rx_group;
                        cap_rings->mr_gaddring = NULL;
                        cap_rings->mr_gremring = NULL;
                        break;
                default:
                        return (B_FALSE);
                }
                break;
        case MAC_CAPAB_TRANSCEIVER:
                mct = cap_data;

                /*
                 * Firmware doesn't have a great way of telling us in advance
                 * whether we'd expect a SFF transceiver. As such, we always
                 * advertise the support for this capability.
                 */
                mct->mct_flags = 0;
                mct->mct_ntransceivers = 1;
                mct->mct_info = i40e_transceiver_info;
                mct->mct_read = i40e_transceiver_read;

                return (B_TRUE);
        case MAC_CAPAB_LED:
                mcl = cap_data;

                mcl->mcl_flags = 0;
                mcl->mcl_modes = MAC_LED_DEFAULT | MAC_LED_IDENT | MAC_LED_OFF |
                    MAC_LED_ON;
                mcl->mcl_set = i40e_gld_led_set;
                break;

        default:
                return (B_FALSE);
        }

        return (B_TRUE);
}

/* ARGSUSED */
static int
i40e_m_setprop_private(i40e_t *i40e, const char *pr_name, uint_t pr_valsize,
    const void *pr_val)
{
        int ret;
        long val;
        char *eptr;

        ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));

        if ((ret = ddi_strtol(pr_val, &eptr, 10, &val)) != 0 ||
            *eptr != '\0') {
                return (ret);
        }

        if (strcmp(pr_name, I40E_PROP_RX_DMA_THRESH) == 0) {
                if (val < I40E_MIN_RX_DMA_THRESH ||
                    val > I40E_MAX_RX_DMA_THRESH) {
                        return (EINVAL);
                }
                i40e->i40e_rx_dma_min = (uint32_t)val;
                return (0);
        }

        if (strcmp(pr_name, I40E_PROP_TX_DMA_THRESH) == 0) {
                if (val < I40E_MIN_TX_DMA_THRESH ||
                    val > I40E_MAX_TX_DMA_THRESH) {
                        return (EINVAL);
                }
                i40e->i40e_tx_dma_min = (uint32_t)val;
                return (0);
        }

        if (strcmp(pr_name, I40E_PROP_RX_ITR) == 0) {
                if (val < I40E_MIN_ITR ||
                    val > I40E_MAX_ITR) {
                        return (EINVAL);
                }
                i40e->i40e_rx_itr = (uint32_t)val;
                i40e_intr_set_itr(i40e, I40E_ITR_INDEX_RX, i40e->i40e_rx_itr);
                return (0);
        }

        if (strcmp(pr_name, I40E_PROP_TX_ITR) == 0) {
                if (val < I40E_MIN_ITR ||
                    val > I40E_MAX_ITR) {
                        return (EINVAL);
                }
                i40e->i40e_tx_itr = (uint32_t)val;
                i40e_intr_set_itr(i40e, I40E_ITR_INDEX_TX, i40e->i40e_tx_itr);
                return (0);
        }

        if (strcmp(pr_name, I40E_PROP_OTHER_ITR) == 0) {
                if (val < I40E_MIN_ITR ||
                    val > I40E_MAX_ITR) {
                        return (EINVAL);
                }
                i40e->i40e_tx_itr = (uint32_t)val;
                i40e_intr_set_itr(i40e, I40E_ITR_INDEX_OTHER,
                    i40e->i40e_other_itr);
                return (0);
        }

        return (ENOTSUP);
}

static int
i40e_m_getprop_private(i40e_t *i40e, const char *pr_name, uint_t pr_valsize,
    void *pr_val)
{
        uint32_t val;

        ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));

        if (strcmp(pr_name, I40E_PROP_RX_DMA_THRESH) == 0) {
                val = i40e->i40e_rx_dma_min;
        } else if (strcmp(pr_name, I40E_PROP_TX_DMA_THRESH) == 0) {
                val = i40e->i40e_tx_dma_min;
        } else if (strcmp(pr_name, I40E_PROP_RX_ITR) == 0) {
                val = i40e->i40e_rx_itr;
        } else if (strcmp(pr_name, I40E_PROP_TX_ITR) == 0) {
                val = i40e->i40e_tx_itr;
        } else if (strcmp(pr_name, I40E_PROP_OTHER_ITR) == 0) {
                val = i40e->i40e_other_itr;
        } else {
                return (ENOTSUP);
        }

        if (snprintf(pr_val, pr_valsize, "%d", val) >= pr_valsize)
                return (ERANGE);
        return (0);
}

/*
 * Annoyingly for private properties MAC seems to ignore default values that
 * aren't strings. That means that we have to translate all of these into
 * uint32_t's and instead we size the buffer to be large enough to hold a
 * uint32_t.
 */
/* ARGSUSED */
static void
i40e_m_propinfo_private(i40e_t *i40e, const char *pr_name,
    mac_prop_info_handle_t prh)
{
        char buf[64];
        uint32_t def;

        if (strcmp(pr_name, I40E_PROP_RX_DMA_THRESH) == 0) {
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
                def = I40E_DEF_RX_DMA_THRESH;
                mac_prop_info_set_range_uint32(prh,
                    I40E_MIN_RX_DMA_THRESH,
                    I40E_MAX_RX_DMA_THRESH);
        } else if (strcmp(pr_name, I40E_PROP_TX_DMA_THRESH) == 0) {
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
                def = I40E_DEF_TX_DMA_THRESH;
                mac_prop_info_set_range_uint32(prh,
                    I40E_MIN_TX_DMA_THRESH,
                    I40E_MAX_TX_DMA_THRESH);
        } else if (strcmp(pr_name, I40E_PROP_RX_ITR) == 0) {
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
                def = I40E_DEF_RX_ITR;
                mac_prop_info_set_range_uint32(prh, I40E_MIN_ITR, I40E_MAX_ITR);
        } else if (strcmp(pr_name, I40E_PROP_TX_ITR) == 0) {
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
                def = I40E_DEF_TX_ITR;
                mac_prop_info_set_range_uint32(prh, I40E_MIN_ITR, I40E_MAX_ITR);
        } else if (strcmp(pr_name, I40E_PROP_OTHER_ITR) == 0) {
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
                def = I40E_DEF_OTHER_ITR;
                mac_prop_info_set_range_uint32(prh, I40E_MIN_ITR, I40E_MAX_ITR);
        } else {
                return;
        }

        (void) snprintf(buf, sizeof (buf), "%d", def);
        mac_prop_info_set_default_str(prh, buf);
}

static int
i40e_update_fec(i40e_t *i40e, link_fec_t fec)
{
        struct i40e_hw *hw = &i40e->i40e_hw_space;
        struct i40e_aq_get_phy_abilities_resp abilities;
        struct i40e_aq_set_phy_config config;
        link_fec_t fec_requested;
        int req_fec;

        ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));

        if (fec == i40e->i40e_fec_requested)
                return (0);

        fec_requested = fec;
        if ((fec & LINK_FEC_AUTO) != 0) {
                req_fec = I40E_AQ_SET_FEC_AUTO;
                fec &= ~LINK_FEC_AUTO;
        } else if ((fec & LINK_FEC_NONE) != 0) {
                req_fec = 0;
                fec &= ~LINK_FEC_NONE;
        } else {
                req_fec = 0;
                if ((fec & LINK_FEC_BASE_R) != 0) {
                        req_fec |= I40E_AQ_SET_FEC_ABILITY_KR |
                            I40E_AQ_SET_FEC_REQUEST_KR;
                        fec &= ~LINK_FEC_BASE_R;
                }
                if ((fec & LINK_FEC_RS) != 0) {
                        req_fec |= I40E_AQ_SET_FEC_ABILITY_RS |
                            I40E_AQ_SET_FEC_REQUEST_RS;
                        fec &= ~LINK_FEC_RS;
                }
                if (req_fec == 0)
                        return (EINVAL);
        }

        /*
         * if fec is not zero now, then there is an invalid fec or
         * combination of settings.
         */
        if (fec != 0)
                return (EINVAL);

        if (i40e_aq_get_phy_capabilities(hw, false, false, &abilities,
            NULL) != I40E_SUCCESS)
                return (EIO);

        bzero(&config, sizeof (config));
        config.abilities = abilities.abilities;
        /* Restart the link */
        config.abilities |= I40E_AQ_PHY_ENABLE_ATOMIC_LINK;
        config.phy_type = abilities.phy_type;
        config.phy_type_ext = abilities.phy_type_ext;
        config.link_speed = abilities.link_speed;
        config.eee_capability = abilities.eee_capability;
        config.eeer = abilities.eeer_val;
        config.low_power_ctrl = abilities.d3_lpan;
        config.fec_config = req_fec & I40E_AQ_PHY_FEC_CONFIG_MASK;
        if (i40e_aq_set_phy_config(hw, &config, NULL) != I40E_SUCCESS)
                return (EIO);

        if (i40e_update_link_info(hw) != I40E_SUCCESS)
                return (EIO);

        i40e->i40e_fec_requested = fec_requested;

        return (0);
}
static int
i40e_m_setprop(void *arg, const char *pr_name, mac_prop_id_t pr_num,
    uint_t pr_valsize, const void *pr_val)
{
        uint32_t new_mtu;
        link_fec_t fec;
        i40e_t *i40e = arg;
        int ret = 0;

        mutex_enter(&i40e->i40e_general_lock);
        if (i40e->i40e_state & I40E_SUSPENDED) {
                mutex_exit(&i40e->i40e_general_lock);
                return (ECANCELED);
        }

        switch (pr_num) {
        /*
         * These properties are always read-only across every device.
         */
        case MAC_PROP_DUPLEX:
        case MAC_PROP_SPEED:
        case MAC_PROP_STATUS:
        case MAC_PROP_MEDIA:
        case MAC_PROP_ADV_100FDX_CAP:
        case MAC_PROP_ADV_1000FDX_CAP:
        case MAC_PROP_ADV_2500FDX_CAP:
        case MAC_PROP_ADV_5000FDX_CAP:
        case MAC_PROP_ADV_10GFDX_CAP:
        case MAC_PROP_ADV_25GFDX_CAP:
        case MAC_PROP_ADV_40GFDX_CAP:
                ret = ENOTSUP;
                break;
        /*
         * These are read-only at this time as we don't support configuring
         * auto-negotiation. See the theory statement in i40e_main.c.
         */
        case MAC_PROP_EN_100FDX_CAP:
        case MAC_PROP_EN_1000FDX_CAP:
        case MAC_PROP_EN_2500FDX_CAP:
        case MAC_PROP_EN_5000FDX_CAP:
        case MAC_PROP_EN_10GFDX_CAP:
        case MAC_PROP_EN_25GFDX_CAP:
        case MAC_PROP_EN_40GFDX_CAP:
        case MAC_PROP_AUTONEG:
        case MAC_PROP_FLOWCTRL:
                ret = ENOTSUP;
                break;

        case MAC_PROP_MTU:
                bcopy(pr_val, &new_mtu, sizeof (new_mtu));
                if (new_mtu == i40e->i40e_sdu)
                        break;

                if (new_mtu < I40E_MIN_MTU ||
                    new_mtu > I40E_MAX_MTU) {
                        ret = EINVAL;
                        break;
                }

                if (i40e->i40e_state & I40E_STARTED) {
                        ret = EBUSY;
                        break;
                }

                ret = mac_maxsdu_update(i40e->i40e_mac_hdl, new_mtu);
                if (ret == 0) {
                        i40e->i40e_sdu = new_mtu;
                        i40e_update_mtu(i40e);
                }
                break;

        case MAC_PROP_EN_FEC_CAP:
                bcopy(pr_val, &fec, sizeof (fec));

                ret = i40e_update_fec(i40e, fec);
                break;

        case MAC_PROP_PRIVATE:
                ret = i40e_m_setprop_private(i40e, pr_name, pr_valsize, pr_val);
                break;
        default:
                ret = ENOTSUP;
                break;
        }

        mutex_exit(&i40e->i40e_general_lock);
        return (ret);
}

static link_fec_t
i40e_fec_to_linkfec(struct i40e_hw *hw)
{
        struct i40e_link_status *ls = &hw->phy.link_info;

        if ((ls->fec_info & I40E_AQ_CONFIG_FEC_KR_ENA) != 0)
                return (LINK_FEC_BASE_R);

        if ((ls->fec_info & I40E_AQ_CONFIG_FEC_RS_ENA) != 0)
                return (LINK_FEC_RS);

        return (LINK_FEC_NONE);
}

mac_ether_media_t
i40e_link_to_media(i40e_t *i40e)
{
        switch (i40e->i40e_link_state) {
        case LINK_STATE_UP:
                break;
        case LINK_STATE_DOWN:
                return (ETHER_MEDIA_NONE);
        default:
                return (ETHER_MEDIA_UNKNOWN);
        }

        switch (i40e->i40e_hw_space.phy.link_info.phy_type) {
        case I40E_PHY_TYPE_SGMII:
                return (ETHER_MEDIA_1000_SGMII);
        case I40E_PHY_TYPE_1000BASE_KX:
                return (ETHER_MEDIA_1000BASE_KX);
        case I40E_PHY_TYPE_10GBASE_KX4:
                return (ETHER_MEDIA_10GBASE_KX4);
        case I40E_PHY_TYPE_10GBASE_KR:
                return (ETHER_MEDIA_10GBASE_KR);
        case I40E_PHY_TYPE_40GBASE_KR4:
                return (ETHER_MEDIA_40GBASE_KR4);
        case I40E_PHY_TYPE_XAUI:
                return (ETHER_MEDIA_10G_XAUI);
        case I40E_PHY_TYPE_XFI:
                return (ETHER_MEDIA_10G_XFI);
        case I40E_PHY_TYPE_SFI:
                return (ETHER_MEDIA_10G_SFI);
        case I40E_PHY_TYPE_XLAUI:
                return (ETHER_MEDIA_40G_XLAUI);
        case I40E_PHY_TYPE_XLPPI:
                return (ETHER_MEDIA_40G_XLPPI);
        case I40E_PHY_TYPE_40GBASE_CR4_CU:
                return (ETHER_MEDIA_40GBASE_CR4);
        case I40E_PHY_TYPE_10GBASE_CR1_CU:
                return (ETHER_MEDIA_10GBASE_CR);
        case I40E_PHY_TYPE_10GBASE_AOC:
                return (ETHER_MEDIA_10GBASE_AOC);
        case I40E_PHY_TYPE_40GBASE_AOC:
                return (ETHER_MEDIA_40GBASE_AOC4);
        case I40E_PHY_TYPE_100BASE_TX:
                return (ETHER_MEDIA_100BASE_TX);
        case I40E_PHY_TYPE_1000BASE_T:
                return (ETHER_MEDIA_1000BASE_T);
        case I40E_PHY_TYPE_10GBASE_T:
                return (ETHER_MEDIA_10GBASE_T);
        case I40E_PHY_TYPE_10GBASE_SR:
                return (ETHER_MEDIA_10GBASE_SR);
        case I40E_PHY_TYPE_10GBASE_LR:
                return (ETHER_MEDIA_10GBASE_LR);
        case I40E_PHY_TYPE_10GBASE_SFPP_CU:
                return (ETHER_MEDIA_10GBASE_CR);
        case I40E_PHY_TYPE_10GBASE_CR1:
                return (ETHER_MEDIA_10GBASE_CR);
        case I40E_PHY_TYPE_40GBASE_CR4:
                return (ETHER_MEDIA_40GBASE_CR4);
        case I40E_PHY_TYPE_40GBASE_SR4:
                return (ETHER_MEDIA_40GBASE_SR4);
        case I40E_PHY_TYPE_40GBASE_LR4:
                return (ETHER_MEDIA_40GBASE_LR4);
        case I40E_PHY_TYPE_1000BASE_SX:
                return (ETHER_MEDIA_1000BASE_SX);
        case I40E_PHY_TYPE_1000BASE_LX:
                return (ETHER_MEDIA_1000BASE_LX);
        case I40E_PHY_TYPE_1000BASE_T_OPTICAL:
                return (ETHER_MEDIA_1000BASE_T);
        case I40E_PHY_TYPE_25GBASE_KR:
                return (ETHER_MEDIA_25GBASE_KR);
        case I40E_PHY_TYPE_25GBASE_CR:
                return (ETHER_MEDIA_25GBASE_CR);
        case I40E_PHY_TYPE_25GBASE_SR:
                return (ETHER_MEDIA_25GBASE_SR);
        case I40E_PHY_TYPE_25GBASE_LR:
                return (ETHER_MEDIA_25GBASE_LR);
        case I40E_PHY_TYPE_25GBASE_AOC:
                return (ETHER_MEDIA_25GBASE_AOC);
        case I40E_PHY_TYPE_25GBASE_ACC:
                return (ETHER_MEDIA_25GBASE_ACC);
        case I40E_PHY_TYPE_2_5GBASE_T:
                return (ETHER_MEDIA_2500BASE_T);
        case I40E_PHY_TYPE_5GBASE_T:
                return (ETHER_MEDIA_5000BASE_T);
        case I40E_PHY_TYPE_EMPTY:
                return (ETHER_MEDIA_NONE);
        /*
         * We don't currently support 20GBASE-KR2 in any way in the GLD. If
         * someone actually can generate this, then we should do this.
         */
        case I40E_PHY_TYPE_20GBASE_KR2:
        case I40E_PHY_TYPE_DEFAULT:
        case I40E_PHY_TYPE_UNRECOGNIZED:
        case I40E_PHY_TYPE_UNSUPPORTED:
        case I40E_PHY_TYPE_NOT_SUPPORTED_HIGH_TEMP:
        default:
                return (ETHER_MEDIA_UNKNOWN);
        }
}

static int
i40e_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num,
    uint_t pr_valsize, void *pr_val)
{
        i40e_t *i40e = arg;
        uint64_t speed;
        int ret = 0;
        uint8_t *u8;
        link_flowctrl_t fctl;

        mutex_enter(&i40e->i40e_general_lock);

        switch (pr_num) {
        case MAC_PROP_DUPLEX:
                if (pr_valsize < sizeof (link_duplex_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                bcopy(&i40e->i40e_link_duplex, pr_val, sizeof (link_duplex_t));
                break;
        case MAC_PROP_SPEED:
                if (pr_valsize < sizeof (uint64_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                speed = i40e->i40e_link_speed * 1000000ULL;
                bcopy(&speed, pr_val, sizeof (speed));
                break;
        case MAC_PROP_STATUS:
                if (pr_valsize < sizeof (link_state_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                bcopy(&i40e->i40e_link_state, pr_val, sizeof (link_state_t));
                break;
        case MAC_PROP_MEDIA:
                *(mac_ether_media_t *)pr_val = i40e_link_to_media(i40e);
                break;
        case MAC_PROP_AUTONEG:
                if (pr_valsize < sizeof (uint8_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                u8 = pr_val;
                *u8 = 1;
                break;
        case MAC_PROP_FLOWCTRL:
                /*
                 * Because we don't currently support hardware flow control, we
                 * just hardcode this to be none.
                 */
                if (pr_valsize < sizeof (link_flowctrl_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                fctl = LINK_FLOWCTRL_NONE;
                bcopy(&fctl, pr_val, sizeof (link_flowctrl_t));
                break;
        case MAC_PROP_MTU:
                if (pr_valsize < sizeof (uint32_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                bcopy(&i40e->i40e_sdu, pr_val, sizeof (uint32_t));
                break;
        case MAC_PROP_ADV_FEC_CAP:
                if (pr_valsize < sizeof (link_fec_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                *(link_fec_t *)pr_val =
                    i40e_fec_to_linkfec(&i40e->i40e_hw_space);
                break;
        case MAC_PROP_EN_FEC_CAP:
                if (pr_valsize < sizeof (link_fec_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                *(link_fec_t *)pr_val = i40e->i40e_fec_requested;
                break;

        /*
         * Because we don't let users control the speeds we may auto-negotiate
         * to, the values of the ADV_ and EN_ will always be the same.
         */
        case MAC_PROP_ADV_100FDX_CAP:
        case MAC_PROP_EN_100FDX_CAP:
                if (pr_valsize < sizeof (uint8_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                u8 = pr_val;
                *u8 = (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_100MB) != 0;
                break;
        case MAC_PROP_ADV_1000FDX_CAP:
        case MAC_PROP_EN_1000FDX_CAP:
                if (pr_valsize < sizeof (uint8_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                u8 = pr_val;
                *u8 = (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_1GB) != 0;
                break;
        case MAC_PROP_ADV_2500FDX_CAP:
        case MAC_PROP_EN_2500FDX_CAP:
                if (pr_valsize < sizeof (uint8_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                u8 = pr_val;
                *u8 = (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_2_5GB) != 0;
                break;
        case MAC_PROP_ADV_5000FDX_CAP:
        case MAC_PROP_EN_5000FDX_CAP:
                if (pr_valsize < sizeof (uint8_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                u8 = pr_val;
                *u8 = (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_5GB) != 0;
                break;
        case MAC_PROP_ADV_10GFDX_CAP:
        case MAC_PROP_EN_10GFDX_CAP:
                if (pr_valsize < sizeof (uint8_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                u8 = pr_val;
                *u8 = (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_10GB) != 0;
                break;
        case MAC_PROP_ADV_25GFDX_CAP:
        case MAC_PROP_EN_25GFDX_CAP:
                if (pr_valsize < sizeof (uint8_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                u8 = pr_val;
                *u8 = (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_25GB) != 0;
                break;
        case MAC_PROP_ADV_40GFDX_CAP:
        case MAC_PROP_EN_40GFDX_CAP:
                if (pr_valsize < sizeof (uint8_t)) {
                        ret = EOVERFLOW;
                        break;
                }
                u8 = pr_val;
                *u8 = (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_40GB) != 0;
                break;
        case MAC_PROP_PRIVATE:
                ret = i40e_m_getprop_private(i40e, pr_name, pr_valsize, pr_val);
                break;
        default:
                ret = ENOTSUP;
                break;
        }

        mutex_exit(&i40e->i40e_general_lock);

        return (ret);
}

static void
i40e_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t pr_num,
    mac_prop_info_handle_t prh)
{
        i40e_t *i40e = arg;

        mutex_enter(&i40e->i40e_general_lock);

        switch (pr_num) {
        case MAC_PROP_DUPLEX:
        case MAC_PROP_SPEED:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                break;
        case MAC_PROP_FLOWCTRL:
                /*
                 * At the moment, the driver doesn't support flow control, hence
                 * why this is set to read-only and none.
                 */
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_link_flowctrl(prh,
                    LINK_FLOWCTRL_NONE);
                break;
        case MAC_PROP_MTU:
                mac_prop_info_set_range_uint32(prh, I40E_MIN_MTU, I40E_MAX_MTU);
                break;
        case MAC_PROP_ADV_FEC_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                if (i40e_is_25G_device(i40e->i40e_hw_space.device_id))
                        mac_prop_info_set_default_fec(prh, LINK_FEC_AUTO);
                break;
        case MAC_PROP_EN_FEC_CAP:
                if (i40e_is_25G_device(i40e->i40e_hw_space.device_id)) {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
                        mac_prop_info_set_default_fec(prh, LINK_FEC_AUTO);
                } else {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                }
                break;

        /*
         * We set the defaults for these based upon the phy's ability to
         * support the speeds. Note, auto-negotiation is required for fiber,
         * hence it is read-only and always enabled. When we have access to
         * copper phys we can revisit this.
         */
        case MAC_PROP_AUTONEG:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh, 1);
                break;
        case MAC_PROP_ADV_100FDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_100MB) != 0);
                break;
        case MAC_PROP_EN_100FDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_100MB) != 0);
                break;
        case MAC_PROP_ADV_1000FDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_1GB) != 0);
                break;
        case MAC_PROP_EN_1000FDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_1GB) != 0);
                break;
        case MAC_PROP_ADV_2500FDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_2_5GB) != 0);
                break;
        case MAC_PROP_EN_2500FDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_2_5GB) != 0);
                break;
        case MAC_PROP_ADV_5000FDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_5GB) != 0);
                break;
        case MAC_PROP_EN_5000FDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_5GB) != 0);
                break;
        case MAC_PROP_ADV_10GFDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_10GB) != 0);
                break;
        case MAC_PROP_EN_10GFDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_10GB) != 0);
                break;
        case MAC_PROP_ADV_25GFDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_25GB) != 0);
                break;
        case MAC_PROP_EN_25GFDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_25GB) != 0);
                break;
        case MAC_PROP_ADV_40GFDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_40GB) != 0);
                break;
        case MAC_PROP_EN_40GFDX_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                mac_prop_info_set_default_uint8(prh,
                    (i40e->i40e_phy.link_speed & I40E_LINK_SPEED_40GB) != 0);
                break;
        case MAC_PROP_PRIVATE:
                i40e_m_propinfo_private(i40e, pr_name, prh);
                break;
        default:
                break;
        }

        mutex_exit(&i40e->i40e_general_lock);
}

#define I40E_M_CALLBACK_FLAGS \
        (MC_IOCTL | MC_GETCAPAB | MC_SETPROP | MC_GETPROP | MC_PROPINFO)

static mac_callbacks_t i40e_m_callbacks = {
        I40E_M_CALLBACK_FLAGS,
        i40e_m_stat,
        i40e_m_start,
        i40e_m_stop,
        i40e_m_promisc,
        i40e_m_multicast,
        NULL,
        NULL,
        NULL,
        i40e_m_ioctl,
        i40e_m_getcapab,
        NULL,
        NULL,
        i40e_m_setprop,
        i40e_m_getprop,
        i40e_m_propinfo
};

boolean_t
i40e_register_mac(i40e_t *i40e)
{
        struct i40e_hw *hw = &i40e->i40e_hw_space;
        int status;
        mac_register_t *mac = mac_alloc(MAC_VERSION);

        if (mac == NULL)
                return (B_FALSE);

        mac->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
        mac->m_driver = i40e;
        mac->m_dip = i40e->i40e_dip;
        mac->m_src_addr = hw->mac.addr;
        mac->m_callbacks = &i40e_m_callbacks;
        mac->m_min_sdu = 0;
        mac->m_max_sdu = i40e->i40e_sdu;
        mac->m_margin = VLAN_TAGSZ;
        mac->m_priv_props = i40e_priv_props;
        mac->m_v12n = MAC_VIRT_LEVEL1;

        status = mac_register(mac, &i40e->i40e_mac_hdl);
        if (status != 0)
                i40e_error(i40e, "mac_register() returned %d", status);
        mac_free(mac);

        return (status == 0);
}