/******************************************************************************

  Copyright (c) 2013-2019, Intel Corporation
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

   3. Neither the name of the Intel Corporation nor the names of its
      contributors may be used to endorse or promote products derived from
      this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE.

******************************************************************************/
/*$FreeBSD$*/

#include <sys/priv.h>

#include "ixl_pf.h"

#ifdef PCI_IOV
#include "ixl_pf_iov.h"
#endif

#ifdef IXL_IW
#include "ixl_iw.h"
#include "ixl_iw_int.h"
#endif

#ifdef DEV_NETMAP
#include <net/netmap.h>
#include <sys/selinfo.h>
#include <dev/netmap/netmap_kern.h>
#endif /* DEV_NETMAP */

MALLOC_DEFINE(M_IXL, "ixl", "ixl driver allocations");

static int	ixl_vsi_setup_queue(struct ixl_vsi *, struct ixl_queue *, int);
static u64	ixl_max_aq_speed_to_value(u8);
static u8	ixl_convert_sysctl_aq_link_speed(u8, bool);
static void	ixl_sbuf_print_bytes(struct sbuf *, u8 *, int, int, bool);
static enum i40e_status_code ixl_set_lla(struct ixl_vsi *);
static const char * ixl_link_speed_string(enum i40e_aq_link_speed);
static int	ixl_ioctl_drvspec(struct ixl_pf	*, caddr_t);
static void	ixl_free_filters(struct ixl_ftl_head *);
static void	ixl_add_vlan_filters(struct ixl_vsi *, const u8 *);
static u_int	ixl_add_maddr(void *, struct sockaddr_dl *, u_int);
static u_int	ixl_match_maddr(void *, struct sockaddr_dl *, u_int);
static char *	ixl_switch_element_string(struct sbuf *, u8, u16);
#ifdef CSUM_ENCAP_VXLAN
static int	ixl_get_udp_port_idx(struct ixl_pf *pf, u_int port);
static void	ixl_sync_udp_filters_task(struct ixl_pf *pf);
#endif


