--- /dev/null	Thu Dec 10 09:27:28 2015
+++ new/usr/src/uts/common/io/i40e/i40e_main.c	Thu Dec 10 09:27:28 2015
@@ -0,0 +1,2269 @@
+/*
+ * 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.
+ */
+
+/*
+ * Initialization entry points and setup for the Intel XL710 (i40e) family
+ * of Ethernet NICs.
+ */
+
+#include "i40e_sw.h"
+
+static char i40e_ident[] = "Intel 40Gb Ethernet";
+static uint8_t i40e_ether_broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+/*
+ * Local function prototypes.
+ */
+
+static int i40e_attach(dev_info_t *, ddi_attach_cmd_t);
+static int i40e_detach(dev_info_t *, ddi_detach_cmd_t);
+static int i40e_quiesce(dev_info_t *);
+static void i40e_unconfigure(dev_info_t *, i40e_t *);
+static void i40e_fm_init(i40e_t *);
+static void i40e_fm_fini(i40e_t *);
+static boolean_t i40e_identify_hardware(i40e_t *);
+static boolean_t i40e_regs_map(i40e_t *);
+static void i40e_init_properties(i40e_t *);
+static boolean_t i40e_alloc_intrs(i40e_t *, dev_info_t *);
+static boolean_t i40e_alloc_trqpairs_locks(i40e_t *);
+static boolean_t i40e_map_intrs_to_vectors(i40e_t *);
+static void i40e_free_trqpairs_locks(i40e_t *);
+static int i40e_intr_callback(dev_info_t *, ddi_cb_action_t, void *, void *,
+    void *);
+static boolean_t i40e_add_intr_handlers(i40e_t *);
+static void i40e_timer(void *);
+static boolean_t i40e_enable_interrupts(i40e_t *);
+static boolean_t i40e_disable_interrupts(i40e_t *);
+static int i40e_get_vsi_id(i40e_t *);
+
+static struct cb_ops i40e_cb_ops = {
+	nulldev,		/* cb_open */
+	nulldev,		/* cb_close */
+	nodev,			/* cb_strategy */
+	nodev,			/* cb_print */
+	nodev,			/* cb_dump */
+	nodev,			/* cb_read */
+	nodev,			/* cb_write */
+	nodev,			/* cb_ioctl */
+	nodev,			/* cb_devmap */
+	nodev,			/* cb_mmap */
+	nodev,			/* cb_segmap */
+	nochpoll,		/* cb_chpoll */
+	ddi_prop_op,		/* cb_prop_op */
+	NULL,			/* cb_stream */
+	D_MP | D_HOTPLUG,	/* cb_flag */
+	CB_REV,			/* cb_rev */
+	nodev,			/* cb_aread */
+	nodev			/* cb_awrite */
+};
+
+static struct dev_ops i40e_dev_ops = {
+	DEVO_REV,		/* devo_rev */
+	0,			/* devo_refcnt */
+	NULL,			/* devo_getinfo */
+	nulldev,		/* devo_identify */
+	nulldev,		/* devo_probe */
+	i40e_attach,		/* devo_attach */
+	i40e_detach,		/* devo_detach */
+	nodev,			/* devo_reset */
+	&i40e_cb_ops,		/* devo_cb_ops */
+	NULL,			/* devo_bus_ops */
+	ddi_power,		/* devo_power */
+	i40e_quiesce,		/* devo_quiesce */
+};
+
+static struct modldrv i40e_modldrv = {
+	&mod_driverops,		/* Type of module.  This one is a driver */
+	i40e_ident,		/* Description string */
+	&i40e_dev_ops		/* driver ops */
+};
+
+static struct modlinkage i40e_modlinkage = {
+	MODREV_1, &i40e_modldrv, NULL
+};
+
+/*
+ * Access attributes for register mapping
+ * XXX KEBE ASKS --> Change these? Currently they're stolen from ixgbe...
+ */
+ddi_device_acc_attr_t i40e_regs_acc_attr = {
+	DDI_DEVICE_ATTR_V1,
+	DDI_STRUCTURE_LE_ACC,
+	DDI_STRICTORDER_ACC,
+	DDI_FLAGERR_ACC
+};
+
+/*
+ * XXX KEBE SAYS NOTE:	I'm currently using the ixgbe approach to things.
+ * It's VERY possible the Fortville/i40e world changes the game enough where
+ * we need to restructure the device capability structure.
+ * See i40e_common_code_init() for examples of why I NOTE this.
+ *
+ * Values pulled will have section number references from:
+ *
+ * BEGIN CSTYLED
+ * http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/xl710-10-40-controller-datasheet.pdf
+ * END CSTYLED
+ */
+static adapter_info_t i40e_XL710_cap = {
+	1536,		/* maximum number of rx queues */ /* 1.1.2 - LQP */
+	1,		/* minimum number of rx queues */
+	256,		/* default number of rx queues */
+	384,		/* maximum number of rx groups */ /* 1.1.6 */
+	1,		/* minimum number of rx groups */
+	1,		/* default number of rx groups */
+	1536,		/* maximum number of tx queues */ /* 1.1.2 - LQP */
+	1,		/* minimum number of tx queues */
+	32,		/* default number of tx queues */
+	9710,		/* maximum MTU size */ /* 1.2, tbl 1-3 */
+	0xFF8,		/* maximum interrupt throttle rate */
+	0,		/* minimum interrupt throttle rate */
+	200,		/* default interrupt throttle rate */
+	1024,		/* maximum admin queue size. XXX KEBE SAYS REFERENCE */
+	256,		/* minimum admin queue size. */
+	256,		/* default admin queue size. */
+	4096,		/* maximum admin queue buffer size. XXX REFERENCE */
+	4096,		/* maximum admin queue buffer size. */
+	4096,		/* maximum admin queue buffer size. */
+	129,		/* maximum total msix vectors */ /* 1.2, tbl 1-2 */
+	48,		/* maximum number of ring vectors */
+	2,		/* maximum number of other vectors */
+	(0), /* "other" interrupt types handled */ /* KEBE SAYS FILL ME IN */
+
+	(0), /* "other" interrupt types enable mask */ /* KEBE SAYS FILL IN */
+
+	(I40E_FLAG_DCA_CAPABLE
+	| I40E_FLAG_RSS_CAPABLE
+	| I40E_FLAG_VMDQ_CAPABLE
+	| I40E_FLAG_RSC_CAPABLE) /* capability flags */
+};
+
+/*
+ * Loopback properties (see sys/netlb.h).
+ */
+/* KEBE SAYS UNCOMMENT ME LATER...
+static lb_property_t lb_normal = {
+	normal,	"normal", I40E_LB_NONE
+};
+
+static lb_property_t lb_mac = {
+	internal, "MAC", I40E_LB_INTERNAL_MAC
+};
+
+static lb_property_t lb_external = {
+	external, "External", I40E_LB_EXTERNAL
+};
+... XXX KEBE */
+
+/*
+ * Generic Lan Driver (GLDv3)/MAC definitions and structures should mostly
+ * be in i40e_gld.c.
+ */
+
+/*
+ * Logging function for this driver.
+ */
+void
+i40e_dev_err(i40e_t *i40e, int level, boolean_t console, const char *fmt,
+    va_list ap)
+{
+	char buf[256];	/* Pardon the constant, seems enough for printing. */
+
+	(void) vsnprintf(buf, sizeof (buf), fmt, ap);
+
+	if (i40e == NULL) {
+		cmn_err(level, (console) ? "%s: %s" : "!%s: %s", MODULE_NAME,
+		    buf);
+	} else {
+		dev_err(i40e->i40e_dip, level, (console) ? "%s" : "!%s",
+		    buf);
+	}
+}
+
+/*
+ * Because there's the stupid trailing-comma problem with the C preprocessor
+ * and variable arguments, I need to instantiate these.	 Pardon the redundant
+ * code.
+ */
+void
+i40e_error(i40e_t *i40e, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	i40e_dev_err(i40e, CE_WARN, B_FALSE, fmt, ap);
+	va_end(ap);
+}
+
+void
+i40e_log(i40e_t *i40e, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	i40e_dev_err(i40e, CE_NOTE, B_FALSE, fmt, ap);
+	va_end(ap);
+}
+
+void
+i40e_notice(i40e_t *i40e, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	i40e_dev_err(i40e, CE_NOTE, B_TRUE, fmt, ap);
+	va_end(ap);
+}
+
+#define	FIRST_TRY_COUNT 40  /* Arbitrary guess at how many elements needed. */
+static boolean_t
+i40e_get_hw_capabilities(i40e_t *i40e, i40e_hw_t *hw)
+{
+	struct i40e_aqc_list_capabilities_element_resp *buf;
+	int nelems = FIRST_TRY_COUNT;
+	int rc, len;
+	uint16_t needed;
+
+	/*
+	 * The common-code call needs a buffer.	 Allocate it, and try.
+	 * if the common-code calls complains the buffer isn't large enough,
+	 * use "needed" to try another allocation.
+	 */
+	do {
+		len = nelems * sizeof (*buf);
+		buf = kmem_alloc(len, KM_SLEEP);
+		ASSERT(buf != NULL);	/* Safe with KM_SLEEP. */
+		rc = i40e_aq_discover_capabilities(hw, buf, len,
+		    &needed, i40e_aqc_opc_list_func_capabilities, NULL);
+		kmem_free(buf, len);
+		if (hw->aq.asq_last_status == I40E_AQ_RC_ENOMEM &&
+		    nelems == FIRST_TRY_COUNT) {
+			if (nelems == needed) {
+				/* Measurable in WTFs... */
+				i40e_error(i40e, "Common code is insane.");
+				return (B_FALSE);
+			}
+			nelems = needed;
+			buf = NULL;	/* i.e. Continue the loop. */
+		} else if (hw->aq.asq_last_status != I40E_AQ_RC_OK) {
+			i40e_error(i40e, "Capability discovery failed: %d", rc);
+			return (B_FALSE);
+		}
+		/* OKAY, we're good, with a freed buffer pointer to exit... */
+		ASSERT(buf != NULL);
+	} while (buf == NULL);
+
+	/*
+	 * XXX KEBE SAYS NOTE: FreeBSD says capture this PF's starting queue
+	 * pair.
+	 */
+	/* i40e->i40e_qbase = hw->func_caps.base_queue; */ /* TODO? */
+	i40e_log(i40e, "The base queue pair is %d", hw->func_caps.base_queue);
+
+	return (B_TRUE);
+}
+#undef	FIRST_TRY_COUNT
+
+/*
+ * Unless a .conf file already overrode i40e_t structure values, they will
+ * be 0, and need to be set in conjuction with the now-available HW report.
+ */
+static void
+i40e_hw_to_instance(i40e_t *i40e, i40e_hw_t *hw)
+{
+	if (i40e->i40e_num_trqpairs == 0) {
+		i40e->i40e_num_trqpairs =
+		    min(hw->func_caps.num_tx_qp + hw->func_caps.num_rx_qp,
+		    MAX_TX_RX_QUEUE_PAIRS);
+	}
+	/*
+	 * XXX KEBE ASKS -> Is there an i40e/XL710 version of receive groups?
+	 * For now, let's just set it to 1.
+	 */
+	i40e->i40e_num_rx_groups = 1;
+
+	/*
+	 * The combination of num_rx_rings and num_rx_groups may be not
+	 * supported by h/w. We need to adjust them to appropriate values.
+	 *
+	 * NOTE:  Right now we only work with XL710, if we get other i40es,
+	 * we may need to adjust.
+	 *
+	 * NOTE2: With only one GLDv3 receive group, we can only do
+	 * "RSS" at the moment.  Also there are two types of VMDQ now in i40e,
+	 * VMDQ and VMDQv2.
+	 */
+	if (i40e->i40e_num_trqpairs == 1)
+		i40e->i40e_classify_mode = I40E_CLASSIFY_NONE;
+	else
+		i40e->i40e_classify_mode = I40E_CLASSIFY_RSS;
+}
+
+/*
+ * Initialize and call Intel common-code routines, includes some setup
+ * the common code expects from the driver.  Also prints on failure, so
+ * the caller doesn't have to.
+ */
+static boolean_t
+i40e_common_code_init(i40e_t *i40e, i40e_hw_t *hw)
+{
+	int rc;
+	
+	/* Clear & reset hardware, using Intel's code. */
+	i40e_clear_hw(hw);
+	rc = i40e_pf_reset(hw);
+	if (rc != 0) {
+		i40e_error(i40e, "i40e_pf_reset() failed: %d\n", rc);
+		/* XXX KEBE ASKS FMA report? */
+		i40e_fm_ereport(i40e, DDI_FM_DEVICE_INVAL_STATE);
+		return (B_FALSE);
+	}
+
+	/* Initialize the shared code. */
+	rc = i40e_init_shared_code(hw);
+	if (rc != 0) {
+		i40e_error(i40e, "i40e_init_shared_code() failed: %d\n", rc);
+		return (B_FALSE);
+	}
+
+	/* Set admin queue parameters */
+	hw->aq.num_arq_entries = i40e->i40e_capab->def_adminq_len;
+	hw->aq.num_asq_entries = i40e->i40e_capab->def_adminq_len;
+	hw->aq.arq_buf_size = i40e->i40e_capab->def_adminq_bufsize;
+	hw->aq.asq_buf_size = i40e->i40e_capab->def_adminq_bufsize;
+	/* Initialize the admin queue */
+	rc = i40e_init_adminq(hw);
+	if (rc != 0) {
+		i40e_error(i40e, "i40e_init_adminq() failed: %d\n", rc);
+		i40e_error(i40e, "This may be due to a mismatch between "
+		    "the firmware and the driver.\n");
+		/* XXX KEBE ASKS FMA report? */
+		i40e_fm_ereport(i40e, DDI_FM_DEVICE_INVAL_STATE);
+		return (B_FALSE);
+	}
+	/* Check for other firmware mismatches. */
+	if (hw->aq.api_maj_ver == I40E_FW_API_VERSION_MAJOR &&
+	    hw->aq.api_min_ver > I40E_FW_API_VERSION_MINOR) {
+		i40e_notice(i40e, "The driver for the device detected a newer "
+		    "version of the NVM image (%d.%d) than expected (%d.%d).\n"
+		    "Please install the most recent version of the network "
+		    "driver.\n", hw->aq.api_maj_ver, hw->aq.api_min_ver,
+		    I40E_FW_API_VERSION_MAJOR, I40E_FW_API_VERSION_MINOR);
+	} else if (hw->aq.api_maj_ver < I40E_FW_API_VERSION_MAJOR ||
+	    hw->aq.api_min_ver < (I40E_FW_API_VERSION_MINOR - 1)) {
+		i40e_notice(i40e, "The driver for the device detected an older"
+		    " version of the NVM image (%d.%d) than expected (%d.%d)."
+		    "\nPlease update the NVM image.\n",
+		    hw->aq.api_maj_ver, hw->aq.api_min_ver,
+		    I40E_FW_API_VERSION_MAJOR, I40E_FW_API_VERSION_MINOR - 1);
+	}
+
+	/* Clear PXE mode */
+	i40e_clear_pxe_mode(hw);
+
+	/*
+	 * Have the common code discover things about the hardware.
+	 */
+	if (!i40e_get_hw_capabilities(i40e, hw)) {
+		i40e_error(i40e, "Obtaining HW capabilities failed.");
+		/* XXX KEBE ASKS FMA report? */
+		i40e_fm_ereport(i40e, DDI_FM_DEVICE_INVAL_STATE);
+		return (B_FALSE);
+	}
+
+	/*
+	 * Now that we know some of the HW capabilities, let's initialize
+	 * them if they aren't already in our i40e.
+	 */
+	i40e_hw_to_instance(i40e, hw);
+
+	/* Set up host memory cache */
+	rc = i40e_init_lan_hmc(hw, hw->func_caps.num_tx_qp,
+	    hw->func_caps.num_rx_qp, 0, 0);
+	if (rc != 0) {
+		i40e_error(i40e, "init_lan_hmc failed: %d\n", rc);
+		return (B_FALSE);
+	}
+
+	rc = i40e_configure_lan_hmc(hw, I40E_HMC_MODEL_DIRECT_ONLY);
+	if (rc != 0) {
+		i40e_error(i40e, "configure_lan_hmc failed: %d\n", rc);
+		return (B_FALSE);
+	}
+
+	/* Disable LLDP from the firmware */
+	i40e_aq_stop_lldp(hw, TRUE, NULL);
+
+	i40e_get_mac_addr(hw, hw->mac.addr);
+	rc = i40e_validate_mac_addr(hw->mac.addr);
+	if (rc != 0) {
+		i40e_error(i40e, "validate_mac_addr failed: %d\n", rc);
+		return (B_FALSE);
+	}
+	bcopy(hw->mac.addr, hw->mac.perm_addr, ETHERADDRL);
+	i40e_get_port_mac_addr(hw, hw->mac.port_addr);
+
+	/*
+	 * We need to obtain the Virtual Station ID (vsi) before we can
+	 * anything else.
+	 */
+	i40e->i40e_vsi_id = i40e_get_vsi_id(i40e);
+	if (i40e->i40e_vsi_id == -1) {
+		i40e_error(i40e, "Failed to get VSI ID");
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * Free any resources required by, or setup by, the Intel common code.
+ */
+static void
+i40e_common_code_fini(i40e_t *i40e)
+{
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+	int rc;
+
+	rc = i40e_shutdown_adminq(hw);
+	if (rc != I40E_SUCCESS)
+		i40e_error(i40e, "i40e_shutdown_adminq() failed: %d", rc);
+
+	rc = i40e_shutdown_lan_hmc(hw);
+	if (rc != I40E_SUCCESS)
+		i40e_error(i40e, "i40e_shutdown_lan_hmc() failed: %d", rc);
+
+	/* XXX KEBE ASKS ---> more here?!? */
+}
+
+static boolean_t
+i40e_final_init(i40e_t *i40e)
+{
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+	struct i40e_osdep *osdep = OS_DEP(hw);
+	uint8_t pbanum[I40E_PBANUM_STRLEN];
+	enum i40e_status_code irc;
+
+	/* XXX KEBE ASKS enter/exit i40e->i40e_gen_lock here ? */
+
+	pbanum[0] = '\0';
+	irc = i40e_read_pba_string(hw, pbanum, sizeof (pbanum));
+	if (irc != I40E_SUCCESS) {
+		i40e_error(i40e, "i40e_read_pba_string() failed: %d", irc);
+		return (B_FALSE);
+	}
+	if (pbanum[0] != '\0') {
+		/* (void) this?!? */
+		(void) ddi_prop_update_string(DDI_DEV_T_NONE, i40e->i40e_dip,
+		    "printed-board-assembly", (char *)pbanum);
+	}
+
+	if (!i40e_set_hw_bus_info(hw))
+		return (B_FALSE);
+
+	if (i40e_check_acc_handle(osdep->reg_handle) != DDI_FM_OK) {
+		ddi_fm_service_impact(i40e->i40e_dip, DDI_SERVICE_LOST);
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * i40e_attach - Driver attach.
+ *
+ * This function is the device specific initialization entry
+ * point. This entry point is required and must be written.
+ * The DDI_ATTACH command must be provided in the attach entry
+ * point. When attach() is called with cmd set to DDI_ATTACH,
+ * all normal kernel services (such as kmem_alloc(9F)) are
+ * available for use by the driver.
+ *
+ * The attach() function will be called once for each instance
+ * of  the  device  on	the  system with cmd set to DDI_ATTACH.
+ * Until attach() succeeds, the only driver entry points which
+ * may be called are open(9E) and getinfo(9E).
+ */
+static int
+i40e_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd)
+{
+	i40e_t *i40e;
+	struct i40e_osdep *osdep;
+	i40e_hw_t *hw;
+	int instance, rc;
+	char taskqname[32];
+
+	/* Vector off bad commands or RESUME. */
+	switch (cmd) {
+	default:
+		return (DDI_FAILURE);
+	case DDI_RESUME:
+		/* return (i40e_resume(devinfo)); */
+		goto attach_fail;  /* XXX NUKE ME!!! */
+	case DDI_ATTACH:
+		break;
+	}
+
+	/* NOW we proceed with the actual attachment code. */
+
+	instance = ddi_get_instance(devinfo);
+	i40e = kmem_zalloc(sizeof (i40e_t), KM_SLEEP);
+	/* No NULL check needed for KM_SLEEP... */
+
+	i40e->i40e_instance = instance;
+	i40e->i40e_dip = devinfo;
+
+	hw = &i40e->i40e_hw_space;
+	osdep = &i40e->i40e_osdep_space;
+	hw->back = osdep;
+	osdep->i40e = i40e;
+
+	ddi_set_driver_private(devinfo, i40e);
+
+	/* Setup FMA support. */
+	i40e_fm_init(i40e);
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_FM_INIT;
+
+	/* Map PCI config space registers. */
+	if (pci_config_setup(devinfo, &osdep->cfg_handle) != DDI_SUCCESS) {
+		i40e_error(i40e, "Failed to map PCI configurations.");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_PCI_CONFIG;
+
+	/*
+	 * Map the device registers.  This involves identifying the
+	 * hardware, followed by the actual mapping.
+	 */
+	if (!i40e_identify_hardware(i40e)) {
+		i40e_error(i40e, "Cannot identify hardware.");
+		goto attach_fail;
+	}
+
+	/* Now the actual mapping. */
+	if (!i40e_regs_map(i40e)) {
+		i40e_error(i40e, "Failed to map device registers.");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_REGS_MAP;
+
+	i40e_init_properties(i40e);
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_PROPS;
+
+	if (!i40e_common_code_init(i40e, hw))
+		goto attach_fail;
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_COMMON_CODE;
+
+	/* Register the interrupt callback and allocate interrupts. */
+	rc = ddi_cb_register(devinfo, DDI_CB_FLAG_INTR, i40e_intr_callback,
+	    i40e, NULL, &i40e->i40e_callback_handle);
+	if (rc != DDI_SUCCESS) {
+		i40e_error(i40e,
+		    "Failed to register an interrupt callback: %d.", rc);
+		goto attach_fail;
+	}
+
+	if (!i40e_alloc_intrs(i40e, devinfo)) {
+		i40e_error(i40e, "Failed to allocate interrupts.");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_ALLOC_INTR;
+
+	/* Allocate tx/rx queue pairs and initialize locks. */
+	if (!i40e_alloc_trqpairs_locks(i40e)) {
+		i40e_error(i40e,
+		    "Failed to allocate receive & transmit rings.");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_ALLOC_RINGSLOCKS;
+
+	/* Map trqpairs to interrupts vectors... */
+	if (!i40e_map_intrs_to_vectors(i40e)) {
+		i40e_error(i40e, "Failed to map interrupts to vectors.");
+		goto attach_fail;
+	}
+	/* ...then add interrupt handlers. */
+	if (!i40e_add_intr_handlers(i40e)) {
+		i40e_error(i40e, "Failed to add the interrupt handlers.");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_ADD_INTR;
+
+	/* Create taskqs for SFP-change & over-temp. */
+	(void) sprintf(taskqname, "i40e%d_sfp_taskq", instance);
+	i40e->i40e_sfp_taskq = ddi_taskq_create(devinfo, taskqname, 1,
+	    TASKQ_DEFAULTPRI, 0);
+	if (i40e->i40e_sfp_taskq == NULL) {
+		i40e_error(i40e, "i40e_sfp_taskq creation failed");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_SFP_TASKQ;
+
+	(void) sprintf(taskqname, "i40e%d_overtemp_taskq", instance);
+	i40e->i40e_overtemp_taskq = ddi_taskq_create(devinfo, taskqname, 1,
+	    TASKQ_DEFAULTPRI, 0);
+	if (i40e->i40e_overtemp_taskq == NULL) {
+		i40e_error(i40e, "i40e_overtemp_taskq creation failed");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_OVERTEMP_TASKQ;
+
+	/* Additional initialization of chipset hardware. */
+	if (!i40e_final_init(i40e)) {
+		i40e_error(i40e, "Final initialization failed.");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_INIT;
+
+	/* Check the FM acc handle.  (No progress needed.) */
+	if (i40e_check_acc_handle(i40e->i40e_osdep_space.cfg_handle) !=
+	    DDI_FM_OK) {
+		ddi_fm_service_impact(i40e->i40e_dip, DDI_SERVICE_LOST);
+		goto attach_fail;
+	}
+
+	/* XXX KEBE SAYS Initialize the statistics. */
+
+	if (!i40e_init_stats(i40e)) {
+		i40e_error(i40e, "Stats initialization failed.");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_STATS;
+
+	/* Register the driver to GLDv3/mac. */
+	if (!i40e_register_mac(i40e)) {
+		i40e_error(i40e, "Failed to register to MAC/GLDv3");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_MAC;
+
+	/* Add a DDI periodic timer. */
+	i40e->i40e_periodic_id = ddi_periodic_add(i40e_timer, i40e,
+	    I40E_CYCLIC_PERIOD, DDI_IPL_0);
+	if (i40e->i40e_periodic_id == 0) {
+		i40e_error(i40e, "Failed to add the link-check timer");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_LINK_TIMER;
+
+	/* XXX KEBE SAYS Enable the interrupts! */
+	if (!i40e_enable_interrupts(i40e)) {
+		i40e_error(i40e, "Failed to enable DDI interrupts");
+		goto attach_fail;
+	}
+	i40e->i40e_attach_progress |= ATTACH_PROGRESS_ENABLE_INTR;
+
+	/* Atomic-or the I40E_INITIALIZED flag. */
+	atomic_or_32(&i40e->i40e_state, I40E_INITIALIZED);
+
+	return (DDI_SUCCESS);
+
+attach_fail:
+	i40e_unconfigure(devinfo, i40e);
+	return (DDI_FAILURE);
+}
+
+/*
+ * i40e_detach - Driver detach.
+ *
+ * The detach() function is the complement of the attach routine.
+ * If cmd is set to DDI_DETACH, detach() is used to remove  the
+ * state  associated  with  a  given  instance of a device node
+ * prior to the removal of that instance from the system.
+ *
+ * The detach() function will be called once for each  instance
+ * of the device for which there has been a successful attach()
+ * once there are no longer  any  opens	 on  the  device.
+ *
+ * Interrupts routine are disabled, All memory allocated by this
+ * driver are freed.
+ */
+static int
+i40e_detach(dev_info_t *devinfo, ddi_detach_cmd_t cmd)
+{
+	i40e_t *i40e;
+
+	switch (cmd) {
+	default:
+		return (DDI_FAILURE);
+	case DDI_SUSPEND:
+		/* XXX KEBE SAYS FILL ME IN */
+		return (DDI_FAILURE);
+	case DDI_DETACH:
+		break;
+	}
+
+	i40e = (i40e_t *)ddi_get_driver_private(devinfo);
+	if (i40e == NULL) {
+		i40e_log(NULL, "i40e_detach() called with no i40e pointer!");
+		return (DDI_FAILURE);
+	}
+
+	/*
+	 * XXX KEBE SAYS FILL IN still-running checks and drain the rx
+	 * buffers that upper layers may have lying about.
+	 */
+
+	/*
+	if (i40e->i40e_state & I40E_STARTED...)
+	 */
+
+
+	/*
+	if (!i40e_rx_drain(i40e))
+		return (DDI_FAILURE);
+	 */
+
+	i40e_unconfigure(devinfo, i40e);
+	return (DDI_SUCCESS);
+}
+
+static void
+i40e_rem_intrs(i40e_t *i40e)
+{
+	int i, rc;
+
+	for (i = 0; i < i40e->i40e_intr_count; i++) {
+		rc = ddi_intr_free(i40e->i40e_intr_handles[i]);
+		if (rc != DDI_SUCCESS) {
+			i40e_log(i40e, "Can't free i40e_intr_handle[%d]: %d",
+			    i, rc);
+		}
+	}
+
+	kmem_free(i40e->i40e_intr_handles, i40e->i40e_intr_size);
+	i40e->i40e_intr_handles = NULL;
+}
+
+static void
+i40e_rem_intr_handlers(i40e_t *i40e)
+{
+	int i, rc;
+
+	for (i = 0; i < i40e->i40e_intr_count; i++) {
+		rc = ddi_intr_remove_handler(i40e->i40e_intr_handles[i]);
+		if (rc != DDI_SUCCESS) {
+			i40e_log(i40e, "Can't remove intr_handler[%d]: %d",
+			    i, rc);
+		}
+	}
+}
+
+static void
+i40e_unconfigure(dev_info_t *devinfo, i40e_t *i40e)
+{
+	int rc;
+
+	/*
+	 * Disable interrupts (and ignore the return code since we're
+	 * detaching.
+	 */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_ENABLE_INTR)
+		(void) i40e_disable_interrupts(i40e);
+
+	/* Remove the link check timer. */
+	if ((i40e->i40e_attach_progress & ATTACH_PROGRESS_LINK_TIMER) &&
+	    i40e->i40e_periodic_id != 0) {
+		ddi_periodic_delete(i40e->i40e_periodic_id);
+		i40e->i40e_periodic_id = 0;
+	}
+
+	/* Unregister from GLDv3/mac. */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_MAC) {
+		rc = mac_unregister(i40e->i40e_mac_hdl);
+		if (rc != 0)
+			i40e_error(i40e, "mac_unregister() failed: %d", rc);
+	}
+
+	/* Free statistics... */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_STATS)
+		kstat_delete((kstat_t *)i40e->i40e_kstats);
+
+	/* if (i40e->i40e_attach_progress & ATTACH_PROGRESS_INIT)
+	   ; */
+
+	/* Remove the taskqs. */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_OVERTEMP_TASKQ)
+		ddi_taskq_destroy(i40e->i40e_overtemp_taskq);
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_SFP_TASKQ)
+		ddi_taskq_destroy(i40e->i40e_sfp_taskq);
+
+	/* XXX KEBE SAYS Remove the interrupt handlers. */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_ADD_INTR)
+		i40e_rem_intr_handlers(i40e);
+
+	/* Remove the tx/rx qpairs and decommission the locks. */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_ALLOC_RINGSLOCKS)
+		i40e_free_trqpairs_locks(i40e);
+
+	/* Remove interrupts and un-register interrupt callback handler. */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_ALLOC_INTR)
+		i40e_rem_intrs(i40e);
+
+	/* Check for unregistering by checking for a NULL callback handle. */
+	if (i40e->i40e_callback_handle != NULL)
+		(void) ddi_cb_unregister(i40e->i40e_callback_handle);
+
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_COMMON_CODE)
+		i40e_common_code_fini(i40e);
+
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_PROPS)
+		(void) ddi_prop_remove_all(devinfo);
+
+	/*
+	 * Free the register handle.
+	 */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_REGS_MAP &&
+	    i40e->i40e_osdep_space.reg_handle != NULL) {
+		ddi_regs_map_free(&i40e->i40e_osdep_space.reg_handle);
+	}
+
+	/*
+	 * Free PCI config handle
+	 */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_PCI_CONFIG &&
+	    i40e->i40e_osdep_space.cfg_handle != NULL) {
+		pci_config_teardown(&i40e->i40e_osdep_space.cfg_handle);
+	}
+
+	/*
+	 * Unregister FMA capabilities
+	 */
+	if (i40e->i40e_attach_progress & ATTACH_PROGRESS_FM_INIT)
+		i40e_fm_fini(i40e);
+
+	/*
+	 * Free the driver data structure
+	 */
+	kmem_free(i40e, sizeof (i40e_t));
+
+	ddi_set_driver_private(devinfo, NULL);
+}
+
+/*
+ * quiesce(9E) entry point.
+ *
+ * This function is called when the system is single-threaded at high
+ * PIL with preemption disabled. Therefore, this function must not be
+ * blocked.
+ *
+ * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
+ * DDI_FAILURE indicates an error condition and should almost never happen.
+ */
+static int
+i40e_quiesce(dev_info_t *devinfo)
+{
+	return (DDI_FAILURE); /* For now. */
+}
+
+/*
+ * illumos Fault Management Architecture (FMA) support.
+ */
+
+int
+i40e_check_acc_handle(ddi_acc_handle_t handle)
+{
+	ddi_fm_error_t de;
+
+	ddi_fm_acc_err_get(handle, &de, DDI_FME_VERSION);
+	ddi_fm_acc_err_clear(handle, DDI_FME_VERSION);
+	return (de.fme_status);
+}
+
+int
+i40e_check_dma_handle(ddi_dma_handle_t handle)
+{
+	ddi_fm_error_t de;
+
+	ddi_fm_dma_err_get(handle, &de, DDI_FME_VERSION);
+	return (de.fme_status);
+}
+
+/*
+ * Fault service error handling callback function.
+ */
+static int
+i40e_fm_error_cb(dev_info_t *dip, ddi_fm_error_t *err, const void *impl_data)
+{
+	_NOTE(ARGUNUSED(impl_data));
+	/*
+	 * as the driver can always deal with an error in any dma or
+	 * access handle, we can just return the fme_status value.
+	 */
+	pci_ereport_post(dip, err, NULL);
+	return (err->fme_status);
+}
+
+static void
+i40e_fm_init(i40e_t *i40e)
+{
+	ddi_iblock_cookie_t iblk;
+
+	/*
+	 * Gather up capabilities. In ixgbe (this driver's inspiration) this
+	 * was done in the caller. For i40e it's being encapsulated here.
+	 * NOTE: We have a looper for most i40e.conf entries, but we need to
+	 * do "fm_capable" early.
+	 */
+	i40e->i40e_fm_capabilities = ddi_prop_get_int(DDI_DEV_T_ANY,
+	    i40e->i40e_dip, DDI_PROP_DONTPASS, "fm_capable",
+	    DDI_FM_EREPORT_CAPABLE | DDI_FM_ACCCHK_CAPABLE |
+	    DDI_FM_DMACHK_CAPABLE | DDI_FM_ERRCB_CAPABLE);
+
+	if (i40e->i40e_fm_capabilities < 0) {
+		i40e->i40e_fm_capabilities = 0;
+	} else if (i40e->i40e_fm_capabilities > 0xf) {
+		i40e->i40e_fm_capabilities = DDI_FM_EREPORT_CAPABLE |
+		    DDI_FM_ACCCHK_CAPABLE | DDI_FM_DMACHK_CAPABLE |
+		    DDI_FM_ERRCB_CAPABLE;
+	}
+
+	/*
+	 * Only register with IO Fault Services if we have some capability
+	 */
+	if (i40e->i40e_fm_capabilities & DDI_FM_ACCCHK_CAPABLE) {
+		i40e_regs_acc_attr.devacc_attr_access = DDI_FLAGERR_ACC;
+	} else {
+		i40e_regs_acc_attr.devacc_attr_access = DDI_DEFAULT_ACC;
+	}
+
+	i40e_set_fma_flags(
+	    (i40e->i40e_fm_capabilities & DDI_FM_DMACHK_CAPABLE) != 0);
+
+	if (i40e->i40e_fm_capabilities) {
+		/*
+		 * Register capabilities with IO Fault Services
+		 */
+		ddi_fm_init(i40e->i40e_dip, &i40e->i40e_fm_capabilities, &iblk);
+
+		/*
+		 * Initialize pci ereport capabilities if ereport capable
+		 */
+		if (DDI_FM_EREPORT_CAP(i40e->i40e_fm_capabilities) ||
+		    DDI_FM_ERRCB_CAP(i40e->i40e_fm_capabilities)) {
+			pci_ereport_setup(i40e->i40e_dip);
+		}
+
+		/*
+		 * Register error callback if error callback capable
+		 */
+		if (DDI_FM_ERRCB_CAP(i40e->i40e_fm_capabilities)) {
+			ddi_fm_handler_register(i40e->i40e_dip,
+			    i40e_fm_error_cb, (void*)i40e);
+		}
+	}
+}
+
+static void
+i40e_fm_fini(i40e_t *i40e)
+{
+	/*
+	 * Only unregister FMA capabilities if they are registered
+	 */
+	if (i40e->i40e_fm_capabilities) {
+
+		/*
+		 * Release any resources allocated by pci_ereport_setup()
+		 */
+		if (DDI_FM_EREPORT_CAP(i40e->i40e_fm_capabilities) ||
+		    DDI_FM_ERRCB_CAP(i40e->i40e_fm_capabilities))
+			pci_ereport_teardown(i40e->i40e_dip);
+
+		/*
+		 * Un-register error callback if error callback capable
+		 */
+		if (DDI_FM_ERRCB_CAP(i40e->i40e_fm_capabilities))
+			ddi_fm_handler_unregister(i40e->i40e_dip);
+
+		/*
+		 * Unregister from IO Fault Service
+		 */
+		ddi_fm_fini(i40e->i40e_dip);
+	}
+}
+
+void
+i40e_fm_ereport(i40e_t *i40e, char *detail)
+{
+	uint64_t ena;
+	char buf[FM_MAX_CLASS];
+
+	(void) snprintf(buf, FM_MAX_CLASS, "%s.%s", DDI_FM_DEVICE, detail);
+	ena = fm_ena_generate(0, FM_ENA_FMT1);
+	if (DDI_FM_EREPORT_CAP(i40e->i40e_fm_capabilities)) {
+		ddi_fm_ereport_post(i40e->i40e_dip, buf, ena, DDI_NOSLEEP,
+		    FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERS0, NULL);
+	}
+}
+
+/*
+ * Identify the chipset to which we're attaching, and make necessary records.
+ * B_TRUE == success.
+ */
+static boolean_t
+i40e_identify_hardware(i40e_t *i40e)
+{
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+	struct i40e_osdep *osdep = &i40e->i40e_osdep_space;
+
+	hw->vendor_id = pci_config_get16(osdep->cfg_handle, PCI_CONF_VENID);
+	hw->device_id = pci_config_get16(osdep->cfg_handle, PCI_CONF_DEVID);
+	hw->revision_id = pci_config_get8(osdep->cfg_handle, PCI_CONF_REVID);
+	hw->subsystem_device_id =
+	    pci_config_get16(osdep->cfg_handle, PCI_CONF_SUBSYSID);
+	hw->subsystem_vendor_id =
+	    pci_config_get16(osdep->cfg_handle, PCI_CONF_SUBVENID);
+	/*
+	 * XXX KEBE SAYS FreeBSD sets bus.device with the slot, and bus.func
+	 * with "pci_get_function()".  Do we need this in illumos?
+	 */
+
+	/* Call common code to set the MAC type for this adapter. */
+	if (i40e_set_mac_type(hw) != I40E_SUCCESS)
+		return (B_FALSE);
+
+	/*
+	 * Now that we have the MAC type, use it to inform our view of the
+	 * capabilities.
+	 */
+	switch (hw->mac.type) {
+	case I40E_MAC_XL710:
+		/* XXX KEBE SAYS FILL ME IN!!! */
+		/* XXX KEBE ASKS - do we need to do this? */
+		i40e->i40e_capab = &i40e_XL710_cap;
+		break;
+#ifdef X722_SUPPORT
+	case I40E_MAC_X722:
+		break;
+	case I40E_MAC_X722_VF:
+#endif /* X722_SUPPORT */
+	case I40E_MAC_VF:
+		/* illumos does not support the _VF types. */
+		i40e_error(i40e,
+		    "i40e_attach() to _VF configuration not supported.");
+		return (B_FALSE);
+	case I40E_MAC_GENERIC:
+		/*
+		 * Unsure what to do with devices the Intel common code
+		 * doesn't know what to do with.  Bail, for now.
+		 */
+		i40e_error(i40e, "i40e doesn't support I40E_MAC_GENERIC.");
+		return (B_FALSE);
+	default:
+		/* Should never reach here, but inform and fail anyway. */
+		i40e_error(i40e, "Unknown i40e MAC type.");
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+static boolean_t
+i40e_regs_map(i40e_t *i40e)
+{
+	dev_info_t *devinfo = i40e->i40e_dip;
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+	struct i40e_osdep *osdep = &i40e->i40e_osdep_space;
+	off_t memsize;
+
+	if (ddi_dev_regsize(devinfo, I40E_ADAPTER_REGSET, &memsize) !=
+	    DDI_SUCCESS) {
+		return (B_FALSE);
+	}
+
+	if (ddi_regs_map_setup(devinfo, I40E_ADAPTER_REGSET,
+	    (caddr_t *)&hw->hw_addr, 0, memsize, &i40e_regs_acc_attr,
+	    &osdep->reg_handle) != DDI_SUCCESS) {
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * Two parts of i40e_init_properties().
+ */
+
+/* Part 1 - read i40e.conf. */
+static void
+i40e_get_conf(i40e_t *i40e)
+{
+	struct adapter_info *capab = i40e->i40e_capab;
+	struct i40e_proplist {
+		char *name;	/* Property name. */
+		int min;	/* Property minimum value. */
+		int max;	/* Property maximum value. */
+		int def;	/* Property default value. */
+		int offset;	/* Offset into i40e_t for the value. */
+	} props[] = {
+		/*
+		 * Just add entries to this table for any integer value.
+		 * XXX KEBE ASKS - should I enforce uint_t instead?
+		 */
+
+		/* Default MTU size. */
+		{ "default_mtu", ETHERMIN, capab->max_mtu, capab->max_mtu,
+		  offsetof(i40e_t, i40e_default_mtu) },
+
+		/* Flow control... */
+		{ "flow_control", I40E_FC_NONE, I40E_FC_DEFAULT, I40E_FC_NONE,
+		  offsetof(i40e_t, i40e_hw_space) +
+		  offsetof(struct i40e_hw, fc) +
+		  offsetof(struct i40e_fc_info, requested_mode) },
+
+		/* Interrupt forcing. */
+		{ "intr_force", I40E_INTR_NONE, I40E_INTR_LEGACY,
+		  I40E_INTR_NONE, offsetof(i40e_t, i40e_intr_force) },
+
+		/*
+		 * HW ring enable/disable.
+		 * (Assume sizeof (boolean_t) == sizeof (uint_t).)
+		 */
+		{ "mr_enable", B_FALSE, B_TRUE, B_TRUE,
+		  offsetof(i40e_t, i40e_mr_enable) },
+
+		/* Number of transmit/receive queue pairs */
+		/* XXX KEBE SAYS SET TO 2 for BRINGUP... */
+		{ "tx_rx_queue_pairs", 0, MAX_TX_RX_QUEUE_PAIRS,
+		  1, offsetof(i40e_t, i40e_num_trqpairs) },
+
+		/*
+		 * Various enables/disables.
+		 * Assume sizeof (boolean_t) == sizeof (uint_t).
+		 * XXX KEBE SAYS - some of these may/should become dladm(1M)
+		 * link properties.
+		 */
+		{ "tx_hcksum_enable", B_FALSE, B_TRUE, B_TRUE,
+		  offsetof(i40e_t, i40e_tx_hcksum_enable) },
+		{ "rx_hcksum_enable", B_FALSE, B_TRUE, B_TRUE,
+		  offsetof(i40e_t, i40e_rx_hcksum_enable) },
+		/* XXX KEBE ASKS - lso_enable set to true by default?!? */
+		{ "lso_enable", B_FALSE, B_TRUE, B_TRUE,
+		  offsetof(i40e_t, i40e_lso_enable) },
+		{ "lro_enable", B_FALSE, B_TRUE, B_FALSE,
+		  offsetof(i40e_t, i40e_lro_enable) },
+
+		/* Must end with a NULL string... */
+		{ NULL, 0, 0, 0, 0}
+	};
+	int i, propval;
+
+#define	PTROF(i40e, offset) ((int *)((uint8_t *)(i40e) + (offset)))
+	for (i = 0; props[i].name != NULL; i++) {
+		struct i40e_proplist *pvp = &(props[i]);
+
+		propval = ddi_prop_get_int(DDI_DEV_T_ANY, i40e->i40e_dip,
+		    DDI_PROP_DONTPASS, pvp->name, pvp->def);
+
+		if (propval > pvp->max)
+			propval = pvp->max;
+
+		if (propval < pvp->min)
+			propval = pvp->min;
+
+		ASSERT(IS_P2ALIGNED(PTROF(i40e, pvp->offset), 4));
+		*PTROF(i40e, pvp->offset) = propval;
+	}
+#undef PTROF
+
+	/* Make additional decisions based on properties initialized above. */
+
+	if (!i40e->i40e_mr_enable) {
+		i40e->i40e_num_trqpairs = 1;
+		i40e->i40e_classify_mode = I40E_CLASSIFY_NONE;
+	}
+	/*
+	 * Else we'll have to do this later, after we grab things from the
+	 * hardware.
+	 */
+
+	/* Some reality checks. */
+
+	/* Can't do LRO unless you have receive HW checksum enabled. */
+	i40e->i40e_lro_enable &= i40e->i40e_rx_hcksum_enable;
+
+	/* XXX KEBE SAYS FILL ME IN! */
+}
+
+/* Part 2 - Set up other parameters in the i40 instance. */
+static void
+i40e_init_params(i40e_t *i40e)
+{
+	/* XXX KEBE SAYS FILL ME IN! */
+	/*
+	 * Wow, lots to fill in here.  ixgbe has 100, 1000, 10000fdx stuff,
+	 * both enable, advertised, and "lp".  We'll have to add 20000 and
+	 * 40000 to that as well, I believe.
+	 */
+}
+
+/* Initialize driver properties... */
+static void
+i40e_init_properties(i40e_t *i40e)
+{
+	/* Split into two parts for easier reading & organization. See above. */
+	i40e_get_conf(i40e);
+	i40e_init_params(i40e);
+}
+
+/*
+ * i40e_alloc_intr_handles - Allocate interrupt handles. Called further down
+ * by the higher-level interrupt allocator: i40e_alloc_intrs().
+ *
+ * For legacy and MSI, only 1 handle is needed.	 For MSI-X, if fewer than 2
+ * handles are available, return failure.  Upon success, this maps the vectors
+ * to rx and tx rings for interrupts.
+ */
+static boolean_t
+i40e_alloc_intr_handles(i40e_t *i40e, dev_info_t *devinfo, int intr_type)
+{
+	int request, count, actual, rc;
+	/*
+	 * XXX KEBE ASKS --> should this even be here?
+	 * ixgbe sets this, but it's ALWAYS 1. Instead of cargo-culting, should
+	 * we be smarter and always assume minimum == 1?
+	 */
+	int minimum = 1;
+
+	switch (intr_type) {
+	case DDI_INTR_TYPE_FIXED:
+	case DDI_INTR_TYPE_MSI:
+		request = 1;
+		break;
+	case DDI_INTR_TYPE_MSIX:
+		/*
+		 * XXX KEBE SAYS FILL ME IN, and consider best number
+		 * of vectors the way ixgbe considered it (or better).
+		 */
+
+		/* XXX KEBE SAYS CONSULT "hw" now! */
+
+		/*
+		 * KEBE SAYS Cap as a function of how many CPU cores are
+		 * available.  Is this a preferred use of ncpus_online?
+		 * KEBE ASKS what happens when more/less go?  Is that an
+		 * interrupt change event?
+		 */
+#if 0
+		/* XXX KEBE ASKS two interrupts for each pair?  Or just one? */
+		request = min(ncpus_online, 2 * i40e->i40e_num_trqpairs + 1);
+		if (request > i40e->i40e_capab->max_ring_vect)
+			request = i40e->i40e_capab->max_ring_vect;
+#else
+		/*
+		 * XXX KEBE SAYS BRINGUP TEST - just do two (adminq and one
+		 * trqpair).
+		 */
+		ASSERT(i40e->i40e_num_trqpairs == 1);
+		request = 2;
+#endif
+		break;
+	default:
+		i40e_log(i40e,
+		    "Who called i40e_alloc_intr_handles() w/%d?", intr_type);
+		return (B_FALSE);
+	}
+
+	/* Counting on left->right evaluation and short-circuiting... */
+	rc = ddi_intr_get_nintrs(devinfo, intr_type, &count);
+	if (rc != DDI_SUCCESS || count < minimum) {
+		i40e_log(i40e, "Get interrupt number failed, "
+		    "returned %d, count %d\n", rc, count);
+		return (B_FALSE);
+	}
+
+	rc = ddi_intr_get_navail(devinfo, intr_type, &count);
+	if (rc != DDI_SUCCESS || count < minimum) {
+		i40e_log(i40e, "Get AVAILABLE interrupt number failed, "
+		    "returned %d, count %d\n", rc, count);
+		return (B_FALSE);
+	}
+
+	actual = 0;
+	i40e->i40e_intr_count = 0;
+	i40e->i40e_intr_count_max = 0;
+	i40e->i40e_intr_count_min = 0;
+
+	/* Actually allocate the array of interrupt handles. */
+	i40e->i40e_intr_size = request * sizeof (ddi_intr_handle_t);
+	i40e->i40e_intr_handles = kmem_alloc(i40e->i40e_intr_size, KM_SLEEP);
+	/*
+	 * KM_SLEEP allocation is okay during attach() and during a
+	 * reallocation.
+	 */
+
+	rc = ddi_intr_alloc(devinfo, i40e->i40e_intr_handles, intr_type, 0,
+	    min(request, count), &actual, DDI_INTR_ALLOC_NORMAL);
+	if (rc != DDI_SUCCESS) {
+		i40e_log(i40e, "Interrupt allocation failed with %d.", rc);
+		goto alloc_handle_fail;
+	}
+
+	i40e->i40e_intr_count = actual;
+	i40e->i40e_intr_count_max = request;
+	i40e->i40e_intr_count_min = minimum;
+
+	/*
+	 * (re)adjust rss number per group & the rss ring count, depending
+	 * on how interrupt allocation went.
+	 */
+	if (actual < i40e->i40e_num_trqpairs + 1) {
+		/* Remember, we need one for the adminq. */
+		i40e->i40e_num_trqpairs = (actual - 1) / 2;
+		if (i40e->i40e_num_trqpairs == 1)
+			i40e->i40e_classify_mode = I40E_CLASSIFY_NONE;
+		else
+			i40e->i40e_classify_mode = I40E_CLASSIFY_RSS;
+	}
+
+	/* Time to map the number of interrupt vectors we have. */
+	if (actual < minimum) {
+		/* XXX KEBE SAYS See cargo-culting comment re: minimum above */
+		i40e_log(i40e, "actual (%d) is less than minimum (%d).",
+		    actual, minimum);
+		goto alloc_handle_fail;
+	}
+
+	/*
+	 * Get & record priority and capabilities for first vector.  Once
+	 * we have it, that's our priority until detach time.  Even if we
+	 * get IRM requests (see i40e_intr_callback()), our priority won't
+	 * change.
+	 */
+	rc = ddi_intr_get_pri(i40e->i40e_intr_handles[0], &i40e->i40e_intr_pri);
+	if (rc != DDI_SUCCESS) {
+		i40e_log(i40e,
+		    "Getting interrupt priority failed with %d.", rc);
+		goto alloc_handle_fail;
+	}
+
+	rc = ddi_intr_get_cap(i40e->i40e_intr_handles[0], &i40e->i40e_intr_cap);
+	if (rc != DDI_SUCCESS) {
+		i40e_log(i40e,
+		    "Getting interrupt capabilities failed with %d.", rc);
+		goto alloc_handle_fail;
+	}
+	
+	/* Okay, we made it! */
+	i40e->i40e_intr_type = intr_type;
+	return (B_TRUE);
+
+alloc_handle_fail:
+
+	i40e_rem_intrs(i40e);
+	return (B_FALSE);
+}
+
+/* Allocate the driver's interrupts. */
+static boolean_t
+i40e_alloc_intrs(i40e_t *i40e, dev_info_t *devinfo)
+{
+	int intr_types, rc;
+
+	rc = ddi_intr_get_supported_types(devinfo, &intr_types);
+	if (rc != DDI_SUCCESS) {
+		i40e_log(i40e, "Get supported interrupt types failed: %d", rc);
+		return (B_FALSE);
+	}
+
+	i40e->i40e_intr_type = 0;
+
+	/* Try installing MSI-X interrupts first. */
+	if ((intr_types & DDI_INTR_TYPE_MSIX) &&
+	    (i40e->i40e_intr_force <= I40E_INTR_MSIX)) {
+		if (i40e_alloc_intr_handles(i40e, devinfo, DDI_INTR_TYPE_MSIX))
+			return (B_TRUE);
+		i40e_log(i40e, "Failed MSI-X attempt, trying MSI...");
+	}
+
+	/*
+	 * If I reach here, I failed to allocate MSI-X interrupts.
+	 * Instead, force all rings & groups to 1, and first try MSI.
+	 */
+	i40e->i40e_num_trqpairs = 1;
+	i40e->i40e_classify_mode = I40E_CLASSIFY_NONE;
+
+	/* Actually attempt an MSI interrupt allocation. */
+	if ((intr_types & DDI_INTR_TYPE_MSI) &&
+	    (i40e->i40e_intr_force <= I40E_INTR_MSI)) {
+		if (i40e_alloc_intr_handles(i40e, devinfo, DDI_INTR_TYPE_MSI))
+			return (B_TRUE);
+
+		i40e_log(i40e, "Failed MSI attempt, trying legacy...");
+	}
+
+	/* And if I reach here, try a legacy interrupt allocation. */
+	if (intr_types & DDI_INTR_TYPE_FIXED) {
+		if (i40e_alloc_intr_handles(i40e, devinfo, DDI_INTR_TYPE_FIXED))
+			return (B_TRUE);
+
+		i40e_log(i40e, "Failed Legacy attempt. Giving up.");
+	}
+
+	/* If I reach here, none of the interrupt allocations succeeded. */
+	return (B_FALSE);
+}
+
+/* ARGSUSED */
+static int
+i40e_intr_callback(dev_info_t *dip, ddi_cb_action_t cbaction, void *cbarg,
+    void *arg, void *unused)
+{
+	i40e_t *i40e = (i40e_t *)arg;
+	/* int count = (int)(uintptr_t)cbarg; */
+
+	VERIFY(i40e->i40e_dip == dip); /* XXX KEBE SAYS change to ASSERT(). */
+
+	switch (cbaction) {
+	case DDI_CB_INTR_ADD:
+	case DDI_CB_INTR_REMOVE:
+		/* Interrupt Resource Management (IRM) requests. */
+		/* XXX KEBE SAYS FILL ME IN WITH ADJUSTMENT CODE! */
+		/* XXX KEBE SAYS that'll use "count" above. */
+		break;
+	default:
+		/* Don't know this cbaction. */
+		return (DDI_ENOTSUP);
+	}
+
+	/* If I make it here, I'm good! */
+	return (DDI_SUCCESS);
+}
+
+/*
+ * Free receive & transmit rings.
+ */
+static void
+i40e_free_trqpairs_locks(i40e_t *i40e)
+{
+	int i;
+	i40e_trqpair_t *itrq;
+
+	if (i40e->i40e_trqpairs != NULL) {
+		itrq = &i40e->i40e_trqpairs[0];
+		for (i = 0; i < i40e->i40e_num_trqpairs; i++) {
+			mutex_destroy(&itrq->itrq_rx_lock);
+			mutex_destroy(&itrq->itrq_tx_lock);
+			/* XXX KEBE SAYS more mutexes and other frees... */
+			itrq++;
+		}
+	
+		kmem_free(i40e->i40e_trqpairs,
+		    sizeof (i40e_trqpair_t) * i40e->i40e_num_trqpairs);
+		i40e->i40e_trqpairs = NULL;
+	}
+
+	mutex_destroy(&i40e->i40e_general_lock);
+	/* mutex_destroy(&i40e->i40e_watchdog_lock); */
+}
+
+/*
+ * Allocate receive & transmit rings.
+ */
+static boolean_t
+i40e_alloc_trqpairs_locks(i40e_t *i40e)
+{
+	int i;
+	void *mutexpri = DDI_INTR_PRI(i40e->i40e_intr_pri);
+	i40e_trqpair_t *itrq;
+
+	/*
+	 * Now that we have the priority for the interrupts, initialize
+	 * all relevant locks.
+	 */
+	mutex_init(&i40e->i40e_general_lock, NULL, MUTEX_DRIVER, mutexpri);
+	/*
+	 * mutex_init(&i40e->i40e_watchdog_lock, NULL, MUTEX_DRIVER, mutexpri);
+	 */
+
+	/*
+	 * Can call with KM_SLEEP because we're attach()-ing.
+	 * If in some future we can't, call instead with NOSLEEP and return
+	 * B_FALSE after any allocations failures & cleanups.
+	 */
+	i40e->i40e_trqpairs = kmem_zalloc(
+	    sizeof (i40e_trqpair_t) * i40e->i40e_num_trqpairs, KM_SLEEP);
+	itrq = &(i40e->i40e_trqpairs[0]);
+	for (i = 0; i < i40e->i40e_num_trqpairs; i++) {
+		itrq->itrq_i40e = i40e;
+		mutex_init(&itrq->itrq_rx_lock, NULL, MUTEX_DRIVER, mutexpri);
+		mutex_init(&itrq->itrq_tx_lock, NULL, MUTEX_DRIVER, mutexpri);
+		itrq->itrq_index = i;
+		/* XXX KEBE SAYS more mutexes and other allocs... */
+		itrq++;
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * ixgbe_map_rxring_to_vector - Map given rx ring to given interrupt vector.
+ */
+static inline void
+i40e_map_rx_to_vector(i40e_t *i40e, int qp_idx, int v_idx)
+{
+	/*
+	 * Set bit in map
+	 */
+	BT_SET(i40e->i40e_vector_map[v_idx].iv_rx_map, qp_idx);
+
+	/*
+	 * Count bits set
+	 */
+	i40e->i40e_vector_map[v_idx].iv_rx_count++;
+
+	/*
+	 * Remember bit position
+	 */
+	i40e->i40e_trqpairs[qp_idx].itrq_rx_intrvec = v_idx;
+	i40e->i40e_trqpairs[qp_idx].itrq_rx_vectorbit = 1 << v_idx;
+}
+
+static inline void
+i40e_map_tx_to_vector(i40e_t *i40e, int qp_idx, int v_idx)
+{
+	/*
+	 * Set bit in map
+	 */
+	BT_SET(i40e->i40e_vector_map[v_idx].iv_tx_map, qp_idx);
+
+	/*
+	 * Count bits set
+	 */
+	i40e->i40e_vector_map[v_idx].iv_tx_count++;
+
+	/*
+	 * Remember bit position
+	 */
+	i40e->i40e_trqpairs[qp_idx].itrq_tx_intrvec = v_idx;
+	i40e->i40e_trqpairs[qp_idx].itrq_tx_vectorbit = 1 << v_idx;
+}
+
+/*
+ * Map different interrupts to MSI-X vectors.
+ */
+static boolean_t
+i40e_map_intrs_to_vectors(i40e_t *i40e)
+{
+	int i, vectornum;
+
+	bzero(&i40e->i40e_vector_map, sizeof (i40e->i40e_vector_map));
+
+	if (i40e->i40e_intr_type != DDI_INTR_TYPE_MSIX) {
+		/*
+		 * We have adminq, plus rx & tx to concern ourselves with.
+		 * XXX KEBE SAYS FIGURE THAT OUT.
+		 */
+		i40e_error(i40e, "Can't support MSI or LEGACY yet.");
+		return (B_FALSE);	/* For now, bail. */
+	}
+
+	/*
+	 * We should have an odd number of MSI-X interrupts.  0 is for the
+	 * adminq, and...
+	 *
+	 * XXXX 1-N is for N/2 qpairs, where rx & tx get their own
+	 * interrupts. XXXX
+	 *
+	 * XXX KEBE ASKS (and maybe BRINGUP) - do rx & tx actually get
+	 * their own interrupts?!?  RIght now, they share.
+	 */
+
+	/* The adminq. */
+	BT_SET(i40e->i40e_vector_map[0].iv_other_map, 0);
+	i40e->i40e_vector_map[0].iv_other_count++;
+
+	vectornum = 1;
+	for (i = 0; i < i40e->i40e_num_trqpairs; i++) {
+		/* Map tx & rx qpairs to the same interrupt vector for now. */
+		i40e_map_tx_to_vector(i40e, i, vectornum);
+		i40e_map_rx_to_vector(i40e, i, vectornum);
+		vectornum = (vectornum + 1) % i40e->i40e_intr_count;
+		if (vectornum == 0)
+			vectornum++;	/* Skip 0, that's for adminq only. */
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * Add appropriate interrupt handlers based on the interrupt type.
+ * Caller must have added rx/tx rings and interrupt vectors to "i40e" prior
+ * to calling here.
+ *
+ * Interrupt handlers are in their own file:  i40e_intr.c.
+ */
+static boolean_t
+i40e_add_intr_handlers(i40e_t *i40e)
+{
+	int rc, vector;
+
+	switch (i40e->i40e_intr_type) {
+	case DDI_INTR_TYPE_MSIX:
+		for (vector = 0; vector < i40e->i40e_intr_count; vector++) {
+			rc = ddi_intr_add_handler(
+			    i40e->i40e_intr_handles[vector],
+			    (ddi_intr_handler_t *)i40e_intr_msix, i40e,
+			    (void *)(uintptr_t)vector);
+			if (rc != DDI_SUCCESS) {
+				i40e_log(i40e, "Add interrupt handler (MSI-X) "
+				    "failed: return %d, vector %d", rc, vector);
+				for (vector--; vector >= 0; vector--) {
+					(void) ddi_intr_remove_handler(
+					    i40e->i40e_intr_handles[vector]);
+				}
+				return (B_FALSE);
+			}
+		}
+		break;
+	case DDI_INTR_TYPE_MSI:
+		rc = ddi_intr_add_handler(i40e->i40e_intr_handles[0],
+		    (ddi_intr_handler_t *)i40e_intr_msi, i40e, NULL);
+		if (rc != DDI_SUCCESS) {
+			i40e_log(i40e, "Add interrupt handler (MSI) failed: "
+			    "return %d", rc);
+			return (B_FALSE);
+		}
+		break;
+	case DDI_INTR_TYPE_FIXED:
+		rc = ddi_intr_add_handler(i40e->i40e_intr_handles[0],
+		    (ddi_intr_handler_t *)i40e_intr_legacy, i40e, NULL);
+		if (rc != DDI_SUCCESS) {
+			i40e_log(i40e, "Add interrupt handler (legacy) failed:"
+			    " return %d", rc);
+			return (B_FALSE);
+		}
+		break;
+	default:
+		i40e_error(i40e,
+		    "i40e_add_intr_handlers() called where intr_type is %d.",
+		    i40e->i40e_intr_type);
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+static boolean_t
+i40e_enable_interrupts(i40e_t *i40e)
+{
+	int i, rc;
+
+	if (i40e->i40e_intr_cap & DDI_INTR_FLAG_BLOCK) {
+		/* MSI/MSI-X block-enable */
+		rc = ddi_intr_block_enable(i40e->i40e_intr_handles,
+		    i40e->i40e_intr_count);
+		if (rc != DDI_SUCCESS) {
+			i40e_error(i40e, "interrupts block-enable failed: %d",
+			    rc);
+			return (B_FALSE);
+		}
+	} else {
+		for (i = 0; i < i40e->i40e_intr_count; i++) {
+			rc = ddi_intr_enable(i40e->i40e_intr_handles[i]);
+			if (rc != DDI_SUCCESS) {
+				i40e_error(i40e,
+				    "Enable interrupt %d failed: %d", i, rc);
+				/* Just Disable It (TM) for the earlier ones. */
+				while (--i >= 0) {
+					(void) ddi_intr_disable(
+					    i40e->i40e_intr_handles[i]);
+				}
+				return (B_FALSE);
+			}
+		}
+	}
+
+	return (B_TRUE);
+}
+
+static boolean_t
+i40e_disable_interrupts(i40e_t *i40e)
+{
+	int i, rc;
+
+	if (i40e->i40e_intr_cap & DDI_INTR_FLAG_BLOCK) {
+		rc = ddi_intr_block_disable(i40e->i40e_intr_handles,
+		    i40e->i40e_intr_count);
+		if (rc != DDI_SUCCESS) {
+			i40e_error(i40e,
+			    "interrupts block-disabled failed: %d", rc);
+			return (B_FALSE);
+			    
+		}
+	} else {
+		for (i = 0; i < i40e->i40e_intr_count; i++) {
+			rc = ddi_intr_disable(i40e->i40e_intr_handles[i]);
+			if (rc != DDI_SUCCESS) {
+				i40e_error(i40e,
+				    "Disable interrupts %d failed: %d", i, rc);
+				return (B_FALSE);
+			}
+		}
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * Timer functions.
+ */
+static void
+i40e_timer(void *arg)
+{
+	i40e_t *i40e = arg;
+
+	mutex_enter(&i40e->i40e_general_lock);
+	/* XXX KEBE SAYS FILL ME IN XXX */
+	/*
+	 * A generic link-check function (that can be called elsewhere) is in
+	 * order.
+	 */
+	mutex_exit(&i40e->i40e_general_lock);
+
+	if ((i40e->i40e_state & I40E_WATCHDOG) == 0)
+		return;
+
+	/* Perform watchdog bits while not holding i40e_general_lock. */
+	/*
+	 * Check state for OVERTEMP or ERROR.  Attempt reset on ERROR.
+	 * If reset succeeds, service-impact with DDI_SERVICE_RESTORED.
+	 */
+	/* Perform a stall check, degrade if need be. */
+}
+
+/*
+ * Watchdog timer functions.
+ */
+void
+i40e_disable_watchdog_timer(i40e_t *i40e)
+{
+	/*
+	 * NOP for now -> disable a bit in i40e to inform the periodic timer
+	 * NOT to do watchdog things.
+	 */
+}
+
+void
+i40e_enable_watchdog_timer(i40e_t *i40e)
+{
+	/*
+	 * NOP for now -> enable a bit in i40e to let the periodic timer
+	 * do watchdog things.
+	 */
+}
+
+/*
+ * Get the hardware state, and scribble away anything that needs scribbling.
+ */
+static void
+i40e_get_hw_state(i40e_t *i40e, i40e_hw_t *hw)
+{
+	int rc;
+	struct i40e_aq_get_phy_abilities_resp abilities;
+
+	ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));
+	
+	i40e_aq_get_link_info(hw, TRUE, NULL, NULL);
+	i40e_get_link_status(hw, (bool *)&i40e->i40e_link_state);
+
+	rc = i40e_aq_get_phy_capabilities(hw, B_FALSE, B_TRUE, &abilities,
+	    NULL);
+	/* Intel or FreeBSD say we may need delay to detect fiber correctly */
+	if (rc == I40E_ERR_UNKNOWN_PHY) {
+		i40e_msec_delay(200);
+		rc = i40e_aq_get_phy_capabilities(hw, B_FALSE, B_TRUE,
+		    &abilities, NULL);
+	}
+
+	if (rc != I40E_SUCCESS) {
+		i40e_error(i40e, "Get physical capabilities failed: %d", rc);
+		/* XXX KEBE ASKS FM faulty? */
+	}
+
+	rc = i40e_update_link_info(hw);
+	if (rc != I40E_SUCCESS) {
+		i40e_error(i40e, "Update link info failed: %d", rc);
+		/* XXX KEBE ASKS FM faulty? */
+	}
+
+	/* Limit phy interrupts to link and modules failure */
+	rc = i40e_aq_set_phy_int_mask(hw,
+	    I40E_AQ_EVENT_LINK_UPDOWN | I40E_AQ_EVENT_MODULE_QUAL_FAIL, NULL);
+	if (rc != I40E_SUCCESS) {
+		/* XXX KEBE ASKS FMA? */
+		i40e_error(i40e, "set phy mask failed: %d\n", rc);
+	}
+}
+
+/*
+ * Used at re-init time, AND by the GLD addmac/remmac code.
+ */
+boolean_t
+i40e_hwadd_mac(i40e_t *i40e, i40e_hw_t *hw, const uint8_t *mac_addr)
+{
+	uint8_t buffer[I40E_AQ_LARGE_BUF];
+	struct i40e_aqc_add_macvlan_element_data *mvlist =
+	    (struct i40e_aqc_add_macvlan_element_data *)buffer;
+
+	ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));
+
+	bcopy(mac_addr, mvlist->mac_addr, ETHERADDRL);
+	mvlist->vlan_tag = 0;	/* No VLAN... we do that some other way... */
+	mvlist->flags = I40E_AQC_MACVLAN_ADD_PERFECT_MATCH |
+	    I40E_AQC_MACVLAN_ADD_IGNORE_VLAN;
+	/* Be conservative in what you send... */
+	mvlist->queue_number = 0;
+	return (i40e_aq_add_macvlan(hw, i40e->i40e_vsi_id, mvlist, 1, NULL) ==
+	    I40E_SUCCESS);
+}
+
+/*
+ * Initialize or reset the unicast addresses for our (currently just one) VSI.
+ */
+static void
+i40e_init_macaddrs(i40e_t *i40e, i40e_hw_t *hw)
+{
+	int i;
+	i40e_ether_addr_t *i40eth, *lastone;
+
+	ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));
+
+	if (i40e->i40e_mac_total > 0) {
+		/*
+		 * We are up and running already, and just need to kick things
+		 * in the pants again.
+		 */
+		/* NOTE:  Skip slot 1 as it's the board's MAC address... */
+		for (i = 1; i < i40e->i40e_mac_used; i++) {
+			i40eth = &(i40e->i40e_mac_addrs[i]);
+			if (!i40e_hwadd_mac(i40e, hw, i40eth->i40eth_mac)) {
+				/*
+				 * Oh shoot, we failed on reset.  Warn loudly,
+				 * and drop ones we can't initialize.
+				 * Do a swap of the last one to this slot,
+				 * so we can continue. Or if we're the last
+				 * slot, do nothing.
+				 *
+				 * Either way, the last slot is emptied, and
+				 * the number of unicast MACs used drops by 1.
+				 */
+				i40e_notice(i40e, "WARNING: <fill me in>");
+				lastone = &(i40e->i40e_mac_addrs[
+				    i40e->i40e_mac_used - 1]);
+				if (lastone != i40eth) {
+					/* Do the swap if we have more... */
+					*i40eth = *lastone;
+					lastone->i40eth_used = 0;
+					i--;
+				} else {
+					/*
+					 * ... or if we're the last one,
+					 * simply mark this as unused.
+					 */
+					i40eth->i40eth_used = 0;
+				}
+				i40e->i40e_mac_used--;
+			}
+		}
+		return;
+	}
+
+	/*
+	 * Actually initialize things.
+	 */
+	i40e->i40e_mac_total = I40E_MAX_MAC;
+
+	/*
+	 * XXX KEBE WARNS...
+	 *
+	 * If there's a way to query the HW for what's left, we'll need to
+	 * do that dynamically.  For now, just cap at I40E_MAX_UNICAST.
+	 * See its definition for whole-board vs. per-port/PF problems.
+	 */
+#if 0
+	/*
+	 * Put the board's ethernet address in for slot #0.
+	 */
+	i40e->i40e_mac_used = 1;
+	i40e->i40e_mac_addrs[0].i40eth_used = 1;
+	/* I hope this is where the port's/board's/PF's MAC address is... */
+	bcopy(hw->mac.addr, i40e->i40e_mac_addrs[0].i40eth_mac, ETHERADDRL);
+#endif
+}
+
+/*
+ * Configure the hardware for the Virtual Station Interface (VSI).  Currently
+ * we only support one, but in the future we could instantiate more than one
+ * per attach-point.
+ */
+static boolean_t
+i40e_config_vsi(i40e_t *i40e, i40e_hw_t *hw)
+{
+	/* Let's hope C99's initializers do the right thing. */
+	struct i40e_vsi_context	context = {0};
+	int err;
+
+	context.seid = i40e->i40e_vsi_id;
+	context.pf_num = hw->pf_id;
+	err = i40e_aq_get_vsi_params(hw, &context, NULL);
+	if (err != I40E_SUCCESS) {
+		i40e_error(i40e, "get VSI params failed with %d\n", err);
+		return (B_FALSE);
+	}
+
+	/*
+	 * Set the queue and traffic class bits.  Keep it simple for now.
+	 */
+	context.info.valid_sections = I40E_AQ_VSI_PROP_QUEUE_MAP_VALID;
+	context.info.mapping_flags |= I40E_AQ_VSI_QUE_MAP_CONTIG;
+	context.info.queue_mapping[0] = 0; 
+	context.info.tc_mapping[0] = 0x0800; 
+
+#if 0	/* XXX KEBE IS Not sure about this... I thought mac did this. */
+	/* Set VLAN receive stripping mode */
+	context.info.valid_sections |= I40E_AQ_VSI_PROP_VLAN_VALID;
+	context.info.port_vlan_flags = I40E_AQ_VSI_PVLAN_MODE_ALL;
+	if (<some-nemo-thing>)
+	    context.info.port_vlan_flags |= I40E_AQ_VSI_PVLAN_EMOD_STR_BOTH;
+	else
+	    context.info.port_vlan_flags |= I40E_AQ_VSI_PVLAN_EMOD_NOTHING;
+#else
+	/* XXX KEBE SAYS he's more sure about these... */
+	context.info.valid_sections |= I40E_AQ_VSI_PROP_VLAN_VALID;
+	context.info.port_vlan_flags = I40E_AQ_VSI_PVLAN_MODE_ALL |
+	    I40E_AQ_VSI_PVLAN_EMOD_NOTHING;
+#endif
+	context.flags = LE_16(I40E_AQ_VSI_TYPE_PF);
+	err = i40e_aq_update_vsi_params(hw, &context, NULL);
+	if (err != I40E_SUCCESS) {
+		i40e_error(i40e, "Update VSI params failed with %d", err);
+		return (B_FALSE);
+	}
+
+	/*
+	 * XXX KEBE SAYS Iterate through the trqpairs, setting up pointers
+	 * to DMA memory.  
+	 */
+	/* XXX KEBE SAYS CHECKPOINT --> GET BACK TO ME */
+
+	return (B_TRUE);
+}
+
+/*
+ * Wrapper to kick the chipset on.
+ */
+static boolean_t
+i40e_chip_start(i40e_t *i40e)
+{
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+	/* Let's hope C99-initializing does the right thing. */
+	struct i40e_filter_control_settings filter = {0};
+	int i, rc;
+
+	if (((hw->aq.fw_maj_ver == 4) && (hw->aq.fw_min_ver < 33)) ||
+	    (hw->aq.fw_maj_ver < 4)) {
+		i40e_msec_delay(75);
+		if (i40e_aq_set_link_restart_an(hw, TRUE, NULL) !=
+		    I40E_SUCCESS) {
+			i40e_error(i40e, "link restart failed, aq_err=%d\n",
+			    hw->aq.asq_last_status);
+			/* XXX KEBE ASKS FMA? */
+			return (B_FALSE);
+		}
+	}
+
+	/* Determine hardware state */
+	i40e_get_hw_state(i40e, hw);
+
+	/* Initialize mac addresses. */
+	i40e_init_macaddrs(i40e, hw);
+
+	/*
+	 * Set up the filter control.
+	 */
+	filter.enable_ethtype = TRUE;
+        filter.enable_macvlan = TRUE;
+
+        rc = i40e_set_filter_control(hw, &filter);
+	if (rc != I40E_SUCCESS) {
+		i40e_error(i40e, "i40e_set_filter_control() returned %d", rc);
+		return (B_FALSE);
+	}
+
+#if 0
+	/*
+	 * XXX KEBE SAYS for BRINGUP, skip this, but configure the RSS
+	 * parameters.  See FreeBSD's ixl_config_rss().
+	 */
+	if (!i40e_config_rss(i40e, hw))
+		return (B_FALSE);
+#endif
+
+	/*
+	 * XXX KEBE SAYS FILL IN vsi information, some of which can be pushed
+	 * down to the trqpairs below, except if/when we become multi-VSI
+	 * aware.  See ixl_initialize_vsi().
+	 */
+	if (!i40e_config_vsi(i40e, hw))
+		return (B_FALSE);
+
+	/* Add the ethernet broadcast address to the HW filter list. */
+	if (!i40e_hwadd_mac(i40e, hw, i40e_ether_broadcast))
+		return (B_FALSE);
+
+	/*
+	 * Each interrupt vector needs to be explicitly enabled.
+	 * NOTE:  Vector 0 is admin-queue/CTL0, rest are CTLN.
+	 */
+
+	/* First, the adminq. */
+
+	wr32(hw, I40E_PFINT_DYN_CTL0,
+	    I40E_PFINT_DYN_CTL0_INTENA_MASK |
+            I40E_PFINT_DYN_CTL0_CLEARPBA_MASK |
+            (I40E_ITR_NONE << I40E_PFINT_DYN_CTL0_ITR_INDX_SHIFT));
+	i40e_flush(hw);
+	/*
+	 * SO MUCH TO FILL IN!
+	 * - ICR0_ENA disable
+	 * - ICR0 read-to-clear
+	 * - ITR for adminq (set to I40E_ITR_8K)
+	 * - XXX KEBE ASKS more?
+	 */
+
+	/* Then the others. */
+	/* XXX KEBE ASKS -- use vector number instead?!? */
+	for (i = 0; i < i40e->i40e_num_trqpairs; i++) {
+		/*
+		 * SO MUCH TO FILL IN!
+		 * - ITR state
+		 * - DYN_CTLN(i)
+		 * - LNKLSTN(i)
+		 * - QINT_[TR]QCTL(i)
+		 * - Various rx/tx bits relating to things we set here.
+		 */
+
+		wr32(hw, I40E_PFINT_DYN_CTLN(i),
+		    I40E_PFINT_DYN_CTLN_INTENA_MASK |
+		    I40E_PFINT_DYN_CTLN_CLEARPBA_MASK |
+		    (I40E_ITR_NONE << I40E_PFINT_DYN_CTLN_ITR_INDX_SHIFT));
+	}
+	i40e_flush(hw);	/* XXX FreeBSD doesn't do this... */
+
+	return (B_TRUE);
+}
+
+void
+i40e_chip_stop(i40e_t *i40e)
+{
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+	int i;
+
+	/* XXX KEBE SAYS FILL ME IN XXX */
+	/*
+	 * Each interrupt vector needs to be explicitly disabled.
+	 * NOTE:  Vector 0 is admin-queue/CTL0, rest are CTLN.
+	 */
+
+	/* First, the adminq. */
+	wr32(hw, I40E_PFINT_DYN_CTL0,
+	    I40E_ITR_NONE << I40E_PFINT_DYN_CTL0_ITR_INDX_SHIFT);
+	/* XXX KEBE ASKS -  flush? */
+
+	/* Then the others. */
+	/* XXX KEBE ASKS -- use vector number instead?!? */
+	for (i = 0; i < i40e->i40e_num_trqpairs; i++) {
+		wr32(hw, I40E_PFINT_DYN_CTLN(i),
+		    I40E_ITR_NONE << I40E_PFINT_DYN_CTLN_ITR_INDX_SHIFT);
+	}
+	/* XXX KEBE ASKS -  flush? */
+}
+
+/*
+ * Actually stop the chipset.  This also includes various buffer frees.
+ */
+void
+i40e_stop(i40e_t *i40e, boolean_t free_allocations)
+{
+	int i;
+
+	/* XXX KEBE SAYS FILL ME IN! */
+	ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));
+
+	/* XXX KEBE SAYS Disable all of the interrupts. */
+
+	/* XXX KEBE SAYS drain any pending tx packets. */
+
+	for (i = 0; i < i40e->i40e_num_trqpairs; i++) {
+		mutex_enter(&i40e->i40e_trqpairs[i].itrq_rx_lock);
+		mutex_enter(&i40e->i40e_trqpairs[i].itrq_tx_lock);
+	}
+
+	i40e_chip_stop(i40e);
+
+	/* XXX KEBE SAYS Clean pending tx resources. */
+
+	for (i = i40e->i40e_num_trqpairs - 1; i >= 0; i--) {
+		mutex_exit(&i40e->i40e_trqpairs[i].itrq_tx_lock);
+		mutex_exit(&i40e->i40e_trqpairs[i].itrq_rx_lock);
+	}
+
+	if (i40e->i40e_link_state == LINK_STATE_UP) {
+		i40e->i40e_link_state = LINK_STATE_UNKNOWN;
+		mac_link_update(i40e->i40e_mac_hdl, i40e->i40e_link_state);
+	}
+
+	if (free_allocations) {
+		i40e_free_dma(i40e);
+		i40e_free_rx_data(i40e);
+	}
+}
+
+/*
+ * Return -1 on failure, let caller deal with it.
+ */
+static int
+i40e_get_vsi_id(i40e_t *i40e)
+{
+	i40e_hw_t *hw = &i40e->i40e_hw_space;
+        struct i40e_aqc_get_switch_config_resp *sw_config;
+        uint8_t aq_buf[I40E_AQ_LARGE_BUF];
+	uint16_t next;	/* Needed for common-code call. */
+	int rc;
+
+	sw_config = (struct i40e_aqc_get_switch_config_resp *)aq_buf;
+	rc = i40e_aq_get_switch_config(hw, sw_config, sizeof(aq_buf), &next,
+	    NULL);
+	if (rc != I40E_SUCCESS) {
+		i40e_error(i40e, "i40e_aq_get_switch_config() failed %d\n", rc);
+		return (-1);
+	}
+
+	/*
+	 * XXX KEBE SAYS - there may be other elements we need later,
+	 * but for now, we just want the basic seid.
+	 */
+	return (sw_config->element[0].seid);
+}
+
+/*
+ * Actually start the chipset.	This also includes various buffer allocations.
+ */
+boolean_t
+i40e_start(i40e_t *i40e, boolean_t alloc)
+{
+	boolean_t rc = B_TRUE;
+	int i;
+
+	/* XXX KEBE SAYS FILL ME IN! */
+	ASSERT(MUTEX_HELD(&i40e->i40e_general_lock));
+
+	if (alloc) {
+		if (!i40e_alloc_rx_data(i40e)) {
+			i40e_error(i40e,
+			    "Failed to allocate receive-ring data");
+			return (B_FALSE);
+		}
+		if (!i40e_alloc_dma(i40e)) {
+			i40e_error(i40e, "Failed to allocate DMA data");
+			return (B_FALSE);
+		}
+	}
+
+	/* Big batch of mutex enters to block things... */
+	for (i = 0; i < i40e->i40e_num_trqpairs; i++) {
+		mutex_enter(&i40e->i40e_trqpairs[i].itrq_rx_lock);
+		mutex_enter(&i40e->i40e_trqpairs[i].itrq_tx_lock);
+	}
+
+
+	/*
+	 * XXX KEBE SAYS ixgbe calls chip_start() here. Did we do that
+	 * already?
+	 */
+	if (!i40e_chip_start(i40e)) {
+		i40e_fm_ereport(i40e, DDI_FM_DEVICE_INVAL_STATE);
+		rc = B_FALSE;
+		goto done;
+	}
+
+	if (i40e_check_acc_handle(i40e->i40e_osdep_space.reg_handle) !=
+	    DDI_FM_OK) {
+		rc = B_FALSE;
+		goto done;
+	}
+
+	/* XXX KEBE SAYS set up the rx/tx rings (including rx/tx buffers?) */
+
+	/* Clear state bits prior to final interrupt enabling. */
+	atomic_and_32(&i40e->i40e_state,
+	    ~(I40E_ERROR | I40E_STALL | I40E_OVERTEMP));
+
+	/* XXX KEBE SAYS enable the interrupts here (really?)! */
+
+done:
+	for (i = i40e->i40e_num_trqpairs - 1; i >= 0; i--) {
+		mutex_exit(&i40e->i40e_trqpairs[i].itrq_tx_lock);
+		mutex_exit(&i40e->i40e_trqpairs[i].itrq_rx_lock);
+	}
+	if (!rc)
+		ddi_fm_service_impact(i40e->i40e_dip, DDI_SERVICE_LOST);
+
+	return (rc);
+}
+
+/*
+ * Module Initialization Functions.
+ */
+int
+_init(void)
+{
+	int status;
+
+	mac_init_ops(&i40e_dev_ops, MODULE_NAME);
+
+	status = mod_install(&i40e_modlinkage);
+
+	if (status != DDI_SUCCESS) {
+		mac_fini_ops(&i40e_dev_ops);
+	}
+
+	return (status);
+}
+
+int
+_fini(void)
+{
+	int status;
+
+	status = mod_remove(&i40e_modlinkage);
+
+	if (status == DDI_SUCCESS) {
+		mac_fini_ops(&i40e_dev_ops);
+	}
+
+	return (status);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	int status;
+
+	status = mod_info(&i40e_modlinkage, modinfop);
+
+	return (status);
+}
