--- /dev/null	Thu Dec 10 09:27:28 2015
+++ new/usr/src/uts/common/io/i40e/i40e_gld.c	Thu Dec 10 09:27:28 2015
@@ -0,0 +1,677 @@
+/*
+ * 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.
+ */
+
+#include "i40e_sw.h"
+
+/*
+ * Add a MAC address on an already-general-locked i40e instance.
+ */
+static int
+i40e_add_mac_locked(i40e_t *i40e, const uint8_t *mac_addr)
+{
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+	int i;
+
+	ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));
+
+	if (i40e->i40e_state & I40E_SUSPENDED)
+		return (ECANCELED);
+
+
+	if (i40e->i40e_mac_used == i40e->i40e_mac_total)
+		return (ENOSPC);
+
+	/*
+	 * The array of i40e_mac_addrs is ordered from
+	 * 0..(i40e_mac_used - 1) to make insertion/deletion rather
+	 * simple.  WE don't care what order they're in, which costs O(N)
+	 * search for both insertion (collision checking) and deletion
+	 * (location), but this doesn't happen much.  If we eliminate
+	 * collision checking (like ixgbe seems to), we go to O(1) for
+	 * insertion.
+	 */
+
+	for (i = 0; i < i40e->i40e_mac_used; i++) {
+		if (bcmp(mac_addr, i40e->i40e_mac_addrs[i].i40eth_mac,
+		    ETHERADDRL) == 0) {
+			/* Ooops, adding an ethernet we already know about. */
+			return (EEXIST);
+		}
+	}
+	ASSERT(i == i40e->i40e_mac_used);
+	ASSERT(!(i40e->i40e_mac_addrs[i].i40eth_used));
+
+	bcopy(mac_addr, i40e->i40e_mac_addrs[i].i40eth_mac, ETHERADDRL);
+	i40e->i40e_mac_used++;
+
+	return (i40e_hwadd_mac(i40e, hw, mac_addr) ? 0 : EIO);
+}
+
+/*
+ * Remove a MAC address on an already-general-locked i40e instance.
+ */
+static int
+i40e_remove_mac_locked(i40e_t *i40e, const uint8_t *mac_addr)
+{
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+	struct i40e_aqc_remove_macvlan_element_data mvlist;
+	int i;
+	i40e_ether_addr_t *nukeme;
+
+	ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));
+
+	if (i40e->i40e_state & I40E_SUSPENDED)
+		return (ECANCELED);
+
+	for (i = 0; i < i40e->i40e_mac_used; i++) {
+		if (bcmp(mac_addr, i40e->i40e_mac_addrs[i].i40eth_mac,
+		    ETHERADDRL) == 0) {
+			break;
+		}
+	}
+
+	if (i == i40e->i40e_mac_used)
+		return (ENOENT);	/* ixgbe uses EINVAL... why? */
+
+	nukeme = &(i40e->i40e_mac_addrs[i]);
+
+	/*
+	 * Start by making sure the common-code/HW is okay with it.
+	 */
+	bzero(&mvlist, sizeof (mvlist));
+	bcopy(mac_addr, mvlist.mac_addr, ETHERADDRL);
+	mvlist.flags = I40E_AQC_MACVLAN_DEL_PERFECT_MATCH |
+	    I40E_AQC_MACVLAN_DEL_IGNORE_VLAN;
+	if (i40e_aq_remove_macvlan(hw, i40e->i40e_vsi_id, &mvlist, 1, NULL) !=
+	    I40E_SUCCESS) {
+		return (EIO);	/* XXX KEBE ASKS - better idea? */
+	}
+
+	/*
+	 * Okay!  Now that the board thinks it's good, we need to update
+	 * ourselves.
+	 */
+	i40e->i40e_mac_used--;
+	*nukeme = i40e->i40e_mac_addrs[i40e->i40e_mac_used];
+	i40e->i40e_mac_addrs[i40e->i40e_mac_used].i40eth_used = 0;
+
+	return (0);
+}
+
+/*
+ * GLDv3 receive group method: Add a MAC address.
+ */
+static int
+i40e_addmac(void *arg, const uint8_t *mac_addr)
+{
+	i40e_t *i40e = arg;
+	int rc;
+
+	mutex_enter(&i40e->i40e_general_lock);
+	rc = i40e_add_mac_locked(i40e, mac_addr);
+	mutex_exit(&i40e->i40e_general_lock);
+	return (rc);
+}
+
+/*
+ * GLDv3 receive group method: Remove a MAC address.
+ */
+static int
+i40e_remmac(void *arg, const uint8_t *mac_addr)
+{
+	i40e_t *i40e = arg;
+	int rc;
+
+	mutex_enter(&i40e->i40e_general_lock);
+	rc = i40e_remove_mac_locked(i40e, mac_addr);
+	mutex_exit(&i40e->i40e_general_lock);
+	return (rc);
+}
+
+/*
+ * GLDv3 method: Bring the device out of the reset/quiesced state that it was
+ * in when the interface was registered.
+ */
+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, B_TRUE)) {
+		rc = EIO;
+		goto done;
+	}
+
+	atomic_or_32(&i40e->i40e_state, I40E_STARTED);
+
+done:
+	mutex_exit(&i40e->i40e_general_lock);
+	if (rc == 0)
+		i40e_enable_watchdog_timer(i40e);
+	return (rc);
+}
+
+/*
+ * GLDv3 method: Stop the device and put it in a reset/quiesced state such
+ * that the interface can be unregistered.
+ */
+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, B_TRUE);
+
+done:
+	mutex_exit(&i40e->i40e_general_lock);
+
+	i40e_disable_watchdog_timer(i40e);
+}
+
+/*
+ * GLDv3 method: Enable or disable promiscuous mode.
+ */
+static int
+i40e_m_promisc(void *arg, boolean_t on)
+{
+	i40e_t *i40e = arg;
+	struct i40e_hw *hw = &i40e->i40e_hw_space;
+	int rc = 0;
+
+	mutex_enter(&i40e->i40e_general_lock);
+	if (i40e->i40e_state & I40E_SUSPENDED) {
+		rc = ECANCELED;
+		goto done;
+	}
+
+	/*
+	 * Turn on or off promiscuous.  The i40e common code has two switches:
+	 * unicast & multicast.  Turn them BOTH on or off.
+	 */
+	rc = i40e_aq_set_vsi_unicast_promiscuous(hw, i40e->i40e_vsi_id,
+	    on, NULL);
+	switch (rc) {
+	case I40E_SUCCESS:
+		rc = 0;	/* May be redundant... */
+		break;
+		/* XXX KEBE SAYS MORE VALUES to map here... */
+	default:
+		rc = EINVAL;
+		goto done;
+	}
+	rc = i40e_aq_set_vsi_multicast_promiscuous(hw, i40e->i40e_vsi_id,
+	    on, NULL);
+	/* NOTE:  If this fails, the unicast one has still taken, however. */
+	switch (rc) {
+	case I40E_SUCCESS:
+		rc = 0;	/* May be redundant... */
+		break;
+		/* XXX KEBE SAYS MORE VALUES to map here... */
+	default:
+		rc = EINVAL;
+		break;
+	}
+
+done:
+	mutex_exit(&i40e->i40e_general_lock);
+	return (rc);
+}
+
+/*
+ * Support functions for GLDv3 add/delete multicast address method.
+ *
+ * NOTE:  It appears that the i40e HW doesn't differentiate between unicast and
+ * multicast in its filters.  For now, act as a front-end to i40e_{add,rem}mac.
+ * IF we overrun with multicast, these functions should instead:
+ * 1.) Keep track of multicasts separately and
+ * 2.) Send a all-multicast indicator to i40e_{add,rem}mac() to produce a
+ * single HW filter that accepts ALL multicast traffic (but not promisc. per se
+ * if that's the case) and opens up more filter space.
+ *
+ * For now, however, these are VERY TINY functions.  If we need to get smarter
+ * per above, we have the scaffolding in place now.
+ */
+static int
+i40e_multicast_add(i40e_t *i40e, const uint8_t *multicast_address)
+{
+	return (i40e_add_mac_locked(i40e, multicast_address));
+}
+
+static int
+i40e_multicast_remove(i40e_t *i40e, const uint8_t *multicast_address)
+{
+	return (i40e_remove_mac_locked(i40e, multicast_address));
+}
+
+/*
+ * GLDv3 method: Add or delete a multicast address.
+ */
+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);
+
+	/*
+	 * NOTE: This is redundant unless the above tiny support functions
+	 * grow more sophisticated.
+	 */
+	if (i40e->i40e_state & I40E_SUSPENDED) {
+		rc = ECANCELED;
+		goto done;
+	}
+
+	rc = (add) ? i40e_multicast_add(i40e, multicast_address) :
+	    i40e_multicast_remove(i40e, multicast_address);
+
+done:
+	mutex_exit(&i40e->i40e_general_lock);
+	return (rc);
+}
+
+/*
+ * GLDv3 method: Pass on M_IOCTL messages passed to the DLD, and support
+ * private IOCTLs for debugging and ndd.  Yes this looks a little STREAMS-ish
+ * with its queue_t and mblk_t arguments.
+ */
+static void
+i40e_m_ioctl(void *arg, queue_t *q, mblk_t *mp)
+{
+	i40e_t *i40e = arg;
+	struct iocblk *iocp;
+	boolean_t send_nak;
+
+	iocp = (struct iocblk *)(uintptr_t)mp->b_rptr;
+	iocp->ioc_error = 0;
+
+	mutex_enter(&i40e->i40e_general_lock);
+	if (i40e->i40e_state & I40E_SUSPENDED) {
+		mutex_exit(&i40e->i40e_general_lock);
+		/*
+		 * ixgbe returns EINVAL here, but other SUSPENDED cases
+		 * in other methods return ECANCELED.  Why different here?
+		 * Because of STREAMS?
+		 */
+		miocnak(q, mp, 0, EINVAL);
+		return;
+	}
+	mutex_exit(&i40e->i40e_general_lock);
+
+	switch (iocp->ioc_cmd) {
+	case LB_GET_INFO_SIZE:
+	case LB_GET_INFO:
+	case LB_GET_MODE:
+	case LB_SET_MODE:
+		/* XXX KEBE SAYS FILL ME IN with loopback ioctl handling. */
+		iocp->ioc_error = ENOTSUP;
+		send_nak = B_TRUE;  /* send_nak = i40e_loopback_ioctl(...) */
+		break;
+
+	default:
+		send_nak = B_TRUE;
+		break;
+	}
+
+	/* Using STREAMS status, figure out what next. */
+	if (send_nak) {
+		/* NAK with a specified error or EINVAL. */
+		miocnak(q, mp, 0,
+		    iocp->ioc_error == 0 ? EINVAL : iocp->ioc_error);
+	} else {
+		/* Callee prepared reply as ACK or NAK). */
+		mp->b_datap->db_type =
+		    iocp->ioc_error == 0 ? M_IOCACK : M_IOCNAK;
+		qreply(q, mp);
+	}
+}
+
+/*
+ * Support functions for GLDv3 capabilities (ring fill-in functions).
+ */
+
+/* Enable a ring interrupt. */
+/* ARGSUSED */
+int
+i40e_rx_ring_intr_enable(mac_intr_handle_t intrh)
+{
+	return (EINVAL);
+}
+
+/* Disable a ring interrupt. */
+/* ARGSUSED */
+int
+i40e_rx_ring_intr_disable(mac_intr_handle_t intrh)
+{
+	return (EINVAL);
+}
+
+/* Fill in transmit ring. */
+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]);
+
+	ASSERT(group_index == -1);
+	ASSERT(ring_index < i40e->i40e_num_trqpairs);
+
+	/*
+	 * Basically, we want to map the ring_index & ring handle handed to us
+	 * from GLDv3 and bidirectionally map it to an i40e_tx_ring_t.  We put
+	 * "rh" into the txr_ring_handle (one direction) and put the
+	 * i40e_tx_ring_t into the infop->mri_driver (other direction).
+	 */
+
+	/* Map the appropriate itrq. */
+	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;
+
+	/*
+	 * Fill in the one lone mintr field, only if MSI or MSIX, the
+	 * mi_ddi_handle.
+	 */
+	if (i40e->i40e_intr_type & (DDI_INTR_TYPE_MSIX | DDI_INTR_TYPE_MSI)) {
+		mintr->mi_ddi_handle =
+		    i40e->i40e_intr_handles[itrq->itrq_tx_intrvec];
+	}
+}
+
+/*
+ * Receive ring start function for GLDv3
+ */
+static int
+i40e_ring_start(mac_ring_driver_t rh, uint64_t gen_num)
+{
+	i40e_trqpair_t *itrq = (i40e_trqpair_t *)rh;
+
+	/*
+	 * 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);
+}
+
+/* Fill in receive ring. */
+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;
+	i40e_trqpair_t *itrq = &(i40e->i40e_trqpairs[ring_index]);
+
+	/*
+	 * Basically, we want to map the ring_index & ring handle handed to us
+	 * from GLDv3 and bidirectionally map it to an i40e qpair.  We put
+	 * "rh" into the macrxring (one direction) and put the i40e_trqpair_t
+	 * into the infop->mri_driver (other direction).
+	 */
+	ASSERT3S(group_index, ==, 0);
+	ASSERT3S(ring_index, <, i40e->i40e_num_trqpairs);
+
+	/* Map the appropriate rxr, use ring_index as an index for now... */
+	itrq->itrq_macrxring = rh;
+
+	infop->mri_driver = (mac_ring_driver_t)itrq;
+	/* XXX KEBE SAYS FILL IN THESE WITH REAL FUNCTIONS... */
+	infop->mri_start = i40e_ring_start;
+	infop->mri_stop = NULL;
+	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;
+	/*
+	 * Fill in the one lone mintr field, only if MSI or MSIX, the
+	 * mi_ddi_handle.
+	 */
+	if (i40e->i40e_intr_type & (DDI_INTR_TYPE_MSIX | DDI_INTR_TYPE_MSI)) {
+		mintr->mi_ddi_handle =
+		    i40e->i40e_intr_handles[itrq->itrq_rx_intrvec];
+	}
+}
+
+/* Fill in receive group. */
+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;
+
+	/* It *IS* called i40e_fill_*RX*_group().... */
+	if (rtype != MAC_RING_TYPE_RX)
+		return;
+
+	i40e->i40e_rx_group_handle = gh;
+
+	/*
+	 * NOTE: If we map rx groups to VSIs OR trqpairs, the following may
+	 * need to change.
+	 */
+	infop->mgi_driver = (mac_group_driver_t)i40e;
+	infop->mgi_start = NULL;
+	infop->mgi_stop = NULL;
+	infop->mgi_addmac = i40e_addmac;
+	infop->mgi_remmac = i40e_remmac;
+
+	ASSERT(i40e->i40e_num_rx_groups == 1);	/* XXX KEBE SAYS FOR NOW... */
+	infop->mgi_count = i40e->i40e_num_trqpairs;
+}
+
+/*
+ * GLDv3 method: Express as GLDv3 capabilities what this driver can do.
+ */
+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;
+
+	switch (cap) {
+	case MAC_CAPAB_HCKSUM:
+		/* Hardware checksumming info. */
+		if (!i40e->i40e_tx_hcksum_enable)
+			return (B_FALSE);
+		/* cap_data points to a uint32_t, needing flags filled in. */
+		*((uint32_t *)cap_data) =
+		    HCKSUM_INET_PARTIAL | HCKSUM_IPHDRCKSUM;
+		break;
+	case MAC_CAPAB_LSO:
+		/* Large-segment offload (LSO) info. */
+		if (i40e->i40e_lso_enable) {
+			mac_capab_lso_t *cap_lso = cap_data;
+
+			/*
+			 * Table 1-5 says LSO works for both IPv4 and IPv6,
+			 * BUT IPv6 LSO isn't supported in GLDv3/MAC yet.
+			 */
+			cap_lso->lso_flags = LSO_TX_BASIC_TCP_IPV4;
+			cap_lso->lso_basic_tcp_ipv4.lso_max = I40E_LSO_MAXLEN;
+		} else {
+			return (B_FALSE);
+		}
+		break;
+	case MAC_CAPAB_RINGS:
+		/* XXX KEBE SAYS FILL ME IN with ring info, incl. functions. */
+		cap_rings = cap_data;
+		/* Anything common across both TX and RX?  Fill that in here. */
+		cap_rings->mr_group_type = MAC_GROUP_TYPE_STATIC;
+		switch (cap_rings->mr_type) {
+		case MAC_RING_TYPE_TX:
+			/* Fill in transmit ring info. */
+			cap_rings->mr_rnum = i40e->i40e_num_trqpairs;
+			cap_rings->mr_gnum = 0;
+			cap_rings->mr_rget = i40e_fill_tx_ring;
+			cap_rings->mr_gget = NULL;
+			/* XXX KEBE ASKS -> make these functions?!? */
+			cap_rings->mr_gaddring = NULL;
+			cap_rings->mr_gremring = NULL;
+			break;
+		case MAC_RING_TYPE_RX:
+			/* Fill in receive ring info. */
+			cap_rings->mr_rnum = i40e->i40e_num_trqpairs;
+			cap_rings->mr_rget = i40e_fill_rx_ring;
+			/* XXX KEBE SAYS FIX FOR rx groups... */
+			cap_rings->mr_gnum = 1;
+			cap_rings->mr_gget = i40e_fill_rx_group;
+			/* XXX KEBE ASKS -> make these functions?!? */
+			cap_rings->mr_gaddring = NULL;
+			cap_rings->mr_gremring = NULL;
+			break;
+		default:
+			/* XXX KEBE ASKS - ixgbe does nothing here, why? */
+			return (B_FALSE);
+		}
+		break;
+	default:
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * GLDv3 method: Set the NIC's properties.
+ */
+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)
+{
+	i40e_t *i40e = arg;
+	int err = 0;
+
+	mutex_enter(&i40e->i40e_general_lock);
+	if (i40e->i40e_state & I40E_SUSPENDED) {
+		err = ECANCELED;
+		goto done;
+	}
+
+	/* XXX KEBE SAYS FILL ME IN */
+	err = ENOTSUP;
+
+done:
+	mutex_exit(&i40e->i40e_general_lock);
+	return (err);
+}
+
+/*
+ * GLDv3 method: Get the NIC's properties.
+ */
+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; */
+	int err = 0;
+
+	switch (pr_num) {
+	case MAC_PROP_PRIVATE:
+		/* XXX KEBE SAYS FILL ME IN  */
+		break;
+	default:
+		err = EINVAL;
+		break;
+	}
+
+	return (err);
+}
+
+/*
+ * GLDv3 method: Get the read/write data about a given property.
+ */
+static void
+i40e_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t pr_num,
+    mac_prop_info_handle_t prh)
+{
+	/* XXX KEBE SAYS FILL ME IN */
+}
+
+#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_default_mtu;
+	mac->m_margin = VLAN_TAGSZ;
+	mac->m_priv_props = NULL;	/* XXX KEBE SAYS FIX ME */
+	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);
+}