/* Sysctls */
static int	ixl_sysctl_set_flowcntl(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_set_advertise(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_supported_speeds(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_current_speed(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_show_fw(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_unallocated_queues(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_pf_tx_itr(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_pf_rx_itr(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_pf_dynamic_itr(SYSCTL_HANDLER_ARGS);

static int	ixl_sysctl_eee_enable(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_set_link_active(SYSCTL_HANDLER_ARGS);

/* Debug Sysctls */
static int 	ixl_sysctl_link_status(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_phy_abilities(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_phy_statistics(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_sw_filter_list(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_hw_res_alloc(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_switch_config(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_hkey(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_hena(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_hlut(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_fw_link_management(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_read_i2c_byte(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_write_i2c_byte(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_fec_fc_ability(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_fec_rs_ability(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_fec_fc_request(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_fec_rs_request(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_fec_auto_enable(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_dump_debug_data(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_fw_lldp(SYSCTL_HANDLER_ARGS);
static int	ixl_sysctl_read_i2c_diag_data(SYSCTL_HANDLER_ARGS);


#ifdef IXL_IW
extern int ixl_enable_iwarp;
extern int ixl_limit_iwarp_msix;
#endif

enum ixl_dyn_itr_dir {
	IXL_DYN_ITR_RX,
	IXL_DYN_ITR_TX
};

static const char * const ixl_fc_string[6] = {
	"None",
	"Rx",
	"Tx",
	"Full",
	"Priority",
	"Default"
};

static char *ixl_fec_string[3] = {
       "CL108 RS-FEC",
       "CL74 FC-FEC/BASE-R",
       "None"
};

/* Functions for setting and checking driver state. Note the functions take
 * bit positions, not bitmasks. The atomic_set_32 and atomic_clear_32
 * operations require bitmasks. This can easily lead to programming error, so
 * we provide wrapper functions to avoid this.
 */

/**
 * ixl_set_state - Set the specified state
 * @s: the state bitmap
 * @bit: the state to set
 *
 * Atomically update the state bitmap with the specified bit set.
 */
inline void
ixl_set_state(volatile u32 *s, enum ixl_state bit)
{
	/* atomic_set_32 expects a bitmask */
	atomic_set_32(s, BIT(bit));
}

/**
 * ixl_clear_state - Clear the specified state
 * @s: the state bitmap
 * @bit: the state to clear
 *
 * Atomically update the state bitmap with the specified bit cleared.
 */
inline void
ixl_clear_state(volatile u32 *s, enum ixl_state bit)
{
	/* atomic_clear_32 expects a bitmask */
	atomic_clear_32(s, BIT(bit));
}

/**
 * ixl_test_state - Test the specified state
 * @s: the state bitmap
 * @bit: the bit to test
 *
 * Return true if the state is set, false otherwise. Use this only if the flow
 * does not need to update the state. If you must update the state as well,
 * prefer ixl_testandset_state.
 */
inline bool
ixl_test_state(volatile u32 *s, enum ixl_state bit)
{
	return !!(*s & BIT(bit));
}

/**
 * ixl_testandset_state - Test and set the specified state
 * @s: the state bitmap
 * @bit: the bit to test
 *
 * Atomically update the state bitmap, setting the specified bit. Returns the
 * previous value of the bit.
 */
inline u32
ixl_testandset_state(volatile u32 *s, enum ixl_state bit)
{
	/* atomic_testandset_32 expects a bit position, as opposed to bitmask
	expected by other atomic functions */
	return atomic_testandset_32(s, bit);
}

void
ixl_debug_core(device_t dev, u32 enabled_mask, u32 mask, char *fmt, ...)
{
	va_list args;

	if (!(mask & enabled_mask))
		return;

	/* Re-implement device_printf() */
	device_print_prettyname(dev);
	va_start(args, fmt);
	vprintf(fmt, args);
	va_end(args);
}

/*
** Put the FW, API, NVM, EEtrackID, and OEM version information into a string
*/
void
ixl_nvm_version_str(struct i40e_hw *hw, struct sbuf *buf)
{
	u8 oem_ver = (u8)(hw->nvm.oem_ver >> 24);
	u16 oem_build = (u16)((hw->nvm.oem_ver >> 16) & 0xFFFF);
	u8 oem_patch = (u8)(hw->nvm.oem_ver & 0xFF);

	sbuf_printf(buf,
	    "fw %d.%d.%05d api %d.%d nvm %x.%02x etid %08x oem %d.%d.%d",
	    hw->aq.fw_maj_ver, hw->aq.fw_min_ver, hw->aq.fw_build,
	    hw->aq.api_maj_ver, hw->aq.api_min_ver,
	    (hw->nvm.version & IXL_NVM_VERSION_HI_MASK) >>
	    IXL_NVM_VERSION_HI_SHIFT,
	    (hw->nvm.version & IXL_NVM_VERSION_LO_MASK) >>
	    IXL_NVM_VERSION_LO_SHIFT,
	    hw->nvm.eetrack,
	    oem_ver, oem_build, oem_patch);
}

void
ixl_print_nvm_version(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct sbuf *sbuf;

	sbuf = sbuf_new_auto();
	ixl_nvm_version_str(hw, sbuf);
	sbuf_finish(sbuf);
	device_printf(dev, "%s\n", sbuf_data(sbuf));
	sbuf_delete(sbuf);
}

/**
 * ixl_get_fw_mode - Check the state of FW
 * @hw: device hardware structure
 *
 * Identify state of FW. It might be in a recovery mode
 * which limits functionality and requires special handling
 * from the driver.
 *
 * @returns FW mode (normal, recovery, unexpected EMP reset)
 */
static enum ixl_fw_mode
ixl_get_fw_mode(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	enum ixl_fw_mode fw_mode = IXL_FW_MODE_NORMAL;
	u32 fwsts = rd32(hw, I40E_GL_FWSTS) & I40E_GL_FWSTS_FWS1B_MASK;

	/* Is set and has one of expected values */
	if ((fwsts >= I40E_XL710_GL_FWSTS_FWS1B_REC_MOD_CORER_MASK &&
	    fwsts <= I40E_XL710_GL_FWSTS_FWS1B_REC_MOD_NVM_MASK) ||
	    fwsts == I40E_X722_GL_FWSTS_FWS1B_REC_MOD_GLOBR_MASK ||
	    fwsts == I40E_X722_GL_FWSTS_FWS1B_REC_MOD_CORER_MASK)
		fw_mode = IXL_FW_MODE_RECOVERY;
	else {
		if (fwsts > I40E_GL_FWSTS_FWS1B_EMPR_0 &&
		    fwsts <= I40E_GL_FWSTS_FWS1B_EMPR_10)
			fw_mode = IXL_FW_MODE_UEMPR;
	}
	return (fw_mode);
}

/**
 * ixl_pf_reset - Reset the PF
 * @pf: PF structure
 *
 * Ensure that FW is in the right state and do the reset
 * if needed.
 *
 * @returns zero on success, or an error code on failure.
 */
int
ixl_pf_reset(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	enum i40e_status_code status;
	enum ixl_fw_mode fw_mode;

	fw_mode = ixl_get_fw_mode(pf);
	ixl_dbg_info(pf, "%s: before PF reset FW mode: 0x%08x\n", __func__, fw_mode);
	if (fw_mode == IXL_FW_MODE_RECOVERY) {
		ixl_set_state(&pf->state, IXL_STATE_RECOVERY_MODE);
		/* Don't try to reset device if it's in recovery mode */
		return (0);
	}

	status = i40e_pf_reset(hw);
	if (status == I40E_SUCCESS)
		return (0);

	/* Check FW mode again in case it has changed while
	 * waiting for reset to complete */
	fw_mode = ixl_get_fw_mode(pf);
	ixl_dbg_info(pf, "%s: after PF reset FW mode: 0x%08x\n", __func__, fw_mode);
	if (fw_mode == IXL_FW_MODE_RECOVERY) {
		ixl_set_state(&pf->state, IXL_STATE_RECOVERY_MODE);
		return (0);
	}

	if (fw_mode == IXL_FW_MODE_UEMPR)
		device_printf(pf->dev,
		    "Entering recovery mode due to repeated FW resets. This may take several minutes. Refer to the Intel(R) Ethernet Adapters and Devices User Guide.\n");
	else
		device_printf(pf->dev, "PF reset failure %s\n",
		    i40e_stat_str(hw, status));
	return (EIO);
}

/**
 * ixl_setup_hmc - Setup LAN Host Memory Cache
 * @pf: PF structure
 *
 * Init and configure LAN Host Memory Cache
 *
 * @returns 0 on success, EIO on error
 */
int
ixl_setup_hmc(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	enum i40e_status_code status;

	status = i40e_init_lan_hmc(hw, hw->func_caps.num_tx_qp,
	    hw->func_caps.num_rx_qp, 0, 0);
	if (status) {
		device_printf(pf->dev, "init_lan_hmc failed: %s\n",
		    i40e_stat_str(hw, status));
		return (EIO);
	}

	status = i40e_configure_lan_hmc(hw, I40E_HMC_MODEL_DIRECT_ONLY);
	if (status) {
		device_printf(pf->dev, "configure_lan_hmc failed: %s\n",
		    i40e_stat_str(hw, status));
		return (EIO);
	}

	return (0);
}

/**
 * ixl_shutdown_hmc - Shutdown LAN Host Memory Cache
 * @pf: PF structure
 *
 * Shutdown Host Memory Cache if configured.
 *
 */
void
ixl_shutdown_hmc(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	enum i40e_status_code status;

	/* HMC not configured, no need to shutdown */
	if (hw->hmc.hmc_obj == NULL)
		return;

	status = i40e_shutdown_lan_hmc(hw);
	if (status)
		device_printf(pf->dev,
		    "Shutdown LAN HMC failed with code %s\n",
		    i40e_stat_str(hw, status));
}

static void
ixl_configure_tx_itr(struct ixl_pf *pf)
{
	struct i40e_hw		*hw = &pf->hw;
	struct ixl_vsi		*vsi = &pf->vsi;
	struct ixl_queue	*que = vsi->queues;

	vsi->tx_itr_setting = pf->tx_itr;

	for (int i = 0; i < vsi->num_queues; i++, que++) {
		struct tx_ring	*txr = &que->txr;

		wr32(hw, I40E_PFINT_ITRN(IXL_TX_ITR, i),
		    vsi->tx_itr_setting);
		txr->itr = vsi->tx_itr_setting;
		txr->latency = IXL_AVE_LATENCY;
	}
}

static void
ixl_configure_rx_itr(struct ixl_pf *pf)
{
	struct i40e_hw		*hw = &pf->hw;
	struct ixl_vsi		*vsi = &pf->vsi;
	struct ixl_queue	*que = vsi->queues;

	vsi->rx_itr_setting = pf->rx_itr;

	for (int i = 0; i < vsi->num_queues; i++, que++) {
		struct rx_ring 	*rxr = &que->rxr;

		wr32(hw, I40E_PFINT_ITRN(IXL_RX_ITR, i),
		    vsi->rx_itr_setting);
		rxr->itr = vsi->rx_itr_setting;
		rxr->latency = IXL_AVE_LATENCY;
	}
}

/*
 * Write PF ITR values to queue ITR registers.
 */
void
ixl_configure_itr(struct ixl_pf *pf)
{
	ixl_configure_tx_itr(pf);
	ixl_configure_rx_itr(pf);
}

/*********************************************************************
 *  Init entry point
 *
 *  This routine is used in two ways. It is used by the stack as
 *  init entry point in network interface structure. It is also used
 *  by the driver as a hw/sw initialization routine to get to a
 *  consistent state.
 *
 *  return 0 on success, positive on failure
 **********************************************************************/
void
ixl_init_locked(struct ixl_pf *pf)
{
	struct i40e_hw	*hw = &pf->hw;
	struct ixl_vsi	*vsi = &pf->vsi;
	struct ifnet	*ifp = vsi->ifp;
	device_t 	dev = pf->dev;
	struct i40e_filter_control_settings	filter;
	int		ret;

	INIT_DEBUGOUT("ixl_init_locked: begin");
	IXL_PF_LOCK_ASSERT(pf);

	/*
	 * Will be done by the reset handler
	 * after it rebuilds the configuration.
	 */
	if (IXL_PF_IS_RESETTING(pf))
		return;

	if (IXL_PF_IN_RECOVERY_MODE(pf)) {
		device_printf(dev, "Running in recovery mode, only firmware update available\n");
		return;
	}

	ixl_stop_locked(pf);

	/*
	 * If the aq is dead here, it probably means something outside of the driver
	 * did something to the adapter, like a PF reset.
	 * So rebuild the driver's state here if that occurs.
	 */
	if (!i40e_check_asq_alive(&pf->hw)) {
		device_printf(dev, "Admin Queue is down; resetting...\n");
		ixl_teardown_hw_structs(pf);
		ixl_reset(pf);
	}

	/* Get the latest mac address... User might use a LAA */
	if (ixl_set_lla(vsi)) {
		device_printf(dev, "LLA address change failed!\n");
		return;
	}

	/* Set the various hardware offload abilities */
	ifp->if_hwassist = 0;
	if (ifp->if_capenable & IFCAP_TSO)
		ifp->if_hwassist |= CSUM_TSO;
	if (ifp->if_capenable & IFCAP_TXCSUM)
		ifp->if_hwassist |= (CSUM_TCP | CSUM_UDP);
	if (ifp->if_capenable & IFCAP_TXCSUM_IPV6)
		ifp->if_hwassist |= (CSUM_TCP_IPV6 | CSUM_UDP_IPV6);
#ifdef CSUM_ENCAP_VXLAN
	if (ifp->if_capenable & IFCAP_VXLAN_HWCSUM)
		ifp->if_hwassist |= (CSUM_INNER_IP6_UDP | CSUM_INNER_IP6_TCP |
		    CSUM_INNER_IP6_TSO | CSUM_INNER_IP | CSUM_INNER_IP_UDP |
		    CSUM_INNER_IP_TCP | CSUM_ENCAP_VXLAN);
	if (ifp->if_capenable & IFCAP_VXLAN_HWTSO)
		ifp->if_hwassist |= (CSUM_INNER_IP6_TSO | CSUM_INNER_IP_TSO);
#endif


	/* Set up the device filtering */
	bzero(&filter, sizeof(filter));
	filter.enable_ethtype = TRUE;
	filter.enable_macvlan = TRUE;
	filter.enable_fdir = FALSE;
	filter.hash_lut_size = I40E_HASH_LUT_SIZE_512;
	if (i40e_set_filter_control(hw, &filter))
		device_printf(dev, "i40e_set_filter_control() failed\n");

	/* Prepare the VSI: rings, hmc contexts, etc... */
	if (ixl_initialize_vsi(vsi)) {
		device_printf(dev, "initialize vsi failed!!\n");
		return;
	}

	/* Set up RSS */
	ixl_config_rss(pf);

	/* Set up MSI/X routing and the ITR settings */
	if (pf->msix > 1) {
		ixl_configure_queue_intr_msix(pf);
		ixl_configure_itr(pf);
	} else
		ixl_configure_legacy(pf);

	ixl_enable_rings(vsi);

	i40e_aq_set_default_vsi(hw, vsi->seid, NULL);

	ixl_reconfigure_filters(vsi);

	/* Check if PROMISC or ALLMULTI flags have been set
	 * by user before bringing interface up */
	ixl_set_promisc(vsi);

	/* Get link info */
	hw->phy.get_link_info = TRUE;
	i40e_get_link_status(hw, &pf->link_up);
	ixl_dbg_link(pf, "%s link_up: %d\n", __func__, pf->link_up);
	ixl_update_link_status(pf);

	/* Inform the stack we're ready */
	ifp->if_drv_flags |= IFF_DRV_RUNNING;

	/* Turn on interrupts after setting the running flag
	 * to avoid missing those asserted immediately after
	 * enabling them */
	ixl_enable_intr(vsi);

#ifdef IXL_IW
	if (ixl_enable_iwarp && pf->iw_enabled) {
		ret = ixl_iw_pf_init(pf);
		if (ret)
			device_printf(dev,
			    "initialize iwarp failed, code %d\n", ret);
	}
#endif
}


/*********************************************************************
 *
 *  Get the hardware capabilities
 *
 **********************************************************************/

int
ixl_get_hw_capabilities(struct ixl_pf *pf)
{
	struct i40e_aqc_list_capabilities_element_resp *buf;
	struct i40e_hw	*hw = &pf->hw;
	device_t 	dev = pf->dev;
	enum i40e_status_code status;
	int len, i2c_intfc_num;
	bool again = TRUE;
	u16 needed;

	if (IXL_PF_IN_RECOVERY_MODE(pf)) {
		hw->func_caps.iwarp = 0;
		return 0;
	}

	len = 40 * sizeof(struct i40e_aqc_list_capabilities_element_resp);
retry:
	if (!(buf = (struct i40e_aqc_list_capabilities_element_resp *)
	    malloc(len, M_IXL, M_NOWAIT | M_ZERO))) {
		device_printf(dev, "Unable to allocate cap memory\n");
                return (ENOMEM);
	}

	/* This populates the hw struct */
        status = i40e_aq_discover_capabilities(hw, buf, len,
	    &needed, i40e_aqc_opc_list_func_capabilities, NULL);
	free(buf, M_IXL);
	if ((pf->hw.aq.asq_last_status == I40E_AQ_RC_ENOMEM) &&
	    (again == TRUE)) {
		/* retry once with a larger buffer */
		again = FALSE;
		len = needed;
		goto retry;
	} else if (status != I40E_SUCCESS) {
		device_printf(dev, "capability discovery failed; status %s, error %s\n",
		    i40e_stat_str(hw, status), i40e_aq_str(hw, hw->aq.asq_last_status));
		return (ENODEV);
	}

#ifdef IXL_DEBUG
	device_printf(dev, "pf_id=%d, num_vfs=%d, msix_pf=%d, "
	    "msix_vf=%d, fd_g=%d, fd_b=%d, tx_qp=%d rx_qp=%d qbase=%d\n",
	    hw->pf_id, hw->func_caps.num_vfs,
	    hw->func_caps.num_msix_vectors,
	    hw->func_caps.num_msix_vectors_vf,
	    hw->func_caps.fd_filters_guaranteed,
	    hw->func_caps.fd_filters_best_effort,
	    hw->func_caps.num_tx_qp,
	    hw->func_caps.num_rx_qp,
	    hw->func_caps.base_queue);
#endif
	/*
	 * Some devices have both MDIO and I2C (e.g. FVL 25G cards). Since this
	 * isn't reported by the FW, check registers to see if an I2C interface
	 * exists.
	 */
	i2c_intfc_num = ixl_find_i2c_interface(pf);
	if (i2c_intfc_num != -1)
		pf->has_i2c = true;

	/* Determine functions to use for driver I2C accesses */
	switch (pf->i2c_access_method) {
	case IXL_I2C_ACCESS_METHOD_BEST_AVAILABLE: {
		if (hw->flags & I40E_HW_FLAG_AQ_PHY_ACCESS_CAPABLE) {
			pf->read_i2c_byte = ixl_read_i2c_byte_aq;
			pf->write_i2c_byte = ixl_write_i2c_byte_aq;
		} else {
			pf->read_i2c_byte = ixl_read_i2c_byte_reg;
			pf->write_i2c_byte = ixl_write_i2c_byte_reg;
		}
		break;
	}
	case IXL_I2C_ACCESS_METHOD_AQ:
		pf->read_i2c_byte = ixl_read_i2c_byte_aq;
		pf->write_i2c_byte = ixl_write_i2c_byte_aq;
		break;
	case IXL_I2C_ACCESS_METHOD_REGISTER_I2CCMD:
		pf->read_i2c_byte = ixl_read_i2c_byte_reg;
		pf->write_i2c_byte = ixl_write_i2c_byte_reg;
		break;
	case IXL_I2C_ACCESS_METHOD_BIT_BANG_I2CPARAMS:
		pf->read_i2c_byte = ixl_read_i2c_byte_bb;
		pf->write_i2c_byte = ixl_write_i2c_byte_bb;
		break;
	default:
		/* Should not happen */
		device_printf(dev, "Error setting I2C access functions\n");
		break;
	}

	/* Keep link active by default */
	ixl_set_state(&pf->state, IXL_STATE_LINK_ACTIVE_ON_DOWN);

	/* Print a subset of the capability information. */
	device_printf(dev,
	    "PF-ID[%d]: VFs %d, MSI-X %d, VF MSI-X %d, QPs %d, %s\n",
	    hw->pf_id, hw->func_caps.num_vfs, hw->func_caps.num_msix_vectors,
	    hw->func_caps.num_msix_vectors_vf, hw->func_caps.num_tx_qp,
	    (hw->func_caps.mdio_port_mode == 2) ? "I2C" :
	    (hw->func_caps.mdio_port_mode == 1 && pf->has_i2c) ? "MDIO & I2C" :
	    (hw->func_caps.mdio_port_mode == 1) ? "MDIO dedicated" :
	    "MDIO shared");

	return (0);
}

void
ixl_cap_txcsum_tso(struct ixl_vsi *vsi, struct ifnet *ifp, int mask)
{
	device_t 	dev = vsi->dev;

	/* Enable/disable TXCSUM/TSO4 */
	if (!(ifp->if_capenable & IFCAP_TXCSUM)
	    && !(ifp->if_capenable & IFCAP_TSO4)) {
		if (mask & IFCAP_TXCSUM) {
			ifp->if_capenable |= IFCAP_TXCSUM;
			/* enable TXCSUM, restore TSO if previously enabled */
			if (vsi->flags & IXL_FLAGS_KEEP_TSO4) {
				vsi->flags &= ~IXL_FLAGS_KEEP_TSO4;
				ifp->if_capenable |= IFCAP_TSO4;
			}
		}
		else if (mask & IFCAP_TSO4) {
			ifp->if_capenable |= (IFCAP_TXCSUM | IFCAP_TSO4);
			vsi->flags &= ~IXL_FLAGS_KEEP_TSO4;
			device_printf(dev,
			    "TSO4 requires txcsum, enabling both...\n");
		}
	} else if((ifp->if_capenable & IFCAP_TXCSUM)
	    && !(ifp->if_capenable & IFCAP_TSO4)) {
		if (mask & IFCAP_TXCSUM)
			ifp->if_capenable &= ~IFCAP_TXCSUM;
		else if (mask & IFCAP_TSO4)
			ifp->if_capenable |= IFCAP_TSO4;
	} else if((ifp->if_capenable & IFCAP_TXCSUM)
	    && (ifp->if_capenable & IFCAP_TSO4)) {
		if (mask & IFCAP_TXCSUM) {
			vsi->flags |= IXL_FLAGS_KEEP_TSO4;
			ifp->if_capenable &= ~(IFCAP_TXCSUM | IFCAP_TSO4);
			device_printf(dev,
			    "TSO4 requires txcsum, disabling both...\n");
		} else if (mask & IFCAP_TSO4)
			ifp->if_capenable &= ~IFCAP_TSO4;
	}

	/* Enable/disable TXCSUM_IPV6/TSO6 */
	if (!(ifp->if_capenable & IFCAP_TXCSUM_IPV6)
	    && !(ifp->if_capenable & IFCAP_TSO6)) {
		if (mask & IFCAP_TXCSUM_IPV6) {
			ifp->if_capenable |= IFCAP_TXCSUM_IPV6;
			if (vsi->flags & IXL_FLAGS_KEEP_TSO6) {
				vsi->flags &= ~IXL_FLAGS_KEEP_TSO6;
				ifp->if_capenable |= IFCAP_TSO6;
			}
		} else if (mask & IFCAP_TSO6) {
			ifp->if_capenable |= (IFCAP_TXCSUM_IPV6 | IFCAP_TSO6);
			vsi->flags &= ~IXL_FLAGS_KEEP_TSO6;
			device_printf(dev,
			    "TSO6 requires txcsum6, enabling both...\n");
		}
	} else if((ifp->if_capenable & IFCAP_TXCSUM_IPV6)
	    && !(ifp->if_capenable & IFCAP_TSO6)) {
		if (mask & IFCAP_TXCSUM_IPV6)
			ifp->if_capenable &= ~IFCAP_TXCSUM_IPV6;
		else if (mask & IFCAP_TSO6)
			ifp->if_capenable |= IFCAP_TSO6;
	} else if ((ifp->if_capenable & IFCAP_TXCSUM_IPV6)
	    && (ifp->if_capenable & IFCAP_TSO6)) {
		if (mask & IFCAP_TXCSUM_IPV6) {
			vsi->flags |= IXL_FLAGS_KEEP_TSO6;
			ifp->if_capenable &= ~(IFCAP_TXCSUM_IPV6 | IFCAP_TSO6);
			device_printf(dev,
			    "TSO6 requires txcsum6, disabling both...\n");
		} else if (mask & IFCAP_TSO6)
			ifp->if_capenable &= ~IFCAP_TSO6;
	}
}

/* For the set_advertise sysctl */
void
ixl_set_initial_advertised_speeds(struct ixl_pf *pf)
{
	device_t dev = pf->dev;
	int err;

	/* Make sure to initialize the device to the complete list of
	 * supported speeds on driver load, to ensure unloading and
	 * reloading the driver will restore this value.
	 */
	err = ixl_set_advertised_speeds(pf, pf->supported_speeds, true);
	if (err) {
		/* Non-fatal error */
		device_printf(dev, "%s: ixl_set_advertised_speeds() error %d\n",
			      __func__, err);
		return;
	}

	pf->advertised_speed =
	    ixl_convert_sysctl_aq_link_speed(pf->supported_speeds, false);
}

int
ixl_teardown_hw_structs(struct ixl_pf *pf)
{
	enum i40e_status_code status = 0;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;

	/* Shutdown LAN HMC */
	if (hw->hmc.hmc_obj) {
		status = i40e_shutdown_lan_hmc(hw);
		if (status) {
			device_printf(dev,
			    "init: LAN HMC shutdown failure; status %s\n",
			    i40e_stat_str(hw, status));
			goto err_out;
		}
	}

	/* Shutdown admin queue */
	ixl_disable_intr0(hw);
	status = i40e_shutdown_adminq(hw);
	if (status)
		device_printf(dev,
		    "init: Admin Queue shutdown failure; status %s\n",
		    i40e_stat_str(hw, status));

	ixl_pf_qmgr_release(&pf->qmgr, &pf->qtag);
err_out:
	return (status);
}

int
ixl_reset(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	u8 set_fc_err_mask;
	int error = 0;

	// XXX: clear_hw() actually writes to hw registers -- maybe this isn't necessary
	i40e_clear_hw(hw);
	i40e_set_mac_type(hw);

	error = ixl_pf_reset(pf);
	if (error)
		goto err_out;

	error = i40e_init_adminq(hw);
	if (error) {
		device_printf(dev, "init: Admin queue init failure;"
		    " status code %d\n", error);
		error = EIO;
		goto err_out;
	}

	i40e_clear_pxe_mode(hw);

	error = ixl_get_hw_capabilities(pf);
	if (error) {
		device_printf(dev, "init: Error retrieving HW capabilities;"
		    " status code %d\n", error);
		goto err_out;
	}

	error = i40e_init_lan_hmc(hw, hw->func_caps.num_tx_qp,
	    hw->func_caps.num_rx_qp, 0, 0);
	if (error) {
		device_printf(dev, "init: LAN HMC init failed; status code %d\n",
		    error);
		error = EIO;
		goto err_out;
	}

	error = i40e_configure_lan_hmc(hw, I40E_HMC_MODEL_DIRECT_ONLY);
	if (error) {
		device_printf(dev, "init: LAN HMC config failed; status code %d\n",
		    error);
		error = EIO;
		goto err_out;
	}

	// XXX: possible fix for panic, but our failure recovery is still broken
	error = ixl_switch_config(pf);
	if (error) {
		device_printf(dev, "init: ixl_switch_config() failed: %d\n",
		     error);
		goto err_out;
	}

	error = i40e_aq_set_phy_int_mask(hw, IXL_DEFAULT_PHY_INT_MASK,
	    NULL);
        if (error) {
		device_printf(dev, "init: i40e_aq_set_phy_mask() failed: err %d,"
		    " aq_err %d\n", error, hw->aq.asq_last_status);
		error = EIO;
		goto err_out;
	}

	error = i40e_set_fc(hw, &set_fc_err_mask, true);
	if (error) {
		device_printf(dev, "init: setting link flow control failed; retcode %d,"
		    " fc_err_mask 0x%02x\n", error, set_fc_err_mask);
		goto err_out;
	}

	// XXX: (Rebuild VSIs?)

	/* Firmware delay workaround */
	if (((hw->aq.fw_maj_ver == 4) && (hw->aq.fw_min_ver < 33)) ||
	    (hw->aq.fw_maj_ver < 4)) {
		i40e_msec_delay(75);
		error = i40e_aq_set_link_restart_an(hw, TRUE, NULL);
		if (error) {
			device_printf(dev, "init: link restart failed, aq_err %d\n",
			    hw->aq.asq_last_status);
			goto err_out;
		}
	}


	/* Re-enable admin queue interrupt */
	if (pf->msix > 1) {
		ixl_configure_intr0_msix(pf);
		ixl_enable_intr0(hw);
	}

err_out:
	return (error);
}

/*
** MSI-X Interrupt Handlers and Tasklets
*/
void
ixl_handle_que(void *context, int pending)
{
	struct ixl_queue *que = context;
	struct ixl_vsi *vsi = que->vsi;
	struct ixl_pf *pf = (struct ixl_pf *)vsi->back;
	struct i40e_hw  *hw = vsi->hw;
	struct tx_ring  *txr = &que->txr;
	struct ifnet    *ifp = vsi->ifp;
	bool		more;

	if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
		more = ixl_rxeof(que, IXL_RX_LIMIT);
		IXL_TX_LOCK(txr);
		ixl_txeof(que);
		if (!drbr_empty(ifp, txr->br))
			ixl_mq_start_locked(ifp, txr);
		IXL_TX_UNLOCK(txr);
		if (more) {
			taskqueue_enqueue(que->tq, &que->task);
			return;
		}
	}

	/* Reenable queue interrupt */
	if (pf->msix > 1)
		ixl_enable_queue(hw, que->me);
	else
		ixl_enable_intr0(hw);
}


/*********************************************************************
 *
 *  Legacy Interrupt Service routine
 *
 **********************************************************************/
void
ixl_intr(void *arg)
{
	struct ixl_pf		*pf = arg;
	struct i40e_hw		*hw =  &pf->hw;
	struct ixl_vsi		*vsi = &pf->vsi;
	struct ixl_queue	*que = vsi->queues;
	struct ifnet		*ifp = vsi->ifp;
	struct tx_ring		*txr = &que->txr;
        u32			icr0;
	bool			more;

	ixl_disable_intr0(hw);

	pf->admin_irq++;

	/* Clear PBA at start of ISR if using legacy interrupts */
	if (pf->msix == 0)
		wr32(hw, I40E_PFINT_DYN_CTL0,
		    I40E_PFINT_DYN_CTLN_CLEARPBA_MASK |
		    (IXL_ITR_NONE << I40E_PFINT_DYN_CTLN_ITR_INDX_SHIFT));

	icr0 = rd32(hw, I40E_PFINT_ICR0);


#ifdef PCI_IOV
	if (icr0 & I40E_PFINT_ICR0_VFLR_MASK)
		taskqueue_enqueue(pf->tq, &pf->vflr_task);
#endif

	if (icr0 & I40E_PFINT_ICR0_ADMINQ_MASK)
		taskqueue_enqueue(pf->tq, &pf->adminq);

	if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
		++que->irqs;

		more = ixl_rxeof(que, IXL_RX_LIMIT);

		IXL_TX_LOCK(txr);
		ixl_txeof(que);
		if (!drbr_empty(vsi->ifp, txr->br))
			ixl_mq_start_locked(ifp, txr);
		IXL_TX_UNLOCK(txr);

		if (more)
			taskqueue_enqueue(que->tq, &que->task);
	}

	ixl_enable_intr0(hw);
}


/*********************************************************************
 *
 *  MSI-X VSI Interrupt Service routine
 *
 **********************************************************************/
void
ixl_msix_que(void *arg)
{
	struct ixl_queue *que = arg;
	struct ixl_vsi	*vsi = que->vsi;
	struct i40e_hw	*hw = vsi->hw;
	struct tx_ring	*txr = &que->txr;
	bool		more_tx, more_rx;

	/* Protect against spurious interrupts */
	if (!(vsi->ifp->if_drv_flags & IFF_DRV_RUNNING))
		return;

	/* There are drivers which disable auto-masking of interrupts,
	 * which is a global setting for all ports. We have to make sure
	 * to mask it to not lose IRQs */
	ixl_disable_queue(hw, que->me);

	++que->irqs;

	more_rx = ixl_rxeof(que, IXL_RX_LIMIT);

	IXL_TX_LOCK(txr);
	more_tx = ixl_txeof(que);
	/*
	** Make certain that if the stack
	** has anything queued the task gets
	** scheduled to handle it.
	*/
	if (!drbr_empty(vsi->ifp, txr->br))
		more_tx = 1;
	IXL_TX_UNLOCK(txr);

	ixl_set_queue_rx_itr(que);
	ixl_set_queue_tx_itr(que);

	if (more_tx || more_rx)
		taskqueue_enqueue(que->tq, &que->task);
	else
		ixl_enable_queue(hw, que->me);

	return;
}


/*********************************************************************
 *
 *  MSI-X Admin Queue Interrupt Service routine
 *
 **********************************************************************/
void
ixl_msix_adminq(void *arg)
{
	struct ixl_pf	*pf = arg;
	struct i40e_hw	*hw = &pf->hw;
	device_t	dev = pf->dev;
	u32		reg, rstat_reg;
	bool		do_task = FALSE;

	++pf->admin_irq;

	reg = rd32(hw, I40E_PFINT_ICR0);

	/* Check on the cause */
	if (reg & I40E_PFINT_ICR0_ADMINQ_MASK)
		do_task = TRUE;

	if (reg & I40E_PFINT_ICR0_MAL_DETECT_MASK) {
		ixl_set_state(&pf->state, IXL_STATE_MDD_PENDING);
		do_task = TRUE;
	}

	if (reg & I40E_PFINT_ICR0_GRST_MASK) {
		const char *reset_type;
		rstat_reg = rd32(hw, I40E_GLGEN_RSTAT);
		rstat_reg = (rstat_reg & I40E_GLGEN_RSTAT_RESET_TYPE_MASK)
		    >> I40E_GLGEN_RSTAT_RESET_TYPE_SHIFT;
		switch (rstat_reg) {
		/* These others might be handled similarly to an EMPR reset */
		case I40E_RESET_CORER:
			reset_type = "CORER";
			break;
		case I40E_RESET_GLOBR:
			reset_type = "GLOBR";
			break;
		case I40E_RESET_EMPR:
			reset_type = "EMPR";
			break;
		default:
			reset_type = "POR";
			break;
		}
		/* overload admin queue task to check reset progress */
		device_printf(dev, "Reset Requested! (%s)\n", reset_type);
		ixl_set_state(&pf->state, IXL_STATE_RESETTING);
		do_task = TRUE;
	}

	if (reg & I40E_PFINT_ICR0_ECC_ERR_MASK) {
		device_printf(dev, "ECC Error detected!\n");
	}

	if (reg & I40E_PFINT_ICR0_HMC_ERR_MASK) {
		reg = rd32(hw, I40E_PFHMC_ERRORINFO);
		if (reg & I40E_PFHMC_ERRORINFO_ERROR_DETECTED_MASK) {
			device_printf(dev, "HMC Error detected!\n");
			device_printf(dev, "INFO 0x%08x\n", reg);
			reg = rd32(hw, I40E_PFHMC_ERRORDATA);
			device_printf(dev, "DATA 0x%08x\n", reg);
			wr32(hw, I40E_PFHMC_ERRORINFO, 0);
		}
	}

	if (reg & I40E_PFINT_ICR0_PCI_EXCEPTION_MASK) {
		device_printf(dev, "PCI Exception detected!\n");
	}

#ifdef PCI_IOV
	if (reg & I40E_PFINT_ICR0_VFLR_MASK) {
		taskqueue_enqueue(pf->tq, &pf->vflr_task);
	}
#endif

	if (do_task)
		taskqueue_enqueue(pf->tq, &pf->adminq);
	else
		ixl_enable_intr0(hw);
}

void
ixl_set_promisc(struct ixl_vsi *vsi)
{
	struct ifnet	*ifp = vsi->ifp;
	struct i40e_hw	*hw = vsi->hw;
	enum i40e_status_code	status;
	bool		uni = FALSE, multi = FALSE;

        if (ifp->if_flags & IFF_PROMISC)
		uni = multi = TRUE;
	else if ((ifp->if_flags & IFF_ALLMULTI) ||
	    (if_llmaddr_count(ifp) >= MAX_MULTICAST_ADDR))
		multi = TRUE;

	status = i40e_aq_set_vsi_unicast_promiscuous(hw,
	    vsi->seid, uni, NULL, TRUE);
	if (status)
		if_printf(ifp,
		    "i40e_aq_set_vsi_unicast_promiscuous status %s, error %s\n",
		    i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));

	status = i40e_aq_set_vsi_multicast_promiscuous(hw,
	    vsi->seid, multi, NULL);
	if (status)
		if_printf(ifp,
		    "i40e_aq_set_vsi_multicast_promiscuous status %s, error %s\n",
		    i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
}

/*
** Creates new filter with given MAC address and VLAN ID
*/
static struct ixl_mac_filter *
ixl_new_filter(struct ixl_ftl_head *headp, const u8 *macaddr, s16 vlan)
{
	struct ixl_mac_filter  *f;

	/* create a new empty filter */
	f = malloc(sizeof(struct ixl_mac_filter),
	    M_IXL, M_NOWAIT | M_ZERO);
	if (f) {
		LIST_INSERT_HEAD(headp, f, ftle);
		bcopy(macaddr, f->macaddr, ETHER_ADDR_LEN);
		f->vlan = vlan;
	}

	return (f);
}

/**
 * ixl_free_filters - Free all filters in given list
 * headp - pointer to list head
 *
 * Frees memory used by each entry in the list.
 * Does not remove filters from HW.
 */
static void
ixl_free_filters(struct ixl_ftl_head *headp)
{
	struct ixl_mac_filter *f, *nf;

	f = LIST_FIRST(headp);
	while (f != NULL) {
		nf = LIST_NEXT(f, ftle);
		free(f, M_IXL);
		f = nf;
	}

	LIST_INIT(headp);
}

static u_int
ixl_add_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
{
	struct ixl_add_maddr_arg *ama = arg;
	struct ixl_vsi *vsi = ama->vsi;
	const u8 *macaddr = (u8*)LLADDR(sdl);
	struct ixl_mac_filter *f;

	/* Does one already exist */
	f = ixl_find_filter(&vsi->ftl, macaddr, IXL_VLAN_ANY);
	if (f != NULL)
		return (0);

	f = ixl_new_filter(&ama->to_add, macaddr, IXL_VLAN_ANY);
	if (f == NULL) {
		device_printf(vsi->dev, "WARNING: no filter available!!\n");
		return (0);
	}
	f->flags |= IXL_FILTER_MC;

	return (1);
}

/*********************************************************************
 * 	Filter Routines
 *
 *	Routines for multicast and vlan filter management.
 *
 *********************************************************************/

/**
 * ixl_add_multi - Add multicast filters to the hardware
 * @vsi: The VSI structure
 *
 * In case number of multicast filters in the IFP exceeds 127 entries,
 * multicast promiscuous mode will be enabled and the filters will be removed
 * from the hardware
 */
void
ixl_add_multi(struct ixl_vsi *vsi)
{
	struct ifnet		*ifp = vsi->ifp;
	struct i40e_hw		*hw = vsi->hw;
	int			mcnt = 0;
	struct ixl_add_maddr_arg cb_arg;
	enum i40e_status_code	status;

	IOCTL_DEBUGOUT("ixl_add_multi: begin");

	mcnt = if_llmaddr_count(ifp);
	if (__predict_false(mcnt >= MAX_MULTICAST_ADDR)) {
		status = i40e_aq_set_vsi_multicast_promiscuous(hw, vsi->seid,
		    TRUE, NULL);
		if (status != I40E_SUCCESS)
			if_printf(ifp, "Failed to enable multicast promiscuous "
			    "mode, status: %s\n", i40e_stat_str(hw, status));
		else
			if_printf(ifp, "Enabled multicast promiscuous mode\n");
		/* Delete all existing MC filters */
		ixl_del_multi(vsi, true);
		return;
	}

	cb_arg.vsi = vsi;
	LIST_INIT(&cb_arg.to_add);

	mcnt = if_foreach_llmaddr(ifp, ixl_add_maddr, &cb_arg);
	if (mcnt > 0)
		ixl_add_hw_filters(vsi, &cb_arg.to_add, mcnt);

	IOCTL_DEBUGOUT("ixl_add_multi: end");
}

static u_int
ixl_match_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
{
	struct ixl_mac_filter *f = arg;

	if (ixl_ether_is_equal(f->macaddr, (u8 *)LLADDR(sdl)))
		return (1);
	else
		return (0);
}

/**
 * ixl_dis_multi_promisc - Disable multicast promiscuous mode
 * @vsi: The VSI structure
 * @vsi_mcnt: Number of multicast filters in the VSI
 *
 * Disable multicast promiscuous mode based on number of entries in the IFP
 * and the VSI, then re-add multicast filters.
 *
 */
static void
ixl_dis_multi_promisc(struct ixl_vsi *vsi, int vsi_mcnt)
{
	struct ifnet		*ifp = vsi->ifp;
	struct i40e_hw		*hw = vsi->hw;
	int			ifp_mcnt = 0;
	enum i40e_status_code	status;

	ifp_mcnt = if_llmaddr_count(ifp);
	/*
	 * Equal lists or empty ifp list mean the list has not been changed
	 * and in such case avoid disabling multicast promiscuous mode as it
	 * was not previously enabled. Case where multicast promiscuous mode has
	 * been enabled is when vsi_mcnt == 0 && ifp_mcnt > 0.
	 */
	if (ifp_mcnt == vsi_mcnt || ifp_mcnt == 0 ||
	    ifp_mcnt >= MAX_MULTICAST_ADDR)
		return;

	status = i40e_aq_set_vsi_multicast_promiscuous(hw, vsi->seid,
	    FALSE, NULL);
	if (status != I40E_SUCCESS) {
		if_printf(ifp, "Failed to disable multicast promiscuous "
		    "mode, status: %s\n", i40e_stat_str(hw, status));

		return;
	}

	if_printf(ifp, "Disabled multicast promiscuous mode\n");

	ixl_add_multi(vsi);
}

/**
 * ixl_del_multi - Delete multicast filters from the hardware
 * @vsi: The VSI structure
 * @all: Bool to determine if all the multicast filters should be removed
 *
 * In case number of multicast filters in the IFP drops to 127 entries,
 * multicast promiscuous mode will be disabled and the filters will be reapplied
 * to the hardware.
 */
void
ixl_del_multi(struct ixl_vsi *vsi, bool all)
{
	int			to_del_cnt = 0, vsi_mcnt = 0;
	struct ifnet		*ifp = vsi->ifp;
	struct ixl_mac_filter	*f, *fn;
	struct ixl_ftl_head	to_del;

	IOCTL_DEBUGOUT("ixl_del_multi: begin");

	LIST_INIT(&to_del);
	/* Search for removed multicast addresses */
	LIST_FOREACH_SAFE(f, &vsi->ftl, ftle, fn) {
		if ((f->flags & IXL_FILTER_MC) == 0)
			continue;

		/* Count all the multicast filters in the VSI for comparison */
		vsi_mcnt++;

		if (!all && if_foreach_llmaddr(ifp, ixl_match_maddr, f) != 0)
			continue;

		LIST_REMOVE(f, ftle);
		LIST_INSERT_HEAD(&to_del, f, ftle);
		to_del_cnt++;
	}

	if (to_del_cnt > 0) {
		ixl_del_hw_filters(vsi, &to_del, to_del_cnt);
		return;
	}

	ixl_dis_multi_promisc(vsi, vsi_mcnt);

	IOCTL_DEBUGOUT("ixl_del_multi: end");
}

/*********************************************************************
 *  Timer routine
 *
 *  This routine checks for link status, updates statistics,
 *  and runs the watchdog check.
 *
 **********************************************************************/
void
ixl_local_timer(void *arg)
{
	struct ixl_pf *pf = arg;
	struct ifnet *ifp = pf->vsi.ifp;
	struct i40e_hw *hw = &pf->hw;
	enum i40e_status_code status;
	sbintime_t stime;

	IXL_PF_LOCK_ASSERT(pf);

	if (ixl_get_fw_mode(pf) == IXL_FW_MODE_RECOVERY) {
		if (!IXL_PF_IN_RECOVERY_MODE(pf)) {
			if (ifp->if_drv_flags & IFF_DRV_RUNNING)
				ixl_stop_locked(pf);
			ixl_set_state(&pf->state,
			    IXL_STATE_RECOVERY_MODE |
			    IXL_STATE_RESETTING);
			device_printf(pf->dev, "Firmware recovery mode detected. Limiting functionality. Refer to Intel(R) Ethernet Adapters and Devices User Guide for details on firmware recovery mode.\n");
		}
	}
	if (ixl_test_state(&pf->state, IXL_STATE_LINK_POLLING)) {
		hw->phy.get_link_info = TRUE;
		status = i40e_get_link_status(hw, &pf->link_up);
		if (status == I40E_SUCCESS) {
			ixl_clear_state(&pf->state, IXL_STATE_LINK_POLLING);
		} else {
			device_printf(pf->dev,
			    "%s: i40e_get_link_status status %s, aq error %s\n",
			    __func__, i40e_stat_str(hw, status),
			    i40e_aq_str(hw, hw->aq.asq_last_status));
			stime = getsbinuptime();
			if (stime - pf->link_poll_start > IXL_PF_MAX_LINK_POLL) {
				device_printf(pf->dev, "Polling link status failed\n");
				ixl_clear_state(&pf->state, IXL_STATE_LINK_POLLING);
			}
		}
	}

	/* Fire off the adminq task */
	taskqueue_enqueue(pf->tq, &pf->adminq);

	if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
		/* Update stats */
		ixl_update_stats_counters(pf);

		/* Increment stat when a queue shows hung */
		if (ixl_queue_hang_check(&pf->vsi))
			pf->watchdog_events++;

#ifdef CSUM_ENCAP_VXLAN
		/* Apply pending UDP filters to HW */
		ixl_sync_udp_filters_task(pf);
#endif
	}

	callout_reset(&pf->timer, hz, ixl_local_timer, pf);
}

void
ixl_link_up_msg(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	struct ifnet *ifp = pf->vsi.ifp;
	char *req_fec_string, *neg_fec_string;
	u8 fec_abilities;

	fec_abilities = hw->phy.link_info.req_fec_info;
	/* If both RS and KR are requested, only show RS */
	if (fec_abilities & I40E_AQ_REQUEST_FEC_RS)
		req_fec_string = ixl_fec_string[0];
	else if (fec_abilities & I40E_AQ_REQUEST_FEC_KR)
		req_fec_string = ixl_fec_string[1];
	else
		req_fec_string = ixl_fec_string[2];

	if (hw->phy.link_info.fec_info & I40E_AQ_CONFIG_FEC_RS_ENA)
		neg_fec_string = ixl_fec_string[0];
	else if (hw->phy.link_info.fec_info & I40E_AQ_CONFIG_FEC_KR_ENA)
		neg_fec_string = ixl_fec_string[1];
	else
		neg_fec_string = ixl_fec_string[2];

	log(LOG_NOTICE, "%s: Link is up, %s Full Duplex, Requested FEC: %s, Negotiated FEC: %s, Autoneg: %s, Flow Control: %s\n",
	    ifp->if_xname,
	    ixl_link_speed_string(hw->phy.link_info.link_speed),
	    req_fec_string, neg_fec_string,
	    (hw->phy.link_info.an_info & I40E_AQ_AN_COMPLETED) ? "True" : "False",
	    (hw->phy.link_info.an_info & I40E_AQ_LINK_PAUSE_TX &&
	        hw->phy.link_info.an_info & I40E_AQ_LINK_PAUSE_RX) ?
		ixl_fc_string[3] : (hw->phy.link_info.an_info & I40E_AQ_LINK_PAUSE_TX) ?
		ixl_fc_string[2] : (hw->phy.link_info.an_info & I40E_AQ_LINK_PAUSE_RX) ?
		ixl_fc_string[1] : ixl_fc_string[0]);
}

/*
** Note: this routine updates the OS on the link state
**	the real check of the hardware only happens with
**	a link interrupt.
*/
void
ixl_update_link_status(struct ixl_pf *pf)
{
	struct ixl_vsi		*vsi = &pf->vsi;
	struct ifnet		*ifp = vsi->ifp;
	device_t		dev = pf->dev;

	ixl_dbg_link(pf, "%s link_up: %d, link_speed: %08x\n",
	    __func__, pf->link_up, pf->link_speed);

	if (pf->link_up) {
		if (vsi->link_active == FALSE) {
			vsi->link_active = TRUE;
#if __FreeBSD_version >= 1100000
			ifp->if_baudrate = ixl_max_aq_speed_to_value(pf->link_speed);
#else
			if_initbaudrate(ifp, ixl_max_aq_speed_to_value(pf->link_speed));
#endif
			if_link_state_change(ifp, LINK_STATE_UP);
			ixl_link_up_msg(pf);
#ifdef PCI_IOV
			ixl_broadcast_link_state(pf);
#endif
		}
	} else { /* Link down */
		if (vsi->link_active == TRUE) {
			if (bootverbose)
				device_printf(dev, "Link is Down\n");
			if_link_state_change(ifp, LINK_STATE_DOWN);
			vsi->link_active = FALSE;
#ifdef PCI_IOV
			ixl_broadcast_link_state(pf);
#endif
		}
	}
}

/*********************************************************************
 *
 *  This routine disables all traffic on the adapter by issuing a
 *  global reset on the MAC and deallocates TX/RX buffers.
 *
 **********************************************************************/

void
ixl_stop_locked(struct ixl_pf *pf)
{
	struct ixl_vsi	*vsi = &pf->vsi;
	struct ifnet	*ifp = vsi->ifp;

	INIT_DEBUGOUT("ixl_stop: begin\n");

	IXL_PF_LOCK_ASSERT(pf);

	/* Tell the stack that the interface is no longer active */
	ifp->if_drv_flags &= ~(IFF_DRV_RUNNING);

#ifdef IXL_IW
	/* Stop iWARP device */
	if (ixl_enable_iwarp && pf->iw_enabled)
		ixl_iw_pf_stop(pf);
#endif

	ixl_disable_rings_intr(vsi);
	ixl_disable_rings(pf, vsi, &pf->qtag);
}

void
ixl_stop(struct ixl_pf *pf)
{
	IXL_PF_LOCK(pf);
	ixl_stop_locked(pf);
	IXL_PF_UNLOCK(pf);
}

/*********************************************************************
 *
 *  Setup MSI-X Interrupt resources and handlers for the VSI
 *
 **********************************************************************/
int
ixl_setup_legacy(struct ixl_pf *pf)
{
	device_t        dev = pf->dev;
	int 		error, rid = 0;

	if (pf->msix == 1)
		rid = 1;
	pf->res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
	    &rid, RF_SHAREABLE | RF_ACTIVE);
	if (pf->res == NULL) {
		device_printf(dev, "bus_alloc_resource_any() for"
		    " legacy/msi interrupt\n");
		return (ENXIO);
	}

	/* Set the handler function */
	error = bus_setup_intr(dev, pf->res,
	    INTR_TYPE_NET | INTR_MPSAFE, NULL,
	    ixl_intr, pf, &pf->tag);
	if (error) {
		pf->res = NULL;
		device_printf(dev, "bus_setup_intr() for legacy/msi"
		    " interrupt handler failed, error %d\n", error);
		return (ENXIO);
	}
	error = bus_describe_intr(dev, pf->res, pf->tag, "irq");
	if (error) {
		/* non-fatal */
		device_printf(dev, "bus_describe_intr() for Admin Queue"
		    " interrupt name failed, error %d\n", error);
	}

	return (0);
}

int
ixl_setup_adminq_tq(struct ixl_pf *pf)
{
	device_t dev = pf->dev;
	int error = 0;

	/* Tasklet for Admin Queue interrupts */
	TASK_INIT(&pf->adminq, 0, ixl_do_adminq, pf);
#ifdef PCI_IOV
	/* VFLR Tasklet */
	TASK_INIT(&pf->vflr_task, 0, ixl_handle_vflr, pf);
#endif
	/* Create and start Admin Queue taskqueue */
	pf->tq = taskqueue_create_fast("ixl_aq", M_NOWAIT,
	    taskqueue_thread_enqueue, &pf->tq);
	if (!pf->tq) {
		device_printf(dev, "taskqueue_create_fast (for AQ) returned NULL!\n");
		return (ENOMEM);
	}
	error = taskqueue_start_threads(&pf->tq, 1, PI_NET, "%s aq",
	    device_get_nameunit(dev));
	if (error) {
		device_printf(dev, "taskqueue_start_threads (for AQ) error: %d\n",
		    error);
		taskqueue_free(pf->tq);
		return (error);
	}
	return (0);
}

int
ixl_setup_queue_tqs(struct ixl_vsi *vsi)
{
	struct ixl_queue *que = vsi->queues;
	device_t dev = vsi->dev;

	/* Create queue tasks and start queue taskqueues */
	for (int i = 0; i < vsi->num_queues; i++, que++) {
		TASK_INIT(&que->tx_task, 0, ixl_deferred_mq_start, que);
		NET_TASK_INIT(&que->task, 0, ixl_handle_que, que);
		que->tq = taskqueue_create_fast("ixl_que", M_NOWAIT,
		    taskqueue_thread_enqueue, &que->tq);
#ifdef RSS
		CPU_SETOF(cpu_id, &cpu_mask);
		taskqueue_start_threads_cpuset(&que->tq, 1, PI_NET,
		    &cpu_mask, "%s (bucket %d)",
		    device_get_nameunit(dev), cpu_id);
#else
		taskqueue_start_threads(&que->tq, 1, PI_NET,
		    "%s (que %d)", device_get_nameunit(dev), que->me);
#endif
	}

	return (0);
}

void
ixl_free_adminq_tq(struct ixl_pf *pf)
{
	if (pf->tq) {
		taskqueue_free(pf->tq);
		pf->tq = NULL;
	}
}

void
ixl_free_queue_tqs(struct ixl_vsi *vsi)
{
	struct ixl_queue *que = vsi->queues;

	for (int i = 0; i < vsi->num_queues; i++, que++) {
		if (que->tq) {
			taskqueue_free(que->tq);
			que->tq = NULL;
		}
	}
}

int
ixl_setup_adminq_msix(struct ixl_pf *pf)
{
	device_t dev = pf->dev;
	int rid, error = 0;

	/* Admin IRQ rid is 1, vector is 0 */
	rid = 1;
	/* Get interrupt resource from bus */
	pf->res = bus_alloc_resource_any(dev,
    	    SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE);
	if (!pf->res) {
		device_printf(dev, "bus_alloc_resource_any() for Admin Queue"
		    " interrupt failed [rid=%d]\n", rid);
		return (ENXIO);
	}
	/* Then associate interrupt with handler */
	error = bus_setup_intr(dev, pf->res,
	    INTR_TYPE_NET | INTR_MPSAFE, NULL,
	    ixl_msix_adminq, pf, &pf->tag);
	if (error) {
		pf->res = NULL;
		device_printf(dev, "bus_setup_intr() for Admin Queue"
		    " interrupt handler failed, error %d\n", error);
		return (ENXIO);
	}
	error = bus_describe_intr(dev, pf->res, pf->tag, "aq");
	if (error) {
		/* non-fatal */
		device_printf(dev, "bus_describe_intr() for Admin Queue"
		    " interrupt name failed, error %d\n", error);
	}
	pf->admvec = 0;

	return (0);
}

/*
 * Allocate interrupt resources from bus and associate an interrupt handler
 * to those for the VSI's queues.
 */
int
ixl_setup_queue_msix(struct ixl_vsi *vsi)
{
	device_t	dev = vsi->dev;
	struct 		ixl_queue *que = vsi->queues;
	int 		error, rid, vector = 1;
#ifdef	RSS
	cpuset_t cpu_mask;
#endif

	/* Queue interrupt vector numbers start at 1 (adminq intr is 0) */
	for (int i = 0; i < vsi->num_queues; i++, vector++, que++) {
		int cpu_id = i;
		rid = vector + 1;
		que->res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
		    RF_SHAREABLE | RF_ACTIVE);
		if (!que->res) {
			device_printf(dev, "bus_alloc_resource_any() for"
			    " Queue %d interrupt failed [rid=%d]\n",
			    que->me, rid);
			return (ENXIO);
		}
		/* Set the handler function */
		error = bus_setup_intr(dev, que->res,
		    INTR_TYPE_NET | INTR_MPSAFE, NULL,
		    ixl_msix_que, que, &que->tag);
		if (error) {
			device_printf(dev, "bus_setup_intr() for Queue %d"
			    " interrupt handler failed, error %d\n",
			    que->me, error);
			bus_release_resource(dev, SYS_RES_IRQ, rid, que->res);
			return (error);
		}
		error = bus_describe_intr(dev, que->res, que->tag, "q%d", i);
		if (error) {
			device_printf(dev, "bus_describe_intr() for Queue %d"
			    " interrupt name failed, error %d\n",
			    que->me, error);
		}
		/* Bind the vector to a CPU */
#ifdef RSS
		cpu_id = rss_getcpu(i % rss_getnumbuckets());
#endif
		error = bus_bind_intr(dev, que->res, cpu_id);
		if (error) {
			device_printf(dev, "bus_bind_intr() for Queue %d"
			    " to CPU %d failed, error %d\n",
			    que->me, cpu_id, error);
		}
		que->msix = vector;
	}

	return (0);
}

/*
 * Allocate MSI/X vectors from the OS.
 * Returns 0 for legacy, 1 for MSI, >1 for MSI-X.
 */
int
ixl_init_msix(struct ixl_pf *pf)
{
	device_t dev = pf->dev;
	struct i40e_hw *hw = &pf->hw;
#ifdef IXL_IW
#if __FreeBSD_version >= 1100000
        cpuset_t cpu_set;
#endif
#endif
	int auto_max_queues;
	int rid, want, vectors, queues, available;
#ifdef IXL_IW
	int iw_want=0, iw_vectors;

	pf->iw_msix = 0;
#endif

	/* Override by tuneable */
	if (!pf->enable_msix)
		goto no_msix;

	/* First try MSI/X */
	rid = PCIR_BAR(IXL_MSIX_BAR);
	pf->msix_mem = bus_alloc_resource_any(dev,
	    SYS_RES_MEMORY, &rid, RF_ACTIVE);
       	if (!pf->msix_mem) {
		/* May not be enabled */
		device_printf(pf->dev,
		    "Unable to map MSI-X table\n");
		goto no_msix;
	}

	available = pci_msix_count(dev);
	if (available < 2) {
		/* system has msix disabled (0), or only one vector (1) */
		device_printf(pf->dev, "Less than two MSI-X vectors available\n");
		bus_release_resource(dev, SYS_RES_MEMORY,
		    rman_get_rid(pf->msix_mem), pf->msix_mem);
		pf->msix_mem = NULL;
		goto no_msix;
	}

	/* Clamp max number of queues based on:
	 * - # of MSI-X vectors available
	 * - # of cpus available
	 * - # of queues that can be assigned to the LAN VSI
	 */
	auto_max_queues = min(mp_ncpus, available - 1);
	if (hw->mac.type == I40E_MAC_X722)
		auto_max_queues = min(auto_max_queues, 128);
	else
		auto_max_queues = min(auto_max_queues, 64);

	/* Override with tunable value if tunable is less than autoconfig count */
	if ((pf->max_queues != 0) && (pf->max_queues <= auto_max_queues))
		queues = pf->max_queues;
	/* Use autoconfig amount if that's lower */
	else if ((pf->max_queues != 0) && (pf->max_queues > auto_max_queues)) {
		device_printf(dev, "ixl_max_queues (%d) is too large, using "
		    "autoconfig amount (%d)...\n",
		    pf->max_queues, auto_max_queues);
		queues = auto_max_queues;
	}
	/* Limit maximum auto-configured queues to 8 if no user value is set */
	else
		queues = min(auto_max_queues, 8);

#ifdef  RSS
	/* If we're doing RSS, clamp at the number of RSS buckets */
	if (queues > rss_getnumbuckets())
		queues = rss_getnumbuckets();
#endif

	/*
	** Want one vector (RX/TX pair) per queue
	** plus an additional for the admin queue.
	*/
	want = queues + 1;
	if (want <= available)	/* Have enough */
		vectors = want;
	else {
               	device_printf(pf->dev,
		    "MSI-X Configuration Problem, "
		    "%d vectors available but %d wanted!\n",
		    available, want);
		pf->msix_mem = NULL;
		goto no_msix; /* Will go to Legacy setup */
	}

#ifdef IXL_IW
	if (ixl_enable_iwarp && hw->func_caps.iwarp) {
#if __FreeBSD_version >= 1100000
		if(bus_get_cpus(dev, INTR_CPUS, sizeof(cpu_set), &cpu_set) == 0)
		{
			iw_want = min(CPU_COUNT(&cpu_set), IXL_IW_MAX_MSIX);
		}
#endif
		if(!iw_want)
			iw_want = min(mp_ncpus, IXL_IW_MAX_MSIX);
		if(ixl_limit_iwarp_msix > 0)
			iw_want = min(iw_want, ixl_limit_iwarp_msix);
		else
			iw_want = min(iw_want, 1);

		available -= vectors;
		if (available > 0) {
			iw_vectors = (available >= iw_want) ?
				iw_want : available;
			vectors += iw_vectors;
		} else
			iw_vectors = 0;
	}
#endif

	ixl_set_msix_enable(dev);
	if (pci_alloc_msix(dev, &vectors) == 0) {
               	device_printf(pf->dev,
		    "Using MSI-X interrupts with %d vectors\n", vectors);
		pf->msix = vectors;
#ifdef IXL_IW
		if (ixl_enable_iwarp && hw->func_caps.iwarp)
		{
			pf->iw_msix = iw_vectors;
			device_printf(pf->dev,
					"Reserving %d MSI-X interrupts for iWARP CEQ and AEQ\n",
					iw_vectors);
		}
#endif

		pf->vsi.num_queues = queues;
#ifdef RSS
		/*
		 * If we're doing RSS, the number of queues needs to
		 * match the number of RSS buckets that are configured.
		 *
		 * + If there's more queues than RSS buckets, we'll end
		 *   up with queues that get no traffic.
		 *
		 * + If there's more RSS buckets than queues, we'll end
		 *   up having multiple RSS buckets map to the same queue,
		 *   so there'll be some contention.
		 */
		if (queues != rss_getnumbuckets()) {
			device_printf(dev,
			    "%s: queues (%d) != RSS buckets (%d)"
			    "; performance will be impacted.\n",
			    __func__, queues, rss_getnumbuckets());
		}
#endif
		return (vectors);
	}
no_msix:
	vectors = pci_msi_count(dev);
	pf->vsi.num_queues = 1;
	pf->max_queues = 1;
	if (vectors == 1 && pci_alloc_msi(dev, &vectors) == 0)
		device_printf(pf->dev, "Using an MSI interrupt\n");
	else {
		vectors = 0;
		device_printf(pf->dev, "Using a Legacy interrupt\n");
	}
	return (vectors);
}

/*
 * Configure admin queue/misc interrupt cause registers in hardware.
 */
void
ixl_configure_intr0_msix(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	u32 reg;

	/* First set up the adminq - vector 0 */
	wr32(hw, I40E_PFINT_ICR0_ENA, 0);  /* disable all */
	rd32(hw, I40E_PFINT_ICR0);         /* read to clear */

	reg = I40E_PFINT_ICR0_ENA_ECC_ERR_MASK |
	    I40E_PFINT_ICR0_ENA_GRST_MASK |
	    I40E_PFINT_ICR0_ENA_HMC_ERR_MASK |
	    I40E_PFINT_ICR0_ENA_ADMINQ_MASK |
	    I40E_PFINT_ICR0_ENA_MAL_DETECT_MASK |
	    I40E_PFINT_ICR0_ENA_VFLR_MASK |
	    I40E_PFINT_ICR0_ENA_PE_CRITERR_MASK |
	    I40E_PFINT_ICR0_ENA_PCI_EXCEPTION_MASK;
	wr32(hw, I40E_PFINT_ICR0_ENA, reg);

	/*
	 * 0x7FF is the end of the queue list.
	 * This means we won't use MSI-X vector 0 for a queue interrupt
	 * in MSI-X mode.
	 */
	wr32(hw, I40E_PFINT_LNKLST0, 0x7FF);
	/* Value is in 2 usec units, so 0x3E is 62*2 = 124 usecs. */
	wr32(hw, I40E_PFINT_ITR0(IXL_RX_ITR), 0x3E);

	wr32(hw, I40E_PFINT_DYN_CTL0,
	    I40E_PFINT_DYN_CTL0_SW_ITR_INDX_MASK |
	    I40E_PFINT_DYN_CTL0_INTENA_MSK_MASK);

	wr32(hw, I40E_PFINT_STAT_CTL0, 0);
}

/*
 * Configure queue interrupt cause registers in hardware.
 */
void
ixl_configure_queue_intr_msix(struct ixl_pf *pf)
{
	struct i40e_hw	*hw = &pf->hw;
	struct ixl_vsi *vsi = &pf->vsi;
	u32		reg;
	u16		vector = 1;

	for (int i = 0; i < vsi->num_queues; i++, vector++) {
		wr32(hw, I40E_PFINT_DYN_CTLN(i), 0);
		/* Set linked list head to point to corresponding RX queue
		 * e.g. vector 1 (LNKLSTN register 0) points to queue pair 0's RX queue */
		reg = ((i << I40E_PFINT_LNKLSTN_FIRSTQ_INDX_SHIFT)
		        & I40E_PFINT_LNKLSTN_FIRSTQ_INDX_MASK) |
		    ((I40E_QUEUE_TYPE_RX << I40E_PFINT_LNKLSTN_FIRSTQ_TYPE_SHIFT)
		        & I40E_PFINT_LNKLSTN_FIRSTQ_TYPE_MASK);
		wr32(hw, I40E_PFINT_LNKLSTN(i), reg);

		reg = I40E_QINT_RQCTL_CAUSE_ENA_MASK |
		(IXL_RX_ITR << I40E_QINT_RQCTL_ITR_INDX_SHIFT) |
		(vector << I40E_QINT_RQCTL_MSIX_INDX_SHIFT) |
		(i << I40E_QINT_RQCTL_NEXTQ_INDX_SHIFT) |
		(I40E_QUEUE_TYPE_TX << I40E_QINT_RQCTL_NEXTQ_TYPE_SHIFT);
		wr32(hw, I40E_QINT_RQCTL(i), reg);

		reg = I40E_QINT_TQCTL_CAUSE_ENA_MASK |
		(IXL_TX_ITR << I40E_QINT_TQCTL_ITR_INDX_SHIFT) |
		(vector << I40E_QINT_TQCTL_MSIX_INDX_SHIFT) |
		(IXL_QUEUE_EOL << I40E_QINT_TQCTL_NEXTQ_INDX_SHIFT) |
		(I40E_QUEUE_TYPE_RX << I40E_QINT_TQCTL_NEXTQ_TYPE_SHIFT);
		wr32(hw, I40E_QINT_TQCTL(i), reg);
	}
}

/*
 * Configure for MSI single vector operation
 */
void
ixl_configure_legacy(struct ixl_pf *pf)
{
	struct i40e_hw	*hw = &pf->hw;
	struct ixl_vsi	*vsi = &pf->vsi;
	struct ixl_queue *que = vsi->queues;
	struct rx_ring 	*rxr = &que->rxr;
	struct tx_ring	*txr = &que->txr;
	u32 reg;

	/* Configure ITR */
	vsi->tx_itr_setting = pf->tx_itr;
	wr32(hw, I40E_PFINT_ITR0(IXL_TX_ITR),
	    vsi->tx_itr_setting);
	txr->itr = vsi->tx_itr_setting;

	vsi->rx_itr_setting = pf->rx_itr;
	wr32(hw, I40E_PFINT_ITR0(IXL_RX_ITR),
	    vsi->rx_itr_setting);
	rxr->itr = vsi->rx_itr_setting;

	/* Setup "other" causes */
	reg = I40E_PFINT_ICR0_ENA_ECC_ERR_MASK
	    | I40E_PFINT_ICR0_ENA_MAL_DETECT_MASK
	    | I40E_PFINT_ICR0_ENA_GRST_MASK
	    | I40E_PFINT_ICR0_ENA_PCI_EXCEPTION_MASK
	    | I40E_PFINT_ICR0_ENA_HMC_ERR_MASK
	    | I40E_PFINT_ICR0_ENA_PE_CRITERR_MASK
	    | I40E_PFINT_ICR0_ENA_VFLR_MASK
	    | I40E_PFINT_ICR0_ENA_ADMINQ_MASK
	    ;
	wr32(hw, I40E_PFINT_ICR0_ENA, reg);

	/* No ITR for non-queue interrupts */
	wr32(hw, I40E_PFINT_STAT_CTL0,
	    IXL_ITR_NONE << I40E_PFINT_STAT_CTL0_OTHER_ITR_INDX_SHIFT);

	/* FIRSTQ_INDX = 0, FIRSTQ_TYPE = 0 (rx) */
	wr32(hw, I40E_PFINT_LNKLST0, 0);

	/* Associate the queue pair to the vector and enable the q int */
	reg = I40E_QINT_RQCTL_CAUSE_ENA_MASK
	    | (IXL_RX_ITR << I40E_QINT_RQCTL_ITR_INDX_SHIFT)
	    | (I40E_QUEUE_TYPE_TX << I40E_QINT_RQCTL_NEXTQ_TYPE_SHIFT);
	wr32(hw, I40E_QINT_RQCTL(0), reg);

	reg = I40E_QINT_TQCTL_CAUSE_ENA_MASK
	    | (IXL_TX_ITR << I40E_QINT_TQCTL_ITR_INDX_SHIFT)
	    | (IXL_QUEUE_EOL << I40E_QINT_TQCTL_NEXTQ_INDX_SHIFT);
	wr32(hw, I40E_QINT_TQCTL(0), reg);
}

int
ixl_allocate_pci_resources(struct ixl_pf *pf)
{
	int             rid;
	struct i40e_hw *hw = &pf->hw;
	device_t        dev = pf->dev;

	/* Map BAR0 */
	rid = PCIR_BAR(0);
	pf->pci_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
	    &rid, RF_ACTIVE);

	if (!(pf->pci_mem)) {
		device_printf(dev, "Unable to allocate bus resource: PCI memory\n");
		return (ENXIO);
	}
	/* Ensure proper PCI device operation */
	pci_enable_busmaster(dev);

	/* Save off the PCI information */
	hw->vendor_id = pci_get_vendor(dev);
	hw->device_id = pci_get_device(dev);
	hw->revision_id = pci_read_config(dev, PCIR_REVID, 1);
	hw->subsystem_vendor_id =
	    pci_read_config(dev, PCIR_SUBVEND_0, 2);
	hw->subsystem_device_id =
	    pci_read_config(dev, PCIR_SUBDEV_0, 2);

	hw->bus.device = pci_get_slot(dev);
	hw->bus.func = pci_get_function(dev);

	/* Save off register access information */
	pf->osdep.mem_bus_space_tag =
		rman_get_bustag(pf->pci_mem);
	pf->osdep.mem_bus_space_handle =
		rman_get_bushandle(pf->pci_mem);
	pf->osdep.mem_bus_space_size = rman_get_size(pf->pci_mem);
	pf->osdep.flush_reg = I40E_GLGEN_STAT;
	pf->hw.hw_addr = (u8 *) &pf->osdep.mem_bus_space_handle;

	pf->hw.back = &pf->osdep;

	return (0);
}

/*
 * Teardown and release the admin queue/misc vector
 * interrupt.
 */
void
ixl_teardown_adminq_msix(struct ixl_pf *pf)
{
	device_t		dev = pf->dev;
	int			rid, error = 0;

	if (pf->admvec) /* we are doing MSI-X */
		rid = pf->admvec + 1;
	else
		(pf->msix != 0) ? (rid = 1):(rid = 0);

	if (pf->tag != NULL) {
		error = bus_teardown_intr(dev, pf->res, pf->tag);
		if (error)
			device_printf(dev,
			    "bus_teardown_intr() for interrupt 0 failed\n");
		pf->tag = NULL;
	}
	if (pf->res != NULL) {
		error = bus_release_resource(dev, SYS_RES_IRQ, rid, pf->res);
		if (error)
			device_printf(dev, "bus_release_resource() for"
			    " interrupt 0 failed [rid=%d]\n", rid);
		pf->res = NULL;
	}
}

int
ixl_teardown_queue_msix(struct ixl_vsi *vsi)
{
	struct ixl_pf		*pf = (struct ixl_pf *)vsi->back;
	struct ixl_queue	*que = vsi->queues;
	device_t		dev = vsi->dev;
	int			rid, error = 0;

	/* We may get here before stations are setup */
	if ((pf->msix < 2) || (que == NULL))
		return (0);

	/* Release all MSI-X queue resources */
	for (int i = 0; i < vsi->num_queues; i++, que++) {
		rid = que->msix + 1;
		if (que->tag != NULL) {
			error = bus_teardown_intr(dev, que->res, que->tag);
			if (error) {
				device_printf(dev, "bus_teardown_intr() for"
				    " Queue %d interrupt failed\n",
				    que->me);
				// return (ENXIO);
			}
			que->tag = NULL;
		}
		if (que->res != NULL) {
			error = bus_release_resource(dev, SYS_RES_IRQ, rid, que->res);
			if (error) {
				device_printf(dev, "bus_release_resource() for"
				    " Queue %d interrupt failed [rid=%d]\n",
				    que->me, rid);
				// return (ENXIO);
			}
			que->res = NULL;
		}
	}

	return (0);
}

void
ixl_free_pci_resources(struct ixl_pf *pf)
{
	device_t		dev = pf->dev;

	ixl_teardown_queue_msix(&pf->vsi);
	ixl_teardown_adminq_msix(pf);

	if (pf->msix > 0)
		pci_release_msi(dev);

	if (pf->msix_mem != NULL)
		bus_release_resource(dev, SYS_RES_MEMORY,
		    rman_get_rid(pf->msix_mem), pf->msix_mem);

	if (pf->pci_mem != NULL)
		bus_release_resource(dev, SYS_RES_MEMORY,
		    rman_get_rid(pf->pci_mem), pf->pci_mem);
}

void
ixl_add_ifmedia(struct ifmedia *media, u64 phy_types)
{
	/* Display supported media types */
	if (phy_types & (I40E_CAP_PHY_TYPE_100BASE_TX))
		ifmedia_add(media, IFM_ETHER | IFM_100_TX, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_1000BASE_T))
		ifmedia_add(media, IFM_ETHER | IFM_1000_T, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_1000BASE_SX))
		ifmedia_add(media, IFM_ETHER | IFM_1000_SX, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_1000BASE_LX))
		ifmedia_add(media, IFM_ETHER | IFM_1000_LX, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_2_5GBASE_T))
		ifmedia_add(media, IFM_ETHER | IFM_2500_T, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_5GBASE_T))
		ifmedia_add(media, IFM_ETHER | IFM_5000_T, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_XAUI) ||
	    phy_types & (I40E_CAP_PHY_TYPE_XFI) ||
	    phy_types & (I40E_CAP_PHY_TYPE_10GBASE_SFPP_CU))
		ifmedia_add(media, IFM_ETHER | IFM_10G_TWINAX, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_10GBASE_SR))
		ifmedia_add(media, IFM_ETHER | IFM_10G_SR, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_10GBASE_LR))
		ifmedia_add(media, IFM_ETHER | IFM_10G_LR, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_10GBASE_T))
		ifmedia_add(media, IFM_ETHER | IFM_10G_T, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_40GBASE_CR4) ||
	    phy_types & (I40E_CAP_PHY_TYPE_40GBASE_CR4_CU) ||
	    phy_types & (I40E_CAP_PHY_TYPE_40GBASE_AOC) ||
	    phy_types & (I40E_CAP_PHY_TYPE_XLAUI) ||
	    phy_types & (I40E_CAP_PHY_TYPE_40GBASE_KR4))
		ifmedia_add(media, IFM_ETHER | IFM_40G_CR4, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_40GBASE_SR4))
		ifmedia_add(media, IFM_ETHER | IFM_40G_SR4, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_40GBASE_LR4))
		ifmedia_add(media, IFM_ETHER | IFM_40G_LR4, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_1000BASE_KX))
		ifmedia_add(media, IFM_ETHER | IFM_1000_KX, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_10GBASE_CR1_CU)
	    || phy_types & (I40E_CAP_PHY_TYPE_10GBASE_CR1))
		ifmedia_add(media, IFM_ETHER | IFM_10G_CR1, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_10GBASE_AOC))
#ifdef IFM_10G_AOC
		ifmedia_add(media, IFM_ETHER | IFM_10G_AOC, 0, NULL);
#else
		ifmedia_add(media, IFM_ETHER | IFM_10G_TWINAX_LONG, 0, NULL);
#endif
	if (phy_types & (I40E_CAP_PHY_TYPE_SFI))
		ifmedia_add(media, IFM_ETHER | IFM_10G_SFI, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_10GBASE_KX4))
		ifmedia_add(media, IFM_ETHER | IFM_10G_KX4, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_10GBASE_KR))
		ifmedia_add(media, IFM_ETHER | IFM_10G_KR, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_20GBASE_KR2))
		ifmedia_add(media, IFM_ETHER | IFM_20G_KR2, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_40GBASE_KR4))
		ifmedia_add(media, IFM_ETHER | IFM_40G_KR4, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_XLPPI))
		ifmedia_add(media, IFM_ETHER | IFM_40G_XLPPI, 0, NULL);

	if (phy_types & (I40E_CAP_PHY_TYPE_25GBASE_KR))
		ifmedia_add(media, IFM_ETHER | IFM_25G_KR, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_25GBASE_CR))
		ifmedia_add(media, IFM_ETHER | IFM_25G_CR, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_25GBASE_SR))
		ifmedia_add(media, IFM_ETHER | IFM_25G_SR, 0, NULL);
	if (phy_types & (I40E_CAP_PHY_TYPE_25GBASE_LR))
#ifdef IFM_25G_LR
		ifmedia_add(media, IFM_ETHER | IFM_25G_LR, 0, NULL);
#else
		ifmedia_add(media, IFM_ETHER | IFM_25G_SR, 0, NULL);
#endif
	if (phy_types & (I40E_CAP_PHY_TYPE_25GBASE_AOC))
#ifdef IFM_25G_AOC
		ifmedia_add(media, IFM_ETHER | IFM_25G_AOC, 0, NULL);
#else
		ifmedia_add(media, IFM_ETHER | IFM_25G_CR, 0, NULL);
#endif
	if (phy_types & (I40E_CAP_PHY_TYPE_25GBASE_ACC))
#ifdef IFM_25G_ACC
		ifmedia_add(media, IFM_ETHER | IFM_25G_ACC, 0, NULL);
#else
		ifmedia_add(media, IFM_ETHER | IFM_25G_CR, 0, NULL);
#endif
}

/*********************************************************************
 *
 *  Setup networking device structure and register an interface.
 *
 **********************************************************************/
int
ixl_setup_interface(device_t dev, struct ixl_vsi *vsi)
{
	struct ixl_pf		*pf = (struct ixl_pf *)vsi->back;
	struct ifnet		*ifp;
	struct i40e_hw		*hw = vsi->hw;
	struct ixl_queue	*que = vsi->queues;
	struct i40e_aq_get_phy_abilities_resp abilities;
	enum i40e_status_code aq_error = 0;

	INIT_DEBUGOUT("ixl_setup_interface: begin");

	ifp = vsi->ifp = if_alloc(IFT_ETHER);
	if (ifp == NULL) {
		device_printf(dev, "can not allocate ifnet structure\n");
		return (ENOMEM);
	}
	if_initname(ifp, device_get_name(dev), device_get_unit(dev));
	ifp->if_mtu = ETHERMTU;
	ifp->if_init = ixl_init;
	ifp->if_softc = vsi;
	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
#ifdef IFF_KNOWSEPOCH
	ifp->if_flags |= IFF_KNOWSEPOCH;
#endif
	ifp->if_ioctl = ixl_ioctl;

#if __FreeBSD_version >= 1100036
	if_setgetcounterfn(ifp, ixl_get_counter);
#endif

	ifp->if_transmit = ixl_mq_start;

	ifp->if_qflush = ixl_qflush;

	ifp->if_snd.ifq_maxlen = que->num_tx_desc - 2;

	vsi->max_frame_size =
	    ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN
	    + ETHER_VLAN_ENCAP_LEN;

	/* Set TSO limits */
	ifp->if_hw_tsomax = IP_MAXPACKET - (ETHER_HDR_LEN + ETHER_CRC_LEN);
	ifp->if_hw_tsomaxsegcount = IXL_MAX_TSO_SEGS;
	ifp->if_hw_tsomaxsegsize = IXL_MAX_DMA_SEG_SIZE;

	/*
	 * Tell the upper layer(s) we support long frames.
	 */
	ifp->if_hdrlen = sizeof(struct ether_vlan_header);

	ifp->if_capabilities |= IFCAP_HWCSUM;
	ifp->if_capabilities |= IFCAP_HWCSUM_IPV6;
	ifp->if_capabilities |= IFCAP_TSO;
	ifp->if_capabilities |= IFCAP_JUMBO_MTU;
	ifp->if_capabilities |= IFCAP_LRO;

	/* VLAN and VXLAN capabilties */
	ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING
			     |  IFCAP_VLAN_HWTSO
			     |  IFCAP_VLAN_MTU
			     |  IFCAP_VLAN_HWCSUM;

#ifdef CSUM_ENCAP_VXLAN
	ifp->if_capabilities |= IFCAP_VXLAN_HWCSUM | IFCAP_VXLAN_HWTSO;
#endif

	ifp->if_capenable = ifp->if_capabilities;

	/*
	** Don't turn this on by default, if vlans are
	** created on another pseudo device (eg. lagg)
	** then vlan events are not passed thru, breaking
	** operation, but with HW FILTER off it works. If
	** using vlans directly on the ixl driver you can
	** enable this and get full hardware tag filtering.
	*/
	ifp->if_capabilities |= IFCAP_VLAN_HWFILTER;

	/*
	 * Specify the media types supported by this adapter and register
	 * callbacks to update media and link information
	 */
	ifmedia_init(&vsi->media, IFM_IMASK, ixl_media_change,
		     ixl_media_status);

	if (!IXL_PF_IN_RECOVERY_MODE(pf)) {
		aq_error = i40e_aq_get_phy_capabilities(hw,
		    FALSE, TRUE, &abilities, NULL);
		/* May need delay to detect fiber correctly */
		if (aq_error == I40E_ERR_UNKNOWN_PHY) {
			i40e_msec_delay(200);
			aq_error = i40e_aq_get_phy_capabilities(hw, FALSE,
			    TRUE, &abilities, NULL);
		}
		if (aq_error) {
			if (aq_error == I40E_ERR_UNKNOWN_PHY)
				device_printf(dev, "Unknown PHY type detected!\n");
			else
				device_printf(dev,
				    "Error getting supported media types, err %d,"
				    " AQ error %d\n", aq_error, hw->aq.asq_last_status);
		} else {
			pf->supported_speeds = abilities.link_speed;
#if __FreeBSD_version >= 1100000
			ifp->if_baudrate = ixl_max_aq_speed_to_value(pf->supported_speeds);
#else
			if_initbaudrate(ifp, ixl_max_aq_speed_to_value(pf->supported_speeds));
#endif

			ixl_add_ifmedia(&vsi->media, hw->phy.phy_types);
		}
	}

	/* Use autoselect media by default */
	ifmedia_add(&vsi->media, IFM_ETHER | IFM_AUTO, 0, NULL);
	ifmedia_set(&vsi->media, IFM_ETHER | IFM_AUTO);

	ether_ifattach(ifp, hw->mac.addr);

	return (0);
}

/*
** Run when the Admin Queue gets a link state change interrupt.
*/
void
ixl_link_event(struct ixl_pf *pf, struct i40e_arq_event_info *e)
{
	struct i40e_hw	*hw = &pf->hw;
	device_t dev = pf->dev;
	struct i40e_link_status *link_info = &hw->phy.link_info;


	/* Driver needs to re-enable delivering of link status events
	 * by FW after each event reception. Call i40e_get_link_status
	 * to do that. To not lose information about link state changes,
	 * which happened between receiving an event and the call,
	 * do not rely on status from event but use most recent
	 * status information retrieved by the call. */
	hw->phy.get_link_info = TRUE;
	i40e_get_link_status(hw, &pf->link_up);

	ixl_dbg_link(pf, "%s IFF_UP: %d link_info: %0x an_info: %0x\n",
	    __func__, if_getflags(pf->vsi.ifp) & IFF_UP,
	    link_info->link_info, link_info->an_info);

	/* Print out message if an unqualified module is found */
	if ((link_info->link_info & I40E_AQ_MEDIA_AVAILABLE) &&
	    (pf->advertised_speed) &&
	    (if_getflags(pf->vsi.ifp) & IFF_UP) &&
	    (!(link_info->an_info & I40E_AQ_QUALIFIED_MODULE)) &&
	    (!(link_info->link_info & I40E_AQ_LINK_UP)))
		device_printf(dev, "Link failed because "
		    "an unqualified module was detected!\n");

	/* Update OS link info */
	ixl_update_link_status(pf);

	/*
	 * There might be packets in the TX queue buffers;
	 * try to send them on link up.
	 */
	if (pf->link_up) {
		struct ixl_vsi *vsi = &pf->vsi;
		struct ixl_queue *que = vsi->queues;
		for (int i = 0; i < vsi->num_queues; i++, que++)
			taskqueue_enqueue(que->tq, &que->tx_task);
	}
}

/*********************************************************************
 *
 *  Get Firmware Switch configuration
 *	- this will need to be more robust when more complex
 *	  switch configurations are enabled.
 *
 **********************************************************************/
int
ixl_switch_config(struct ixl_pf *pf)
{
	struct i40e_hw	*hw = &pf->hw;
	struct ixl_vsi	*vsi = &pf->vsi;
	device_t 	dev = vsi->dev;
	struct i40e_aqc_get_switch_config_resp *sw_config;
	u8	aq_buf[I40E_AQ_LARGE_BUF];
	int	ret;
	u16	next = 0;

	memset(&aq_buf, 0, sizeof(aq_buf));
	sw_config = (struct i40e_aqc_get_switch_config_resp *)aq_buf;
	ret = i40e_aq_get_switch_config(hw, sw_config,
	    sizeof(aq_buf), &next, NULL);
	if (ret) {
		device_printf(dev, "aq_get_switch_config() failed, error %d,"
		    " aq_error %d\n", ret, pf->hw.aq.asq_last_status);
		return (ret);
	}
	if (pf->dbg_mask & IXL_DBG_SWITCH_INFO) {
		device_printf(dev,
		    "Switch config: header reported: %d in structure, %d total\n",
		    sw_config->header.num_reported, sw_config->header.num_total);
		for (int i = 0; i < sw_config->header.num_reported; i++) {
			device_printf(dev,
			    "-> %d: type=%d seid=%d uplink=%d downlink=%d\n", i,
			    sw_config->element[i].element_type,
			    sw_config->element[i].seid,
			    sw_config->element[i].uplink_seid,
			    sw_config->element[i].downlink_seid);
		}
	}
	/* Simplified due to a single VSI */
	vsi->uplink_seid = sw_config->element[0].uplink_seid;
	vsi->downlink_seid = sw_config->element[0].downlink_seid;
	vsi->seid = sw_config->element[0].seid;
	return (ret);
}

/*********************************************************************
 *
 *  Initialize the VSI:  this handles contexts, which means things
 *  			 like the number of descriptors, buffer size,
 *			 plus we init the rings thru this function.
 *
 **********************************************************************/
int
ixl_initialize_vsi(struct ixl_vsi *vsi)
{
	struct ixl_pf		*pf = vsi->back;
	struct ixl_queue	*que = vsi->queues;
	device_t		dev = vsi->dev;
	struct i40e_hw		*hw = vsi->hw;
	struct i40e_vsi_context	ctxt;
	int 			tc_queues;
	int			err = 0;

	memset(&ctxt, 0, sizeof(ctxt));
	ctxt.seid = vsi->seid;
	if (pf->veb_seid != 0)
		ctxt.uplink_seid = pf->veb_seid;
	ctxt.pf_num = hw->pf_id;
	err = i40e_aq_get_vsi_params(hw, &ctxt, NULL);
	if (err) {
		device_printf(dev, "i40e_aq_get_vsi_params() failed, error %d"
		    " aq_error %d\n", err, hw->aq.asq_last_status);
		return (err);
	}
	ixl_dbg(pf, IXL_DBG_SWITCH_INFO,
	    "get_vsi_params: seid: %d, uplinkseid: %d, vsi_number: %d, "
	    "vsis_allocated: %d, vsis_unallocated: %d, flags: 0x%x, "
	    "pfnum: %d, vfnum: %d, stat idx: %d, enabled: %d\n", ctxt.seid,
	    ctxt.uplink_seid, ctxt.vsi_number,
	    ctxt.vsis_allocated, ctxt.vsis_unallocated,
	    ctxt.flags, ctxt.pf_num, ctxt.vf_num,
	    ctxt.info.stat_counter_idx, ctxt.info.up_enable_bits);
	/*
	** Set the queue and traffic class bits
	**  - when multiple traffic classes are supported
	**    this will need to be more robust.
	*/
	ctxt.info.valid_sections = I40E_AQ_VSI_PROP_QUEUE_MAP_VALID;
	ctxt.info.mapping_flags |= I40E_AQ_VSI_QUE_MAP_CONTIG;
	/* In contig mode, que_mapping[0] is first queue index used by this VSI */
	ctxt.info.queue_mapping[0] = 0;
	/*
	 * This VSI will only use traffic class 0; start traffic class 0's
	 * queue allocation at queue 0, and assign it 2^tc_queues queues (though
	 * the driver may not use all of them).
	 */
	tc_queues = fls(pf->qtag.num_allocated) - 1;
	ctxt.info.tc_mapping[0] = ((pf->qtag.first_qidx << I40E_AQ_VSI_TC_QUE_OFFSET_SHIFT)
	    & I40E_AQ_VSI_TC_QUE_OFFSET_MASK) |
	    ((tc_queues << I40E_AQ_VSI_TC_QUE_NUMBER_SHIFT)
	    & I40E_AQ_VSI_TC_QUE_NUMBER_MASK);

	/* Set VLAN receive stripping mode */
	ctxt.info.valid_sections |= I40E_AQ_VSI_PROP_VLAN_VALID;
	ctxt.info.port_vlan_flags = I40E_AQ_VSI_PVLAN_MODE_ALL;
	if (if_getcapenable(vsi->ifp) & IFCAP_VLAN_HWTAGGING)
		ctxt.info.port_vlan_flags |= I40E_AQ_VSI_PVLAN_EMOD_STR_BOTH;
	else
		ctxt.info.port_vlan_flags |= I40E_AQ_VSI_PVLAN_EMOD_NOTHING;

#ifdef IXL_IW
	/* Set TCP Enable for iWARP capable VSI */
	if (ixl_enable_iwarp && pf->iw_enabled) {
		ctxt.info.valid_sections |=
		    htole16(I40E_AQ_VSI_PROP_QUEUE_OPT_VALID);
		ctxt.info.queueing_opt_flags |= I40E_AQ_VSI_QUE_OPT_TCP_ENA;
	}
#endif
	/* Save VSI number and info for use later */
	vsi->vsi_num = ctxt.vsi_number;
	bcopy(&ctxt.info, &vsi->info, sizeof(vsi->info));

	ctxt.flags = htole16(I40E_AQ_VSI_TYPE_PF);

	err = i40e_aq_update_vsi_params(hw, &ctxt, NULL);
	if (err) {
		device_printf(dev, "i40e_aq_update_vsi_params() failed, error %d,"
		    " aq_error %d\n", err, hw->aq.asq_last_status);
		return (err);
	}

	for (int i = 0; i < vsi->num_queues; i++, que++) {
		struct tx_ring		*txr = &que->txr;
		struct rx_ring 		*rxr = &que->rxr;
		struct i40e_hmc_obj_txq tctx;
		struct i40e_hmc_obj_rxq rctx;
		u32			txctl;

		/* Setup the HMC TX Context  */
		bzero(&tctx, sizeof(tctx));
		tctx.new_context = 1;
		tctx.base = (txr->dma.pa/IXL_TX_CTX_BASE_UNITS);
		tctx.qlen = que->num_tx_desc;
		tctx.fc_ena = 0;	/* Disable FCoE */
		/*
		 * This value needs to pulled from the VSI that this queue
		 * is assigned to. Index into array is traffic class.
		 */
		tctx.rdylist = vsi->info.qs_handle[0];
		/*
		 * Set these to enable Head Writeback
		 * - Address is last entry in TX ring (reserved for HWB index)
		 * Leave these as 0 for Descriptor Writeback
		 */
		if (vsi->enable_head_writeback) {
			tctx.head_wb_ena = 1;
			tctx.head_wb_addr = txr->dma.pa +
			    (que->num_tx_desc * sizeof(struct i40e_tx_desc));
		}
		tctx.rdylist_act = 0;
		err = i40e_clear_lan_tx_queue_context(hw, i);
		if (err) {
			device_printf(dev, "Unable to clear TX context\n");
			break;
		}
		err = i40e_set_lan_tx_queue_context(hw, i, &tctx);
		if (err) {
			device_printf(dev, "Unable to set TX context\n");
			break;
		}
		/* Associate the ring with this PF */
		txctl = I40E_QTX_CTL_PF_QUEUE;
		txctl |= ((hw->pf_id << I40E_QTX_CTL_PF_INDX_SHIFT) &
		    I40E_QTX_CTL_PF_INDX_MASK);
		wr32(hw, I40E_QTX_CTL(i), txctl);
		ixl_flush(hw);

		/* Do ring (re)init */
		ixl_init_tx_ring(que);

		/* Next setup the HMC RX Context  */
		if (vsi->max_frame_size <= MCLBYTES)
			rxr->mbuf_sz = MCLBYTES;
		else
			rxr->mbuf_sz = MJUMPAGESIZE;

		u16 max_rxmax = rxr->mbuf_sz * hw->func_caps.rx_buf_chain_len;

		/* Set up an RX context for the HMC */
		memset(&rctx, 0, sizeof(struct i40e_hmc_obj_rxq));
		rctx.dbuff = rxr->mbuf_sz >> I40E_RXQ_CTX_DBUFF_SHIFT;
		/* ignore header split for now */
		rctx.hbuff = 0 >> I40E_RXQ_CTX_HBUFF_SHIFT;
		rctx.rxmax = (vsi->max_frame_size < max_rxmax) ?
		    vsi->max_frame_size : max_rxmax;
		rctx.dtype = 0;
		rctx.dsize = 1;		/* do 32byte descriptors */
		rctx.hsplit_0 = 0;	/* no header split */
		rctx.base = (rxr->dma.pa/IXL_RX_CTX_BASE_UNITS);
		rctx.qlen = que->num_rx_desc;
		rctx.tphrdesc_ena = 1;
		rctx.tphwdesc_ena = 1;
		rctx.tphdata_ena = 0;	/* Header Split related */
		rctx.tphhead_ena = 0;	/* Header Split related */
		rctx.lrxqthresh = 2;	/* Interrupt at <128 desc avail */
		rctx.crcstrip = 1;
		rctx.l2tsel = 1;
		rctx.showiv = 0;	/* Don't strip inner VLAN header */
		rctx.fc_ena = 0;	/* Disable FCoE */
		rctx.prefena = 1;	/* Prefetch descriptors */

		err = i40e_clear_lan_rx_queue_context(hw, i);
		if (err) {
			device_printf(dev,
			    "Unable to clear RX context %d\n", i);
			break;
		}
		err = i40e_set_lan_rx_queue_context(hw, i, &rctx);
		if (err) {
			device_printf(dev, "Unable to set RX context %d\n", i);
			break;
		}
		err = ixl_init_rx_ring(que);
		if (err) {
			device_printf(dev, "Fail in init_rx_ring %d\n", i);
			break;
		}
#ifdef DEV_NETMAP
		/* preserve queue */
		if (vsi->ifp->if_capenable & IFCAP_NETMAP) {
			struct netmap_adapter *na = NA(vsi->ifp);
#if ((__FreeBSD_version >= 1102505 && __FreeBSD_version < 1200000) || \
    __FreeBSD_version >= 1200062)
			struct netmap_kring *kring = na->rx_rings[i];
#else
			struct netmap_kring *kring = &na->rx_rings[i];
#endif
			int t = na->num_rx_desc - 1 - nm_kr_rxspace(kring);
			wr32(vsi->hw, I40E_QRX_TAIL(que->me), t);
		} else
#endif /* DEV_NETMAP */
		wr32(vsi->hw, I40E_QRX_TAIL(que->me), que->num_rx_desc - 1);
	}
	return (err);
}

void
ixl_vsi_free_queues(struct ixl_vsi *vsi)
{
	struct ixl_pf		*pf = (struct ixl_pf *)vsi->back;
	struct ixl_queue	*que = vsi->queues;

	if (NULL == vsi->queues)
		return;

	for (int i = 0; i < vsi->num_queues; i++, que++) {
		struct tx_ring *txr = &que->txr;
		struct rx_ring *rxr = &que->rxr;

		if (!mtx_initialized(&txr->mtx)) /* uninitialized */
			continue;
		IXL_TX_LOCK(txr);
		if (txr->br)
			buf_ring_free(txr->br, M_IXL);
		ixl_free_que_tx(que);
		if (txr->base)
			i40e_free_dma_mem(&pf->hw, &txr->dma);
		IXL_TX_UNLOCK(txr);
		IXL_TX_LOCK_DESTROY(txr);

		if (!mtx_initialized(&rxr->mtx)) /* uninitialized */
			continue;
		IXL_RX_LOCK(rxr);
		ixl_free_que_rx(que);
		if (rxr->base)
			i40e_free_dma_mem(&pf->hw, &rxr->dma);
		IXL_RX_UNLOCK(rxr);
		IXL_RX_LOCK_DESTROY(rxr);
	}

	sysctl_ctx_free(&vsi->sysctl_ctx);
}


/*********************************************************************
 *
 *  Free all VSI structs.
 *
 **********************************************************************/
void
ixl_free_vsi(struct ixl_vsi *vsi)
{
	/* Free station queues */
	ixl_vsi_free_queues(vsi);
	if (vsi->queues)
		free(vsi->queues, M_IXL);

	/* Free VSI filter list */
	ixl_free_filters(&vsi->ftl);
	vsi->num_hw_filters = 0;
}

/*
 * Fill out fields in queue struct and setup tx/rx memory and structs
 */
static int
ixl_vsi_setup_queue(struct ixl_vsi *vsi, struct ixl_queue *que, int index)
{
	struct ixl_pf	*pf = (struct ixl_pf *)vsi->back;
	device_t dev = pf->dev;
	struct i40e_hw *hw = &pf->hw;
	struct tx_ring *txr = &que->txr;
	struct rx_ring *rxr = &que->rxr;
	int error = 0;
	int rsize, tsize;

	que->num_tx_desc = vsi->num_tx_desc;
	que->num_rx_desc = vsi->num_rx_desc;
	que->me = index;
	que->vsi = vsi;

	txr->que = que;
	txr->tail = I40E_QTX_TAIL(que->me);

	/* Initialize the TX lock */
	snprintf(txr->mtx_name, sizeof(txr->mtx_name), "%s:tx(%d)",
	    device_get_nameunit(dev), que->me);
	mtx_init(&txr->mtx, txr->mtx_name, NULL, MTX_DEF);
	/*
	 * Create the TX descriptor ring
	 *
	 * In Head Writeback mode, the descriptor ring is one bigger
	 * than the number of descriptors for space for the HW to
	 * write back index of last completed descriptor.
	 */
	if (vsi->enable_head_writeback) {
		tsize = roundup2((que->num_tx_desc *
		    sizeof(struct i40e_tx_desc)) +
		    sizeof(u32), DBA_ALIGN);
	} else {
		tsize = roundup2((que->num_tx_desc *
		    sizeof(struct i40e_tx_desc)), DBA_ALIGN);
	}
	if (i40e_allocate_dma_mem(hw,
	    &txr->dma, i40e_mem_reserved, tsize, DBA_ALIGN)) {
		device_printf(dev,
		    "Unable to allocate TX Descriptor memory\n");
		error = ENOMEM;
		goto err_destroy_tx_mtx;
	}
	txr->base = (struct i40e_tx_desc *)txr->dma.va;
	bzero((void *)txr->base, tsize);
	/* Now allocate transmit soft structs for the ring */
	if (ixl_allocate_tx_data(que)) {
		device_printf(dev,
		    "Critical Failure setting up TX structures\n");
		error = ENOMEM;
		goto err_free_tx_dma;
	}
	/* Allocate a buf ring */
	txr->br = buf_ring_alloc(DEFAULT_TXBRSZ, M_IXL,
	    M_NOWAIT, &txr->mtx);
	if (txr->br == NULL) {
		device_printf(dev,
		    "Critical Failure setting up TX buf ring\n");
		error = ENOMEM;
		goto err_free_tx_data;
	}

	rsize = roundup2(que->num_rx_desc *
	    sizeof(union i40e_rx_desc), DBA_ALIGN);
	rxr->que = que;
	rxr->tail = I40E_QRX_TAIL(que->me);

	/* Initialize the RX side lock */
	snprintf(rxr->mtx_name, sizeof(rxr->mtx_name), "%s:rx(%d)",
	    device_get_nameunit(dev), que->me);
	mtx_init(&rxr->mtx, rxr->mtx_name, NULL, MTX_DEF);

	if (i40e_allocate_dma_mem(hw,
	    &rxr->dma, i40e_mem_reserved, rsize, 4096)) {
		device_printf(dev,
		    "Unable to allocate RX Descriptor memory\n");
		error = ENOMEM;
		goto err_destroy_rx_mtx;
	}
	rxr->base = (union i40e_rx_desc *)rxr->dma.va;
	bzero((void *)rxr->base, rsize);
	/* Allocate receive soft structs for the ring*/
	if (ixl_allocate_rx_data(que)) {
		device_printf(dev,
		    "Critical Failure setting up receive structs\n");
		error = ENOMEM;
		goto err_free_rx_dma;
	}

	return (0);

err_free_rx_dma:
	i40e_free_dma_mem(&pf->hw, &rxr->dma);
err_destroy_rx_mtx:
	mtx_destroy(&rxr->mtx);
	/* err_free_tx_buf_ring */
	buf_ring_free(txr->br, M_IXL);
err_free_tx_data:
	ixl_free_que_tx(que);
err_free_tx_dma:
	i40e_free_dma_mem(&pf->hw, &txr->dma);
err_destroy_tx_mtx:
	mtx_destroy(&txr->mtx);

	return (error);
}

int
ixl_vsi_setup_queues(struct ixl_vsi *vsi)
{
	struct ixl_queue	*que;
	int			error = 0;

	for (int i = 0; i < vsi->num_queues; i++) {
		que = &vsi->queues[i];
		error = ixl_vsi_setup_queue(vsi, que, i);
		if (error)
			break;
	}
	if (error == 0)
		sysctl_ctx_init(&vsi->sysctl_ctx);

	return (error);
}


/*********************************************************************
 *
 *  Allocate memory for the VSI (virtual station interface) and their
 *  associated queues, rings and the descriptors associated with each,
 *  called only once at attach.
 *
 **********************************************************************/
int
ixl_setup_stations(struct ixl_pf *pf)
{
	device_t		dev = pf->dev;
	struct ixl_vsi		*vsi;
	int			error = 0;

	vsi = &pf->vsi;
	vsi->back = (void *)pf;
	vsi->hw = &pf->hw;
	vsi->id = 0;
	vsi->num_vlans = 0;
	vsi->back = pf;

	if (pf->msix > 1)
		vsi->flags |= IXL_FLAGS_USES_MSIX;

	bit_nclear(vsi->vlans_map, 0, EVL_VLID_MASK);

	/* Get memory for the station queues */
        if (!(vsi->queues =
            (struct ixl_queue *) malloc(sizeof(struct ixl_queue) *
            vsi->num_queues, M_IXL, M_NOWAIT | M_ZERO))) {
                device_printf(dev, "Unable to allocate queue memory\n");
                error = ENOMEM;
		goto ixl_setup_stations_err;
        }

	/* Then setup each queue */
	error = ixl_vsi_setup_queues(vsi);
ixl_setup_stations_err:
	return (error);
}

/*
** Provide a update to the queue RX
** interrupt moderation value.
*/
void
ixl_set_queue_rx_itr(struct ixl_queue *que)
{
	struct ixl_vsi	*vsi = que->vsi;
	struct ixl_pf	*pf = (struct ixl_pf *)vsi->back;
	struct i40e_hw	*hw = vsi->hw;
	struct rx_ring	*rxr = &que->rxr;
	u16		rx_itr;
	u16		rx_latency = 0;
	int		rx_bytes;

	/* Idle, do nothing */
	if (rxr->bytes == 0)
		return;

	if (pf->dynamic_rx_itr) {
		rx_bytes = rxr->bytes/rxr->itr;
		rx_itr = rxr->itr;

		/* Adjust latency range */
		switch (rxr->latency) {
		case IXL_LOW_LATENCY:
			if (rx_bytes > 10) {
				rx_latency = IXL_AVE_LATENCY;
				rx_itr = IXL_ITR_20K;
			}
			break;
		case IXL_AVE_LATENCY:
			if (rx_bytes > 20) {
				rx_latency = IXL_BULK_LATENCY;
				rx_itr = IXL_ITR_8K;
			} else if (rx_bytes <= 10) {
				rx_latency = IXL_LOW_LATENCY;
				rx_itr = IXL_ITR_100K;
			}
			break;
		case IXL_BULK_LATENCY:
			if (rx_bytes <= 20) {
				rx_latency = IXL_AVE_LATENCY;
				rx_itr = IXL_ITR_20K;
			}
			break;
       		 }

		rxr->latency = rx_latency;

		if (rx_itr != rxr->itr) {
			/* do an exponential smoothing */
			rx_itr = (10 * rx_itr * rxr->itr) /
			    ((9 * rx_itr) + rxr->itr);
			rxr->itr = min(rx_itr, IXL_MAX_ITR);
			wr32(hw, I40E_PFINT_ITRN(IXL_RX_ITR,
			    que->me), rxr->itr);
		}
	} else { /* We may have have toggled to non-dynamic */
		if (vsi->rx_itr_setting & IXL_ITR_DYNAMIC)
			vsi->rx_itr_setting = pf->rx_itr;
		/* Update the hardware if needed */
		if (rxr->itr != vsi->rx_itr_setting) {
			rxr->itr = vsi->rx_itr_setting;
			wr32(hw, I40E_PFINT_ITRN(IXL_RX_ITR,
			    que->me), rxr->itr);
		}
	}
	rxr->bytes = 0;
	rxr->packets = 0;
	return;
}


/*
** Provide a update to the queue TX
** interrupt moderation value.
*/
void
ixl_set_queue_tx_itr(struct ixl_queue *que)
{
	struct ixl_vsi	*vsi = que->vsi;
	struct ixl_pf	*pf = (struct ixl_pf *)vsi->back;
	struct i40e_hw	*hw = vsi->hw;
	struct tx_ring	*txr = &que->txr;
	u16		tx_itr;
	u16		tx_latency = 0;
	int		tx_bytes;


	/* Idle, do nothing */
	if (txr->bytes == 0)
		return;

	if (pf->dynamic_tx_itr) {
		tx_bytes = txr->bytes/txr->itr;
		tx_itr = txr->itr;

		switch (txr->latency) {
		case IXL_LOW_LATENCY:
			if (tx_bytes > 10) {
				tx_latency = IXL_AVE_LATENCY;
				tx_itr = IXL_ITR_20K;
			}
			break;
		case IXL_AVE_LATENCY:
			if (tx_bytes > 20) {
				tx_latency = IXL_BULK_LATENCY;
				tx_itr = IXL_ITR_8K;
			} else if (tx_bytes <= 10) {
				tx_latency = IXL_LOW_LATENCY;
				tx_itr = IXL_ITR_100K;
			}
			break;
		case IXL_BULK_LATENCY:
			if (tx_bytes <= 20) {
				tx_latency = IXL_AVE_LATENCY;
				tx_itr = IXL_ITR_20K;
			}
			break;
		}

		txr->latency = tx_latency;

		if (tx_itr != txr->itr) {
       	         /* do an exponential smoothing */
			tx_itr = (10 * tx_itr * txr->itr) /
			    ((9 * tx_itr) + txr->itr);
			txr->itr = min(tx_itr, IXL_MAX_ITR);
			wr32(hw, I40E_PFINT_ITRN(IXL_TX_ITR,
			    que->me), txr->itr);
		}

	} else { /* We may have have toggled to non-dynamic */
		if (vsi->tx_itr_setting & IXL_ITR_DYNAMIC)
			vsi->tx_itr_setting = pf->tx_itr;
		/* Update the hardware if needed */
		if (txr->itr != vsi->tx_itr_setting) {
			txr->itr = vsi->tx_itr_setting;
			wr32(hw, I40E_PFINT_ITRN(IXL_TX_ITR,
			    que->me), txr->itr);
		}
	}
	txr->bytes = 0;
	txr->packets = 0;
	return;
}

void
ixl_vsi_add_sysctls(struct ixl_vsi * vsi, const char * sysctl_name, bool queues_sysctls)
{
	struct sysctl_oid *tree;
	struct sysctl_oid_list *child;
	struct sysctl_oid_list *vsi_list;

	tree = device_get_sysctl_tree(vsi->dev);
	child = SYSCTL_CHILDREN(tree);
	vsi->vsi_node = SYSCTL_ADD_NODE(&vsi->sysctl_ctx, child, OID_AUTO, sysctl_name,
			CTLFLAG_RD, NULL, "VSI Number");

	vsi_list = SYSCTL_CHILDREN(vsi->vsi_node);
	ixl_add_sysctls_eth_stats(&vsi->sysctl_ctx, vsi_list, &vsi->eth_stats);

	/* Copy of netstat RX errors counter for validation purposes */
	SYSCTL_ADD_UQUAD(&vsi->sysctl_ctx, vsi_list, OID_AUTO, "rx_errors",
			CTLFLAG_RD, &vsi->ierrors,
			"RX packet errors");

	if (queues_sysctls)
		ixl_vsi_add_queues_stats(vsi);
}

/*
 * Used to set the Tx ITR value for all of the PF LAN VSI's queues.
 * Writes to the ITR registers immediately.
 */
static int
ixl_sysctl_pf_tx_itr(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	device_t dev = pf->dev;
	int error = 0;
	int requested_tx_itr;

	requested_tx_itr = pf->tx_itr;
	error = sysctl_handle_int(oidp, &requested_tx_itr, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);
	if (pf->dynamic_tx_itr) {
		device_printf(dev,
		    "Cannot set TX itr value while dynamic TX itr is enabled\n");
		    return (EINVAL);
	}
	if (requested_tx_itr < 0 || requested_tx_itr > IXL_MAX_ITR) {
		device_printf(dev,
		    "Invalid TX itr value; value must be between 0 and %d\n",
		        IXL_MAX_ITR);
		return (EINVAL);
	}

	pf->tx_itr = requested_tx_itr;
	ixl_configure_tx_itr(pf);

	return (error);
}

/*
 * Used to set the Rx ITR value for all of the PF LAN VSI's queues.
 * Writes to the ITR registers immediately.
 */
static int
ixl_sysctl_pf_rx_itr(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	device_t dev = pf->dev;
	int error = 0;
	int requested_rx_itr;

	requested_rx_itr = pf->rx_itr;
	error = sysctl_handle_int(oidp, &requested_rx_itr, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);
	if (pf->dynamic_rx_itr) {
		device_printf(dev,
		    "Cannot set RX itr value while dynamic RX itr is enabled\n");
		    return (EINVAL);
	}
	if (requested_rx_itr < 0 || requested_rx_itr > IXL_MAX_ITR) {
		device_printf(dev,
		    "Invalid RX itr value; value must be between 0 and %d\n",
		        IXL_MAX_ITR);
		return (EINVAL);
	}

	pf->rx_itr = requested_rx_itr;
	ixl_configure_rx_itr(pf);

	return (error);
}

static int
ixl_sysctl_pf_dynamic_itr(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	enum ixl_dyn_itr_dir direction = arg2;
	device_t dev = pf->dev;
	int error = 0;
	int requested;

	if (direction == IXL_DYN_ITR_RX)
		requested = pf->dynamic_rx_itr;
	else
		requested = pf->dynamic_tx_itr;

	error = sysctl_handle_int(oidp, &requested, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);

	if (requested != 0 && requested != 1) {
		device_printf(dev,
		    "Invalid dynamic ITR value; value must be 0 or 1\n");
		return (EINVAL);
	}

	if (requested) {
		if (direction == IXL_DYN_ITR_RX) {
			pf->rx_itr = IXL_ITR_20K;
			ixl_configure_rx_itr(pf);
		} else {
			pf->tx_itr = IXL_ITR_20K;
			ixl_configure_tx_itr(pf);
		}

	}

	if (direction == IXL_DYN_ITR_RX)
		pf->dynamic_rx_itr = requested;
	else
		pf->dynamic_tx_itr = requested;

	return (error);
}
void
ixl_add_hw_stats(struct ixl_pf *pf)
{
	device_t dev = pf->dev;
	struct i40e_hw_port_stats *pf_stats = &pf->stats;

	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
	struct sysctl_oid *tree = device_get_sysctl_tree(dev);
	struct sysctl_oid_list *child = SYSCTL_CHILDREN(tree);

	/* Driver statistics */
	SYSCTL_ADD_UQUAD(ctx, child, OID_AUTO, "watchdog_events",
			CTLFLAG_RD, &pf->watchdog_events,
			"Watchdog timeouts");
	SYSCTL_ADD_UQUAD(ctx, child, OID_AUTO, "admin_irq",
			CTLFLAG_RD, &pf->admin_irq,
			"Admin Queue IRQ Handled");

	ixl_vsi_add_sysctls(&pf->vsi, "pf", true);
	/* MAC stats */
	ixl_add_sysctls_mac_stats(ctx, child, pf_stats);
}

void
ixl_add_sysctls_mac_stats(struct sysctl_ctx_list *ctx,
	struct sysctl_oid_list *child,
	struct i40e_hw_port_stats *stats)
{
	struct sysctl_oid *stat_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "mac",
				    CTLFLAG_RD, NULL, "Mac Statistics");
	struct sysctl_oid_list *stat_list = SYSCTL_CHILDREN(stat_node);

	struct i40e_eth_stats *eth_stats = &stats->eth;
	ixl_add_sysctls_eth_stats(ctx, stat_list, eth_stats);

	struct ixl_sysctl_info ctls[] =
	{
		{&stats->crc_errors, "crc_errors", "CRC Errors"},
		{&stats->illegal_bytes, "illegal_bytes", "Illegal Byte Errors"},
		{&stats->mac_local_faults, "local_faults", "MAC Local Faults"},
		{&stats->mac_remote_faults, "remote_faults", "MAC Remote Faults"},
		{&stats->rx_length_errors, "rx_length_errors", "Receive Length Errors"},
		/* Packet Reception Stats */
		{&stats->rx_size_64, "rx_frames_64", "64 byte frames received"},
		{&stats->rx_size_127, "rx_frames_65_127", "65-127 byte frames received"},
		{&stats->rx_size_255, "rx_frames_128_255", "128-255 byte frames received"},
		{&stats->rx_size_511, "rx_frames_256_511", "256-511 byte frames received"},
		{&stats->rx_size_1023, "rx_frames_512_1023", "512-1023 byte frames received"},
		{&stats->rx_size_1522, "rx_frames_1024_1522", "1024-1522 byte frames received"},
		{&stats->rx_size_big, "rx_frames_big", "1523-9522 byte frames received"},
		{&stats->rx_undersize, "rx_undersize", "Undersized packets received"},
		{&stats->rx_fragments, "rx_fragmented", "Fragmented packets received"},
		{&stats->rx_oversize, "rx_oversized", "Oversized packets received"},
		{&stats->rx_jabber, "rx_jabber", "Received Jabber"},
		{&stats->checksum_error, "checksum_errors", "Checksum Errors"},
		/* Packet Transmission Stats */
		{&stats->tx_size_64, "tx_frames_64", "64 byte frames transmitted"},
		{&stats->tx_size_127, "tx_frames_65_127", "65-127 byte frames transmitted"},
		{&stats->tx_size_255, "tx_frames_128_255", "128-255 byte frames transmitted"},
		{&stats->tx_size_511, "tx_frames_256_511", "256-511 byte frames transmitted"},
		{&stats->tx_size_1023, "tx_frames_512_1023", "512-1023 byte frames transmitted"},
		{&stats->tx_size_1522, "tx_frames_1024_1522", "1024-1522 byte frames transmitted"},
		{&stats->tx_size_big, "tx_frames_big", "1523-9522 byte frames transmitted"},
		/* Flow control */
		{&stats->link_xon_tx, "xon_txd", "Link XON transmitted"},
		{&stats->link_xon_rx, "xon_recvd", "Link XON received"},
		{&stats->link_xoff_tx, "xoff_txd", "Link XOFF transmitted"},
		{&stats->link_xoff_rx, "xoff_recvd", "Link XOFF received"},
		/* End */
		{0,0,0}
	};

	struct ixl_sysctl_info *entry = ctls;
	while (entry->stat != 0)
	{
		SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, entry->name,
				CTLFLAG_RD, entry->stat,
				entry->description);
		entry++;
	}
}

void
ixl_set_rss_key(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	struct ixl_vsi *vsi = &pf->vsi;
	device_t	dev = pf->dev;
	u32 rss_seed[IXL_RSS_KEY_SIZE_REG];
	enum i40e_status_code status;

#ifdef RSS
        /* Fetch the configured RSS key */
        rss_getkey((uint8_t *) &rss_seed);
#else
	ixl_get_default_rss_key(rss_seed);
#endif
	/* Fill out hash function seed */
	if (hw->mac.type == I40E_MAC_X722) {
		struct i40e_aqc_get_set_rss_key_data key_data;
		bcopy(rss_seed, &key_data, 52);
		status = i40e_aq_set_rss_key(hw, vsi->vsi_num, &key_data);
		if (status)
			device_printf(dev,
			    "i40e_aq_set_rss_key status %s, error %s\n",
			    i40e_stat_str(hw, status),
			    i40e_aq_str(hw, hw->aq.asq_last_status));
	} else {
		for (int i = 0; i < IXL_RSS_KEY_SIZE_REG; i++)
			i40e_write_rx_ctl(hw, I40E_PFQF_HKEY(i), rss_seed[i]);
	}
}

/*
 * Configure enabled PCTYPES for RSS.
 */
void
ixl_set_rss_pctypes(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	u64		set_hena = 0, hena;

#ifdef RSS
	u32		rss_hash_config;

	rss_hash_config = rss_gethashconfig();
	if (rss_hash_config & RSS_HASHTYPE_RSS_IPV4)
                set_hena |= ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV4_OTHER);
	if (rss_hash_config & RSS_HASHTYPE_RSS_TCP_IPV4)
                set_hena |= ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV4_TCP);
	if (rss_hash_config & RSS_HASHTYPE_RSS_UDP_IPV4)
                set_hena |= ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV4_UDP);
	if (rss_hash_config & RSS_HASHTYPE_RSS_IPV6)
                set_hena |= ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV6_OTHER);
	if (rss_hash_config & RSS_HASHTYPE_RSS_IPV6_EX)
		set_hena |= ((u64)1 << I40E_FILTER_PCTYPE_FRAG_IPV6);
	if (rss_hash_config & RSS_HASHTYPE_RSS_TCP_IPV6)
                set_hena |= ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV6_TCP);
        if (rss_hash_config & RSS_HASHTYPE_RSS_UDP_IPV6)
                set_hena |= ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV6_UDP);
#else
	if (hw->mac.type == I40E_MAC_X722)
		set_hena = IXL_DEFAULT_RSS_HENA_X722;
	else
		set_hena = IXL_DEFAULT_RSS_HENA_XL710;
#endif
	hena = (u64)i40e_read_rx_ctl(hw, I40E_PFQF_HENA(0)) |
	    ((u64)i40e_read_rx_ctl(hw, I40E_PFQF_HENA(1)) << 32);
	hena |= set_hena;
	i40e_write_rx_ctl(hw, I40E_PFQF_HENA(0), (u32)hena);
	i40e_write_rx_ctl(hw, I40E_PFQF_HENA(1), (u32)(hena >> 32));

}

void
ixl_set_rss_hlut(struct ixl_pf *pf)
{
	struct i40e_hw	*hw = &pf->hw;
	device_t	dev = pf->dev;
	struct ixl_vsi *vsi = &pf->vsi;
	int		i, que_id;
	int		lut_entry_width;
	u32		lut = 0;
	enum i40e_status_code status;

	lut_entry_width = pf->hw.func_caps.rss_table_entry_width;

	/* Populate the LUT with max no. of queues in round robin fashion */
	u8 hlut_buf[512];
	for (i = 0; i < pf->hw.func_caps.rss_table_size; i++) {
#ifdef RSS
		/*
		 * Fetch the RSS bucket id for the given indirection entry.
		 * Cap it at the number of configured buckets (which is
		 * num_queues.)
		 */
		que_id = rss_get_indirection_to_bucket(i);
		que_id = que_id % vsi->num_queues;
#else
		que_id = i % vsi->num_queues;
#endif
		lut = (que_id & ((0x1 << lut_entry_width) - 1));
		hlut_buf[i] = lut;
	}

	if (hw->mac.type == I40E_MAC_X722) {
		status = i40e_aq_set_rss_lut(hw, vsi->vsi_num, TRUE, hlut_buf, sizeof(hlut_buf));
		if (status)
			device_printf(dev, "i40e_aq_set_rss_lut status %s, error %s\n",
			    i40e_stat_str(hw, status), i40e_aq_str(hw, hw->aq.asq_last_status));
	} else {
		for (i = 0; i < pf->hw.func_caps.rss_table_size >> 2; i++)
			wr32(hw, I40E_PFQF_HLUT(i), ((u32 *)hlut_buf)[i]);
		ixl_flush(hw);
	}
}

/*
** Setup the PF's RSS parameters.
*/
void
ixl_config_rss(struct ixl_pf *pf)
{
	ixl_set_rss_key(pf);
	ixl_set_rss_pctypes(pf);
	ixl_set_rss_hlut(pf);
}

/**
 * ixl_register_vlan - Handler for a vlan_config event
 * @arg: pointer to main VSI, provided when handler was registered
 * @ifp: interface used as a parent for the VLAN
 * @vtag: VLAN tag
 *
 * Keeps track of configured VLANs. Adds a HW filter
 * if VLAN HW filtering capability is enabled.
 */
void
ixl_register_vlan(void *arg, struct ifnet *ifp, u16 vtag)
{
	struct ixl_vsi	*vsi = ifp->if_softc;
	struct i40e_hw	*hw = vsi->hw;
	struct ixl_pf	*pf = (struct ixl_pf *)vsi->back;

	if (ifp->if_softc !=  arg)   /* Not our event */
		return;

	if ((vtag == 0) || (vtag > 4095))	/* Invalid */
		return;

	IXL_PF_LOCK(pf);
	/*
	 * Keep track of registered VLANS to know what
	 * filters have to be configured when VLAN_HWFILTER
	 * capability is enabled.
	 */
	bit_set(vsi->vlans_map, vtag);
	++vsi->num_vlans;
	if ((if_getcapenable(ifp) & IFCAP_VLAN_HWFILTER) == 0)
		goto exit_unlock;

	if (vsi->num_vlans < IXL_MAX_VLAN_FILTERS)
		ixl_add_filter(vsi, hw->mac.addr, vtag);
	else if (vsi->num_vlans == IXL_MAX_VLAN_FILTERS) {
		/*
		 * There is not enough HW resources to add filters
		 * for all registered VLANs. Re-configure filtering
		 * to allow reception of all expected traffic.
		 */
		device_printf(vsi->dev,
		    "Not enough HW filters for all VLANs. VLAN HW filtering disabled");
		ixl_del_all_vlan_filters(vsi, hw->mac.addr);
		ixl_add_filter(vsi, hw->mac.addr, IXL_VLAN_ANY);
	}
exit_unlock:
	IXL_PF_UNLOCK(pf);
}

/**
 * ixl_unregister_vlan - Handler for a vlan_unconfig event
 * @arg: pointer to main VSI, provided when handler was registered
 * @ifp: interface used as a parent for the VLAN
 * @vtag: VLAN tag
 *
 * Keeps track of removed VLANs. Removes a HW filter
 * if VLAN HW filtering capability is enabled.
 */
void
ixl_unregister_vlan(void *arg, struct ifnet *ifp, u16 vtag)
{
	struct ixl_vsi	*vsi = ifp->if_softc;
	struct i40e_hw	*hw = vsi->hw;
	struct ixl_pf	*pf = (struct ixl_pf *)vsi->back;

	if (ifp->if_softc !=  arg)
		return;

	if ((vtag == 0) || (vtag > 4095))	/* Invalid */
		return;

	IXL_PF_LOCK(pf);
	bit_clear(vsi->vlans_map, vtag);
	--vsi->num_vlans;

	if ((if_getcapenable(ifp) & IFCAP_VLAN_HWFILTER) == 0)
		goto exit_unlock;

	/* One filter is used for untagged frames */
	if (vsi->num_vlans < IXL_MAX_VLAN_FILTERS - 1)
		ixl_del_filter(vsi, hw->mac.addr, vtag);
	else if (vsi->num_vlans == IXL_MAX_VLAN_FILTERS - 1) {
		ixl_del_filter(vsi, hw->mac.addr, IXL_VLAN_ANY);
		ixl_add_vlan_filters(vsi, hw->mac.addr);
	}

exit_unlock:
	IXL_PF_UNLOCK(pf);
}

#ifdef CSUM_ENCAP_VXLAN
/**
 * ixl_get_udp_pot_idx - Get an index of given port from map of UDP ports
 *                       used for VXLANs.
 * @pf: PF structure
 * @port: Port number to be queried
 *
 * @return The index of the queried port if the port is not marked
 *     for deletion, -1 if port was not found or is marked for deletion.
 */
static int
ixl_get_udp_port_idx(struct ixl_pf *pf, u_int port)
{
	u8 i;

	for (i = 0; i < I40E_MAX_PF_UDP_OFFLOAD_PORTS; ++i) {
		/* Do not report ports with pending deletions as
		 * being available.
		 */
		if (!port && (pf->pending_udp_bitmap & BIT_ULL(i)))
			continue;
		if (pf->udp_ports[i].is_marked_for_deletion == TRUE)
			continue;
		if (pf->udp_ports[i].port == port)
			return i;
	}

	return -1;
}

/**
 * ixl_register_vxlan - Handler for a vxlan_start event
 * @arg: pointer to main VSI, provided when handler was registered
 * @ifp: interface used as a parent for the VXLAN
 * @family: address family
 * @port: UDP port requested by if_vxlan device
 *
 * Keeps track of configured VXLAN UDP ports. Issues a request of UDP tunnel
 * creation to the ixl_sync_udp_filters() task.
 */
void
ixl_register_vxlan(void *arg, struct ifnet *ifp, sa_family_t family, u_int port)
{
	struct ixl_vsi *vsi = (struct ixl_vsi *)arg;
	struct ixl_pf *pf = (struct ixl_pf *)vsi->back;
	int idx, next_idx;

	MPASS(family == AF_INET || family == AF_INET6);

	/* Check if interface has VXLAN offloads enabled */
	if (!(vsi->ifp->if_capenable & (IFCAP_VXLAN_HWCSUM | IFCAP_VXLAN_HWTSO)))
		return;

	idx = ixl_get_udp_port_idx(pf, port);

	/* Check if port already exists */
	if (idx != -1) {
		device_printf(pf->dev, "port %d already offloaded\n", port);
		return;
	}

	/* Now check if there is space to add the new port */
	next_idx = ixl_get_udp_port_idx(pf, 0);

	if (next_idx == -1) {
		device_printf(pf->dev, "maximum number of offloaded UDP ports reached, not adding port %d\n",
		    port);
		return;
	}

	pf->udp_ports[next_idx].ifp = ifp;
	pf->udp_ports[next_idx].port = port;
	pf->udp_ports[next_idx].filter_index = IXL_UDP_PORT_INDEX_UNUSED;
	pf->udp_ports[next_idx].is_marked_for_deletion = FALSE;
	pf->pending_udp_bitmap |= BIT_ULL(next_idx);

	ixl_set_state(&pf->state, IXL_STATE_UDP_FILTER_SYNC_PENDING);
}

/**
 * ixl_register_vxlan - Handler for a vxlan_stop event
 * @arg: pointer to main VSI, provided when handler was registered
 * @ifp: interface used as a parent for the VXLAN
 * @family: address family
 * @port: UDP port requested by if_vxlan device
 *
 * Keeps track of deleted VXLAN UDP ports. Issues a request of UDP tunnel
 * deletion to the ixl_sync_udp_filters() task.
 */
void
ixl_unregister_vxlan(void *arg, struct ifnet *ifp, sa_family_t family,
    u_int port)
{
	struct ixl_vsi *vsi = (struct ixl_vsi *)arg;
	struct ixl_pf *pf = (struct ixl_pf *)vsi->back;
	int idx;

	MPASS(family == AF_INET || family == AF_INET6);

	idx = ixl_get_udp_port_idx(pf, port);

	/* Check if port already exists */
	if (idx == -1)
		goto not_found;

	/* If port exists, set the value to 0. When ixl_register_vxlan() looks for
	 * an empty entry for a new tunnel, it looks for entries with port set to 0.
	 * Also, mark current entry for deletion and make the deletion pending.
	 */
	pf->udp_ports[idx].port = 0;
	pf->udp_ports[idx].is_marked_for_deletion = TRUE;

	/* Toggle pending bit instead of setting it. This way if we are
	 * deleting a port that has yet to be added we just clear the pending
	 * bit and don't have to worry about it.
	 */
	pf->pending_udp_bitmap ^= BIT_ULL(idx);
	ixl_set_state(&pf->state, IXL_STATE_UDP_FILTER_SYNC_PENDING);

	return;

not_found:
	device_printf(pf->dev, "UDP port %d was not found, not deleting\n", port);
}

/**
 * ixl_sync_udp_filters - Set all ports to be deleted as pending
 * 						  and request the sync with hardware.
 */
void
ixl_sync_udp_filters(struct ixl_pf *pf)
{
	u8 i;

	/* loop through and set pending bit for all active UDP filters */
	for (i = 0; i < I40E_MAX_PF_UDP_OFFLOAD_PORTS; i++) {
		if (pf->udp_ports[i].port)
			pf->pending_udp_bitmap |= BIT_ULL(i);
	}

	ixl_set_state(&pf->state, IXL_STATE_UDP_FILTER_SYNC_PENDING);
}

/**
 * ixl_sync_udp_filters_task - Synchronize UDP ports list with hardware
 */
static void
ixl_sync_udp_filters_task(struct ixl_pf *pf)
{
	struct ixl_udp_port_config *udp_port;
	struct i40e_hw *hw = &pf->hw;
	enum i40e_status_code status;
	bool is_marked_for_deletion;
	u8 i, filter_index;
	u16 port;

	if (IXL_PF_HAS_PENDING_UDP_FILTER_SYNC(pf) == 0)
		return;

	for (i = 0; i < I40E_MAX_PF_UDP_OFFLOAD_PORTS; ++i) {
		if (pf->pending_udp_bitmap & BIT_ULL(i)) {
			status = I40E_SUCCESS;

			udp_port = &pf->udp_ports[i];
			pf->pending_udp_bitmap &= ~BIT_ULL(i);

			port = udp_port->port;
			is_marked_for_deletion = udp_port->is_marked_for_deletion;
			filter_index = udp_port->filter_index;

			if (is_marked_for_deletion == FALSE && port > 0) {
				status = i40e_aq_add_udp_tunnel(hw, port,
				    I40E_AQC_TUNNEL_TYPE_VXLAN, &filter_index, NULL);
			} else if (filter_index != IXL_UDP_PORT_INDEX_UNUSED)
				status = i40e_aq_del_udp_tunnel(hw, filter_index, NULL);

			if (status) {
				device_printf(pf->dev,
				    "VXLAN %s port %d, index %d failed, err %s aq_err %s\n",
				    is_marked_for_deletion ? "delete" : "add", port,
					filter_index, i40e_stat_str(&pf->hw, status),
				    i40e_aq_str(&pf->hw, pf->hw.aq.asq_last_status));

				if (is_marked_for_deletion == FALSE) {
					/* failed to add, just reset port,
					 * drop pending bit for any deletion
					 */
					udp_port->port = 0;
					pf->pending_udp_bitmap &= ~BIT_ULL(i);
				}
			} else {
				if (is_marked_for_deletion == FALSE) {
					/* record filter index on success */
					udp_port->filter_index = filter_index;
				}
				/* clear the deletion flag */
				udp_port->is_marked_for_deletion = FALSE;
			}
		}
	}

	ixl_clear_state(&pf->state, IXL_STATE_UDP_FILTER_SYNC_PENDING);
}

/**
 * ixl_is_vxlan_in_use - Is VXLAN HW offload currently in use
 * @pf: PF structure
 *
 * Checks if there is a UDP port registered for VXLAN HW offload.
 */
bool
ixl_is_vxlan_in_use(struct ixl_pf *pf)
{
	for (int i = 0; i < I40E_MAX_PF_UDP_OFFLOAD_PORTS; ++i) {
		if (pf->udp_ports[i].port != 0 &&
		    !pf->udp_ports[i].is_marked_for_deletion)
			return (TRUE);
	}
	return (FALSE);
}
#endif /* CSUM_ENCAP_VXLAN */

/*
 * In some firmware versions there is default MAC/VLAN filter
 * configured which interferes with filters managed by driver.
 * Make sure it's removed.
 */
void
ixl_del_default_hw_filters(struct ixl_vsi *vsi)
{
	struct i40e_aqc_remove_macvlan_element_data e;

	bzero(&e, sizeof(e));
	bcopy(vsi->hw->mac.perm_addr, e.mac_addr, ETHER_ADDR_LEN);
	e.vlan_tag = 0;
	e.flags = I40E_AQC_MACVLAN_DEL_PERFECT_MATCH;
	i40e_aq_remove_macvlan(vsi->hw, vsi->seid, &e, 1, NULL);

	bzero(&e, sizeof(e));
	bcopy(vsi->hw->mac.perm_addr, e.mac_addr, ETHER_ADDR_LEN);
	e.vlan_tag = 0;
	e.flags = I40E_AQC_MACVLAN_DEL_PERFECT_MATCH |
		I40E_AQC_MACVLAN_DEL_IGNORE_VLAN;
	i40e_aq_remove_macvlan(vsi->hw, vsi->seid, &e, 1, NULL);
}

static enum i40e_status_code
ixl_set_lla(struct ixl_vsi *vsi)
{
	struct i40e_hw	*hw = vsi->hw;
	u8		tmpaddr[ETHER_ADDR_LEN];
	enum i40e_status_code status;

	status = I40E_SUCCESS;

	bcopy(IF_LLADDR(vsi->ifp), tmpaddr, ETHER_ADDR_LEN);
	if (memcmp(hw->mac.addr, tmpaddr, ETHER_ADDR_LEN) == 0)
		goto set_lla_exit;

	status = i40e_validate_mac_addr(tmpaddr);
	if (status != I40E_SUCCESS)
		goto set_lla_exit;

	ixl_del_all_vlan_filters(vsi, hw->mac.addr);
	bcopy(tmpaddr, hw->mac.addr, ETHER_ADDR_LEN);
	status = i40e_aq_mac_address_write(hw,
			I40E_AQC_WRITE_TYPE_LAA_ONLY,
			hw->mac.addr, NULL);
	if (status != I40E_SUCCESS)
		goto set_lla_exit;

	/*
	 * New filters are configured by ixl_reconfigure_filters
	 * at the end of ixl_init_locked.
	 */
set_lla_exit:
	return (status);
}

/*
** Initialize filter list and add filters that the hardware
** needs to know about.
**
** Requires VSI's seid to be set before calling.
*/
void
ixl_init_filters(struct ixl_vsi *vsi)
{
	struct ixl_pf *pf = (struct ixl_pf *)vsi->back;

	ixl_dbg_filter(pf, "%s: start\n", __func__);

	/* Initialize mac filter list for VSI */
	LIST_INIT(&vsi->ftl);
	vsi->num_hw_filters = 0;

	/* Receive broadcast Ethernet frames */
	i40e_aq_set_vsi_broadcast(&pf->hw, vsi->seid, TRUE, NULL);

	if (IXL_VSI_IS_VF(vsi))
		return;

	ixl_del_default_hw_filters(vsi);

	ixl_add_filter(vsi, vsi->hw->mac.addr, IXL_VLAN_ANY);

	/*
	 * Prevent Tx flow control frames from being sent out by
	 * non-firmware transmitters.
	 * This affects every VSI in the PF.
	 */
#ifndef IXL_DEBUG_FC
	i40e_add_filter_to_drop_tx_flow_control_frames(vsi->hw, vsi->seid);
#else
	if (pf->enable_tx_fc_filter)
		i40e_add_filter_to_drop_tx_flow_control_frames(vsi->hw, vsi->seid);
#endif
}

void
ixl_reconfigure_filters(struct ixl_vsi *vsi)
{
	struct i40e_hw *hw = vsi->hw;
	struct ixl_ftl_head tmp;
	int cnt;

	/*
	 * The ixl_add_hw_filters function adds filters configured
	 * in HW to a list in VSI. Move all filters to a temporary
	 * list to avoid corrupting it by concatenating to itself.
	 */
	LIST_INIT(&tmp);
	LIST_CONCAT(&tmp, &vsi->ftl, ixl_mac_filter, ftle);
	cnt = vsi->num_hw_filters;
	vsi->num_hw_filters = 0;

	ixl_add_hw_filters(vsi, &tmp, cnt);

	/* Filter could be removed if MAC address was changed */
	ixl_add_filter(vsi, hw->mac.addr, IXL_VLAN_ANY);

	if ((if_getcapenable(vsi->ifp) & IFCAP_VLAN_HWFILTER) == 0)
		return;
	/*
	 * VLAN HW filtering is enabled, make sure that filters
	 * for all registered VLAN tags are configured
	 */
	ixl_add_vlan_filters(vsi, hw->mac.addr);
}

/*
** This routine adds macvlan filters
*/
void
ixl_add_filter(struct ixl_vsi *vsi, const u8 *macaddr, s16 vlan)
{
	struct ixl_mac_filter	*f, *tmp;
	struct ixl_pf		*pf;
	device_t		dev;
	struct ixl_ftl_head	to_add;
	int			to_add_cnt;

	pf = vsi->back;
	dev = pf->dev;
	to_add_cnt = 1;

	ixl_dbg_filter(pf, "ixl_add_filter: " MAC_FORMAT ", vlan %4d\n",
	    MAC_FORMAT_ARGS(macaddr), vlan);

	/* Does one already exist */
	f = ixl_find_filter(&vsi->ftl, macaddr, vlan);
	if (f != NULL)
		return;

	LIST_INIT(&to_add);
	f = ixl_new_filter(&to_add, macaddr, vlan);
	if (f == NULL) {
		device_printf(dev, "WARNING: no filter available!!\n");
		return;
	}
	if (f->vlan != IXL_VLAN_ANY)
		f->flags |= IXL_FILTER_VLAN;
	else
		vsi->num_macs++;

	/*
	** Is this the first vlan being registered, if so we
	** need to remove the ANY filter that indicates we are
	** not in a vlan, and replace that with a 0 filter.
	*/
	if ((vlan != IXL_VLAN_ANY) && (vsi->num_vlans == 1)) {
		tmp = ixl_find_filter(&vsi->ftl, macaddr, IXL_VLAN_ANY);
		if (tmp != NULL) {
			struct ixl_ftl_head to_del;

			/* Prepare new filter first to avoid removing
			 * VLAN_ANY filter if allocation fails */
			f = ixl_new_filter(&to_add, macaddr, 0);
			if (f == NULL) {
				device_printf(dev, "WARNING: no filter available!!\n");
				free(LIST_FIRST(&to_add), M_IXL);
				return;
			}
			to_add_cnt++;

			LIST_REMOVE(tmp, ftle);
			LIST_INIT(&to_del);
			LIST_INSERT_HEAD(&to_del, tmp, ftle);
			ixl_del_hw_filters(vsi, &to_del, 1);
		}
	}

	ixl_add_hw_filters(vsi, &to_add, to_add_cnt);
}

/**
 * ixl_add_vlan_filters - Add MAC/VLAN filters for all registered VLANs
 * @vsi: pointer to VSI
 * @macaddr: MAC address
 *
 * Adds MAC/VLAN filter for each VLAN configured on the interface
 * if there is enough HW filters. Otherwise adds a single filter
 * for all tagged and untagged frames to allow all configured VLANs
 * to recieve traffic.
 */
void
ixl_add_vlan_filters(struct ixl_vsi *vsi, const u8 *macaddr)
{
	struct ixl_ftl_head to_add;
	struct ixl_mac_filter *f;
	int to_add_cnt = 0;
	int i, vlan = 0;

	if (vsi->num_vlans == 0 || vsi->num_vlans > IXL_MAX_VLAN_FILTERS) {
		ixl_add_filter(vsi, macaddr, IXL_VLAN_ANY);
		return;
	}

	LIST_INIT(&to_add);

	/* Add filter for untagged frames if it does not exist yet */
	f = ixl_find_filter(&vsi->ftl, macaddr, 0);
	if (f == NULL) {
		f = ixl_new_filter(&to_add, macaddr, 0);
		if (f == NULL) {
			device_printf(vsi->dev, "WARNING: no filter available!!\n");
			return;
		}
		to_add_cnt++;
	}

	for (i = 1; i < EVL_VLID_MASK; i = vlan + 1) {
		bit_ffs_at(vsi->vlans_map, i, IXL_VLANS_MAP_LEN, &vlan);
		if (vlan == -1)
			break;

		/* Does one already exist */
		f = ixl_find_filter(&vsi->ftl, macaddr, vlan);
		if (f != NULL)
			continue;

		f = ixl_new_filter(&to_add, macaddr, vlan);
		if (f == NULL) {
			device_printf(vsi->dev, "WARNING: no filter available!!\n");
			ixl_free_filters(&to_add);
			return;
		}
		to_add_cnt++;
	}

	ixl_add_hw_filters(vsi, &to_add, to_add_cnt);
}

void
ixl_del_filter(struct ixl_vsi *vsi, const u8 *macaddr, s16 vlan)
{
	struct ixl_mac_filter *f, *tmp;
	struct ixl_ftl_head ftl_head;
	int to_del_cnt = 1;

	ixl_dbg_filter((struct ixl_pf *)vsi->back,
	    "ixl_del_filter: " MAC_FORMAT ", vlan %4d\n",
	    MAC_FORMAT_ARGS(macaddr), vlan);

	f = ixl_find_filter(&vsi->ftl, macaddr, vlan);
	if (f == NULL)
		return;

	LIST_REMOVE(f, ftle);
	LIST_INIT(&ftl_head);
	LIST_INSERT_HEAD(&ftl_head, f, ftle);
	vsi->num_macs--;

	/* If this is not the last vlan just remove the filter */
	if (vlan == IXL_VLAN_ANY || vsi->num_vlans > 0) {
		ixl_del_hw_filters(vsi, &ftl_head, to_del_cnt);
		return;
	}

	/* It's the last vlan, we need to switch back to a non-vlan filter */
	tmp = ixl_find_filter(&vsi->ftl, macaddr, 0);
	if (tmp != NULL) {
		LIST_REMOVE(tmp, ftle);
		LIST_INSERT_AFTER(f, tmp, ftle);
		to_del_cnt++;
	}
	ixl_del_hw_filters(vsi, &ftl_head, to_del_cnt);

	ixl_add_filter(vsi, macaddr, IXL_VLAN_ANY);
}

/**
 * ixl_del_all_vlan_filters - Delete all VLAN filters with given MAC
 * @vsi: VSI which filters need to be removed
 * @macaddr: MAC address
 *
 * Remove all MAC/VLAN filters with a given MAC address. For multicast
 * addresses there is always single filter for all VLANs used (IXL_VLAN_ANY)
 * so skip them to speed up processing. Those filters should be removed
 * using ixl_del_filter function.
 */
void
ixl_del_all_vlan_filters(struct ixl_vsi *vsi, const u8 *macaddr)
{
	struct ixl_mac_filter *f, *tmp;
	struct ixl_ftl_head to_del;
	int to_del_cnt = 0;

	LIST_INIT(&to_del);

	LIST_FOREACH_SAFE(f, &vsi->ftl, ftle, tmp) {
		if ((f->flags & IXL_FILTER_MC) != 0 ||
		    !ixl_ether_is_equal(f->macaddr, macaddr))
			continue;

		LIST_REMOVE(f, ftle);
		LIST_INSERT_HEAD(&to_del, f, ftle);
		to_del_cnt++;
	}

	ixl_dbg_filter((struct ixl_pf *)vsi->back,
	    "%s: " MAC_FORMAT ", to_del_cnt: %d\n",
	    __func__, MAC_FORMAT_ARGS(macaddr), to_del_cnt);
	if (to_del_cnt > 0)
		ixl_del_hw_filters(vsi, &to_del, to_del_cnt);
}

/*
** Find the filter with both matching mac addr and vlan id
*/
struct ixl_mac_filter *
ixl_find_filter(struct ixl_ftl_head *headp, const u8 *macaddr, s16 vlan)
{
	struct ixl_mac_filter	*f;

	LIST_FOREACH(f, headp, ftle) {
		if (ixl_ether_is_equal(f->macaddr, macaddr) &&
		    (f->vlan == vlan)) {
			return (f);
		}
	}

	return (NULL);
}

/*
** This routine takes additions to the vsi filter
** table and creates an Admin Queue call to create
** the filters in the hardware.
*/
void
ixl_add_hw_filters(struct ixl_vsi *vsi, struct ixl_ftl_head *to_add, int cnt)
{
	struct i40e_aqc_add_macvlan_element_data *a, *b;
	struct ixl_mac_filter	*f, *fn;
	struct ixl_pf		*pf;
	struct i40e_hw		*hw;
	device_t		dev;
	enum i40e_status_code	status;
	int			j = 0;

	pf = vsi->back;
	dev = pf->dev;
	hw = &pf->hw;
	IXL_PF_LOCK_ASSERT(pf);

	ixl_dbg_filter(pf,
	    "ixl_add_hw_filters: cnt: %d\n", cnt);

	if (cnt < 1) {
		ixl_dbg_info(pf, "ixl_add_hw_filters: cnt == 0\n");
		return;
	}

	a = malloc(sizeof(struct i40e_aqc_add_macvlan_element_data) * cnt,
	    M_IXL, M_NOWAIT | M_ZERO);
	if (a == NULL) {
		device_printf(dev, "add_hw_filters failed to get memory\n");
		return;
	}

	LIST_FOREACH(f, to_add, ftle) {
		b = &a[j]; // a pox on fvl long names :)
		bcopy(f->macaddr, b->mac_addr, ETHER_ADDR_LEN);
		if (f->vlan == IXL_VLAN_ANY) {
			b->vlan_tag = 0;
			b->flags = I40E_AQC_MACVLAN_ADD_IGNORE_VLAN;
		} else {
			b->vlan_tag = f->vlan;
			b->flags = 0;
		}
		b->flags |= I40E_AQC_MACVLAN_ADD_PERFECT_MATCH;
		/* Some FW versions do not set match method
		 * when adding filters fails. Initialize it with
		 * expected error value to allow detection which
		 * filters were not added */
		b->match_method = I40E_AQC_MM_ERR_NO_RES;
		ixl_dbg_filter(pf, "ADD: " MAC_FORMAT "\n",
		    MAC_FORMAT_ARGS(f->macaddr));

		if (++j == cnt)
			break;
	}
	if (j != cnt) {
		/* Something went wrong */
		device_printf(dev,
		    "%s ERROR: list of filters to short expected: %d, found: %d\n",
		    __func__, cnt, j);
		ixl_free_filters(to_add);
		goto out_free;
	}

	status = i40e_aq_add_macvlan(hw, vsi->seid, a, j, NULL);
	if (status == I40E_SUCCESS) {
		LIST_CONCAT(&vsi->ftl, to_add, ixl_mac_filter, ftle);
		vsi->num_hw_filters += j;
		goto out_free;
	}

	device_printf(dev,
	    "i40e_aq_add_macvlan status %s, error %s\n",
	    i40e_stat_str(hw, status),
	    i40e_aq_str(hw, hw->aq.asq_last_status));
	j = 0;

	/* Verify which filters were actually configured in HW
	 * and add them to the list */
	LIST_FOREACH_SAFE(f, to_add, ftle, fn) {
		LIST_REMOVE(f, ftle);
		if (a[j].match_method == I40E_AQC_MM_ERR_NO_RES) {
			ixl_dbg_filter(pf,
			    "%s filter " MAC_FORMAT " VTAG: %d not added\n",
			    __func__,
			    MAC_FORMAT_ARGS(f->macaddr),
			    f->vlan);
			free(f, M_IXL);
		} else {
			LIST_INSERT_HEAD(&vsi->ftl, f, ftle);
			vsi->num_hw_filters++;
		}
		j++;
	}

out_free:
	free(a, M_IXL);
}

/*
** This routine takes removals in the vsi filter
** table and creates an Admin Queue call to delete
** the filters in the hardware.
*/
void
ixl_del_hw_filters(struct ixl_vsi *vsi, struct ixl_ftl_head *to_del, int cnt)
{
	struct i40e_aqc_remove_macvlan_element_data *d, *e;
	struct ixl_pf		*pf;
	struct i40e_hw		*hw;
	device_t		dev;
	struct ixl_mac_filter	*f, *f_temp;
	enum i40e_status_code	status;
	int			j = 0;

	pf = vsi->back;
	hw = &pf->hw;
	dev = pf->dev;

	ixl_dbg_filter(pf, "%s: start, cnt: %d\n", __func__, cnt);

	d = malloc(sizeof(struct i40e_aqc_remove_macvlan_element_data) * cnt,
	    M_IXL, M_NOWAIT | M_ZERO);
	if (d == NULL) {
		device_printf(dev, "%s: failed to get memory\n", __func__);
		return;
	}

	LIST_FOREACH_SAFE(f, to_del, ftle, f_temp) {
		e = &d[j]; // a pox on fvl long names :)
		bcopy(f->macaddr, e->mac_addr, ETHER_ADDR_LEN);
		e->flags = I40E_AQC_MACVLAN_DEL_PERFECT_MATCH;
		if (f->vlan == IXL_VLAN_ANY) {
			e->vlan_tag = 0;
			e->flags |= I40E_AQC_MACVLAN_DEL_IGNORE_VLAN;
		} else {
			e->vlan_tag = f->vlan;
		}

		ixl_dbg_filter(pf, "DEL: " MAC_FORMAT "\n",
		    MAC_FORMAT_ARGS(f->macaddr));

		/* delete entry from the list */
		LIST_REMOVE(f, ftle);
		free(f, M_IXL);
		if (++j == cnt)
			break;
	}
	if (j != cnt || !LIST_EMPTY(to_del)) {
		/* Something went wrong */
		device_printf(dev,
		    "%s ERROR: wrong size of list of filters, expected: %d, found: %d\n",
		    __func__, cnt, j);
		ixl_free_filters(to_del);
		goto out_free;
	}
	status = i40e_aq_remove_macvlan(hw, vsi->seid, d, j, NULL);
	if (status) {
		device_printf(dev,
		    "%s: i40e_aq_remove_macvlan status %s, error %s\n",
		    __func__, i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
		for (int i = 0; i < j; i++) {
			if (d[i].error_code == 0)
				continue;
			device_printf(dev,
			    "%s Filter does not exist " MAC_FORMAT " VTAG: %d\n",
			    __func__, MAC_FORMAT_ARGS(d[i].mac_addr),
			    d[i].vlan_tag);
		}
	}

	vsi->num_hw_filters -= j;

out_free:
	free(d, M_IXL);

	ixl_dbg_filter(pf, "%s: end\n", __func__);
	return;
}

int
ixl_enable_tx_ring(struct ixl_pf *pf, struct ixl_pf_qtag *qtag, u16 vsi_qidx)
{
	struct i40e_hw	*hw = &pf->hw;
	int		error = 0;
	u32		reg;
	u16		pf_qidx;

	pf_qidx = ixl_pf_qidx_from_vsi_qidx(qtag, vsi_qidx);

	ixl_dbg(pf, IXL_DBG_EN_DIS,
	    "Enabling PF TX ring %4d / VSI TX ring %4d...\n",
	    pf_qidx, vsi_qidx);

	i40e_pre_tx_queue_cfg(hw, pf_qidx, TRUE);

	reg = rd32(hw, I40E_QTX_ENA(pf_qidx));
	reg |= I40E_QTX_ENA_QENA_REQ_MASK |
	    I40E_QTX_ENA_QENA_STAT_MASK;
	wr32(hw, I40E_QTX_ENA(pf_qidx), reg);
	/* Verify the enable took */
	for (int j = 0; j < 10; j++) {
		reg = rd32(hw, I40E_QTX_ENA(pf_qidx));
		if (reg & I40E_QTX_ENA_QENA_STAT_MASK)
			break;
		i40e_usec_delay(10);
	}
	if ((reg & I40E_QTX_ENA_QENA_STAT_MASK) == 0) {
		device_printf(pf->dev, "TX queue %d still disabled!\n",
		    pf_qidx);
		error = ETIMEDOUT;
	}

	return (error);
}

int
ixl_enable_rx_ring(struct ixl_pf *pf, struct ixl_pf_qtag *qtag, u16 vsi_qidx)
{
	struct i40e_hw	*hw = &pf->hw;
	int		error = 0;
	u32		reg;
	u16		pf_qidx;

	pf_qidx = ixl_pf_qidx_from_vsi_qidx(qtag, vsi_qidx);

	ixl_dbg(pf, IXL_DBG_EN_DIS,
	    "Enabling PF RX ring %4d / VSI RX ring %4d...\n",
	    pf_qidx, vsi_qidx);

	reg = rd32(hw, I40E_QRX_ENA(pf_qidx));
	reg |= I40E_QRX_ENA_QENA_REQ_MASK |
	    I40E_QRX_ENA_QENA_STAT_MASK;
	wr32(hw, I40E_QRX_ENA(pf_qidx), reg);
	/* Verify the enable took */
	for (int j = 0; j < 10; j++) {
		reg = rd32(hw, I40E_QRX_ENA(pf_qidx));
		if (reg & I40E_QRX_ENA_QENA_STAT_MASK)
			break;
		i40e_usec_delay(10);
	}
	if ((reg & I40E_QRX_ENA_QENA_STAT_MASK) == 0) {
		device_printf(pf->dev, "RX queue %d still disabled!\n",
		    pf_qidx);
		error = ETIMEDOUT;
	}

	return (error);
}

int
ixl_enable_ring(struct ixl_pf *pf, struct ixl_pf_qtag *qtag, u16 vsi_qidx)
{
	int error = 0;

	error = ixl_enable_tx_ring(pf, qtag, vsi_qidx);
	/* Called function already prints error message */
	if (error)
		return (error);
	error = ixl_enable_rx_ring(pf, qtag, vsi_qidx);
	return (error);
}

/* For PF VSI only */
int
ixl_enable_rings(struct ixl_vsi *vsi)
{
	struct ixl_pf	*pf = vsi->back;
	int		error = 0;

	for (int i = 0; i < vsi->num_queues; i++) {
		error = ixl_enable_ring(pf, &pf->qtag, i);
		if (error)
			return (error);
	}

	return (error);
}

/*
 * Returns error on first ring that is detected hung.
 */
int
ixl_disable_tx_ring(struct ixl_pf *pf, struct ixl_pf_qtag *qtag, u16 vsi_qidx)
{
	struct i40e_hw	*hw = &pf->hw;
	int		error = 0;
	u32		reg;
	u16		pf_qidx;

	pf_qidx = ixl_pf_qidx_from_vsi_qidx(qtag, vsi_qidx);

	ixl_dbg(pf, IXL_DBG_EN_DIS,
	    "Disabling PF TX ring %4d / VSI TX ring %4d...\n",
	    pf_qidx, vsi_qidx);

	i40e_pre_tx_queue_cfg(hw, pf_qidx, FALSE);
	i40e_usec_delay(500);

	reg = rd32(hw, I40E_QTX_ENA(pf_qidx));
	reg &= ~I40E_QTX_ENA_QENA_REQ_MASK;
	wr32(hw, I40E_QTX_ENA(pf_qidx), reg);
	/* Verify the disable took */
	for (int j = 0; j < 10; j++) {
		reg = rd32(hw, I40E_QTX_ENA(pf_qidx));
		if (!(reg & I40E_QTX_ENA_QENA_STAT_MASK))
			break;
		i40e_msec_delay(10);
	}
	if (reg & I40E_QTX_ENA_QENA_STAT_MASK) {
		device_printf(pf->dev, "TX queue %d still enabled!\n",
		    pf_qidx);
		error = ETIMEDOUT;
	}

	return (error);
}

/*
 * Returns error on first ring that is detected hung.
 */
int
ixl_disable_rx_ring(struct ixl_pf *pf, struct ixl_pf_qtag *qtag, u16 vsi_qidx)
{
	struct i40e_hw	*hw = &pf->hw;
	int		error = 0;
	u32		reg;
	u16		pf_qidx;

	pf_qidx = ixl_pf_qidx_from_vsi_qidx(qtag, vsi_qidx);

	ixl_dbg(pf, IXL_DBG_EN_DIS,
	    "Disabling PF RX ring %4d / VSI RX ring %4d...\n",
	    pf_qidx, vsi_qidx);

	reg = rd32(hw, I40E_QRX_ENA(pf_qidx));
	reg &= ~I40E_QRX_ENA_QENA_REQ_MASK;
	wr32(hw, I40E_QRX_ENA(pf_qidx), reg);
	/* Verify the disable took */
	for (int j = 0; j < 10; j++) {
		reg = rd32(hw, I40E_QRX_ENA(pf_qidx));
		if (!(reg & I40E_QRX_ENA_QENA_STAT_MASK))
			break;
		i40e_msec_delay(10);
	}
	if (reg & I40E_QRX_ENA_QENA_STAT_MASK) {
		device_printf(pf->dev, "RX queue %d still enabled!\n",
		    pf_qidx);
		error = ETIMEDOUT;
	}

	return (error);
}

int
ixl_disable_ring(struct ixl_pf *pf, struct ixl_pf_qtag *qtag, u16 vsi_qidx)
{
	int error = 0;

	error = ixl_disable_tx_ring(pf, qtag, vsi_qidx);
	/* Called function already prints error message */
	if (error)
		return (error);
	error = ixl_disable_rx_ring(pf, qtag, vsi_qidx);
	return (error);
}

int
ixl_disable_rings(struct ixl_pf *pf, struct ixl_vsi *vsi, struct ixl_pf_qtag *qtag)
{
	int error = 0;

	for (int i = 0; i < vsi->num_queues; i++)
		error = ixl_disable_ring(pf, qtag, i);

	return (error);
}

static void
ixl_handle_tx_mdd_event(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct ixl_vf *vf;
	bool mdd_detected = false;
	bool pf_mdd_detected = false;
	bool vf_mdd_detected = false;
	u16 vf_num, queue;
	u8 pf_num, event;
	u8 pf_mdet_num, vp_mdet_num;
	u32 reg;

	/* find what triggered the MDD event */
	reg = rd32(hw, I40E_GL_MDET_TX);
	if (reg & I40E_GL_MDET_TX_VALID_MASK) {
		pf_num = (reg & I40E_GL_MDET_TX_PF_NUM_MASK) >>
		    I40E_GL_MDET_TX_PF_NUM_SHIFT;
		vf_num = (reg & I40E_GL_MDET_TX_VF_NUM_MASK) >>
		    I40E_GL_MDET_TX_VF_NUM_SHIFT;
		event = (reg & I40E_GL_MDET_TX_EVENT_MASK) >>
		    I40E_GL_MDET_TX_EVENT_SHIFT;
		queue = (reg & I40E_GL_MDET_TX_QUEUE_MASK) >>
		    I40E_GL_MDET_TX_QUEUE_SHIFT;
		wr32(hw, I40E_GL_MDET_TX, 0xffffffff);
		mdd_detected = true;
	}

	if (!mdd_detected)
		return;

	reg = rd32(hw, I40E_PF_MDET_TX);
	if (reg & I40E_PF_MDET_TX_VALID_MASK) {
		wr32(hw, I40E_PF_MDET_TX, 0xFFFF);
		pf_mdet_num = hw->pf_id;
		pf_mdd_detected = true;
	}

	/* Check if MDD was caused by a VF */
	for (int i = 0; i < pf->num_vfs; i++) {
		vf = &(pf->vfs[i]);
		reg = rd32(hw, I40E_VP_MDET_TX(i));
		if (reg & I40E_VP_MDET_TX_VALID_MASK) {
			wr32(hw, I40E_VP_MDET_TX(i), 0xFFFF);
			vp_mdet_num = i;
			vf->num_mdd_events++;
			vf_mdd_detected = true;
		}
	}

	/* Print out an error message */
	if (vf_mdd_detected && pf_mdd_detected)
		device_printf(dev,
		    "Malicious Driver Detection event %d"
		    " on TX queue %d, pf number %d (PF-%d), vf number %d (VF-%d)\n",
		    event, queue, pf_num, pf_mdet_num, vf_num, vp_mdet_num);
	else if (vf_mdd_detected && !pf_mdd_detected)
		device_printf(dev,
		    "Malicious Driver Detection event %d"
		    " on TX queue %d, pf number %d, vf number %d (VF-%d)\n",
		    event, queue, pf_num, vf_num, vp_mdet_num);
	else if (!vf_mdd_detected && pf_mdd_detected)
		device_printf(dev,
		    "Malicious Driver Detection event %d"
		    " on TX queue %d, pf number %d (PF-%d)\n",
		    event, queue, pf_num, pf_mdet_num);
	/* Theoretically shouldn't happen */
	else
		device_printf(dev,
		    "TX Malicious Driver Detection event (unknown)\n");
}

static void
ixl_handle_rx_mdd_event(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct ixl_vf *vf;
	bool mdd_detected = false;
	bool pf_mdd_detected = false;
	bool vf_mdd_detected = false;
	u16 queue;
	u8 pf_num, event;
	u8 pf_mdet_num, vp_mdet_num;
	u32 reg;

	/*
	 * GL_MDET_RX doesn't contain VF number information, unlike
	 * GL_MDET_TX.
	 */
	reg = rd32(hw, I40E_GL_MDET_RX);
	if (reg & I40E_GL_MDET_RX_VALID_MASK) {
		pf_num = (reg & I40E_GL_MDET_RX_FUNCTION_MASK) >>
		    I40E_GL_MDET_RX_FUNCTION_SHIFT;
		event = (reg & I40E_GL_MDET_RX_EVENT_MASK) >>
		    I40E_GL_MDET_RX_EVENT_SHIFT;
		queue = (reg & I40E_GL_MDET_RX_QUEUE_MASK) >>
		    I40E_GL_MDET_RX_QUEUE_SHIFT;
		wr32(hw, I40E_GL_MDET_RX, 0xffffffff);
		mdd_detected = true;
	}

	if (!mdd_detected)
		return;

	reg = rd32(hw, I40E_PF_MDET_RX);
	if (reg & I40E_PF_MDET_RX_VALID_MASK) {
		wr32(hw, I40E_PF_MDET_RX, 0xFFFF);
		pf_mdet_num = hw->pf_id;
		pf_mdd_detected = true;
	}

	/* Check if MDD was caused by a VF */
	for (int i = 0; i < pf->num_vfs; i++) {
		vf = &(pf->vfs[i]);
		reg = rd32(hw, I40E_VP_MDET_RX(i));
		if (reg & I40E_VP_MDET_RX_VALID_MASK) {
			wr32(hw, I40E_VP_MDET_RX(i), 0xFFFF);
			vp_mdet_num = i;
			vf->num_mdd_events++;
			vf_mdd_detected = true;
		}
	}

	/* Print out an error message */
	if (vf_mdd_detected && pf_mdd_detected)
		device_printf(dev,
		    "Malicious Driver Detection event %d"
		    " on RX queue %d, pf number %d (PF-%d), (VF-%d)\n",
		    event, queue, pf_num, pf_mdet_num, vp_mdet_num);
	else if (vf_mdd_detected && !pf_mdd_detected)
		device_printf(dev,
		    "Malicious Driver Detection event %d"
		    " on RX queue %d, pf number %d, (VF-%d)\n",
		    event, queue, pf_num, vp_mdet_num);
	else if (!vf_mdd_detected && pf_mdd_detected)
		device_printf(dev,
		    "Malicious Driver Detection event %d"
		    " on RX queue %d, pf number %d (PF-%d)\n",
		    event, queue, pf_num, pf_mdet_num);
	/* Theoretically shouldn't happen */
	else
		device_printf(dev,
		    "RX Malicious Driver Detection event (unknown)\n");
}

/**
 * ixl_handle_mdd_event
 *
 * Called from interrupt handler to identify possibly malicious vfs
 * (But also detects events from the PF, as well)
 **/
void
ixl_handle_mdd_event(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	u32 reg;

	/*
	 * Handle both TX/RX because it's possible they could
	 * both trigger in the same interrupt.
	 */
	ixl_handle_tx_mdd_event(pf);
	ixl_handle_rx_mdd_event(pf);

	/* re-enable mdd interrupt cause */
	reg = rd32(hw, I40E_PFINT_ICR0_ENA);
	reg |= I40E_PFINT_ICR0_ENA_MAL_DETECT_MASK;
	wr32(hw, I40E_PFINT_ICR0_ENA, reg);
	ixl_flush(hw);
}

void
ixl_enable_intr(struct ixl_vsi *vsi)
{
	struct ixl_pf		*pf = (struct ixl_pf *)vsi->back;
	struct i40e_hw		*hw = vsi->hw;
	struct ixl_queue	*que = vsi->queues;

	if (pf->msix > 1) {
		for (int i = 0; i < vsi->num_queues; i++, que++)
			ixl_enable_queue(hw, que->me);
	} else
		ixl_enable_intr0(hw);
}

void
ixl_disable_rings_intr(struct ixl_vsi *vsi)
{
	struct i40e_hw		*hw = vsi->hw;
	struct ixl_queue	*que = vsi->queues;

	for (int i = 0; i < vsi->num_queues; i++, que++)
		ixl_disable_queue(hw, que->me);
}

void
ixl_enable_intr0(struct i40e_hw *hw)
{
	u32		reg;

	/* Use IXL_ITR_NONE so ITR isn't updated here */
	reg = I40E_PFINT_DYN_CTL0_INTENA_MASK |
	    I40E_PFINT_DYN_CTL0_CLEARPBA_MASK |
	    (IXL_ITR_NONE << I40E_PFINT_DYN_CTL0_ITR_INDX_SHIFT);
	wr32(hw, I40E_PFINT_DYN_CTL0, reg);
}

void
ixl_disable_intr0(struct i40e_hw *hw)
{
	u32		reg;

	reg = IXL_ITR_NONE << I40E_PFINT_DYN_CTL0_ITR_INDX_SHIFT;
	wr32(hw, I40E_PFINT_DYN_CTL0, reg);
	ixl_flush(hw);
}

void
ixl_enable_queue(struct i40e_hw *hw, int id)
{
	u32		reg;

	reg = I40E_PFINT_DYN_CTLN_INTENA_MASK |
	    I40E_PFINT_DYN_CTLN_CLEARPBA_MASK |
	    (IXL_ITR_NONE << I40E_PFINT_DYN_CTLN_ITR_INDX_SHIFT);
	wr32(hw, I40E_PFINT_DYN_CTLN(id), reg);
}

void
ixl_disable_queue(struct i40e_hw *hw, int id)
{
	u32		reg;

	reg = IXL_ITR_NONE << I40E_PFINT_DYN_CTLN_ITR_INDX_SHIFT;
	wr32(hw, I40E_PFINT_DYN_CTLN(id), reg);
}

void
ixl_update_stats_counters(struct ixl_pf *pf)
{
	struct i40e_hw	*hw = &pf->hw;
	struct ixl_vsi	*vsi = &pf->vsi;
	struct ixl_vf	*vf;

	struct i40e_hw_port_stats *nsd = &pf->stats;
	struct i40e_hw_port_stats *osd = &pf->stats_offsets;

	/* Update hw stats */
	ixl_stat_update32(hw, I40E_GLPRT_CRCERRS(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->crc_errors, &nsd->crc_errors);
	ixl_stat_update32(hw, I40E_GLPRT_ILLERRC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->illegal_bytes, &nsd->illegal_bytes);
	ixl_stat_update48(hw, I40E_GLPRT_GORCH(hw->port),
			   I40E_GLPRT_GORCL(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.rx_bytes, &nsd->eth.rx_bytes);
	ixl_stat_update48(hw, I40E_GLPRT_GOTCH(hw->port),
			   I40E_GLPRT_GOTCL(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.tx_bytes, &nsd->eth.tx_bytes);
	ixl_stat_update32(hw, I40E_GLPRT_RDPC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.rx_discards,
			   &nsd->eth.rx_discards);
	ixl_stat_update48(hw, I40E_GLPRT_UPRCH(hw->port),
			   I40E_GLPRT_UPRCL(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.rx_unicast,
			   &nsd->eth.rx_unicast);
	ixl_stat_update48(hw, I40E_GLPRT_UPTCH(hw->port),
			   I40E_GLPRT_UPTCL(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.tx_unicast,
			   &nsd->eth.tx_unicast);
	ixl_stat_update48(hw, I40E_GLPRT_MPRCH(hw->port),
			   I40E_GLPRT_MPRCL(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.rx_multicast,
			   &nsd->eth.rx_multicast);
	ixl_stat_update48(hw, I40E_GLPRT_MPTCH(hw->port),
			   I40E_GLPRT_MPTCL(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.tx_multicast,
			   &nsd->eth.tx_multicast);
	ixl_stat_update48(hw, I40E_GLPRT_BPRCH(hw->port),
			   I40E_GLPRT_BPRCL(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.rx_broadcast,
			   &nsd->eth.rx_broadcast);
	ixl_stat_update48(hw, I40E_GLPRT_BPTCH(hw->port),
			   I40E_GLPRT_BPTCL(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->eth.tx_broadcast,
			   &nsd->eth.tx_broadcast);

	ixl_stat_update32(hw, I40E_GLPRT_TDOLD(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->tx_dropped_link_down,
			   &nsd->tx_dropped_link_down);
	ixl_stat_update32(hw, I40E_GLPRT_MLFC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->mac_local_faults,
			   &nsd->mac_local_faults);
	ixl_stat_update32(hw, I40E_GLPRT_MRFC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->mac_remote_faults,
			   &nsd->mac_remote_faults);
	ixl_stat_update32(hw, I40E_GLPRT_RLEC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_length_errors,
			   &nsd->rx_length_errors);

	/* Flow control (LFC) stats */
	ixl_stat_update32(hw, I40E_GLPRT_LXONRXC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->link_xon_rx, &nsd->link_xon_rx);
	ixl_stat_update32(hw, I40E_GLPRT_LXONTXC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->link_xon_tx, &nsd->link_xon_tx);
	ixl_stat_update32(hw, I40E_GLPRT_LXOFFRXC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->link_xoff_rx, &nsd->link_xoff_rx);
	ixl_stat_update32(hw, I40E_GLPRT_LXOFFTXC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->link_xoff_tx, &nsd->link_xoff_tx);

	/* Packet size stats rx */
	ixl_stat_update48(hw, I40E_GLPRT_PRC64H(hw->port),
			   I40E_GLPRT_PRC64L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_size_64, &nsd->rx_size_64);
	ixl_stat_update48(hw, I40E_GLPRT_PRC127H(hw->port),
			   I40E_GLPRT_PRC127L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_size_127, &nsd->rx_size_127);
	ixl_stat_update48(hw, I40E_GLPRT_PRC255H(hw->port),
			   I40E_GLPRT_PRC255L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_size_255, &nsd->rx_size_255);
	ixl_stat_update48(hw, I40E_GLPRT_PRC511H(hw->port),
			   I40E_GLPRT_PRC511L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_size_511, &nsd->rx_size_511);
	ixl_stat_update48(hw, I40E_GLPRT_PRC1023H(hw->port),
			   I40E_GLPRT_PRC1023L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_size_1023, &nsd->rx_size_1023);
	ixl_stat_update48(hw, I40E_GLPRT_PRC1522H(hw->port),
			   I40E_GLPRT_PRC1522L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_size_1522, &nsd->rx_size_1522);
	ixl_stat_update48(hw, I40E_GLPRT_PRC9522H(hw->port),
			   I40E_GLPRT_PRC9522L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_size_big, &nsd->rx_size_big);

	/* Packet size stats tx */
	ixl_stat_update48(hw, I40E_GLPRT_PTC64H(hw->port),
			   I40E_GLPRT_PTC64L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->tx_size_64, &nsd->tx_size_64);
	ixl_stat_update48(hw, I40E_GLPRT_PTC127H(hw->port),
			   I40E_GLPRT_PTC127L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->tx_size_127, &nsd->tx_size_127);
	ixl_stat_update48(hw, I40E_GLPRT_PTC255H(hw->port),
			   I40E_GLPRT_PTC255L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->tx_size_255, &nsd->tx_size_255);
	ixl_stat_update48(hw, I40E_GLPRT_PTC511H(hw->port),
			   I40E_GLPRT_PTC511L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->tx_size_511, &nsd->tx_size_511);
	ixl_stat_update48(hw, I40E_GLPRT_PTC1023H(hw->port),
			   I40E_GLPRT_PTC1023L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->tx_size_1023, &nsd->tx_size_1023);
	ixl_stat_update48(hw, I40E_GLPRT_PTC1522H(hw->port),
			   I40E_GLPRT_PTC1522L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->tx_size_1522, &nsd->tx_size_1522);
	ixl_stat_update48(hw, I40E_GLPRT_PTC9522H(hw->port),
			   I40E_GLPRT_PTC9522L(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->tx_size_big, &nsd->tx_size_big);

	ixl_stat_update32(hw, I40E_GLPRT_RUC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_undersize, &nsd->rx_undersize);
	ixl_stat_update32(hw, I40E_GLPRT_RFC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_fragments, &nsd->rx_fragments);
	ixl_stat_update32(hw, I40E_GLPRT_ROC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_oversize, &nsd->rx_oversize);
	ixl_stat_update32(hw, I40E_GLPRT_RJC(hw->port),
			   pf->stat_offsets_loaded,
			   &osd->rx_jabber, &nsd->rx_jabber);
	/* EEE */
	i40e_get_phy_lpi_status(hw, nsd);

	i40e_lpi_stat_update(hw, pf->stat_offsets_loaded,
			  &osd->tx_lpi_count, &nsd->tx_lpi_count,
			  &osd->rx_lpi_count, &nsd->rx_lpi_count);

	pf->stat_offsets_loaded = true;
	/* End hw stats */

	/* Update vsi stats */
	ixl_update_vsi_stats(vsi);

	for (int i = 0; i < pf->num_vfs; i++) {
		vf = &pf->vfs[i];
		if (vf->vf_flags & VF_FLAG_ENABLED)
			ixl_update_eth_stats(&pf->vfs[i].vsi);
	}
}

int
ixl_prepare_for_reset(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	struct ixl_vsi *vsi = &pf->vsi;
	device_t dev = pf->dev;
	int error = 0;

	/* Teardown */
	ixl_teardown_queue_msix(vsi);

	callout_drain(&pf->timer);

	ixl_disable_intr0(hw);
	ixl_teardown_adminq_msix(pf);

	error = i40e_shutdown_adminq(hw);
	if (error)
		device_printf(dev,
		    "Shutdown Admin queue failed with code %d\n", error);

	/* Free ring buffers, locks and filters */
	ixl_vsi_free_queues(vsi);

	ixl_pf_qmgr_release(&pf->qmgr, &pf->qtag);

	ixl_shutdown_hmc(pf);

	return (error);
}

int
ixl_rebuild_hw_structs_after_reset(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	struct ixl_vsi *vsi = &pf->vsi;
	device_t dev = pf->dev;
	enum i40e_get_fw_lldp_status_resp lldp_status;
	int error = 0;

	device_printf(dev, "Rebuilding driver state...\n");

	/* Setup */
	error = i40e_init_adminq(hw);
	if (error != 0 && error != I40E_ERR_FIRMWARE_API_VERSION) {
		device_printf(dev, "Unable to initialize Admin Queue, error %d\n",
		    error);
		goto ixl_rebuild_hw_structs_after_reset_err;
	}

	i40e_clear_pxe_mode(hw);

	error = ixl_get_hw_capabilities(pf);
	if (error) {
		device_printf(dev, "ixl_get_hw_capabilities failed: %d\n", error);
		goto ixl_rebuild_hw_structs_after_reset_err;
	}
	ixl_configure_intr0_msix(pf);
	ixl_enable_intr0(hw);

	/* Do not setup LAN HMC and a queue manager while in recovery mode */
	if (!IXL_PF_IN_RECOVERY_MODE(pf)) {
		error = ixl_setup_hmc(pf);
		if (error)
			goto ixl_rebuild_hw_structs_after_reset_err;

		if (!pf->qmgr.qinfo) {
			/* Init queue allocation manager */
			error = ixl_pf_qmgr_init(&pf->qmgr, hw->func_caps.num_rx_qp);
			if (error) {
				device_printf(dev, "Failed to init queue manager for PF queues, error %d\n",
				    error);
				goto ixl_rebuild_hw_structs_after_reset_err;
			}
		}
		/* reserve a contiguous allocation for the PF's VSI */
		error = ixl_pf_qmgr_alloc_contiguous(&pf->qmgr, vsi->num_queues, &pf->qtag);
		if (error) {
			device_printf(dev, "Failed to reserve queues for PF LAN VSI, error %d\n",
			    error);
			/* TODO: error handling */
		} else
			device_printf(dev, "Allocating %d queues for PF LAN VSI; %d queues active\n",
			    pf->qtag.num_allocated, pf->qtag.num_active);

		error = ixl_switch_config(pf);
		if (error) {
			device_printf(dev, "%s: ixl_switch_config() failed: %d\n",
			     __func__, error);
			goto ixl_rebuild_hw_structs_after_reset_err;
		}
	} /* not in recovery mode */

	/* Remove default filters reinstalled by FW on reset */
	ixl_del_default_hw_filters(vsi);

	if (ixl_vsi_setup_queues(vsi)) {
		device_printf(dev, "setup queues failed!\n");
		error = ENOMEM;
		goto ixl_rebuild_hw_structs_after_reset_err;
	}

	ixl_vsi_add_sysctls(vsi, "pf", true);

	if (pf->msix > 1) {
		error = ixl_setup_adminq_msix(pf);
		if (error) {
			device_printf(dev, "ixl_setup_adminq_msix() error: %d\n",
			    error);
			goto ixl_rebuild_hw_structs_after_reset_err;
		}

		ixl_configure_intr0_msix(pf);
		ixl_enable_intr0(hw);

		error = ixl_setup_queue_msix(vsi);
		if (error) {
			device_printf(dev, "ixl_setup_queue_msix() error: %d\n",
			    error);
			goto ixl_rebuild_hw_structs_after_reset_err;
		}
		error = ixl_setup_queue_tqs(vsi);
		if (error) {
			device_printf(dev, "ixl_setup_queue_tqs() error: %d\n",
			    error);
			goto ixl_rebuild_hw_structs_after_reset_err;
		}
	} else {
		error = ixl_setup_legacy(pf);
		if (error) {
			device_printf(dev, "ixl_setup_legacy() error: %d\n",
			    error);
			goto ixl_rebuild_hw_structs_after_reset_err;
		}
	}

	/* Do not bring interface up in recovery mode */
	if (IXL_PF_IN_RECOVERY_MODE(pf))
		return (error);

	/* Determine link state */
	ixl_attach_get_link_status(pf);

	/* Receive broadcast Ethernet frames */
	i40e_aq_set_vsi_broadcast(hw, vsi->seid, TRUE, NULL);

	i40e_aq_set_dcb_parameters(hw, TRUE, NULL);

	/* Query device FW LLDP status */
	if (i40e_get_fw_lldp_status(hw, &lldp_status) == I40E_SUCCESS) {
		if (lldp_status == I40E_GET_FW_LLDP_STATUS_DISABLED) {
			ixl_set_state(&pf->state,
			    IXL_STATE_FW_LLDP_DISABLED);
		} else {
			ixl_clear_state(&pf->state,
			    IXL_STATE_FW_LLDP_DISABLED);
		}
	}

	IXL_PF_LOCK(pf);
	callout_reset(&pf->timer, hz, ixl_local_timer, pf);
	IXL_PF_UNLOCK(pf);

	device_printf(dev, "Rebuilding driver state done.\n");
	return (0);

ixl_rebuild_hw_structs_after_reset_err:
	device_printf(dev, "Reload the driver to recover\n");
	return (error);
}

void
ixl_handle_empr_reset(struct ixl_pf *pf)
{
	struct ifnet *ifp = pf->vsi.ifp;

	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0)
		ixl_stop(pf);

	ixl_prepare_for_reset(pf);
	/*
	 * i40e_pf_reset checks the type of reset and acts
	 * accordingly. If EMP or Core reset was performed
	 * doing PF reset is not necessary and it sometimes
	 * fails.
	 */
	ixl_pf_reset(pf);

	if (!IXL_PF_IN_RECOVERY_MODE(pf) &&
	    ixl_get_fw_mode(pf) == IXL_FW_MODE_RECOVERY) {
		ixl_set_state(&pf->state, IXL_STATE_RECOVERY_MODE);
		device_printf(pf->dev,
		    "Firmware recovery mode detected. Limiting functionality. Refer to Intel(R) Ethernet Adapters and Devices User Guide for details on firmware recovery mode.\n");
		pf->link_up = FALSE;
		ixl_update_link_status(pf);
	}

	ixl_rebuild_hw_structs_after_reset(pf);

	ixl_clear_state(&pf->state, IXL_STATE_RESETTING);

	/*
	 * User may request change of interface state while reset
	 * is in progress. To avoid getting out of sync with expected
	 * state don't try to save the IFF_DRV_RUNNING flag but
	 * use interface flags to check if init should be run.
	 */
	if ((ifp->if_flags & IFF_UP) != 0)
		ixl_init(pf);
}

/*
** Tasklet handler for MSI-X Adminq interrupts
**  - do outside interrupt since it might sleep
*/
void
ixl_do_adminq(void *context, int pending)
{
	struct ixl_pf			*pf = context;
	struct i40e_hw			*hw = &pf->hw;
	struct i40e_arq_event_info	event;
	i40e_status			ret;
	device_t			dev = pf->dev;
	u32				loop = 0;
	u16				opcode, arq_pending;

	if (IXL_PF_IS_RESETTING(pf)) {
		/* Flag cleared at end of this function */
		ixl_handle_empr_reset(pf);
		return;
	}

	if (ixl_test_state(&pf->state, IXL_STATE_MDD_PENDING))
		ixl_handle_mdd_event(pf);

	/* Admin Queue handling */
	event.buf_len = IXL_AQ_BUF_SZ;
	event.msg_buf = malloc(event.buf_len,
	    M_IXL, M_NOWAIT | M_ZERO);
	if (!event.msg_buf) {
		device_printf(dev, "%s: Unable to allocate memory for Admin"
		    " Queue event!\n", __func__);
		return;
	}

	IXL_PF_LOCK(pf);
	/* clean and process any events */
	do {
		ret = i40e_clean_arq_element(hw, &event, &arq_pending);
		if (ret)
			break;
		opcode = LE16_TO_CPU(event.desc.opcode);
		ixl_dbg(pf, IXL_DBG_AQ,
		    "Admin Queue event: %#06x\n", opcode);
		switch (opcode) {
		case i40e_aqc_opc_get_link_status:
			ixl_link_event(pf, &event);
			break;
		case i40e_aqc_opc_send_msg_to_pf:
#ifdef PCI_IOV
			ixl_handle_vf_msg(pf, &event);
#endif
			break;
		case i40e_aqc_opc_event_lan_overflow:
		default:
			break;
		}

	} while (arq_pending && (loop++ < IXL_ADM_LIMIT));

	free(event.msg_buf, M_IXL);

	/* If there are still messages to process, reschedule. */
	if (arq_pending > 0)
		taskqueue_enqueue(pf->tq, &pf->adminq);
	else
		ixl_enable_intr0(hw);

	IXL_PF_UNLOCK(pf);
}

/**
 * Update VSI-specific ethernet statistics counters.
 **/
void
ixl_update_eth_stats(struct ixl_vsi *vsi)
{
	struct ixl_pf *pf = (struct ixl_pf *)vsi->back;
	struct i40e_hw *hw = &pf->hw;
	struct i40e_eth_stats *es;
	struct i40e_eth_stats *oes;
	u16 stat_idx = vsi->info.stat_counter_idx;

	es = &vsi->eth_stats;
	oes = &vsi->eth_stats_offsets;

	/* Gather up the stats that the hw collects */
	ixl_stat_update32(hw, I40E_GLV_TEPC(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->tx_errors, &es->tx_errors);
	ixl_stat_update32(hw, I40E_GLV_RDPC(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->rx_discards, &es->rx_discards);

	ixl_stat_update48(hw, I40E_GLV_GORCH(stat_idx),
			   I40E_GLV_GORCL(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->rx_bytes, &es->rx_bytes);
	ixl_stat_update48(hw, I40E_GLV_UPRCH(stat_idx),
			   I40E_GLV_UPRCL(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->rx_unicast, &es->rx_unicast);
	ixl_stat_update48(hw, I40E_GLV_MPRCH(stat_idx),
			   I40E_GLV_MPRCL(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->rx_multicast, &es->rx_multicast);
	ixl_stat_update48(hw, I40E_GLV_BPRCH(stat_idx),
			   I40E_GLV_BPRCL(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->rx_broadcast, &es->rx_broadcast);

	ixl_stat_update48(hw, I40E_GLV_GOTCH(stat_idx),
			   I40E_GLV_GOTCL(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->tx_bytes, &es->tx_bytes);
	ixl_stat_update48(hw, I40E_GLV_UPTCH(stat_idx),
			   I40E_GLV_UPTCL(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->tx_unicast, &es->tx_unicast);
	ixl_stat_update48(hw, I40E_GLV_MPTCH(stat_idx),
			   I40E_GLV_MPTCL(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->tx_multicast, &es->tx_multicast);
	ixl_stat_update48(hw, I40E_GLV_BPTCH(stat_idx),
			   I40E_GLV_BPTCL(stat_idx),
			   vsi->stat_offsets_loaded,
			   &oes->tx_broadcast, &es->tx_broadcast);
	vsi->stat_offsets_loaded = true;
}

void
ixl_update_vsi_stats(struct ixl_vsi *vsi)
{
	struct ixl_pf		*pf;
	struct i40e_eth_stats	*es;
	u64			tx_discards, csum_errs;

	struct i40e_hw_port_stats *nsd;

	pf = vsi->back;
	es = &vsi->eth_stats;
	nsd = &pf->stats;

	ixl_update_eth_stats(vsi);

	tx_discards = es->tx_discards + nsd->tx_dropped_link_down;
	csum_errs = 0;
	for (int i = 0; i < vsi->num_queues; i++) {
		tx_discards += vsi->queues[i].txr.br->br_drops;
		csum_errs += vsi->queues[i].rx_csum_errs;
	}
	nsd->checksum_error = csum_errs;

	/* Update ifnet stats */
	IXL_SET_IPACKETS(vsi, es->rx_unicast +
	                   es->rx_multicast +
			   es->rx_broadcast);
	IXL_SET_OPACKETS(vsi, es->tx_unicast +
	                   es->tx_multicast +
			   es->tx_broadcast);
	IXL_SET_IBYTES(vsi, es->rx_bytes);
	IXL_SET_OBYTES(vsi, es->tx_bytes);
	IXL_SET_IMCASTS(vsi, es->rx_multicast);
	IXL_SET_OMCASTS(vsi, es->tx_multicast);

	IXL_SET_IERRORS(vsi, nsd->crc_errors + nsd->illegal_bytes +
	    nsd->checksum_error + nsd->rx_length_errors +
	    nsd->rx_undersize + nsd->rx_fragments + nsd->rx_oversize +
	    nsd->rx_jabber);
	IXL_SET_OERRORS(vsi, es->tx_errors);
	IXL_SET_IQDROPS(vsi, es->rx_discards + nsd->eth.rx_discards);
	IXL_SET_OQDROPS(vsi, tx_discards);
	IXL_SET_NOPROTO(vsi, es->rx_unknown_protocol);
	IXL_SET_COLLISIONS(vsi, 0);
}

/**
 * Reset all of the stats for the given pf
 **/
void
ixl_pf_reset_stats(struct ixl_pf *pf)
{
	bzero(&pf->stats, sizeof(struct i40e_hw_port_stats));
	bzero(&pf->stats_offsets, sizeof(struct i40e_hw_port_stats));
	pf->stat_offsets_loaded = false;
}

/**
 * Resets all stats of the given vsi
 **/
void
ixl_vsi_reset_stats(struct ixl_vsi *vsi)
{
	bzero(&vsi->eth_stats, sizeof(struct i40e_eth_stats));
	bzero(&vsi->eth_stats_offsets, sizeof(struct i40e_eth_stats));
	vsi->stat_offsets_loaded = false;
}

/**
 * Read and update a 48 bit stat from the hw
 *
 * Since the device stats are not reset at PFReset, they likely will not
 * be zeroed when the driver starts.  We'll save the first values read
 * and use them as offsets to be subtracted from the raw values in order
 * to report stats that count from zero.
 **/
void
ixl_stat_update48(struct i40e_hw *hw, u32 hireg, u32 loreg,
	bool offset_loaded, u64 *offset, u64 *stat)
{
	u64 new_data;

#if defined(__FreeBSD__) && (__FreeBSD_version >= 1000000) && defined(__amd64__)
	new_data = rd64(hw, loreg);
#else
	/*
	 * Use two rd32's instead of one rd64; FreeBSD versions before
	 * 10 don't support 64-bit bus reads/writes.
	 */
	new_data = rd32(hw, loreg);
	new_data |= ((u64)(rd32(hw, hireg) & 0xFFFF)) << 32;
#endif

	if (!offset_loaded)
		*offset = new_data;
	if (new_data >= *offset)
		*stat = new_data - *offset;
	else
		*stat = (new_data + ((u64)1 << 48)) - *offset;
	*stat &= 0xFFFFFFFFFFFFULL;
}

/**
 * Read and update a 32 bit stat from the hw
 **/
void
ixl_stat_update32(struct i40e_hw *hw, u32 reg,
	bool offset_loaded, u64 *offset, u64 *stat)
{
	u32 new_data;

	new_data = rd32(hw, reg);
	if (!offset_loaded)
		*offset = new_data;
	if (new_data >= *offset)
		*stat = (u32)(new_data - *offset);
	else
		*stat = (u32)((new_data + ((u64)1 << 32)) - *offset);
}

void
ixl_add_device_sysctls(struct ixl_pf *pf)
{
	device_t dev = pf->dev;
	struct i40e_hw *hw = &pf->hw;

	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
	struct sysctl_oid_list *ctx_list =
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev));

	struct sysctl_oid *debug_node;
	struct sysctl_oid_list *debug_list;

	struct sysctl_oid *fec_node;
	struct sysctl_oid_list *fec_list;
	struct sysctl_oid *eee_node;
	struct sysctl_oid_list *eee_list;
	/* Set up sysctls */
	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "fc", CTLTYPE_INT | CTLFLAG_RW,
	    pf, 0, ixl_sysctl_set_flowcntl, "I", IXL_SYSCTL_HELP_FC);

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "advertise_speed", CTLTYPE_INT | CTLFLAG_RW,
	    pf, 0, ixl_sysctl_set_advertise, "I", IXL_SYSCTL_HELP_SET_ADVERTISE);

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "supported_speeds", CTLTYPE_INT | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_supported_speeds, "I", IXL_SYSCTL_HELP_SUPPORTED_SPEED);

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "current_speed", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_current_speed, "A", "Current Port Speed");

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "fw_version", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_show_fw, "A", "Firmware version");

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "unallocated_queues", CTLTYPE_INT | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_unallocated_queues, "I",
	    "Queues not allocated to a PF or VF");

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "tx_itr", CTLTYPE_INT | CTLFLAG_RW,
	    pf, 0, ixl_sysctl_pf_tx_itr, "I",
	    "Immediately set TX ITR value for all queues");

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "rx_itr", CTLTYPE_INT | CTLFLAG_RW,
	    pf, 0, ixl_sysctl_pf_rx_itr, "I",
	    "Immediately set RX ITR value for all queues");

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "dynamic_rx_itr", CTLTYPE_INT | CTLFLAG_RW,
	    pf, IXL_DYN_ITR_RX, ixl_sysctl_pf_dynamic_itr, "I",
	    "Enable dynamic RX ITR");

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "dynamic_tx_itr", CTLTYPE_INT | CTLFLAG_RW,
	    pf, IXL_DYN_ITR_TX, ixl_sysctl_pf_dynamic_itr, "I",
	    "Enable dynamic TX ITR");

	SYSCTL_ADD_INT(ctx, ctx_list,
	    OID_AUTO, "tx_ring_size", CTLFLAG_RD,
	    &pf->vsi.num_tx_desc, 0, "TX ring size");

	SYSCTL_ADD_INT(ctx, ctx_list,
	    OID_AUTO, "rx_ring_size", CTLFLAG_RD,
	    &pf->vsi.num_rx_desc, 0, "RX ring size");

	/* Add FEC sysctls for 25G adapters */
	if (i40e_is_25G_device(hw->device_id)) {
		fec_node = SYSCTL_ADD_NODE(ctx, ctx_list,
		    OID_AUTO, "fec", CTLFLAG_RD, NULL, "FEC Sysctls");
		fec_list = SYSCTL_CHILDREN(fec_node);

		SYSCTL_ADD_PROC(ctx, fec_list,
		    OID_AUTO, "fc_ability", CTLTYPE_INT | CTLFLAG_RW,
		    pf, 0, ixl_sysctl_fec_fc_ability, "I", "FC FEC ability enabled");

		SYSCTL_ADD_PROC(ctx, fec_list,
		    OID_AUTO, "rs_ability", CTLTYPE_INT | CTLFLAG_RW,
		    pf, 0, ixl_sysctl_fec_rs_ability, "I", "RS FEC ability enabled");

		SYSCTL_ADD_PROC(ctx, fec_list,
		    OID_AUTO, "fc_requested", CTLTYPE_INT | CTLFLAG_RW,
		    pf, 0, ixl_sysctl_fec_fc_request, "I", "FC FEC mode requested on link");

		SYSCTL_ADD_PROC(ctx, fec_list,
		    OID_AUTO, "rs_requested", CTLTYPE_INT | CTLFLAG_RW,
		    pf, 0, ixl_sysctl_fec_rs_request, "I", "RS FEC mode requested on link");

		SYSCTL_ADD_PROC(ctx, fec_list,
		    OID_AUTO, "auto_fec_enabled", CTLTYPE_INT | CTLFLAG_RW,
		    pf, 0, ixl_sysctl_fec_auto_enable, "I", "Let FW decide FEC ability/request modes");
	}

	SYSCTL_ADD_PROC(ctx, ctx_list,
	    OID_AUTO, "fw_lldp", CTLTYPE_INT | CTLFLAG_RW,
	    pf, 0, ixl_sysctl_fw_lldp, "I", IXL_SYSCTL_HELP_FW_LLDP);

	eee_node = SYSCTL_ADD_NODE(ctx, ctx_list,
	    OID_AUTO, "eee", CTLFLAG_RD, NULL,
	    "Energy Efficient Ethernet (EEE) Sysctls");
	eee_list = SYSCTL_CHILDREN(eee_node);

	SYSCTL_ADD_PROC(ctx, eee_list,
	    OID_AUTO, "enable", CTLTYPE_INT | CTLFLAG_RW,
	    pf, 0, ixl_sysctl_eee_enable, "I",
	    "Enable Energy Efficient Ethernet (EEE)");

	SYSCTL_ADD_UINT(ctx, eee_list, OID_AUTO, "tx_lpi_status",
	    CTLFLAG_RD, &pf->stats.tx_lpi_status, 0,
	    "TX LPI status");

	SYSCTL_ADD_UINT(ctx, eee_list, OID_AUTO, "rx_lpi_status",
	    CTLFLAG_RD, &pf->stats.rx_lpi_status, 0,
	    "RX LPI status");

	SYSCTL_ADD_UQUAD(ctx, eee_list, OID_AUTO, "tx_lpi_count",
	    CTLFLAG_RD, &pf->stats.tx_lpi_count,
	    "TX LPI count");

	SYSCTL_ADD_UQUAD(ctx, eee_list, OID_AUTO, "rx_lpi_count",
	    CTLFLAG_RD, &pf->stats.rx_lpi_count,
	    "RX LPI count");
	SYSCTL_ADD_PROC(ctx, ctx_list, OID_AUTO,
	    "link_active_on_if_down",
	    CTLTYPE_INT | CTLFLAG_RWTUN,
	    pf, 0, ixl_sysctl_set_link_active, "I",
	    IXL_SYSCTL_HELP_SET_LINK_ACTIVE);

	/* Add sysctls meant to print debug information, but don't list them
	 * in "sysctl -a" output. */
	debug_node = SYSCTL_ADD_NODE(ctx, ctx_list,
	    OID_AUTO, "debug", CTLFLAG_RD | CTLFLAG_SKIP, NULL, "Debug Sysctls");
	debug_list = SYSCTL_CHILDREN(debug_node);

	SYSCTL_ADD_UINT(ctx, debug_list,
	    OID_AUTO, "shared_debug_mask", CTLFLAG_RW,
	    &pf->hw.debug_mask, 0, "Shared code debug message level");

	SYSCTL_ADD_UINT(ctx, debug_list,
	    OID_AUTO, "core_debug_mask", CTLFLAG_RW,
	    &pf->dbg_mask, 0, "Non-shared code debug message level");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "link_status", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_link_status, "A", IXL_SYSCTL_HELP_LINK_STATUS);

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "phy_abilities_init", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 1, ixl_sysctl_phy_abilities, "A", "Initial PHY Abilities");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "phy_abilities", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_phy_abilities, "A", "PHY Abilities");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "filter_list", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_sw_filter_list, "A", "SW Filter List");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "hw_res_alloc", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_hw_res_alloc, "A", "HW Resource Allocation");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "switch_config", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_switch_config, "A", "HW Switch Configuration");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "rss_key", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_hkey, "A", "View RSS key");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "rss_lut", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_hlut, "A", "View RSS lookup table");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "rss_hena", CTLTYPE_ULONG | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_hena, "LU", "View enabled packet types for RSS");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "disable_fw_link_management", CTLTYPE_INT | CTLFLAG_WR,
	    pf, 0, ixl_sysctl_fw_link_management, "I", "Disable FW Link Management");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "dump_debug_data", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_dump_debug_data, "A", "Dump Debug Data from FW");

	SYSCTL_ADD_PROC(ctx, debug_list,
	    OID_AUTO, "phy_statistics", CTLTYPE_STRING | CTLFLAG_RD,
	    pf, 0, ixl_sysctl_phy_statistics, "A", "PHY Statistics");

	if (pf->has_i2c) {
		SYSCTL_ADD_PROC(ctx, debug_list,
		    OID_AUTO, "read_i2c_byte", CTLTYPE_INT | CTLFLAG_RW,
		    pf, 0, ixl_sysctl_read_i2c_byte, "I", IXL_SYSCTL_HELP_READ_I2C);

		SYSCTL_ADD_PROC(ctx, debug_list,
		    OID_AUTO, "write_i2c_byte", CTLTYPE_INT | CTLFLAG_RW,
		    pf, 0, ixl_sysctl_write_i2c_byte, "I", IXL_SYSCTL_HELP_WRITE_I2C);

		SYSCTL_ADD_PROC(ctx, debug_list,
		    OID_AUTO, "read_i2c_diag_data", CTLTYPE_STRING | CTLFLAG_RD,
		    pf, 0, ixl_sysctl_read_i2c_diag_data, "A", "Dump selected diagnostic data from FW");
	}

#ifdef PCI_IOV
	SYSCTL_ADD_UINT(ctx, debug_list,
	    OID_AUTO, "vc_debug_level", CTLFLAG_RW, &pf->vc_debug_lvl,
	    0, "PF/VF Virtual Channel debug level");
#endif
}

/*
 * Primarily for finding out how many queues can be assigned to VFs,
 * at runtime.
 */
static int
ixl_sysctl_unallocated_queues(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int queues;

	IXL_PF_LOCK(pf);
	queues = (int)ixl_pf_qmgr_get_num_free(&pf->qmgr);
	IXL_PF_UNLOCK(pf);

	return sysctl_handle_int(oidp, NULL, queues, req);
}

/*
** Set flow control using sysctl:
** 	0 - off
**	1 - rx pause
**	2 - tx pause
**	3 - full
*/
int
ixl_sysctl_set_flowcntl(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	int requested_fc, error = 0;
	enum i40e_status_code aq_error = 0;
	u8 fc_aq_err = 0;

	/* Get request */
	requested_fc = pf->fc;
	error = sysctl_handle_int(oidp, &requested_fc, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);
	if (IXL_PF_IN_RECOVERY_MODE(pf)) {
		device_printf(dev, "Interface is currently in FW recovery mode. "
				"Setting flow control not supported\n");
		return (EINVAL);
	}
	if (requested_fc < 0 || requested_fc > 3) {
		device_printf(dev,
		    "Invalid fc mode; valid modes are 0 through 3\n");
		return (EINVAL);
	}

	/* Set fc ability for port */
	hw->fc.requested_mode = requested_fc;
	aq_error = i40e_set_fc(hw, &fc_aq_err, TRUE);
	if (aq_error) {
		device_printf(dev,
		    "%s: Error setting Flow Control mode %d; fc_err %#x\n",
		    __func__, aq_error, fc_aq_err);
		return (EIO);
	}
	pf->fc = requested_fc;

	/* Get new link state */
	i40e_msec_delay(250);
	hw->phy.get_link_info = TRUE;
	i40e_get_link_status(hw, &pf->link_up);
	ixl_dbg_link(pf, "%s link_up: %d\n", __func__, pf->link_up);

	return (0);
}


static const char *
ixl_link_speed_string(enum i40e_aq_link_speed link_speed)
{
	const char * link_speed_str[] = {
		"Unknown",
		"100 Mbps",
		"1 Gbps",
		"10 Gbps",
		"40 Gbps",
		"20 Gbps",
		"25 Gbps",
		"2.5 Gbps",
		"5 Gbps"
	};
	int index;

	switch (link_speed) {
	case I40E_LINK_SPEED_100MB:
		index = 1;
		break;
	case I40E_LINK_SPEED_1GB:
		index = 2;
		break;
	case I40E_LINK_SPEED_10GB:
		index = 3;
		break;
	case I40E_LINK_SPEED_40GB:
		index = 4;
		break;
	case I40E_LINK_SPEED_20GB:
		index = 5;
		break;
	case I40E_LINK_SPEED_25GB:
		index = 6;
		break;
	case I40E_LINK_SPEED_2_5GB:
		index = 7;
		break;
	case I40E_LINK_SPEED_5GB:
		index = 8;
		break;
	case I40E_LINK_SPEED_UNKNOWN:
	default:
		index = 0;
		break;
	}

	return (link_speed_str[index]);
}

int
ixl_sysctl_current_speed(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	int error = 0;

	ixl_update_link_status(pf);

	error = sysctl_handle_string(oidp,
	    __DECONST(void *,
		ixl_link_speed_string(hw->phy.link_info.link_speed)),
	    8, req);

	return (error);
}

/*
 * Converts 8-bit speeds value to and from sysctl flags and
 * Admin Queue flags.
 */
static u8
ixl_convert_sysctl_aq_link_speed(u8 speeds, bool to_aq)
{
#define SPEED_MAP_SIZE 8
	static u16 speedmap[SPEED_MAP_SIZE] = {
		(I40E_LINK_SPEED_100MB | (0x1 << 8)),
		(I40E_LINK_SPEED_1GB   | (0x2 << 8)),
		(I40E_LINK_SPEED_10GB  | (0x4 << 8)),
		(I40E_LINK_SPEED_20GB  | (0x8 << 8)),
		(I40E_LINK_SPEED_25GB  | (0x10 << 8)),
		(I40E_LINK_SPEED_40GB  | (0x20 << 8)),
		(I40E_LINK_SPEED_2_5GB | (0x40 << 8)),
		(I40E_LINK_SPEED_5GB   | (0x80 << 8)),
	};
	u8 retval = 0;

	for (int i = 0; i < SPEED_MAP_SIZE; i++) {
		if (to_aq)
			retval |= (speeds & (speedmap[i] >> 8)) ? (speedmap[i] & 0xff) : 0;
		else
			retval |= (speeds & speedmap[i]) ? (speedmap[i] >> 8) : 0;
	}

	return (retval);
}

int
ixl_set_advertised_speeds(struct ixl_pf *pf, int speeds, bool from_aq)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct i40e_aq_get_phy_abilities_resp abilities;
	struct i40e_aq_set_phy_config config;
	enum i40e_status_code aq_error = 0;

	ixl_dbg_link(pf, "%s speeds: %0x from_aq: %d\n",
	    __func__, speeds, from_aq);

	/* Get current capability information */
	aq_error = i40e_aq_get_phy_capabilities(hw,
	    FALSE, FALSE, &abilities, NULL);
	if (aq_error) {
		device_printf(dev,
		    "%s: Error getting phy capabilities %d,"
		    " aq error: %d\n", __func__, aq_error,
		    hw->aq.asq_last_status);
		return (EIO);
	}

	/* Prepare new config */
	bzero(&config, sizeof(config));
	if (from_aq)
		config.link_speed = speeds;
	else
		config.link_speed = ixl_convert_sysctl_aq_link_speed(speeds, true);
	config.phy_type = abilities.phy_type;
	config.phy_type_ext = abilities.phy_type_ext;
	config.abilities = abilities.abilities
	    | I40E_AQ_PHY_ENABLE_ATOMIC_LINK;
	config.eee_capability = abilities.eee_capability;
	config.eeer = abilities.eeer_val;
	config.low_power_ctrl = abilities.d3_lpan;
	config.fec_config = abilities.fec_cfg_curr_mod_ext_info
	    & I40E_AQ_PHY_FEC_CONFIG_MASK;

	/* Do aq command & restart link */
	aq_error = i40e_aq_set_phy_config(hw, &config, NULL);
	if (aq_error) {
		device_printf(dev,
		    "%s: Error setting new phy config %d,"
		    " aq error: %d\n", __func__, aq_error,
		    hw->aq.asq_last_status);
		return (EIO);
	}

	return (0);
}

/*
** Supported link speeds
**	Flags:
**	 0x1 - 100 Mb
**	 0x2 - 1G
**	 0x4 - 10G
**	 0x8 - 20G
**	0x10 - 25G
**	0x20 - 40G
**	0x40 - 2.5G
**	0x80 - 5G
*/
static int
ixl_sysctl_supported_speeds(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int supported = ixl_convert_sysctl_aq_link_speed(pf->supported_speeds, false);

	return sysctl_handle_int(oidp, NULL, supported, req);
}

/*
** Control link advertise speed:
**	Flags:
**	 0x1 - advertise 100 Mb
**	 0x2 - advertise 1G
**	 0x4 - advertise 10G
**	 0x8 - advertise 20G
**	0x10 - advertise 25G
**	0x20 - advertise 40G
**	0x40 - advertise 2.5G
**	0x80 - advertise 5G
**
**	Set to 0 to disable link
*/
int
ixl_sysctl_set_advertise(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	device_t dev = pf->dev;
	u8 converted_speeds;
	int requested_ls = 0;
	int error = 0;

	/* Read in new mode */
	requested_ls = pf->advertised_speed;
	error = sysctl_handle_int(oidp, &requested_ls, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);
	if (IXL_PF_IN_RECOVERY_MODE(pf)) {
		device_printf(dev, "Interface is currently in FW recovery mode. "
				"Setting advertise speed not supported\n");
		return (EINVAL);
	}

	/* Error out if bits outside of possible flag range are set */
	if ((requested_ls & ~((u8)0xFF)) != 0) {
		device_printf(dev, "Input advertised speed out of range; "
		    "valid flags are: 0x%02x\n",
		    ixl_convert_sysctl_aq_link_speed(pf->supported_speeds, false));
		return (EINVAL);
	}

	/* Check if adapter supports input value */
	converted_speeds = ixl_convert_sysctl_aq_link_speed((u8)requested_ls, true);
	if ((converted_speeds | pf->supported_speeds) != pf->supported_speeds) {
		device_printf(dev, "Invalid advertised speed; "
		    "valid flags are: 0x%02x\n",
		    ixl_convert_sysctl_aq_link_speed(pf->supported_speeds, false));
		return (EINVAL);
	}

	error = ixl_set_advertised_speeds(pf, requested_ls, false);
	if (error)
		return (error);

	pf->advertised_speed = requested_ls;
	ixl_update_link_status(pf);
	return (0);
}

/*
 * Input: bitmap of enum i40e_aq_link_speed
 */
static u64
ixl_max_aq_speed_to_value(u8 link_speeds)
{
	if (link_speeds & I40E_LINK_SPEED_40GB)
		return IF_Gbps(40);
	if (link_speeds & I40E_LINK_SPEED_25GB)
		return IF_Gbps(25);
	if (link_speeds & I40E_LINK_SPEED_20GB)
		return IF_Gbps(20);
	if (link_speeds & I40E_LINK_SPEED_10GB)
		return IF_Gbps(10);
	if (link_speeds & I40E_LINK_SPEED_5GB)
		return IF_Gbps(5);
	if (link_speeds & I40E_LINK_SPEED_2_5GB)
		return IF_Mbps(2500);
	if (link_speeds & I40E_LINK_SPEED_1GB)
		return IF_Gbps(1);
	if (link_speeds & I40E_LINK_SPEED_100MB)
		return IF_Mbps(100);
	else
		/* Minimum supported link speed */
		return IF_Mbps(100);
}

/*
** Get the width and transaction speed of
** the bus this adapter is plugged into.
*/
void
ixl_get_bus_info(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
        u16 link;
        u32 offset, num_ports;
	u64 max_speed;

	/* Some devices don't use PCIE */
	if (hw->mac.type == I40E_MAC_X722)
		return;

        /* Read PCI Express Capabilities Link Status Register */
        pci_find_cap(dev, PCIY_EXPRESS, &offset);
        link = pci_read_config(dev, offset + PCIER_LINK_STA, 2);

	/* Fill out hw struct with PCIE info */
	i40e_set_pci_config_data(hw, link);

	/* Use info to print out bandwidth messages */
        device_printf(dev,"PCI Express Bus: Speed %s %s\n",
            ((hw->bus.speed == i40e_bus_speed_8000) ? "8.0GT/s":
            (hw->bus.speed == i40e_bus_speed_5000) ? "5.0GT/s":
            (hw->bus.speed == i40e_bus_speed_2500) ? "2.5GT/s":"Unknown"),
            (hw->bus.width == i40e_bus_width_pcie_x8) ? "Width x8" :
            (hw->bus.width == i40e_bus_width_pcie_x4) ? "Width x4" :
            (hw->bus.width == i40e_bus_width_pcie_x2) ? "Width x2" :
            (hw->bus.width == i40e_bus_width_pcie_x1) ? "Width x1" :
            ("Unknown"));

	/*
	 * If adapter is in slot with maximum supported speed,
	 * no warning message needs to be printed out.
	 */
	if (hw->bus.speed >= i40e_bus_speed_8000
	    && hw->bus.width >= i40e_bus_width_pcie_x8)
		return;

	num_ports = bitcount32(hw->func_caps.valid_functions);
	max_speed = ixl_max_aq_speed_to_value(pf->supported_speeds) / 1000000;

	if ((num_ports * max_speed) > hw->bus.speed * hw->bus.width) {
                device_printf(dev, "PCI-Express bandwidth available"
                    " for this device may be insufficient for"
                    " optimal performance.\n");
                device_printf(dev, "Please move the device to a different"
		    " PCI-e link with more lanes and/or higher"
		    " transfer rate.\n");
        }
}

static int
ixl_sysctl_show_fw(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf	*pf = (struct ixl_pf *)arg1;
	struct i40e_hw	*hw = &pf->hw;
	struct sbuf	*sbuf;

	sbuf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	ixl_nvm_version_str(hw, sbuf);
	sbuf_finish(sbuf);
	sbuf_delete(sbuf);

	return (0);
}

void
ixl_print_nvm_cmd(device_t dev, struct i40e_nvm_access *nvma)
{
	u8 nvma_ptr = nvma->config & 0xFF;
	u8 nvma_flags = (nvma->config & 0xF00) >> 8;
	const char * cmd_str;

	switch (nvma->command) {
	case I40E_NVM_READ:
		if (nvma_ptr == 0xF && nvma_flags == 0xF &&
		    nvma->offset == 0 && nvma->data_size == 1) {
			device_printf(dev, "NVMUPD: Get Driver Status Command\n");
			return;
		}
		cmd_str = "READ ";
		break;
	case I40E_NVM_WRITE:
		cmd_str = "WRITE";
		break;
	default:
		device_printf(dev, "NVMUPD: unknown command: 0x%08x\n", nvma->command);
		return;
	}
	device_printf(dev,
	    "NVMUPD: cmd: %s ptr: 0x%02x flags: 0x%01x offset: 0x%08x data_s: 0x%08x\n",
	    cmd_str, nvma_ptr, nvma_flags, nvma->offset, nvma->data_size);
}

int
ixl_handle_nvmupd_cmd(struct ixl_pf *pf, struct ifdrv *ifd)
{
	struct i40e_hw *hw = &pf->hw;
	struct i40e_nvm_access *nvma;
	device_t dev = pf->dev;
	enum i40e_status_code status = 0;
	size_t nvma_size, ifd_len, exp_len;
	int err, perrno;

	DEBUGFUNC("ixl_handle_nvmupd_cmd");

	/* Sanity checks */
	nvma_size = sizeof(struct i40e_nvm_access);
	ifd_len = ifd->ifd_len;

	if (ifd_len < nvma_size ||
	    ifd->ifd_data == NULL) {
		device_printf(dev, "%s: incorrect ifdrv length or data pointer\n",
		    __func__);
		device_printf(dev, "%s: ifdrv length: %zu, sizeof(struct i40e_nvm_access): %zu\n",
		    __func__, ifd_len, nvma_size);
		device_printf(dev, "%s: data pointer: %p\n", __func__,
		    ifd->ifd_data);
		return (EINVAL);
	}

	nvma = malloc(ifd_len, M_IXL, M_WAITOK);
	err = copyin(ifd->ifd_data, nvma, ifd_len);
	if (err) {
		device_printf(dev, "%s: Cannot get request from user space\n",
		    __func__);
		free(nvma, M_IXL);
		return (err);
	}

	if (pf->dbg_mask & IXL_DBG_NVMUPD)
		ixl_print_nvm_cmd(dev, nvma);

	if (IXL_PF_IS_RESETTING(pf)) {
		int count = 0;
		while (count++ < 100) {
			i40e_msec_delay(100);
			if (!(IXL_PF_IS_RESETTING(pf)))
				break;
		}
	}

	if (IXL_PF_IS_RESETTING(pf)) {
		device_printf(dev,
		    "%s: timeout waiting for EMP reset to finish\n",
		    __func__);
		free(nvma, M_IXL);
		return (-EBUSY);
	}

	if (nvma->data_size < 1 || nvma->data_size > 4096) {
		device_printf(dev,
		    "%s: invalid request, data size not in supported range\n",
		    __func__);
		free(nvma, M_IXL);
		return (EINVAL);
	}

	/*
	 * Older versions of the NVM update tool don't set ifd_len to the size
	 * of the entire buffer passed to the ioctl. Check the data_size field
	 * in the contained i40e_nvm_access struct and ensure everything is
	 * copied in from userspace.
	 */
	exp_len = nvma_size + nvma->data_size - 1; /* One byte is kept in struct */

	if (ifd_len < exp_len) {
		ifd_len = exp_len;
		nvma = realloc(nvma, ifd_len, M_IXL, M_WAITOK);
		/* coverity[tainted_data_argument] */
		err = copyin(ifd->ifd_data, nvma, ifd_len);
		if (err) {
			device_printf(dev, "%s: Cannot get request from user space\n",
					__func__);
			free(nvma, M_IXL);
			return (err);
		}
	}

	IXL_PF_LOCK(pf);
	status = i40e_nvmupd_command(hw, nvma, nvma->data, &perrno);
	IXL_PF_UNLOCK(pf);

	err = copyout(nvma, ifd->ifd_data, ifd_len);
	free(nvma, M_IXL);
	if (err) {
		device_printf(dev, "%s: Cannot return data to user space\n",
				__func__);
		return (err);
	}

	/* Let the nvmupdate report errors, show them only when debug is enabled */
	if (status != 0 && (pf->dbg_mask & IXL_DBG_NVMUPD) != 0)
		device_printf(dev, "i40e_nvmupd_command status %s, perrno %d\n",
		    i40e_stat_str(hw, status), perrno);

	/*
	 * -EPERM is actually ERESTART, which the kernel interprets as it needing
	 * to run this ioctl again. So use -EACCES for -EPERM instead.
	 */
	if (perrno == -EPERM)
		return (-EACCES);
	else
		return (perrno);
}

int
ixl_handle_i2c_eeprom_read_cmd(struct ixl_pf *pf, struct ifreq *ifr)
{
	struct ifi2creq i2c;
	int error = 0;
	int i;

	if (pf->read_i2c_byte == NULL)
		return (EINVAL);

#ifdef ifr_data
	error = copyin(ifr->ifr_data, &i2c, sizeof(i2c));
#else
	error = copyin(ifr_data_get_ptr(ifr), &i2c, sizeof(i2c));
#endif

	if (error != 0)
		return (error);
	if (i2c.dev_addr != 0xA0 && i2c.dev_addr != 0xA2) {
		error = EINVAL;
		return (error);
	}
	if (i2c.len > sizeof(i2c.data)) {
		error = EINVAL;
		return (error);
	}

	for (i = 0; i < i2c.len; ++i) {
		if (pf->read_i2c_byte(pf, i2c.offset + i,
		    i2c.dev_addr, &i2c.data[i]))
		return (EIO);
	}

#ifdef ifr_data
	error = copyout(&i2c, ifr->ifr_data, sizeof(i2c));
#else
	error = copyout(&i2c, ifr_data_get_ptr(ifr), sizeof(i2c));
#endif

	return (error);
}

/*********************************************************************
 *
 *  Media Ioctl callback
 *
 *  This routine is called whenever the user queries the status of
 *  the interface using ifconfig.
 *
 *  When adding new media types here, make sure to add them to
 *  ixl_add_ifmedia(), too.
 *
 **********************************************************************/
void
ixl_media_status(struct ifnet * ifp, struct ifmediareq * ifmr)
{
	struct ixl_vsi	*vsi = ifp->if_softc;
	struct ixl_pf	*pf = vsi->back;
	struct i40e_hw  *hw = &pf->hw;

	INIT_DEBUGOUT("ixl_media_status: begin");

	/* Don't touch PF during reset */
	if (IXL_PF_IS_RESETTING(pf))
		return;

	IXL_PF_LOCK(pf);

	i40e_get_link_status(hw, &pf->link_up);
	ixl_dbg_link(pf, "%s link_up: %d\n", __func__, pf->link_up);
	ixl_update_link_status(pf);

	ifmr->ifm_status = IFM_AVALID;
	ifmr->ifm_active = IFM_ETHER;

	if (!pf->link_up) {
		IXL_PF_UNLOCK(pf);
		return;
	}

	ifmr->ifm_status |= IFM_ACTIVE;

	/* Hardware always does full-duplex */
	ifmr->ifm_active |= IFM_FDX;

	switch (hw->phy.link_info.phy_type) {
		/* 100 M */
		case I40E_PHY_TYPE_100BASE_TX:
			ifmr->ifm_active |= IFM_100_TX;
			break;
		/* 1 G */
		case I40E_PHY_TYPE_1000BASE_T:
			ifmr->ifm_active |= IFM_1000_T;
			break;
		case I40E_PHY_TYPE_1000BASE_SX:
			ifmr->ifm_active |= IFM_1000_SX;
			break;
		case I40E_PHY_TYPE_1000BASE_LX:
			ifmr->ifm_active |= IFM_1000_LX;
			break;
		case I40E_PHY_TYPE_1000BASE_T_OPTICAL:
			ifmr->ifm_active |= IFM_1000_T;
			break;
		/* 2.5 G */
		case I40E_PHY_TYPE_2_5GBASE_T_LINK_STATUS:
			ifmr->ifm_active |= IFM_2500_T;
			break;
		/* 5 G */
		case I40E_PHY_TYPE_5GBASE_T_LINK_STATUS:
			ifmr->ifm_active |= IFM_5000_T;
			break;
		/* 10 G */
		case I40E_PHY_TYPE_10GBASE_SFPP_CU:
			ifmr->ifm_active |= IFM_10G_TWINAX;
			break;
		case I40E_PHY_TYPE_10GBASE_SR:
			ifmr->ifm_active |= IFM_10G_SR;
			break;
		case I40E_PHY_TYPE_10GBASE_LR:
			ifmr->ifm_active |= IFM_10G_LR;
			break;
		case I40E_PHY_TYPE_10GBASE_T:
			ifmr->ifm_active |= IFM_10G_T;
			break;
		case I40E_PHY_TYPE_XAUI:
		case I40E_PHY_TYPE_XFI:
			ifmr->ifm_active |= IFM_10G_TWINAX;
			break;
		case I40E_PHY_TYPE_10GBASE_AOC:
#ifdef IFM_10G_AOC
			ifmr->ifm_active |= IFM_10G_AOC;
#else
			ifmr->ifm_active |= IFM_10G_TWINAX;
#endif
			break;
		/* 25 G */
		case I40E_PHY_TYPE_25GBASE_KR:
			ifmr->ifm_active |= IFM_25G_KR;
			break;
		case I40E_PHY_TYPE_25GBASE_CR:
			ifmr->ifm_active |= IFM_25G_CR;
			break;
		case I40E_PHY_TYPE_25GBASE_SR:
			ifmr->ifm_active |= IFM_25G_SR;
			break;
		case I40E_PHY_TYPE_25GBASE_LR:
#ifdef IFM_25G_LR
			ifmr->ifm_active |= IFM_25G_LR;
#else
			ifmr->ifm_active |= IFM_25G_SR;
#endif
			break;
		case I40E_PHY_TYPE_25GBASE_AOC:
#ifdef IFM_25G_AOC
			ifmr->ifm_active |= IFM_25G_AOC;
#else
			ifmr->ifm_active |= IFM_25G_SR;
#endif
			break;
		case I40E_PHY_TYPE_25GBASE_ACC:
#ifdef IFM_25G_ACC
			ifmr->ifm_active |= IFM_25G_ACC;
#else
			ifmr->ifm_active |= IFM_25G_CR;
#endif
			break;
		/* 40 G */
		case I40E_PHY_TYPE_40GBASE_CR4:
		case I40E_PHY_TYPE_40GBASE_CR4_CU:
			ifmr->ifm_active |= IFM_40G_CR4;
			break;
		case I40E_PHY_TYPE_40GBASE_SR4:
			ifmr->ifm_active |= IFM_40G_SR4;
			break;
		case I40E_PHY_TYPE_40GBASE_LR4:
			ifmr->ifm_active |= IFM_40G_LR4;
			break;
		case I40E_PHY_TYPE_XLAUI:
			ifmr->ifm_active |= IFM_OTHER;
			break;
		case I40E_PHY_TYPE_1000BASE_KX:
			ifmr->ifm_active |= IFM_1000_KX;
			break;
		case I40E_PHY_TYPE_SGMII:
			ifmr->ifm_active |= IFM_1000_SGMII;
			break;
		/* ERJ: What's the difference between these? */
		case I40E_PHY_TYPE_10GBASE_CR1_CU:
		case I40E_PHY_TYPE_10GBASE_CR1:
			ifmr->ifm_active |= IFM_10G_CR1;
			break;
		case I40E_PHY_TYPE_10GBASE_KX4:
			ifmr->ifm_active |= IFM_10G_KX4;
			break;
		case I40E_PHY_TYPE_10GBASE_KR:
			ifmr->ifm_active |= IFM_10G_KR;
			break;
		case I40E_PHY_TYPE_SFI:
			ifmr->ifm_active |= IFM_10G_SFI;
			break;
		/* Our single 20G media type */
		case I40E_PHY_TYPE_20GBASE_KR2:
			ifmr->ifm_active |= IFM_20G_KR2;
			break;
		case I40E_PHY_TYPE_40GBASE_KR4:
			ifmr->ifm_active |= IFM_40G_KR4;
			break;
		case I40E_PHY_TYPE_XLPPI:
		case I40E_PHY_TYPE_40GBASE_AOC:
			ifmr->ifm_active |= IFM_40G_XLPPI;
			break;
		/* Unknown to driver */
		default:
			ifmr->ifm_active |= IFM_UNKNOWN;
			break;
	}
	/* Report flow control status as well */
	if (hw->phy.link_info.an_info & I40E_AQ_LINK_PAUSE_TX)
		ifmr->ifm_active |= IFM_ETH_TXPAUSE;
	if (hw->phy.link_info.an_info & I40E_AQ_LINK_PAUSE_RX)
		ifmr->ifm_active |= IFM_ETH_RXPAUSE;

	IXL_PF_UNLOCK(pf);
}

void
ixl_init(void *arg)
{
	struct ixl_pf *pf = arg;

	IXL_PF_LOCK(pf);
	ixl_init_locked(pf);
	IXL_PF_UNLOCK(pf);
}

/*
 * NOTE: Fortville does not support forcing media speeds. Instead,
 * use the set_advertise sysctl to set the speeds Fortville
 * will advertise or be allowed to operate at.
 */
int
ixl_media_change(struct ifnet * ifp)
{
	struct ixl_vsi *vsi = ifp->if_softc;
	struct ifmedia *ifm = &vsi->media;

	INIT_DEBUGOUT("ixl_media_change: begin");

	if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER)
		return (EINVAL);

	if_printf(ifp, "Use 'advertise_speed' sysctl to change advertised speeds\n");

	return (ENODEV);
}

static int
ixl_ioctl_drvspec(struct ixl_pf	*pf, caddr_t data)
{
	struct ifdrv	*ifd = (struct ifdrv *)data;
	int error;

	IOCTL_DEBUGOUT("ioctl: SIOCxDRVSPEC (Get/Set Driver-specific "
	    "Info)\n");

	/*
	 * ifioctl forwards SIOCxDRVSPEC without performing
	 * a privilege check. Perform one here to ensure
	 * that non-privileged threads cannot access this interface.
	 */
	error = priv_check(curthread, PRIV_DRIVER);
	if (error)
		return (error);

	/* NVM update command */
	if (ifd->ifd_cmd != I40E_NVM_ACCESS)
		return (EINVAL);

	return ixl_handle_nvmupd_cmd(pf, ifd);
}

/*********************************************************************
 *  Ioctl entry point
 *
 *  ixl_ioctl is called when the user wants to configure the
 *  interface.
 *
 *  return 0 on success, positive on failure
 **********************************************************************/

int
ixl_ioctl(struct ifnet * ifp, u_long command, caddr_t data)
{
	struct ixl_vsi	*vsi = ifp->if_softc;
	struct ixl_pf	*pf = vsi->back;
	struct ifreq	*ifr = (struct ifreq *)data;
#if defined(INET) || defined(INET6)
	struct ifaddr *ifa = (struct ifaddr *)data;
	bool		avoid_reset = FALSE;
#endif
	int             error = 0;

	if (IXL_PF_IN_RECOVERY_MODE(pf)) {
		/* We are in recovery mode supporting only NVM update */
		switch (command) {
		case SIOCSDRVSPEC:
		case SIOCGDRVSPEC:
			error = ixl_ioctl_drvspec(pf, data);
			break;
		default:
			error = EINVAL;
			break;
		}

		return (error);
	}

	switch (command) {

        case SIOCSIFADDR:
		IOCTL_DEBUGOUT("ioctl: SIOCSIFADDR (Set Interface Address)");
#ifdef INET
		if (ifa->ifa_addr->sa_family == AF_INET)
			avoid_reset = TRUE;
#endif
#ifdef INET6
		if (ifa->ifa_addr->sa_family == AF_INET6)
			avoid_reset = TRUE;
#endif
#if defined(INET) || defined(INET6)
		/*
		** Calling init results in link renegotiation,
		** so we avoid doing it when possible.
		*/
		if (avoid_reset) {
			ifp->if_flags |= IFF_UP;
			if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
				IXL_PF_LOCK(pf);
				ixl_set_link(pf, true);
				ixl_init_locked(pf);
				IXL_PF_UNLOCK(pf);
			}
#ifdef INET
			if (!(ifp->if_flags & IFF_NOARP))
				arp_ifinit(ifp, ifa);
#endif
			break;
		}
#endif
		error = ether_ioctl(ifp, command, data);
		break;
	case SIOCSIFMTU:
		IOCTL_DEBUGOUT("ioctl: SIOCSIFMTU (Set Interface MTU)");
		if (ifr->ifr_mtu > IXL_MAX_FRAME -
		   ETHER_HDR_LEN - ETHER_CRC_LEN - ETHER_VLAN_ENCAP_LEN) {
			error = EINVAL;
		} else {
			IXL_PF_LOCK(pf);
			ifp->if_mtu = ifr->ifr_mtu;
			vsi->max_frame_size =
				ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN
			    + ETHER_VLAN_ENCAP_LEN;
			if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
				ixl_init_locked(pf);
			}
			IXL_PF_UNLOCK(pf);
		}
		break;
	case SIOCSIFFLAGS:
		IOCTL_DEBUGOUT("ioctl: SIOCSIFFLAGS (Set Interface Flags)");
		IXL_PF_LOCK(pf);
		if (ifp->if_flags & IFF_UP) {
			if ((ifp->if_drv_flags & IFF_DRV_RUNNING)) {
				if ((ifp->if_flags ^ pf->if_flags) &
				    (IFF_PROMISC | IFF_ALLMULTI)) {
					ixl_set_promisc(vsi);
				}
			} else {
				ixl_set_link(pf, true);
				ixl_init_locked(pf);
			}
		} else {
			if (ifp->if_drv_flags & IFF_DRV_RUNNING)
				ixl_stop_locked(pf);
			if (!ixl_test_state(&pf->state, IXL_STATE_LINK_ACTIVE_ON_DOWN))
				ixl_set_link(pf, false);
		}

		pf->if_flags = ifp->if_flags;
		IXL_PF_UNLOCK(pf);
		break;
	case SIOCSDRVSPEC:
	case SIOCGDRVSPEC:
		error = ixl_ioctl_drvspec(pf, data);
		break;
	case SIOCADDMULTI:
		IOCTL_DEBUGOUT("ioctl: SIOCADDMULTI");
		if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
			IXL_PF_LOCK(pf);
			ixl_disable_rings_intr(vsi);
			ixl_add_multi(vsi);
			ixl_enable_intr(vsi);
			IXL_PF_UNLOCK(pf);
		}
		break;
	case SIOCDELMULTI:
		IOCTL_DEBUGOUT("ioctl: SIOCDELMULTI");
		if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
			IXL_PF_LOCK(pf);
			ixl_disable_rings_intr(vsi);
			ixl_del_multi(vsi, false);
			ixl_enable_intr(vsi);
			IXL_PF_UNLOCK(pf);
		}
		break;
	case SIOCSIFMEDIA:
	case SIOCGIFMEDIA:
	case SIOCGIFXMEDIA:
		IOCTL_DEBUGOUT("ioctl: SIOCxIFMEDIA (Get/Set Interface Media)");
		error = ifmedia_ioctl(ifp, ifr, &vsi->media, command);
		break;
	case SIOCSIFCAP:
	{
		int mask = ifr->ifr_reqcap ^ ifp->if_capenable;
		IOCTL_DEBUGOUT("ioctl: SIOCSIFCAP (Set Capabilities)");

		ixl_cap_txcsum_tso(vsi, ifp, mask);

		if (mask & IFCAP_RXCSUM)
			ifp->if_capenable ^= IFCAP_RXCSUM;
		if (mask & IFCAP_RXCSUM_IPV6)
			ifp->if_capenable ^= IFCAP_RXCSUM_IPV6;
		if (mask & IFCAP_LRO)
			ifp->if_capenable ^= IFCAP_LRO;
		if (mask & IFCAP_VLAN_HWTAGGING)
			ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING;
		if (mask & IFCAP_VLAN_HWFILTER)
			ifp->if_capenable ^= IFCAP_VLAN_HWFILTER;
		if (mask & IFCAP_VLAN_HWTSO)
			ifp->if_capenable ^= IFCAP_VLAN_HWTSO;
#ifdef CSUM_ENCAP_VXLAN
		if (mask & IFCAP_VXLAN_HWCSUM)
			ifp->if_capenable ^= IFCAP_VXLAN_HWCSUM;
		if (mask & IFCAP_VXLAN_HWTSO)
			ifp->if_capenable ^= IFCAP_VXLAN_HWTSO;
#endif
		if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
			IXL_PF_LOCK(pf);
			ixl_init_locked(pf);
			IXL_PF_UNLOCK(pf);
		}
		VLAN_CAPABILITIES(ifp);

		break;
	}
#if __FreeBSD_version >= 1003000
	case SIOCGI2C:
	{
		IOCTL_DEBUGOUT("ioctl: SIOCGI2C (Get I2C Data)");
		if (!pf->has_i2c)
			return (ENOTTY);

		error = ixl_handle_i2c_eeprom_read_cmd(pf, ifr);
		break;
	}
#endif
	default:
		IOCTL_DEBUGOUT("ioctl: UNKNOWN (0x%X)\n", (int)command);
		error = ether_ioctl(ifp, command, data);
		break;
	}

	return (error);
}

int
ixl_find_i2c_interface(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	bool i2c_en, port_matched;
	u32 reg;

	for (int i = 0; i < 4; i++) {
		reg = rd32(hw, I40E_GLGEN_MDIO_I2C_SEL(i));
		i2c_en = (reg & I40E_GLGEN_MDIO_I2C_SEL_MDIO_I2C_SEL_MASK);
		port_matched = ((reg & I40E_GLGEN_MDIO_I2C_SEL_PHY_PORT_NUM_MASK)
		    >> I40E_GLGEN_MDIO_I2C_SEL_PHY_PORT_NUM_SHIFT)
		    & BIT(hw->port);
		if (i2c_en && port_matched)
			return (i);
	}

	return (-1);
}

void
ixl_set_link(struct ixl_pf *pf, bool enable)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct i40e_aq_get_phy_abilities_resp abilities;
	struct i40e_aq_set_phy_config config;
	enum i40e_status_code aq_error = 0;
	u32 phy_type, phy_type_ext;

	ixl_dbg_link(pf, "%s enable: %d\n", __func__, enable);

	/* Get initial capability information */
	aq_error = i40e_aq_get_phy_capabilities(hw,
	    FALSE, TRUE, &abilities, NULL);
	if (aq_error) {
		device_printf(dev,
		    "%s: Error getting phy capabilities %d,"
		    " aq error: %d\n", __func__, aq_error,
		    hw->aq.asq_last_status);
		return;
	}

	phy_type = abilities.phy_type;
	phy_type_ext = abilities.phy_type_ext;

	/* Get current capability information */
	aq_error = i40e_aq_get_phy_capabilities(hw,
	    FALSE, FALSE, &abilities, NULL);
	if (aq_error) {
		device_printf(dev,
		    "%s: Error getting phy capabilities %d,"
		    " aq error: %d\n", __func__, aq_error,
		    hw->aq.asq_last_status);
		return;
	}

	/* Prepare new config */
	memset(&config, 0, sizeof(config));
	config.link_speed = abilities.link_speed;
	config.abilities = abilities.abilities &
	    ~I40E_AQ_PHY_ENABLE_ATOMIC_LINK;
	config.eee_capability = abilities.eee_capability;
	config.eeer = abilities.eeer_val;
	config.low_power_ctrl = abilities.d3_lpan;
	config.fec_config = abilities.fec_cfg_curr_mod_ext_info
	    & I40E_AQ_PHY_FEC_CONFIG_MASK;

	config.abilities &= ~(I40E_AQ_PHY_FLAG_PAUSE_TX |
	    I40E_AQ_PHY_FLAG_PAUSE_RX);

	switch (pf->fc) {
	case I40E_FC_FULL:
		config.abilities |= I40E_AQ_PHY_FLAG_PAUSE_TX |
		    I40E_AQ_PHY_FLAG_PAUSE_RX;
		break;
	case I40E_FC_RX_PAUSE:
		config.abilities |= I40E_AQ_PHY_FLAG_PAUSE_RX;
		break;
	case I40E_FC_TX_PAUSE:
		config.abilities |= I40E_AQ_PHY_FLAG_PAUSE_TX;
		break;
	default:
		break;
	}

	if (enable) {
		config.phy_type = phy_type;
		config.phy_type_ext = phy_type_ext;

	} else {
		config.phy_type = 0;
		config.phy_type_ext = 0;
	}

	aq_error = i40e_aq_set_phy_config(hw, &config, NULL);
	if (aq_error) {
		device_printf(dev,
		    "%s: Error setting new phy config %d,"
		    " aq error: %d\n", __func__, aq_error,
		    hw->aq.asq_last_status);
		return;
	}

	aq_error = i40e_aq_set_link_restart_an(hw, enable, NULL);
	if (aq_error) {
		device_printf(dev,
		    "%s: Error set link config %d,"
		    " aq error: %d\n", __func__, aq_error,
		    hw->aq.asq_last_status);
		return;
	}
}


static char *
ixl_phy_type_string(u32 bit_pos, bool ext)
{
	static char * phy_types_str[32] = {
		"SGMII",
		"1000BASE-KX",
		"10GBASE-KX4",
		"10GBASE-KR",
		"40GBASE-KR4",
		"XAUI",
		"XFI",
		"SFI",
		"XLAUI",
		"XLPPI",
		"40GBASE-CR4",
		"10GBASE-CR1",
		"SFP+ Active DA",
		"QSFP+ Active DA",
		"Reserved (14)",
		"Reserved (15)",
		"Reserved (16)",
		"100BASE-TX",
		"1000BASE-T",
		"10GBASE-T",
		"10GBASE-SR",
		"10GBASE-LR",
		"10GBASE-SFP+Cu",
		"10GBASE-CR1",
		"40GBASE-CR4",
		"40GBASE-SR4",
		"40GBASE-LR4",
		"1000BASE-SX",
		"1000BASE-LX",
		"1000BASE-T Optical",
		"20GBASE-KR2",
		"Reserved (31)"
	};
	static char * ext_phy_types_str[8] = {
		"25GBASE-KR",
		"25GBASE-CR",
		"25GBASE-SR",
		"25GBASE-LR",
		"25GBASE-AOC",
		"25GBASE-ACC",
		"2.5GBASE-T",
		"5GBASE-T"
	};

	if (ext && bit_pos > 7) return "Invalid_Ext";
	if (bit_pos > 31) return "Invalid";

	return (ext) ? ext_phy_types_str[bit_pos] : phy_types_str[bit_pos];
}

static char *
ixl_phy_type_string_ls(u8 val)
{
	if (val >= 0x1F)
		return ixl_phy_type_string(val - 0x1F, true);
	else
		return ixl_phy_type_string(val, false);
}

int
ixl_aq_get_link_status(struct ixl_pf *pf, struct i40e_aqc_get_link_status *link_status)
{
	device_t dev = pf->dev;
	struct i40e_hw *hw = &pf->hw;
	struct i40e_aq_desc desc;
	enum i40e_status_code status;

	struct i40e_aqc_get_link_status *aq_link_status =
		(struct i40e_aqc_get_link_status *)&desc.params.raw;

	i40e_fill_default_direct_cmd_desc(&desc, i40e_aqc_opc_get_link_status);
	link_status->command_flags = CPU_TO_LE16(I40E_AQ_LSE_ENABLE);
	status = i40e_asq_send_command(hw, &desc, NULL, 0, NULL);
	if (status) {
		device_printf(dev,
		    "%s: i40e_aqc_opc_get_link_status status %s, aq error %s\n",
		    __func__, i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
		return (EIO);
	}

	bcopy(aq_link_status, link_status, sizeof(struct i40e_aqc_get_link_status));
	return (0);
}

static int
ixl_sysctl_link_status(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	device_t dev = pf->dev;
	struct sbuf *buf;
	int error = 0;

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (!buf) {
		device_printf(dev, "Could not allocate sbuf for sysctl output.\n");
		return (ENOMEM);
	}

	struct i40e_aqc_get_link_status link_status;
	error = ixl_aq_get_link_status(pf, &link_status);
	if (error) {
		sbuf_delete(buf);
		return (error);
	}

	sbuf_printf(buf, "\n"
	    "PHY Type : 0x%02x<%s>\n"
	    "Speed    : 0x%02x\n"
	    "Link info: 0x%02x\n"
	    "AN info  : 0x%02x\n"
	    "Ext info : 0x%02x\n"
	    "Loopback : 0x%02x\n"
	    "Max Frame: %d\n"
	    "Config   : 0x%02x\n"
	    "Power    : 0x%02x",
	    link_status.phy_type,
	    ixl_phy_type_string_ls(link_status.phy_type),
	    link_status.link_speed,
	    link_status.link_info,
	    link_status.an_info,
	    link_status.ext_info,
	    link_status.loopback,
	    link_status.max_frame_size,
	    link_status.config,
	    link_status.power_desc);

	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);

	sbuf_delete(buf);
	return (error);
}

static int
ixl_sysctl_phy_abilities(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	enum i40e_status_code status;
	struct i40e_aq_get_phy_abilities_resp abilities;
	struct sbuf *buf;
	int error = 0;

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (!buf) {
		device_printf(dev, "Could not allocate sbuf for sysctl output.\n");
		return (ENOMEM);
	}

	status = i40e_aq_get_phy_capabilities(hw,
	    FALSE, arg2 != 0, &abilities, NULL);
	if (status) {
		device_printf(dev,
		    "%s: i40e_aq_get_phy_capabilities() status %s, aq error %s\n",
		    __func__, i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
		sbuf_delete(buf);
		return (EIO);
	}

	sbuf_printf(buf, "\n"
	    "PHY Type : %08x",
	    abilities.phy_type);

	if (abilities.phy_type != 0) {
		sbuf_printf(buf, "<");
		for (int i = 0; i < 32; i++)
			if ((1 << i) & abilities.phy_type)
				sbuf_printf(buf, "%s,", ixl_phy_type_string(i, false));
		sbuf_printf(buf, ">");
	}

	sbuf_printf(buf, "\nPHY Ext  : %02x",
	    abilities.phy_type_ext);

	if (abilities.phy_type_ext != 0) {
		sbuf_printf(buf, "<");
		for (int i = 0; i < 4; i++)
			if ((1 << i) & abilities.phy_type_ext)
				sbuf_printf(buf, "%s,",
				    ixl_phy_type_string(i, true));
		sbuf_printf(buf, ">");
	}

	sbuf_printf(buf, "\nSpeed    : %02x", abilities.link_speed);
	if (abilities.link_speed != 0) {
		u8 link_speed;
		sbuf_printf(buf, " <");
		for (int i = 0; i < 8; i++) {
			link_speed = (1 << i) & abilities.link_speed;
			if (link_speed)
				sbuf_printf(buf, "%s, ",
				    ixl_link_speed_string(link_speed));
		}
		sbuf_printf(buf, ">");
	}

	sbuf_printf(buf, "\n"
	    "Abilities: %02x\n"
	    "EEE cap  : %04x\n"
	    "EEER reg : %08x\n"
	    "D3 Lpan  : %02x\n"
	    "ID       : %02x %02x %02x %02x\n"
	    "ModType  : %02x %02x %02x\n"
	    "ModType E: %01x\n"
	    "FEC Cfg  : %02x\n"
	    "Ext CC   : %02x",
	    abilities.abilities, abilities.eee_capability,
	    abilities.eeer_val, abilities.d3_lpan,
	    abilities.phy_id[0], abilities.phy_id[1],
	    abilities.phy_id[2], abilities.phy_id[3],
	    abilities.module_type[0], abilities.module_type[1],
	    abilities.module_type[2], (abilities.fec_cfg_curr_mod_ext_info & 0xe0) >> 5,
	    abilities.fec_cfg_curr_mod_ext_info & 0x1F,
	    abilities.ext_comp_code);

	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);

	sbuf_delete(buf);
	return (error);
}

static int
ixl_sysctl_phy_statistics(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct sbuf *buf;
	int error = 0;

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (buf == NULL) {
		device_printf(dev, "Could not allocate sbuf for sysctl output.\n");
		return (ENOMEM);
	}

	if (hw->mac.type == I40E_MAC_X722) {
		sbuf_printf(buf, "\n"
		    "PCS Link Control Register:                          unavailable\n"
		    "PCS Link Status 1:                                  unavailable\n"
		    "PCS Link Status 2:                                  unavailable\n"
		    "XGMII FIFO Status:                                  unavailable\n"
		    "Auto-Negotiation (AN) Status:                       unavailable\n"
		    "KR PCS Status:                                      unavailable\n"
		    "KR FEC Status 1 – FEC Correctable Blocks Counter:   unavailable\n"
		    "KR FEC Status 2 – FEC Uncorrectable Blocks Counter: unavailable"
		);
	} else {
		sbuf_printf(buf, "\n"
		    "PCS Link Control Register:                          %#010X\n"
		    "PCS Link Status 1:                                  %#010X\n"
		    "PCS Link Status 2:                                  %#010X\n"
		    "XGMII FIFO Status:                                  %#010X\n"
		    "Auto-Negotiation (AN) Status:                       %#010X\n"
		    "KR PCS Status:                                      %#010X\n"
		    "KR FEC Status 1 – FEC Correctable Blocks Counter:   %#010X\n"
		    "KR FEC Status 2 – FEC Uncorrectable Blocks Counter: %#010X",
		    rd32(hw, I40E_PRTMAC_PCS_LINK_CTRL),
		    rd32(hw, I40E_PRTMAC_PCS_LINK_STATUS1(0)),
		    rd32(hw, I40E_PRTMAC_PCS_LINK_STATUS2),
		    rd32(hw, I40E_PRTMAC_PCS_XGMII_FIFO_STATUS),
		    rd32(hw, I40E_PRTMAC_PCS_AN_LP_STATUS),
		    rd32(hw, I40E_PRTMAC_PCS_KR_STATUS),
		    rd32(hw, I40E_PRTMAC_PCS_FEC_KR_STATUS1),
		    rd32(hw, I40E_PRTMAC_PCS_FEC_KR_STATUS2)
		);
	}

	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);

	sbuf_delete(buf);
	return (error);
}

static int
ixl_sysctl_sw_filter_list(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct ixl_vsi *vsi = &pf->vsi;
	struct ixl_mac_filter *f;
	device_t dev = pf->dev;
	int error = 0, ftl_len = 0, ftl_counter = 0;

	struct sbuf *buf;

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (!buf) {
		device_printf(dev, "Could not allocate sbuf for sysctl output.\n");
		return (ENOMEM);
	}

	sbuf_printf(buf, "\n");

	/* Print MAC filters */
	sbuf_printf(buf, "PF Filters:\n");
	LIST_FOREACH(f, &vsi->ftl, ftle)
		ftl_len++;

	if (ftl_len < 1)
		sbuf_printf(buf, "(none)\n");
	else {
		LIST_FOREACH(f, &vsi->ftl, ftle) {
			sbuf_printf(buf,
			    MAC_FORMAT ", vlan %4d, flags %#06x",
			    MAC_FORMAT_ARGS(f->macaddr), f->vlan, f->flags);
			/* don't print '\n' for last entry */
			if (++ftl_counter != ftl_len)
				sbuf_printf(buf, "\n");
		}
	}

#ifdef PCI_IOV
	struct ixl_vf *vf;
	if (pf->num_vfs > 0) {
		sbuf_printf(buf, "\n\n");
		for (int i = 0; i < pf->num_vfs; i++) {
			vf = &pf->vfs[i];
			if (!(vf->vf_flags & VF_FLAG_ENABLED))
				continue;

			vsi = &vf->vsi;
			ftl_len = 0, ftl_counter = 0;
			sbuf_printf(buf, "VF-%d Filters:\n", vf->vf_num);
			LIST_FOREACH(f, &vsi->ftl, ftle)
				ftl_len++;

			if (ftl_len < 1)
				sbuf_printf(buf, "(none)\n");
			else {
				LIST_FOREACH(f, &vsi->ftl, ftle) {
					sbuf_printf(buf,
					    MAC_FORMAT ", vlan %4d, flags %#06x\n",
					    MAC_FORMAT_ARGS(f->macaddr), f->vlan, f->flags);
				}
			}
		}
	}
#endif

	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);
	sbuf_delete(buf);

	return (error);
}

#define IXL_SW_RES_SIZE 0x14
int
ixl_res_alloc_cmp(const void *a, const void *b)
{
	const struct i40e_aqc_switch_resource_alloc_element_resp *one, *two;
	one = (const struct i40e_aqc_switch_resource_alloc_element_resp *)a;
	two = (const struct i40e_aqc_switch_resource_alloc_element_resp *)b;

	return ((int)one->resource_type - (int)two->resource_type);
}

/*
 * Longest string length: 25
 */
char *
ixl_switch_res_type_string(u8 type)
{
	static char * ixl_switch_res_type_strings[IXL_SW_RES_SIZE] = {
		"VEB",
		"VSI",
		"Perfect Match MAC address",
		"S-tag",
		"(Reserved)",
		"Multicast hash entry",
		"Unicast hash entry",
		"VLAN",
		"VSI List entry",
		"(Reserved)",
		"VLAN Statistic Pool",
		"Mirror Rule",
		"Queue Set",
		"Inner VLAN Forward filter",
		"(Reserved)",
		"Inner MAC",
		"IP",
		"GRE/VN1 Key",
		"VN2 Key",
		"Tunneling Port"
	};

	if (type < IXL_SW_RES_SIZE)
		return ixl_switch_res_type_strings[type];
	else
		return "(Reserved)";
}

static int
ixl_sysctl_hw_res_alloc(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct sbuf *buf;
	enum i40e_status_code status;
	int error = 0;

	u8 num_entries;
	struct i40e_aqc_switch_resource_alloc_element_resp resp[IXL_SW_RES_SIZE];

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (!buf) {
		device_printf(dev, "Could not allocate sbuf for output.\n");
		return (ENOMEM);
	}

	bzero(resp, sizeof(resp));
	status = i40e_aq_get_switch_resource_alloc(hw, &num_entries,
				resp,
				IXL_SW_RES_SIZE,
				NULL);
	if (status) {
		device_printf(dev,
		    "%s: get_switch_resource_alloc() error %s, aq error %s\n",
		    __func__, i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
		sbuf_delete(buf);
		return (error);
	}

	/* Sort entries by type for display */
	qsort(resp, num_entries,
	    sizeof(struct i40e_aqc_switch_resource_alloc_element_resp),
	    &ixl_res_alloc_cmp);

	sbuf_cat(buf, "\n");
	sbuf_printf(buf, "# of entries: %d\n", num_entries);
	sbuf_printf(buf,
	    "                     Type | Guaranteed | Total | Used   | Un-allocated\n"
	    "                          | (this)     | (all) | (this) | (all)       \n");
	for (int i = 0; i < num_entries; i++) {
		sbuf_printf(buf,
		    "%25s | %10d   %5d   %6d   %12d",
		    ixl_switch_res_type_string(resp[i].resource_type),
		    resp[i].guaranteed,
		    resp[i].total,
		    resp[i].used,
		    resp[i].total_unalloced);
		if (i < num_entries - 1)
			sbuf_cat(buf, "\n");
	}

	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);

	sbuf_delete(buf);
	return (error);
}

enum ixl_sw_seid_offset {
	IXL_SW_SEID_EMP = 1,
	IXL_SW_SEID_MAC_START = 2,
	IXL_SW_SEID_MAC_END = 5,
	IXL_SW_SEID_PF_START = 16,
	IXL_SW_SEID_PF_END = 31,
	IXL_SW_SEID_VF_START = 32,
	IXL_SW_SEID_VF_END = 159,
};

/*
 * Caller must init and delete sbuf; this function will clear and
 * finish it for caller.
 *
 * Note: The SEID argument only applies for elements defined by FW at
 * power-on; these include the EMP, Ports, PFs and VFs.
 */
static char *
ixl_switch_element_string(struct sbuf *s, u8 element_type, u16 seid)
{
	sbuf_clear(s);

	/* If SEID is in certain ranges, then we can infer the
	 * mapping of SEID to switch element.
	 */
	if (seid == IXL_SW_SEID_EMP) {
		sbuf_cat(s, "EMP");
		goto out;
	} else if (seid >= IXL_SW_SEID_MAC_START &&
	    seid <= IXL_SW_SEID_MAC_END) {
		sbuf_printf(s, "MAC  %2d",
		    seid - IXL_SW_SEID_MAC_START);
		goto out;
	} else if (seid >= IXL_SW_SEID_PF_START &&
	    seid <= IXL_SW_SEID_PF_END) {
		sbuf_printf(s, "PF  %3d",
		    seid - IXL_SW_SEID_PF_START);
		goto out;
	} else if (seid >= IXL_SW_SEID_VF_START &&
	    seid <= IXL_SW_SEID_VF_END) {
		sbuf_printf(s, "VF  %3d",
		    seid - IXL_SW_SEID_VF_START);
		goto out;
	}

	switch (element_type) {
	case I40E_AQ_SW_ELEM_TYPE_BMC:
		sbuf_cat(s, "BMC");
		break;
	case I40E_AQ_SW_ELEM_TYPE_PV:
		sbuf_cat(s, "PV");
		break;
	case I40E_AQ_SW_ELEM_TYPE_VEB:
		sbuf_cat(s, "VEB");
		break;
	case I40E_AQ_SW_ELEM_TYPE_PA:
		sbuf_cat(s, "PA");
		break;
	case I40E_AQ_SW_ELEM_TYPE_VSI:
		sbuf_printf(s, "VSI");
		break;
	default:
		sbuf_cat(s, "?");
		break;
	}

out:
	sbuf_finish(s);
	return sbuf_data(s);
}

static int
ixl_sw_cfg_elem_seid_cmp(const void *a, const void *b)
{
	const struct i40e_aqc_switch_config_element_resp *one, *two;
	one = (const struct i40e_aqc_switch_config_element_resp *)a;
	two = (const struct i40e_aqc_switch_config_element_resp *)b;

	return ((int)one->seid - (int)two->seid);
}

static int
ixl_sysctl_switch_config(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct sbuf *buf;
	struct sbuf *nmbuf;
	enum i40e_status_code status;
	int error = 0;
	u16 next = 0;
	u8 aq_buf[I40E_AQ_LARGE_BUF];

	struct i40e_aqc_switch_config_element_resp *elem;
	struct i40e_aqc_get_switch_config_resp *sw_config;
	sw_config = (struct i40e_aqc_get_switch_config_resp *)aq_buf;

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (!buf) {
		device_printf(dev, "Could not allocate sbuf for sysctl output.\n");
		return (ENOMEM);
	}

	status = i40e_aq_get_switch_config(hw, sw_config,
	    sizeof(aq_buf), &next, NULL);
	if (status) {
		device_printf(dev,
		    "%s: aq_get_switch_config() error %s, aq error %s\n",
		    __func__, i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
		sbuf_delete(buf);
		return error;
	}
	if (next)
		device_printf(dev, "%s: TODO: get more config with SEID %d\n",
		    __func__, next);

	nmbuf = sbuf_new_auto();
	if (!nmbuf) {
		device_printf(dev, "Could not allocate sbuf for name output.\n");
		sbuf_delete(buf);
		return (ENOMEM);
	}

	/* Sort entries by SEID for display */
	qsort(sw_config->element, sw_config->header.num_reported,
	    sizeof(struct i40e_aqc_switch_config_element_resp),
	    &ixl_sw_cfg_elem_seid_cmp);

	sbuf_cat(buf, "\n");
	/* Assuming <= 255 elements in switch */
	sbuf_printf(buf, "# of reported elements: %d\n", sw_config->header.num_reported);
	sbuf_printf(buf, "total # of elements: %d\n", sw_config->header.num_total);
	/* Exclude:
	 * Revision -- all elements are revision 1 for now
	 */
	sbuf_printf(buf,
	    "SEID (  Name  ) |  Up  (  Name  ) | Down (  Name  ) | Conn Type\n"
	    "                |                 |                 | (uplink)\n");
	for (int i = 0; i < sw_config->header.num_reported; i++) {
		elem = &sw_config->element[i];

		// "%4d (%8s) | %8s   %8s   %#8x",
		sbuf_printf(buf, "%4d", elem->seid);
		sbuf_cat(buf, " ");
		sbuf_printf(buf, "(%8s)", ixl_switch_element_string(nmbuf,
		    elem->element_type, elem->seid));
		sbuf_cat(buf, " | ");
		sbuf_printf(buf, "%4d", elem->uplink_seid);
		sbuf_cat(buf, " ");
		sbuf_printf(buf, "(%8s)", ixl_switch_element_string(nmbuf,
		    0, elem->uplink_seid));
		sbuf_cat(buf, " | ");
		sbuf_printf(buf, "%4d", elem->downlink_seid);
		sbuf_cat(buf, " ");
		sbuf_printf(buf, "(%8s)", ixl_switch_element_string(nmbuf,
		    0, elem->downlink_seid));
		sbuf_cat(buf, " | ");
		sbuf_printf(buf, "%8d", elem->connection_type);
		if (i < sw_config->header.num_reported - 1)
			sbuf_cat(buf, "\n");
	}
	sbuf_delete(nmbuf);

	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);

	sbuf_delete(buf);

	return (error);
}

static int
ixl_sysctl_hkey(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct sbuf *buf;
	int error = 0;
	enum i40e_status_code status;
	u32 reg;

	struct i40e_aqc_get_set_rss_key_data key_data;

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (!buf) {
		device_printf(dev, "Could not allocate sbuf for output.\n");
		return (ENOMEM);
	}

	bzero(&key_data, sizeof(key_data));

	sbuf_cat(buf, "\n");
	if (hw->mac.type == I40E_MAC_X722) {
		status = i40e_aq_get_rss_key(hw, pf->vsi.vsi_num, &key_data);
		if (status)
			device_printf(dev, "i40e_aq_get_rss_key status %s, error %s\n",
			    i40e_stat_str(hw, status), i40e_aq_str(hw, hw->aq.asq_last_status));
	} else {
		for (int i = 0; i < IXL_RSS_KEY_SIZE_REG; i++) {
			reg = i40e_read_rx_ctl(hw, I40E_PFQF_HKEY(i));
			bcopy(&reg, ((caddr_t)&key_data) + (i << 2), 4);
		}
	}

	ixl_sbuf_print_bytes(buf, (u8 *)&key_data, sizeof(key_data), 0, true);

	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);
	sbuf_delete(buf);

	return (error);
}

static void
ixl_sbuf_print_bytes(struct sbuf *sb, u8 *buf, int length, int label_offset, bool text)
{
	int i, j, k, width;
	char c;

	if (length < 1 || buf == NULL) return;

	int byte_stride = 16;
	int lines = length / byte_stride;
	int rem = length % byte_stride;
	if (rem > 0)
		lines++;

	for (i = 0; i < lines; i++) {
		width = (rem > 0 && i == lines - 1)
		    ? rem : byte_stride;

		sbuf_printf(sb, "%4d | ", label_offset + i * byte_stride);

		for (j = 0; j < width; j++)
			sbuf_printf(sb, "%02x ", buf[i * byte_stride + j]);

		if (width < byte_stride) {
			for (k = 0; k < (byte_stride - width); k++)
				sbuf_printf(sb, "   ");
		}

		if (!text) {
			sbuf_printf(sb, "\n");
			continue;
		}

		for (j = 0; j < width; j++) {
			c = (char)buf[i * byte_stride + j];
			if (c < 32 || c > 126)
				sbuf_printf(sb, ".");
			else
				sbuf_printf(sb, "%c", c);

			if (j == width - 1)
				sbuf_printf(sb, "\n");
		}
	}
}

static int
ixl_sysctl_hlut(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct sbuf *buf;
	int error = 0;
	enum i40e_status_code status;
	u8 hlut[512];
	u32 reg;

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (!buf) {
		device_printf(dev, "Could not allocate sbuf for output.\n");
		return (ENOMEM);
	}

	bzero(hlut, sizeof(hlut));
	sbuf_cat(buf, "\n");
	if (hw->mac.type == I40E_MAC_X722) {
		status = i40e_aq_get_rss_lut(hw, pf->vsi.vsi_num, TRUE, hlut, sizeof(hlut));
		if (status)
			device_printf(dev, "i40e_aq_get_rss_lut status %s, error %s\n",
			    i40e_stat_str(hw, status), i40e_aq_str(hw, hw->aq.asq_last_status));
	} else {
		for (int i = 0; i < hw->func_caps.rss_table_size >> 2; i++) {
			reg = rd32(hw, I40E_PFQF_HLUT(i));
			bcopy(&reg, &hlut[i << 2], 4);
		}
	}
	ixl_sbuf_print_bytes(buf, hlut, 512, 0, false);

	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);
	sbuf_delete(buf);

	return (error);
}

static int
ixl_sysctl_hena(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	u64 hena;

	hena = (u64)i40e_read_rx_ctl(hw, I40E_PFQF_HENA(0)) |
	    ((u64)i40e_read_rx_ctl(hw, I40E_PFQF_HENA(1)) << 32);

	return sysctl_handle_long(oidp, NULL, hena, req);
}

/*
 * Sysctl to disable firmware's link management
 *
 * 1 - Disable link management on this port
 * 0 - Re-enable link management
 *
 * On normal NVMs, firmware manages link by default.
 */
static int
ixl_sysctl_fw_link_management(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	int requested_mode = -1;
	enum i40e_status_code status = 0;
	int error = 0;

	/* Read in new mode */
	error = sysctl_handle_int(oidp, &requested_mode, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);
	/* Check for sane value */
	if (requested_mode < 0 || requested_mode > 1) {
		device_printf(dev, "Valid modes are 0 or 1\n");
		return (EINVAL);
	}

	/* Set new mode */
	status = i40e_aq_set_phy_debug(hw, !!(requested_mode) << 4, NULL);
	if (status) {
		device_printf(dev,
		    "%s: Error setting new phy debug mode %s,"
		    " aq error: %s\n", __func__, i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
		return (EIO);
	}

	return (0);
}

/*
 * Read some diagnostic data from a (Q)SFP+ module
 *
 *             SFP A2   QSFP Lower Page
 * Temperature 96-97	22-23
 * Vcc         98-99    26-27
 * TX power    102-103  34-35..40-41
 * RX power    104-105  50-51..56-57
 */
static int
ixl_sysctl_read_i2c_diag_data(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	device_t dev = pf->dev;
	struct sbuf *sbuf;
	int error = 0;
	u8 output;

	if (req->oldptr == NULL) {
		error = SYSCTL_OUT(req, 0, 128);
		return (0);
	}

	error = pf->read_i2c_byte(pf, 0, 0xA0, &output);
	if (error) {
		device_printf(dev, "Error reading from i2c\n");
		return (error);
	}

	/* 0x3 for SFP; 0xD/0x11 for QSFP+/QSFP28 */
	if (output == 0x3) {
		/*
		 * Check for:
		 * - Internally calibrated data
		 * - Diagnostic monitoring is implemented
		 */
		pf->read_i2c_byte(pf, 92, 0xA0, &output);
		if (!(output & 0x60)) {
			device_printf(dev, "Module doesn't support diagnostics: %02X\n", output);
			return (0);
		}

		sbuf = sbuf_new_for_sysctl(NULL, NULL, 128, req);

		for (u8 offset = 96; offset < 100; offset++) {
			pf->read_i2c_byte(pf, offset, 0xA2, &output);
			sbuf_printf(sbuf, "%02X ", output);
		}
		for (u8 offset = 102; offset < 106; offset++) {
			pf->read_i2c_byte(pf, offset, 0xA2, &output);
			sbuf_printf(sbuf, "%02X ", output);
		}
	} else if (output == 0xD || output == 0x11) {
		/*
		 * QSFP+ modules are always internally calibrated, and must indicate
		 * what types of diagnostic monitoring are implemented
		 */
		sbuf = sbuf_new_for_sysctl(NULL, NULL, 128, req);

		for (u8 offset = 22; offset < 24; offset++) {
			pf->read_i2c_byte(pf, offset, 0xA0, &output);
			sbuf_printf(sbuf, "%02X ", output);
		}
		for (u8 offset = 26; offset < 28; offset++) {
			pf->read_i2c_byte(pf, offset, 0xA0, &output);
			sbuf_printf(sbuf, "%02X ", output);
		}
		/* Read the data from the first lane */
		for (u8 offset = 34; offset < 36; offset++) {
			pf->read_i2c_byte(pf, offset, 0xA0, &output);
			sbuf_printf(sbuf, "%02X ", output);
		}
		for (u8 offset = 50; offset < 52; offset++) {
			pf->read_i2c_byte(pf, offset, 0xA0, &output);
			sbuf_printf(sbuf, "%02X ", output);
		}
	} else {
		device_printf(dev, "Module is not SFP/SFP+/SFP28/QSFP+ (%02X)\n", output);
		return (0);
	}

	sbuf_finish(sbuf);
	sbuf_delete(sbuf);

	return (0);
}

/*
 * Sysctl to read a byte from I2C bus.
 *
 * Input: 32-bit value:
 * 	bits 0-7:   device address (0xA0 or 0xA2)
 * 	bits 8-15:  offset (0-255)
 *	bits 16-31: unused
 * Output: 8-bit value read
 */
static int
ixl_sysctl_read_i2c_byte(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	device_t dev = pf->dev;
	int input = -1, error = 0;

	u8 dev_addr, offset, output;

	/* Read in I2C read parameters */
	error = sysctl_handle_int(oidp, &input, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);
	/* Validate device address */
	dev_addr = input & 0xFF;
	if (dev_addr != 0xA0 && dev_addr != 0xA2) {
		return (EINVAL);
	}
	offset = (input >> 8) & 0xFF;

	error = pf->read_i2c_byte(pf, offset, dev_addr, &output);
	if (error)
		return (error);

	device_printf(dev, "%02X\n", output);
	return (0);
}

/*
 * Sysctl to write a byte to the I2C bus.
 *
 * Input: 32-bit value:
 * 	bits 0-7:   device address (0xA0 or 0xA2)
 * 	bits 8-15:  offset (0-255)
 *	bits 16-23: value to write
 *	bits 24-31: unused
 * Output: 8-bit value written
 */
static int
ixl_sysctl_write_i2c_byte(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	device_t dev = pf->dev;
	int input = -1, error = 0;

	u8 dev_addr, offset, value;

	/* Read in I2C write parameters */
	error = sysctl_handle_int(oidp, &input, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);
	/* Validate device address */
	dev_addr = input & 0xFF;
	if (dev_addr != 0xA0 && dev_addr != 0xA2) {
		return (EINVAL);
	}
	offset = (input >> 8) & 0xFF;
	value = (input >> 16) & 0xFF;

	error = pf->write_i2c_byte(pf, offset, dev_addr, value);
	if (error)
		return (error);

	device_printf(dev, "%02X written\n", value);
	return (0);
}

static int
ixl_get_fec_config(struct ixl_pf *pf, struct i40e_aq_get_phy_abilities_resp *abilities,
    u8 bit_pos, int *is_set)
{
	device_t dev = pf->dev;
	struct i40e_hw *hw = &pf->hw;
	enum i40e_status_code status;

	if (IXL_PF_IN_RECOVERY_MODE(pf))
		return (EIO);

	status = i40e_aq_get_phy_capabilities(hw,
	    FALSE, FALSE, abilities, NULL);
	if (status) {
		device_printf(dev,
		    "%s: i40e_aq_get_phy_capabilities() status %s, aq error %s\n",
		    __func__, i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
		return (EIO);
	}

	*is_set = !!(abilities->fec_cfg_curr_mod_ext_info & bit_pos);
	return (0);
}

static int
ixl_set_fec_config(struct ixl_pf *pf, struct i40e_aq_get_phy_abilities_resp *abilities,
    u8 bit_pos, int set)
{
	device_t dev = pf->dev;
	struct i40e_hw *hw = &pf->hw;
	struct i40e_aq_set_phy_config config;
	enum i40e_status_code status;

	/* Set new PHY config */
	memset(&config, 0, sizeof(config));
	config.fec_config = abilities->fec_cfg_curr_mod_ext_info & ~(bit_pos);
	if (set)
		config.fec_config |= bit_pos;
	if (config.fec_config != abilities->fec_cfg_curr_mod_ext_info) {
		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;
		status = i40e_aq_set_phy_config(hw, &config, NULL);

		if (status) {
			device_printf(dev,
			    "%s: i40e_aq_set_phy_config() status %s, aq error %s\n",
			    __func__, i40e_stat_str(hw, status),
			    i40e_aq_str(hw, hw->aq.asq_last_status));
			return (EIO);
		}
	}

	return (0);
}

static int
ixl_sysctl_fec_fc_ability(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int mode, error = 0;

	struct i40e_aq_get_phy_abilities_resp abilities;
	error = ixl_get_fec_config(pf, &abilities, I40E_AQ_ENABLE_FEC_KR, &mode);
	if (error)
		return (error);
	/* Read in new mode */
	error = sysctl_handle_int(oidp, &mode, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);

	return ixl_set_fec_config(pf, &abilities, I40E_AQ_SET_FEC_ABILITY_KR, !!(mode));
}

static int
ixl_sysctl_fec_rs_ability(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int mode, error = 0;

	struct i40e_aq_get_phy_abilities_resp abilities;
	error = ixl_get_fec_config(pf, &abilities, I40E_AQ_ENABLE_FEC_RS, &mode);
	if (error)
		return (error);
	/* Read in new mode */
	error = sysctl_handle_int(oidp, &mode, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);

	return ixl_set_fec_config(pf, &abilities, I40E_AQ_SET_FEC_ABILITY_RS, !!(mode));
}

static int
ixl_sysctl_fec_fc_request(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int mode, error = 0;

	struct i40e_aq_get_phy_abilities_resp abilities;
	error = ixl_get_fec_config(pf, &abilities, I40E_AQ_REQUEST_FEC_KR, &mode);
	if (error)
		return (error);
	/* Read in new mode */
	error = sysctl_handle_int(oidp, &mode, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);

	return ixl_set_fec_config(pf, &abilities, I40E_AQ_SET_FEC_REQUEST_KR, !!(mode));
}

static int
ixl_sysctl_fec_rs_request(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int mode, error = 0;

	struct i40e_aq_get_phy_abilities_resp abilities;
	error = ixl_get_fec_config(pf, &abilities, I40E_AQ_REQUEST_FEC_RS, &mode);
	if (error)
		return (error);
	/* Read in new mode */
	error = sysctl_handle_int(oidp, &mode, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);

	return ixl_set_fec_config(pf, &abilities, I40E_AQ_SET_FEC_REQUEST_RS, !!(mode));
}

static int
ixl_sysctl_fec_auto_enable(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int mode, error = 0;

	struct i40e_aq_get_phy_abilities_resp abilities;
	error = ixl_get_fec_config(pf, &abilities, I40E_AQ_ENABLE_FEC_AUTO, &mode);
	if (error)
		return (error);
	/* Read in new mode */
	error = sysctl_handle_int(oidp, &mode, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);

	return ixl_set_fec_config(pf, &abilities, I40E_AQ_SET_FEC_AUTO, !!(mode));
}

static int
ixl_sysctl_dump_debug_data(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	struct sbuf *buf;
	int error = 0;
	enum i40e_status_code status;

	buf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
	if (!buf) {
		device_printf(dev, "Could not allocate sbuf for output.\n");
		return (ENOMEM);
	}

	u8 *final_buff;
	/* This amount is only necessary if reading the entire cluster into memory */
#define IXL_FINAL_BUFF_SIZE	(1280 * 1024)
	final_buff = malloc(IXL_FINAL_BUFF_SIZE, M_IXL, M_WAITOK);
	int final_buff_len = 0;

	u8 cluster_id = 1;
	bool more = true;

	u8 dump_buf[4096];
	u16 curr_buff_size = 4096;
	u8 curr_next_table = 0;
	u32 curr_next_index = 0;

	u16 ret_buff_size;
	u8 ret_next_table;
	u32 ret_next_index;

	sbuf_cat(buf, "\n");

	while (more) {
		status = i40e_aq_debug_dump(hw, cluster_id, curr_next_table, curr_next_index, curr_buff_size,
		    dump_buf, &ret_buff_size, &ret_next_table, &ret_next_index, NULL);
		if (status) {
			device_printf(dev, "i40e_aq_debug_dump status %s, error %s\n",
			    i40e_stat_str(hw, status), i40e_aq_str(hw, hw->aq.asq_last_status));
			goto free_out;
		}

		/* copy info out of temp buffer */
		bcopy(dump_buf, (caddr_t)final_buff + final_buff_len, ret_buff_size);
		final_buff_len += ret_buff_size;

		if (ret_next_table != curr_next_table) {
			/* We're done with the current table; we can dump out read data. */
			sbuf_printf(buf, "%d:", curr_next_table);
			int bytes_printed = 0;
			while (bytes_printed <= final_buff_len) {
				sbuf_printf(buf, "%16D", ((caddr_t)final_buff + bytes_printed), "");
				bytes_printed += 16;
			}
				sbuf_cat(buf, "\n");

			/* The entire cluster has been read; we're finished */
			if (ret_next_table == 0xFF)
				break;

			/* Otherwise clear the output buffer and continue reading */
			bzero(final_buff, IXL_FINAL_BUFF_SIZE);
			final_buff_len = 0;
		}

		if (ret_next_index == 0xFFFFFFFF)
			ret_next_index = 0;

		bzero(dump_buf, sizeof(dump_buf));
		curr_next_table = ret_next_table;
		curr_next_index = ret_next_index;
	}

free_out:
	free(final_buff, M_IXL);
	error = sbuf_finish(buf);
	if (error)
		device_printf(dev, "Error finishing sbuf: %d\n", error);
	sbuf_delete(buf);

	return (error);
}

static int
ixl_start_fw_lldp(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	enum i40e_status_code status;

	status = i40e_aq_start_lldp(hw, false, NULL);
	if (status != I40E_SUCCESS) {
		switch (hw->aq.asq_last_status) {
		case I40E_AQ_RC_EEXIST:
			device_printf(pf->dev,
			    "FW LLDP agent is already running\n");
			break;
		case I40E_AQ_RC_EPERM:
			device_printf(pf->dev,
			    "Device configuration forbids SW from starting "
			    "the LLDP agent. Set the \"LLDP Agent\" UEFI HII "
			    "attribute to \"Enabled\" to use this sysctl\n");
			return (EINVAL);
		default:
			device_printf(pf->dev,
			    "Starting FW LLDP agent failed: error: %s, %s\n",
			    i40e_stat_str(hw, status),
			    i40e_aq_str(hw, hw->aq.asq_last_status));
			return (EINVAL);
		}
	}

	ixl_clear_state(&pf->state, IXL_STATE_FW_LLDP_DISABLED);
	return (0);
}

static int
ixl_stop_fw_lldp(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	enum i40e_status_code status;

	if (hw->func_caps.npar_enable != 0) {
		device_printf(dev,
		    "Disabling FW LLDP agent is not supported on this device\n");
		return (EINVAL);
	}

	if ((hw->flags & I40E_HW_FLAG_FW_LLDP_STOPPABLE) == 0) {
		device_printf(dev,
		    "Disabling FW LLDP agent is not supported in this FW version. Please update FW to enable this feature.\n");
		return (EINVAL);
	}

	status = i40e_aq_stop_lldp(hw, true, false, NULL);
	if (status != I40E_SUCCESS) {
		if (hw->aq.asq_last_status != I40E_AQ_RC_EPERM) {
			device_printf(dev,
			    "Disabling FW LLDP agent failed: error: %s, %s\n",
			    i40e_stat_str(hw, status),
			    i40e_aq_str(hw, hw->aq.asq_last_status));
			return (EINVAL);
		}

		device_printf(dev, "FW LLDP agent is already stopped\n");
	}

	i40e_aq_set_dcb_parameters(hw, true, NULL);
	ixl_set_state(&pf->state, IXL_STATE_FW_LLDP_DISABLED);
	return (0);
}

static int
ixl_sysctl_fw_lldp(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int state, new_state, error = 0;

	state = new_state = !ixl_test_state(&pf->state, IXL_STATE_FW_LLDP_DISABLED);

	/* Read in new mode */
	error = sysctl_handle_int(oidp, &new_state, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);

	/* Already in requested state */
	if (new_state == state)
		return (error);

	if (new_state == 0)
		return ixl_stop_fw_lldp(pf);

	return ixl_start_fw_lldp(pf);
}

static int
ixl_sysctl_eee_enable(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf         *pf = (struct ixl_pf *)arg1;
	int                   state, new_state;
	int                   sysctl_handle_status = 0;
	enum i40e_status_code cmd_status;

	/* Init states' values */
	state = new_state = ixl_test_state(&pf->state, IXL_STATE_EEE_ENABLED);

	/* Get requested mode */
	sysctl_handle_status = sysctl_handle_int(oidp, &new_state, 0, req);
	if ((sysctl_handle_status) || (req->newptr == NULL))
		return (sysctl_handle_status);

	/* Check if state has changed */
	if (new_state == state)
		return (0);

	/* Set new state */
	cmd_status = i40e_enable_eee(&pf->hw, (bool)(!!new_state));

	/* Save new state or report error */
	if (!cmd_status) {
		if (new_state == 0)
			ixl_clear_state(&pf->state, IXL_STATE_EEE_ENABLED);
		else
			ixl_set_state(&pf->state, IXL_STATE_EEE_ENABLED);
	} else if (cmd_status == I40E_ERR_CONFIG)
		return (EPERM);
	else
		return (EIO);

	return (0);
}

static int
ixl_sysctl_set_link_active(SYSCTL_HANDLER_ARGS)
{
	struct ixl_pf *pf = (struct ixl_pf *)arg1;
	int error, state;

	state = ixl_test_state(&pf->state, IXL_STATE_LINK_ACTIVE_ON_DOWN);

	error = sysctl_handle_int(oidp, &state, 0, req);
	if ((error) || (req->newptr == NULL))
		return (error);

	if (state == 0)
		ixl_clear_state(&pf->state, IXL_STATE_LINK_ACTIVE_ON_DOWN);
	else
		ixl_set_state(&pf->state, IXL_STATE_LINK_ACTIVE_ON_DOWN);

	return (0);
}

int
ixl_attach_get_link_status(struct ixl_pf *pf)
{
	struct i40e_hw *hw = &pf->hw;
	device_t dev = pf->dev;
	enum i40e_status_code status;

	if (((hw->aq.fw_maj_ver == 4) && (hw->aq.fw_min_ver < 33)) ||
	    (hw->aq.fw_maj_ver < 4)) {
		i40e_msec_delay(75);
		status = i40e_aq_set_link_restart_an(hw, TRUE, NULL);
		if (status != I40E_SUCCESS) {
			device_printf(dev,
			    "%s link restart failed, status: %s aq_err=%s\n",
			    __func__, i40e_stat_str(hw, status),
			    i40e_aq_str(hw, hw->aq.asq_last_status));
			return (EINVAL);
		}
	}

	/* Determine link state */
	hw->phy.get_link_info = TRUE;
	status = i40e_get_link_status(hw, &pf->link_up);
	if (status != I40E_SUCCESS) {
		device_printf(dev,
		    "%s get link status, status: %s aq_err=%s\n",
		    __func__, i40e_stat_str(hw, status),
		    i40e_aq_str(hw, hw->aq.asq_last_status));
		/*
		 * Most probably FW has not finished configuring PHY.
		 * Retry periodically in a timer callback.
		 */
		ixl_set_state(&pf->state, IXL_STATE_LINK_POLLING);
		pf->link_poll_start = getsbinuptime();
		return (EAGAIN);
	}
	ixl_dbg_link(pf, "%s link_up: %d\n", __func__, pf->link_up);

	/* Flow Control mode not set by user, read current FW settings */
	if (pf->fc == -1)
		pf->fc = hw->fc.current_mode;

	return (0);
}

