summaryrefslogtreecommitdiffstats
path: root/sys/dev/ixl/if_ixlv.c
diff options
context:
space:
mode:
authorjfv <jfv@FreeBSD.org>2014-08-25 22:04:29 +0000
committerjfv <jfv@FreeBSD.org>2014-08-25 22:04:29 +0000
commitc988cfb907aeea93ac1dc1a10bfbc2221271281d (patch)
tree6fcf026ea614db21de0f732d9f7bbca7b03de800 /sys/dev/ixl/if_ixlv.c
parent4e198665c508b72fb221139ec91629bc98646b9c (diff)
downloadFreeBSD-src-c988cfb907aeea93ac1dc1a10bfbc2221271281d.zip
FreeBSD-src-c988cfb907aeea93ac1dc1a10bfbc2221271281d.tar.gz
MFC of the Intel Base driver for the Intel XL710 Ethernet Controller Family
- It was decided to change the driver name to if_ixl for FreeBSD - This release adds the VF Driver to the tree, it can be built into the kernel or as the if_ixlv module - The VF driver is independent for the first time, this will be desireable when full SRIOV capability is added to the OS. Submitted by: jack.vogel@intel.com and eric.joyner@intel.com
Diffstat (limited to 'sys/dev/ixl/if_ixlv.c')
-rw-r--r--sys/dev/ixl/if_ixlv.c2742
1 files changed, 2742 insertions, 0 deletions
diff --git a/sys/dev/ixl/if_ixlv.c b/sys/dev/ixl/if_ixlv.c
new file mode 100644
index 0000000..0e6e572
--- /dev/null
+++ b/sys/dev/ixl/if_ixlv.c
@@ -0,0 +1,2742 @@
+/******************************************************************************
+
+ Copyright (c) 2013-2014, 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 "opt_inet.h"
+#include "opt_inet6.h"
+#include "ixl.h"
+#include "ixlv.h"
+
+/*********************************************************************
+ * Driver version
+ *********************************************************************/
+char ixlv_driver_version[] = "1.1.4";
+
+/*********************************************************************
+ * PCI Device ID Table
+ *
+ * Used by probe to select devices to load on
+ * Last field stores an index into ixlv_strings
+ * Last entry must be all 0s
+ *
+ * { Vendor ID, Device ID, SubVendor ID, SubDevice ID, String Index }
+ *********************************************************************/
+
+static ixl_vendor_info_t ixlv_vendor_info_array[] =
+{
+ {I40E_INTEL_VENDOR_ID, I40E_DEV_ID_VF, 0, 0, 0},
+ {I40E_INTEL_VENDOR_ID, I40E_DEV_ID_VF_HV, 0, 0, 0},
+ /* required last entry */
+ {0, 0, 0, 0, 0}
+};
+
+/*********************************************************************
+ * Table of branding strings
+ *********************************************************************/
+
+static char *ixlv_strings[] = {
+ "Intel(R) Ethernet Connection XL710 VF Driver"
+};
+
+
+/*********************************************************************
+ * Function prototypes
+ *********************************************************************/
+static int ixlv_probe(device_t);
+static int ixlv_attach(device_t);
+static int ixlv_detach(device_t);
+static int ixlv_shutdown(device_t);
+static void ixlv_init_locked(struct ixlv_sc *);
+static int ixlv_allocate_pci_resources(struct ixlv_sc *);
+static void ixlv_free_pci_resources(struct ixlv_sc *);
+static int ixlv_assign_msix(struct ixlv_sc *);
+static int ixlv_init_msix(struct ixlv_sc *);
+static int ixlv_init_taskqueue(struct ixlv_sc *);
+static int ixlv_setup_queues(struct ixlv_sc *);
+static void ixlv_config_rss(struct ixlv_sc *);
+static void ixlv_stop(struct ixlv_sc *);
+static void ixlv_add_multi(struct ixl_vsi *);
+static void ixlv_del_multi(struct ixl_vsi *);
+static void ixlv_update_link_status(struct ixlv_sc *);
+static void ixlv_free_queues(struct ixl_vsi *);
+static int ixlv_setup_interface(device_t, struct ixlv_sc *);
+
+static int ixlv_media_change(struct ifnet *);
+static void ixlv_media_status(struct ifnet *, struct ifmediareq *);
+
+static void ixlv_local_timer(void *);
+
+static int ixlv_add_mac_filter(struct ixlv_sc *, u8 *, u16);
+static void ixlv_init_filters(struct ixlv_sc *);
+static void ixlv_free_filters(struct ixlv_sc *);
+
+static void ixlv_msix_que(void *);
+static void ixlv_msix_adminq(void *);
+static void ixlv_do_adminq(void *, int);
+static void ixlv_sched_aq(void *);
+static void ixlv_handle_que(void *, int);
+static int ixlv_reset(struct ixlv_sc *);
+static int ixlv_reset_complete(struct i40e_hw *);
+static void ixlv_set_queue_rx_itr(struct ixl_queue *);
+static void ixlv_set_queue_tx_itr(struct ixl_queue *);
+
+static void ixlv_enable_adminq_irq(struct i40e_hw *);
+static void ixlv_disable_adminq_irq(struct i40e_hw *);
+static void ixlv_enable_queue_irq(struct i40e_hw *, int);
+static void ixlv_disable_queue_irq(struct i40e_hw *, int);
+
+static void ixlv_setup_vlan_filters(struct ixlv_sc *);
+static void ixlv_register_vlan(void *, struct ifnet *, u16);
+static void ixlv_unregister_vlan(void *, struct ifnet *, u16);
+
+static void ixlv_cap_txcsum_tso(struct ixl_vsi *,
+ struct ifnet *, int);
+
+static void ixlv_add_stats_sysctls(struct ixlv_sc *);
+
+/*********************************************************************
+ * FreeBSD Device Interface Entry Points
+ *********************************************************************/
+
+static device_method_t ixlv_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ixlv_probe),
+ DEVMETHOD(device_attach, ixlv_attach),
+ DEVMETHOD(device_detach, ixlv_detach),
+ DEVMETHOD(device_shutdown, ixlv_shutdown),
+ {0, 0}
+};
+
+static driver_t ixlv_driver = {
+ "ixlv", ixlv_methods, sizeof(struct ixlv_sc),
+};
+
+devclass_t ixlv_devclass;
+DRIVER_MODULE(ixlv, pci, ixlv_driver, ixlv_devclass, 0, 0);
+
+MODULE_DEPEND(ixlv, pci, 1, 1, 1);
+MODULE_DEPEND(ixlv, ether, 1, 1, 1);
+
+/*
+** TUNEABLE PARAMETERS:
+*/
+
+static SYSCTL_NODE(_hw, OID_AUTO, ixlv, CTLFLAG_RD, 0,
+ "IXLV driver parameters");
+
+/*
+** Number of descriptors per ring:
+** - TX and RX are the same size
+*/
+static int ixlv_ringsz = DEFAULT_RING;
+TUNABLE_INT("hw.ixlv.ringsz", &ixlv_ringsz);
+SYSCTL_INT(_hw_ixlv, OID_AUTO, ring_size, CTLFLAG_RDTUN,
+ &ixlv_ringsz, 0, "Descriptor Ring Size");
+
+/* Set to zero to auto calculate */
+int ixlv_max_queues = 0;
+TUNABLE_INT("hw.ixlv.max_queues", &ixlv_max_queues);
+SYSCTL_INT(_hw_ixlv, OID_AUTO, max_queues, CTLFLAG_RDTUN,
+ &ixlv_max_queues, 0, "Number of Queues");
+
+/*
+** Number of entries in Tx queue buf_ring.
+** Increasing this will reduce the number of
+** errors when transmitting fragmented UDP
+** packets.
+*/
+static int ixlv_txbrsz = DEFAULT_TXBRSZ;
+TUNABLE_INT("hw.ixlv.txbrsz", &ixlv_txbrsz);
+SYSCTL_INT(_hw_ixlv, OID_AUTO, txbr_size, CTLFLAG_RDTUN,
+ &ixlv_txbrsz, 0, "TX Buf Ring Size");
+
+/*
+** Controls for Interrupt Throttling
+** - true/false for dynamic adjustment
+** - default values for static ITR
+*/
+int ixlv_dynamic_rx_itr = 0;
+TUNABLE_INT("hw.ixlv.dynamic_rx_itr", &ixlv_dynamic_rx_itr);
+SYSCTL_INT(_hw_ixlv, OID_AUTO, dynamic_rx_itr, CTLFLAG_RDTUN,
+ &ixlv_dynamic_rx_itr, 0, "Dynamic RX Interrupt Rate");
+
+int ixlv_dynamic_tx_itr = 0;
+TUNABLE_INT("hw.ixlv.dynamic_tx_itr", &ixlv_dynamic_tx_itr);
+SYSCTL_INT(_hw_ixlv, OID_AUTO, dynamic_tx_itr, CTLFLAG_RDTUN,
+ &ixlv_dynamic_tx_itr, 0, "Dynamic TX Interrupt Rate");
+
+int ixlv_rx_itr = IXL_ITR_8K;
+TUNABLE_INT("hw.ixlv.rx_itr", &ixlv_rx_itr);
+SYSCTL_INT(_hw_ixlv, OID_AUTO, rx_itr, CTLFLAG_RDTUN,
+ &ixlv_rx_itr, 0, "RX Interrupt Rate");
+
+int ixlv_tx_itr = IXL_ITR_4K;
+TUNABLE_INT("hw.ixlv.tx_itr", &ixlv_tx_itr);
+SYSCTL_INT(_hw_ixlv, OID_AUTO, tx_itr, CTLFLAG_RDTUN,
+ &ixlv_tx_itr, 0, "TX Interrupt Rate");
+
+
+/*********************************************************************
+ * Device identification routine
+ *
+ * ixlv_probe determines if the driver should be loaded on
+ * the hardware based on PCI vendor/device id of the device.
+ *
+ * return BUS_PROBE_DEFAULT on success, positive on failure
+ *********************************************************************/
+
+static int
+ixlv_probe(device_t dev)
+{
+ ixl_vendor_info_t *ent;
+
+ u16 pci_vendor_id, pci_device_id;
+ u16 pci_subvendor_id, pci_subdevice_id;
+ char device_name[256];
+
+ INIT_DEBUGOUT("ixlv_probe: begin");
+
+ pci_vendor_id = pci_get_vendor(dev);
+ if (pci_vendor_id != I40E_INTEL_VENDOR_ID)
+ return (ENXIO);
+
+ pci_device_id = pci_get_device(dev);
+ pci_subvendor_id = pci_get_subvendor(dev);
+ pci_subdevice_id = pci_get_subdevice(dev);
+
+ ent = ixlv_vendor_info_array;
+ while (ent->vendor_id != 0) {
+ if ((pci_vendor_id == ent->vendor_id) &&
+ (pci_device_id == ent->device_id) &&
+
+ ((pci_subvendor_id == ent->subvendor_id) ||
+ (ent->subvendor_id == 0)) &&
+
+ ((pci_subdevice_id == ent->subdevice_id) ||
+ (ent->subdevice_id == 0))) {
+ sprintf(device_name, "%s, Version - %s",
+ ixlv_strings[ent->index],
+ ixlv_driver_version);
+ device_set_desc_copy(dev, device_name);
+ return (BUS_PROBE_DEFAULT);
+ }
+ ent++;
+ }
+ return (ENXIO);
+}
+
+/*********************************************************************
+ * Device initialization routine
+ *
+ * The attach entry point is called when the driver is being loaded.
+ * This routine identifies the type of hardware, allocates all resources
+ * and initializes the hardware.
+ *
+ * return 0 on success, positive on failure
+ *********************************************************************/
+
+static int
+ixlv_attach(device_t dev)
+{
+ struct ixlv_sc *sc;
+ struct i40e_hw *hw;
+ struct ixl_vsi *vsi;
+ int bufsz, error = 0, retries = 0;
+
+ INIT_DBG_DEV(dev, "begin");
+
+ /* Allocate, clear, and link in our primary soft structure */
+ sc = device_get_softc(dev);
+ sc->dev = sc->osdep.dev = dev;
+ hw = &sc->hw;
+ vsi = &sc->vsi;
+ vsi->dev = dev;
+
+ /* Allocate filter lists */
+ ixlv_init_filters(sc);
+
+ /* Core Lock Init*/
+ mtx_init(&sc->mtx, device_get_nameunit(dev),
+ "IXL SC Lock", MTX_DEF);
+ mtx_init(&sc->aq_task_mtx, device_get_nameunit(dev),
+ "IXL AQ Task Lock", MTX_DEF);
+
+ /* Set up the timer & aq watchdog callouts */
+ callout_init_mtx(&sc->timer, &sc->mtx, 0);
+ callout_init_mtx(&sc->aq_task, &sc->aq_task_mtx, 0);
+
+ /* Save off the information about this board */
+ 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);
+
+ /* Do PCI setup - map BAR0, etc */
+ if (ixlv_allocate_pci_resources(sc)) {
+ device_printf(dev, "%s: Allocation of PCI resources failed\n",
+ __func__);
+ error = ENXIO;
+ goto err_early;
+ }
+
+ INIT_DBG_DEV(dev, "Allocated PCI resources and MSIX vectors");
+
+ error = i40e_set_mac_type(hw);
+ if (error) {
+ device_printf(dev, "%s: set_mac_type failed: %d\n",
+ __func__, error);
+ goto err_pci_res;
+ }
+
+ error = ixlv_reset_complete(hw);
+ if (error) {
+ device_printf(dev, "%s: Device is still being reset\n",
+ __func__);
+ goto err_pci_res;
+ }
+
+ INIT_DBG_DEV(dev, "VF Device is ready for configuration");
+
+ hw->aq.num_arq_entries = IXL_AQ_LEN;
+ hw->aq.num_asq_entries = IXL_AQ_LEN;
+ hw->aq.arq_buf_size = IXL_AQ_BUFSZ;
+ hw->aq.asq_buf_size = IXL_AQ_BUFSZ;
+
+ error = i40e_init_adminq(hw);
+ if (error) {
+ device_printf(dev, "%s: init_adminq failed: %d\n",
+ __func__, error);
+ goto err_pci_res;
+ }
+
+ INIT_DBG_DEV(dev, "Initialized Admin Queue");
+
+ error = ixlv_send_api_ver(sc);
+ if (error) {
+ device_printf(dev, "%s: unable to send to PF (%d)\n",
+ __func__, error);
+ goto err_aq;
+ }
+
+ while (!i40e_asq_done(hw)) {
+ if (++retries > IXLV_AQ_MAX_ERR) {
+ device_printf(dev, "%s: Admin Queue timeout "
+ "(waiting for send_api_ver)\n", __func__);
+ error = ENXIO;
+ goto err_aq;
+ }
+ i40e_msec_delay(10);
+ }
+
+ INIT_DBG_DEV(dev, "Sent API version message to PF");
+
+ /* Wait for API version msg to arrive */
+ error = ixlv_verify_api_ver(sc);
+ if (error) {
+ device_printf(dev,
+ "%s: Unable to verify API version, error %d\n",
+ __func__, error);
+ goto err_aq;
+ }
+
+ INIT_DBG_DEV(dev, "PF API version verified");
+
+ /* Need API version before sending reset message */
+ error = ixlv_reset(sc);
+ if (error) {
+ device_printf(dev, "VF reset failed; reload the driver\n");
+ goto err_aq;
+ }
+
+ INIT_DBG_DEV(dev, "VF reset complete");
+
+ /* Ask for VF config from PF */
+ error = ixlv_send_vf_config_msg(sc);
+ if (error) {
+ device_printf(dev,
+ "%s: Unable to send VF config request, error %d\n",
+ __func__, error);
+ goto err_aq;
+ }
+
+ retries = 0;
+ while (!i40e_asq_done(hw)) {
+ if (++retries > IXLV_AQ_MAX_ERR) {
+ device_printf(dev, "%s: Admin Queue timeout "
+ "(waiting for send_vf_config_msg)\n", __func__);
+ error = ENXIO;
+ goto err_aq;
+ }
+ i40e_msec_delay(10);
+ }
+
+ INIT_DBG_DEV(dev, "Sent VF config message to PF");
+
+ bufsz = sizeof(struct i40e_virtchnl_vf_resource) +
+ (I40E_MAX_VF_VSI * sizeof(struct i40e_virtchnl_vsi_resource));
+ sc->vf_res = malloc(bufsz, M_DEVBUF, M_NOWAIT);
+ if (!sc->vf_res) {
+ device_printf(dev,
+ "%s: Unable to allocate memory for VF configuration"
+ " message from PF\n", __func__);
+ error = ENOMEM;
+ goto err_aq;
+ }
+
+ /* Check for VF config response */
+ error = ixlv_get_vf_config(sc);
+ if (error) {
+ device_printf(dev,
+ "%s: Unable to get VF configuration from PF\n",
+ __func__);
+ error = EBUSY;
+ goto err_res_buf;
+ }
+
+ INIT_DBG_DEV(dev, "Received valid VF config from PF");
+ INIT_DBG_DEV(dev, "VSIs %d, Queues %d, Max Vectors %d, Max MTU %d",
+ sc->vf_res->num_vsis,
+ sc->vf_res->num_queue_pairs,
+ sc->vf_res->max_vectors,
+ sc->vf_res->max_mtu);
+ INIT_DBG_DEV(dev, "Offload flags: %#010x",
+ sc->vf_res->vf_offload_flags);
+
+ /* got VF config message back from PF, now we can parse it */
+ for (int i = 0; i < sc->vf_res->num_vsis; i++) {
+ if (sc->vf_res->vsi_res[i].vsi_type == I40E_VSI_SRIOV)
+ sc->vsi_res = &sc->vf_res->vsi_res[i];
+ }
+ if (!sc->vsi_res) {
+ device_printf(dev, "%s: no LAN VSI found\n", __func__);
+ goto err_res_buf;
+ }
+
+ INIT_DBG_DEV(dev, "Resource Acquisition complete");
+
+ /* If no mac address was assigned just make a random one */
+ if (!ixlv_check_ether_addr(hw->mac.addr)) {
+ u8 addr[ETHER_ADDR_LEN];
+ arc4rand(&addr, sizeof(addr), 0);
+ addr[0] &= 0xFE;
+ addr[0] |= 0x02;
+ bcopy(addr, hw->mac.addr, sizeof(addr));
+ }
+
+ vsi->id = sc->vsi_res->vsi_id;
+ vsi->back = (void *)sc;
+
+ /* Link in this virtual environment is always 'up' */
+ vsi->link_up = TRUE;
+
+ /* This allocates the memory and early settings */
+ if (ixlv_setup_queues(sc) != 0) {
+ device_printf(dev, "%s: setup queues failed!\n",
+ __func__);
+ goto out;
+ }
+
+ /* Setup the stack interface */
+ if (ixlv_setup_interface(dev, sc) != 0) {
+ device_printf(dev, "%s: setup interface failed!\n",
+ __func__);
+ goto out;
+ }
+
+ INIT_DBG_DEV(dev, "Queue memory and interface setup");
+
+ /* Do queue interrupt setup */
+ ixlv_assign_msix(sc);
+
+ /* Start AdminQ taskqueue */
+ ixlv_init_taskqueue(sc);
+
+ /* Start the admin queue scheduler timer */
+ callout_reset(&sc->aq_task, 2 * hz, ixlv_sched_aq, sc);
+
+ /* Initialize stats */
+ bzero(&sc->vsi.eth_stats, sizeof(struct i40e_eth_stats));
+ ixlv_add_stats_sysctls(sc);
+
+ /* Register for VLAN events */
+ vsi->vlan_attach = EVENTHANDLER_REGISTER(vlan_config,
+ ixlv_register_vlan, vsi, EVENTHANDLER_PRI_FIRST);
+ vsi->vlan_detach = EVENTHANDLER_REGISTER(vlan_unconfig,
+ ixlv_unregister_vlan, vsi, EVENTHANDLER_PRI_FIRST);
+
+ /* We want AQ enabled early */
+ ixlv_enable_adminq_irq(hw);
+
+ /* Set things up to run init */
+ sc->aq_pending = 0;
+ sc->aq_required = 0;
+ sc->init_state = IXLV_INIT_READY;
+
+ INIT_DBG_DEV(dev, "end");
+ return (error);
+
+out:
+ ixlv_free_queues(vsi);
+err_res_buf:
+ free(sc->vf_res, M_DEVBUF);
+err_aq:
+ i40e_shutdown_adminq(hw);
+err_pci_res:
+ ixlv_free_pci_resources(sc);
+err_early:
+ mtx_destroy(&sc->mtx);
+ mtx_destroy(&sc->aq_task_mtx);
+ ixlv_free_filters(sc);
+ INIT_DBG_DEV(dev, "end: error %d", error);
+ return (error);
+}
+
+/*********************************************************************
+ * Device removal routine
+ *
+ * The detach entry point is called when the driver is being removed.
+ * This routine stops the adapter and deallocates all the resources
+ * that were allocated for driver operation.
+ *
+ * return 0 on success, positive on failure
+ *********************************************************************/
+
+static int
+ixlv_detach(device_t dev)
+{
+ struct ixlv_sc *sc = device_get_softc(dev);
+ struct ixl_vsi *vsi = &sc->vsi;
+ int retries = 0;
+
+ INIT_DBG_DEV(dev, "begin");
+
+ /* Make sure VLANS are not using driver */
+ if (vsi->ifp->if_vlantrunk != NULL) {
+ device_printf(dev, "Vlan in use, detach first\n");
+ INIT_DBG_DEV(dev, "end");
+ return (EBUSY);
+ }
+
+ /* Stop driver */
+ if (vsi->ifp->if_drv_flags & IFF_DRV_RUNNING) {
+ mtx_lock(&sc->mtx);
+ ixlv_stop(sc);
+ mtx_unlock(&sc->mtx);
+
+ /*
+ ** Ensure queues are disabled before examining
+ ** admin queue state later in detach.
+ */
+ while (vsi->ifp->if_drv_flags & IFF_DRV_RUNNING
+ && ++retries < IXLV_AQ_MAX_ERR) {
+ i40e_msec_delay(10);
+ }
+#ifdef IXL_DEBUG
+ if (retries >= IXLV_AQ_MAX_ERR)
+ device_printf(dev, "Issue disabling queues for detach\n");
+#endif
+ }
+
+ /* Unregister VLAN events */
+ if (vsi->vlan_attach != NULL)
+ EVENTHANDLER_DEREGISTER(vlan_config, vsi->vlan_attach);
+ if (vsi->vlan_detach != NULL)
+ EVENTHANDLER_DEREGISTER(vlan_unconfig, vsi->vlan_detach);
+
+ /* Stop AQ callout */
+ callout_drain(&sc->aq_task);
+ callout_stop(&sc->aq_task);
+
+#ifdef IXL_DEBUG
+ /* Report on possible AQ failures */
+ if (sc->aq_required || sc->aq_pending) {
+ device_printf(dev, "AQ status on detach:\n");
+ device_printf(dev, "required : 0x%4b\n", sc->aq_required,
+ IXLV_FLAGS);
+ device_printf(dev, "pending : 0x%4b\n", sc->aq_pending,
+ IXLV_FLAGS);
+ device_printf(dev, "current_op: %d\n", sc->current_op);
+ }
+#endif
+
+ i40e_shutdown_adminq(&sc->hw);
+ while (taskqueue_cancel(sc->tq, &sc->aq_irq, NULL) != 0)
+ taskqueue_drain(sc->tq, &sc->aq_irq);
+ taskqueue_free(sc->tq);
+
+ /* force the state down */
+ vsi->ifp->if_flags &= ~IFF_UP;
+ ether_ifdetach(vsi->ifp);
+ if_free(vsi->ifp);
+
+ free(sc->vf_res, M_DEVBUF);
+ ixlv_free_pci_resources(sc);
+ ixlv_free_queues(vsi);
+ mtx_destroy(&sc->mtx);
+ mtx_destroy(&sc->aq_task_mtx);
+ ixlv_free_filters(sc);
+
+ bus_generic_detach(dev);
+ INIT_DBG_DEV(dev, "end");
+ return (0);
+}
+
+/*********************************************************************
+ *
+ * Shutdown entry point
+ *
+ **********************************************************************/
+
+static int
+ixlv_shutdown(device_t dev)
+{
+ struct ixlv_sc *sc = device_get_softc(dev);
+
+ INIT_DBG_DEV(dev, "begin");
+
+ mtx_lock(&sc->mtx);
+ ixlv_stop(sc);
+ mtx_unlock(&sc->mtx);
+
+ INIT_DBG_DEV(dev, "end");
+ return (0);
+}
+
+/*
+ * Configure TXCSUM(IPV6) and TSO(4/6)
+ * - the hardware handles these together so we
+ * need to tweak them
+ */
+static void
+ixlv_cap_txcsum_tso(struct ixl_vsi *vsi, struct ifnet *ifp, int mask)
+{
+ /* 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;
+ if_printf(ifp,
+ "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);
+ if_printf(ifp,
+ "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;
+ if_printf(ifp,
+ "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);
+ if_printf(ifp,
+ "TSO6 requires txcsum6, disabling both...\n");
+ } else if (mask & IFCAP_TSO6)
+ ifp->if_capenable &= ~IFCAP_TSO6;
+ }
+}
+
+/*********************************************************************
+ * Ioctl entry point
+ *
+ * ixlv_ioctl is called when the user wants to configure the
+ * interface.
+ *
+ * return 0 on success, positive on failure
+ **********************************************************************/
+
+static int
+ixlv_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
+{
+ struct ixl_vsi *vsi = ifp->if_softc;
+ struct ixlv_sc *sc = 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;
+
+
+ switch (command) {
+
+ case SIOCSIFADDR:
+#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))
+ ixlv_init(sc);
+ if (!(ifp->if_flags & IFF_NOARP))
+ arp_ifinit(ifp, ifa);
+ } else
+ error = ether_ioctl(ifp, command, data);
+ break;
+#endif
+ case SIOCSIFMTU:
+ IOCTL_DBG_IF2(ifp, "SIOCSIFMTU (Set Interface MTU)");
+ mtx_lock(&sc->mtx);
+ if (ifr->ifr_mtu > IXL_MAX_FRAME -
+ ETHER_HDR_LEN - ETHER_CRC_LEN - ETHER_VLAN_ENCAP_LEN) {
+ error = EINVAL;
+ IOCTL_DBG_IF(ifp, "mtu too large");
+ } else {
+ IOCTL_DBG_IF2(ifp, "mtu: %lu -> %d", ifp->if_mtu, ifr->ifr_mtu);
+ // ERJ: Interestingly enough, these types don't match
+ ifp->if_mtu = ifr->ifr_mtu;
+ vsi->max_frame_size =
+ ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN
+ + ETHER_VLAN_ENCAP_LEN;
+
+ ixlv_init_locked(sc);
+ }
+ mtx_unlock(&sc->mtx);
+ break;
+ case SIOCSIFFLAGS:
+ IOCTL_DBG_IF2(ifp, "SIOCSIFFLAGS (Set Interface Flags)");
+ mtx_lock(&sc->mtx);
+ if (ifp->if_flags & IFF_UP) {
+ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
+ ixlv_init_locked(sc);
+ } else
+ if (ifp->if_drv_flags & IFF_DRV_RUNNING)
+ ixlv_stop(sc);
+ sc->if_flags = ifp->if_flags;
+ mtx_unlock(&sc->mtx);
+ break;
+ case SIOCADDMULTI:
+ IOCTL_DBG_IF2(ifp, "SIOCADDMULTI");
+ if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
+ mtx_lock(&sc->mtx);
+ ixlv_disable_intr(vsi);
+ ixlv_add_multi(vsi);
+ ixlv_enable_intr(vsi);
+ mtx_unlock(&sc->mtx);
+ }
+ break;
+ case SIOCDELMULTI:
+ IOCTL_DBG_IF2(ifp, "SIOCDELMULTI");
+ if (sc->init_state == IXLV_RUNNING) {
+ mtx_lock(&sc->mtx);
+ ixlv_disable_intr(vsi);
+ ixlv_del_multi(vsi);
+ ixlv_enable_intr(vsi);
+ mtx_unlock(&sc->mtx);
+ }
+ break;
+ case SIOCSIFMEDIA:
+ case SIOCGIFMEDIA:
+ IOCTL_DBG_IF2(ifp, "SIOCxIFMEDIA (Get/Set Interface Media)");
+ error = ifmedia_ioctl(ifp, ifr, &sc->media, command);
+ break;
+ case SIOCSIFCAP:
+ {
+ int mask = ifr->ifr_reqcap ^ ifp->if_capenable;
+ IOCTL_DBG_IF2(ifp, "SIOCSIFCAP (Set Capabilities)");
+
+ ixlv_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;
+ if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
+ ixlv_init(sc);
+ }
+ VLAN_CAPABILITIES(ifp);
+
+ break;
+ }
+
+ default:
+ IOCTL_DBG_IF2(ifp, "UNKNOWN (0x%X)", (int)command);
+ error = ether_ioctl(ifp, command, data);
+ break;
+ }
+
+ return (error);
+}
+
+/*
+** To do a reinit on the VF is unfortunately more complicated
+** than a physical device, we must have the PF more or less
+** completely recreate our memory, so many things that were
+** done only once at attach in traditional drivers now must be
+** redone at each reinitialization. This function does that
+** 'prelude' so we can then call the normal locked init code.
+*/
+int
+ixlv_reinit_locked(struct ixlv_sc *sc)
+{
+ struct i40e_hw *hw = &sc->hw;
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct ifnet *ifp = vsi->ifp;
+ struct ixlv_vlan_filter *vf;
+ int error = 0;
+
+ INIT_DBG_IF(ifp, "begin");
+
+ if (ifp->if_drv_flags & IFF_DRV_RUNNING)
+ ixlv_stop(sc);
+
+ if ((sc->init_state == IXLV_RESET_REQUIRED) ||
+ (sc->init_state == IXLV_RESET_PENDING))
+ error = ixlv_reset(sc);
+
+ /* set the state in case we went thru RESET */
+ sc->init_state = IXLV_RUNNING;
+
+ if (vsi->num_vlans != 0)
+ SLIST_FOREACH(vf, sc->vlan_filters, next)
+ vf->flags = IXL_FILTER_ADD;
+ else { /* clean any stale filters */
+ while (!SLIST_EMPTY(sc->vlan_filters)) {
+ vf = SLIST_FIRST(sc->vlan_filters);
+ SLIST_REMOVE_HEAD(sc->vlan_filters, next);
+ free(vf, M_DEVBUF);
+ }
+ }
+
+ ixlv_enable_adminq_irq(hw);
+ sc->aq_pending = 0;
+ sc->aq_required = 0;
+
+ INIT_DBG_IF(ifp, "end");
+ return (error);
+}
+
+
+static void
+ixlv_init_locked(struct ixlv_sc *sc)
+{
+ struct i40e_hw *hw = &sc->hw;
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct ixl_queue *que = vsi->queues;
+ struct ifnet *ifp = vsi->ifp;
+ int error = 0;
+
+ INIT_DBG_IF(ifp, "begin");
+
+ /* Verify we have the core lock */
+ if (!mtx_owned(&sc->mtx)) {
+ if_printf(ifp, "%s: sc mutex not owned; acquire"
+ "before calling this function!\n", __func__);
+ goto init_done;
+ }
+
+ /* Do a reinit first if an init has already been done */
+ if ((sc->init_state == IXLV_RUNNING) ||
+ (sc->init_state == IXLV_RESET_REQUIRED) ||
+ (sc->init_state == IXLV_RESET_PENDING))
+ error = ixlv_reinit_locked(sc);
+ /* Don't bother with init if we failed reinit */
+ if (error)
+ goto init_done;
+
+ /* Check for an LAA mac address... */
+ bcopy(IF_LLADDR(ifp), hw->mac.addr, ETHER_ADDR_LEN);
+
+ 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_OFFLOAD_IPV4 & ~CSUM_IP);
+ if (ifp->if_capenable & IFCAP_TXCSUM_IPV6)
+ ifp->if_hwassist |= CSUM_OFFLOAD_IPV6;
+
+ /* Add mac filter for this VF to PF */
+ error = ixlv_add_mac_filter(sc, hw->mac.addr, 0);
+
+ // send message, then enqueue another task
+ if (!error || error == EEXIST) {
+ sc->aq_required |= IXLV_FLAG_AQ_ADD_MAC_FILTER;
+ callout_reset(&sc->aq_task, IXLV_CALLOUT_TIMO,
+ ixlv_sched_aq, sc);
+ }
+
+ /* Setup vlan's if needed */
+ ixlv_setup_vlan_filters(sc);
+
+ /*
+ ** Prepare the queues for operation
+ */
+ for (int i = 0; i < vsi->num_queues; i++, que++) {
+ struct rx_ring *rxr = &que->rxr;
+
+ ixl_init_tx_ring(que);
+
+ /* Need to set mbuf size now */
+ if (vsi->max_frame_size <= 2048)
+ rxr->mbuf_sz = MCLBYTES;
+ else
+ rxr->mbuf_sz = MJUMPAGESIZE;
+ ixl_init_rx_ring(que);
+ }
+
+ /* Configure queues */
+ sc->aq_required |= IXLV_FLAG_AQ_CONFIGURE_QUEUES;
+ callout_reset(&sc->aq_task, IXLV_CALLOUT_TIMO,
+ ixlv_sched_aq, sc);
+
+ /* Set up RSS */
+ ixlv_config_rss(sc);
+
+ /* Map vectors */
+ sc->aq_required |= IXLV_FLAG_AQ_MAP_VECTORS;
+ callout_reset(&sc->aq_task, IXLV_CALLOUT_TIMO,
+ ixlv_sched_aq, sc);
+
+ /* Enable queues */
+ sc->aq_required |= IXLV_FLAG_AQ_ENABLE_QUEUES;
+ callout_reset(&sc->aq_task, IXLV_CALLOUT_TIMO,
+ ixlv_sched_aq, sc);
+
+ /* Start the local timer */
+ callout_reset(&sc->timer, hz, ixlv_local_timer, sc);
+
+ sc->init_state = IXLV_RUNNING;
+
+init_done:
+ INIT_DBG_IF(ifp, "end");
+ return;
+}
+
+/*
+** Init entry point for the stack
+*/
+void
+ixlv_init(void *arg)
+{
+ struct ixlv_sc *sc = arg;
+
+ mtx_lock(&sc->mtx);
+ ixlv_init_locked(sc);
+ mtx_unlock(&sc->mtx);
+ return;
+}
+
+/*
+ * Allocate MSI/X vectors, setup the AQ vector early
+ */
+static int
+ixlv_init_msix(struct ixlv_sc *sc)
+{
+ device_t dev = sc->dev;
+ int rid, want, vectors, queues, available;
+
+ rid = PCIR_BAR(IXL_BAR);
+ sc->msix_mem = bus_alloc_resource_any(dev,
+ SYS_RES_MEMORY, &rid, RF_ACTIVE);
+ if (!sc->msix_mem) {
+ /* May not be enabled */
+ device_printf(sc->dev,
+ "Unable to map MSIX table \n");
+ goto fail;
+ }
+
+ available = pci_msix_count(dev);
+ if (available == 0) { /* system has msix disabled */
+ bus_release_resource(dev, SYS_RES_MEMORY,
+ rid, sc->msix_mem);
+ sc->msix_mem = NULL;
+ goto fail;
+ }
+
+ /* Figure out a reasonable auto config value */
+ queues = (mp_ncpus > (available - 1)) ? (available - 1) : mp_ncpus;
+
+ /* Override with hardcoded value if sane */
+ if ((ixlv_max_queues != 0) && (ixlv_max_queues <= queues))
+ queues = ixlv_max_queues;
+
+ /* Enforce the VF max value */
+ if (queues > IXLV_MAX_QUEUES)
+ queues = IXLV_MAX_QUEUES;
+
+ /*
+ ** 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(sc->dev,
+ "MSIX Configuration Problem, "
+ "%d vectors available but %d wanted!\n",
+ available, want);
+ goto fail;
+ }
+
+ if (pci_alloc_msix(dev, &vectors) == 0) {
+ device_printf(sc->dev,
+ "Using MSIX interrupts with %d vectors\n", vectors);
+ sc->msix = vectors;
+ sc->vsi.num_queues = queues;
+ }
+
+ /*
+ ** Explicitly set the guest PCI BUSMASTER capability
+ ** and we must rewrite the ENABLE in the MSIX control
+ ** register again at this point to cause the host to
+ ** successfully initialize us.
+ */
+ {
+ u16 pci_cmd_word;
+ int msix_ctrl;
+ pci_cmd_word = pci_read_config(dev, PCIR_COMMAND, 2);
+ pci_cmd_word |= PCIM_CMD_BUSMASTEREN;
+ pci_write_config(dev, PCIR_COMMAND, pci_cmd_word, 2);
+ pci_find_cap(dev, PCIY_MSIX, &rid);
+ rid += PCIR_MSIX_CTRL;
+ msix_ctrl = pci_read_config(dev, rid, 2);
+ msix_ctrl |= PCIM_MSIXCTRL_MSIX_ENABLE;
+ pci_write_config(dev, rid, msix_ctrl, 2);
+ }
+
+ /* Next we need to setup the vector for the Admin Queue */
+ rid = 1; // zero vector + 1
+ sc->res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
+ &rid, RF_SHAREABLE | RF_ACTIVE);
+ if (sc->res == NULL) {
+ device_printf(dev,"Unable to allocate"
+ " bus resource: AQ interrupt \n");
+ goto fail;
+ }
+ if (bus_setup_intr(dev, sc->res,
+ INTR_TYPE_NET | INTR_MPSAFE, NULL,
+ ixlv_msix_adminq, sc, &sc->tag)) {
+ sc->res = NULL;
+ device_printf(dev, "Failed to register AQ handler");
+ goto fail;
+ }
+ bus_describe_intr(dev, sc->res, sc->tag, "adminq");
+
+ return (vectors);
+
+fail:
+ /* The VF driver MUST use MSIX */
+ return (0);
+}
+
+static int
+ixlv_allocate_pci_resources(struct ixlv_sc *sc)
+{
+ int rid;
+ device_t dev = sc->dev;
+
+ rid = PCIR_BAR(0);
+ sc->pci_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+ &rid, RF_ACTIVE);
+
+ if (!(sc->pci_mem)) {
+ device_printf(dev,"Unable to allocate bus resource: memory\n");
+ return (ENXIO);
+ }
+
+ sc->osdep.mem_bus_space_tag =
+ rman_get_bustag(sc->pci_mem);
+ sc->osdep.mem_bus_space_handle =
+ rman_get_bushandle(sc->pci_mem);
+ sc->osdep.mem_bus_space_size = rman_get_size(sc->pci_mem);
+ sc->hw.hw_addr = (u8 *) &sc->osdep.mem_bus_space_handle;
+
+ sc->hw.back = &sc->osdep;
+
+ /* May need to pre-emptively disable adminq interrupts */
+ ixlv_disable_adminq_irq(&sc->hw);
+
+ /*
+ ** Now setup MSI/X, it will return
+ ** us the number of supported vectors
+ */
+ sc->msix = ixlv_init_msix(sc);
+
+ /* We fail without MSIX support */
+ if (sc->msix == 0)
+ return (ENXIO);
+
+ return (0);
+}
+
+static void
+ixlv_free_pci_resources(struct ixlv_sc *sc)
+{
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct ixl_queue *que = vsi->queues;
+ device_t dev = sc->dev;
+
+ /* We may get here before stations are setup */
+ if (que == NULL)
+ goto early;
+
+ /*
+ ** Release all msix queue resources:
+ */
+ for (int i = 0; i < vsi->num_queues; i++, que++) {
+ int rid = que->msix + 1;
+ if (que->tag != NULL) {
+ bus_teardown_intr(dev, que->res, que->tag);
+ que->tag = NULL;
+ }
+ if (que->res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, rid, que->res);
+ }
+
+early:
+ /* Clean the AdminQ interrupt */
+ if (sc->tag != NULL) {
+ bus_teardown_intr(dev, sc->res, sc->tag);
+ sc->tag = NULL;
+ }
+ if (sc->res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, 1, sc->res);
+
+ pci_release_msi(dev);
+
+ if (sc->msix_mem != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY,
+ PCIR_BAR(IXL_BAR), sc->msix_mem);
+
+ if (sc->pci_mem != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY,
+ PCIR_BAR(0), sc->pci_mem);
+
+ return;
+}
+
+static int
+ixlv_init_taskqueue(struct ixlv_sc *sc)
+{
+ int error = 0;
+
+ /* Tasklet for AQ Interrupts */
+ TASK_INIT(&sc->aq_irq, 0, ixlv_do_adminq, sc);
+
+ sc->tq = taskqueue_create_fast("ixl_adm", M_NOWAIT,
+ taskqueue_thread_enqueue, &sc->tq);
+ taskqueue_start_threads(&sc->tq, 1, PI_NET, "%s sc->tq",
+ device_get_nameunit(sc->dev));
+
+ return (error);
+}
+
+/*********************************************************************
+ *
+ * Setup MSIX Interrupt resources and handlers for the VSI queues
+ *
+ **********************************************************************/
+static int
+ixlv_assign_msix(struct ixlv_sc *sc)
+{
+ device_t dev = sc->dev;
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct ixl_queue *que = vsi->queues;
+ struct tx_ring *txr;
+ int error, rid, vector = 1;
+
+ for (int i = 0; i < vsi->num_queues; i++, vector++, que++) {
+ rid = vector + 1;
+ txr = &que->txr;
+ que->res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (que->res == NULL) {
+ device_printf(dev,"Unable to allocate"
+ " bus resource: que interrupt [%d]\n", vector);
+ return (ENXIO);
+ }
+ /* Set the handler function */
+ error = bus_setup_intr(dev, que->res,
+ INTR_TYPE_NET | INTR_MPSAFE, NULL,
+ ixlv_msix_que, que, &que->tag);
+ if (error) {
+ que->res = NULL;
+ device_printf(dev, "Failed to register que handler");
+ return (error);
+ }
+ bus_describe_intr(dev, que->res, que->tag, "que %d", i);
+ /* Bind the vector to a CPU */
+ bus_bind_intr(dev, que->res, i);
+ que->msix = vector;
+ vsi->que_mask |= (u64)(1 << que->msix);
+ TASK_INIT(&que->tx_task, 0, ixl_deferred_mq_start, que);
+ TASK_INIT(&que->task, 0, ixlv_handle_que, que);
+ que->tq = taskqueue_create_fast("ixlv_que", M_NOWAIT,
+ taskqueue_thread_enqueue, &que->tq);
+ taskqueue_start_threads(&que->tq, 1, PI_NET, "%s que",
+ device_get_nameunit(sc->dev));
+ }
+
+ return (0);
+}
+
+/*
+** XXX: Assumes the vf's admin queue has been initialized.
+*/
+static int
+ixlv_reset(struct ixlv_sc *sc)
+{
+ struct i40e_hw *hw = &sc->hw;
+ device_t dev = sc->dev;
+ int error = 0;
+
+ /* Ask the PF to reset us if we are initiating */
+ if (sc->init_state != IXLV_RESET_PENDING)
+ ixlv_request_reset(sc);
+
+ i40e_msec_delay(100);
+ error = ixlv_reset_complete(hw);
+ if (error) {
+ device_printf(dev, "%s: VF reset failed\n",
+ __func__);
+ return (error);
+ }
+
+ error = i40e_shutdown_adminq(hw);
+ if (error) {
+ device_printf(dev, "%s: shutdown_adminq failed: %d\n",
+ __func__, error);
+ return (error);
+ }
+
+ error = i40e_init_adminq(hw);
+ if (error) {
+ device_printf(dev, "%s: init_adminq failed: %d\n",
+ __func__, error);
+ return(error);
+ }
+
+ return (0);
+}
+
+static int
+ixlv_reset_complete(struct i40e_hw *hw)
+{
+ u32 reg;
+
+ for (int i = 0; i < 100; i++) {
+ reg = rd32(hw, I40E_VFGEN_RSTAT) &
+ I40E_VFGEN_RSTAT_VFR_STATE_MASK;
+
+ if ((reg == I40E_VFR_VFACTIVE) ||
+ (reg == I40E_VFR_COMPLETED))
+ return (0);
+ i40e_usec_delay(20);
+ }
+
+ return (EBUSY);
+}
+
+
+/*********************************************************************
+ *
+ * Setup networking device structure and register an interface.
+ *
+ **********************************************************************/
+static int
+ixlv_setup_interface(device_t dev, struct ixlv_sc *sc)
+{
+ struct ifnet *ifp;
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct ixl_queue *que = vsi->queues;
+
+ INIT_DBG_DEV(dev, "begin");
+
+ ifp = vsi->ifp = if_alloc(IFT_ETHER);
+ if (ifp == NULL) {
+ device_printf(dev, "can not allocate ifnet structure\n");
+ return (-1);
+ }
+
+ if_initname(ifp, device_get_name(dev), device_get_unit(dev));
+
+ ifp->if_mtu = ETHERMTU;
+ ifp->if_baudrate = 4000000000; // ??
+ ifp->if_init = ixlv_init;
+ ifp->if_softc = vsi;
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+ ifp->if_ioctl = ixlv_ioctl;
+
+ ifp->if_transmit = ixl_mq_start;
+
+ ifp->if_qflush = ixl_qflush;
+ ifp->if_snd.ifq_maxlen = que->num_desc - 2;
+
+ ether_ifattach(ifp, sc->hw.mac.addr);
+
+ vsi->max_frame_size =
+ ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN
+ + ETHER_VLAN_ENCAP_LEN;
+
+ /*
+ * Tell the upper layer(s) we support long frames.
+ */
+ ifp->if_data.ifi_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_VLAN_HWTAGGING
+ | IFCAP_VLAN_HWTSO
+ | IFCAP_VLAN_MTU
+ | IFCAP_VLAN_HWCSUM
+ | IFCAP_LRO;
+ 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(&sc->media, IFM_IMASK, ixlv_media_change,
+ ixlv_media_status);
+
+ // JFV Add media types later?
+
+ ifmedia_add(&sc->media, IFM_ETHER | IFM_AUTO, 0, NULL);
+ ifmedia_set(&sc->media, IFM_ETHER | IFM_AUTO);
+
+ INIT_DBG_DEV(dev, "end");
+ return (0);
+}
+
+/*
+** Allocate and setup the interface queues
+*/
+static int
+ixlv_setup_queues(struct ixlv_sc *sc)
+{
+ device_t dev = sc->dev;
+ struct ixl_vsi *vsi;
+ struct ixl_queue *que;
+ struct tx_ring *txr;
+ struct rx_ring *rxr;
+ int rsize, tsize;
+ int error = I40E_SUCCESS;
+
+ vsi = &sc->vsi;
+ vsi->back = (void *)sc;
+ vsi->hw = &sc->hw;
+ vsi->num_vlans = 0;
+
+ /* Get memory for the station queues */
+ if (!(vsi->queues =
+ (struct ixl_queue *) malloc(sizeof(struct ixl_queue) *
+ vsi->num_queues, M_DEVBUF, M_NOWAIT | M_ZERO))) {
+ device_printf(dev, "Unable to allocate queue memory\n");
+ error = ENOMEM;
+ goto early;
+ }
+
+ for (int i = 0; i < vsi->num_queues; i++) {
+ que = &vsi->queues[i];
+ que->num_desc = ixlv_ringsz;
+ que->me = i;
+ que->vsi = vsi;
+ /* mark the queue as active */
+ vsi->active_queues |= (u64)1 << que->me;
+
+ txr = &que->txr;
+ txr->que = que;
+ txr->tail = I40E_QTX_TAIL1(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, the extra int is
+ ** added as the location for HEAD WB.
+ */
+ tsize = roundup2((que->num_desc *
+ sizeof(struct i40e_tx_desc)) +
+ sizeof(u32), DBA_ALIGN);
+ if (i40e_allocate_dma(&sc->hw,
+ &txr->dma, tsize, DBA_ALIGN)) {
+ device_printf(dev,
+ "Unable to allocate TX Descriptor memory\n");
+ error = ENOMEM;
+ goto fail;
+ }
+ 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 fail;
+ }
+ /* Allocate a buf ring */
+ txr->br = buf_ring_alloc(ixlv_txbrsz, M_DEVBUF,
+ M_WAITOK, &txr->mtx);
+ if (txr->br == NULL) {
+ device_printf(dev,
+ "Critical Failure setting up TX buf ring\n");
+ error = ENOMEM;
+ goto fail;
+ }
+
+ /*
+ * Next the RX queues...
+ */
+ rsize = roundup2(que->num_desc *
+ sizeof(union i40e_rx_desc), DBA_ALIGN);
+ rxr = &que->rxr;
+ rxr->que = que;
+ rxr->tail = I40E_QRX_TAIL1(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(&sc->hw,
+ &rxr->dma, rsize, 4096)) { //JFV - should this be DBA?
+ device_printf(dev,
+ "Unable to allocate RX Descriptor memory\n");
+ error = ENOMEM;
+ goto fail;
+ }
+ 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 fail;
+ }
+ }
+
+ return (0);
+
+fail:
+ free(vsi->queues, M_DEVBUF);
+ for (int i = 0; i < vsi->num_queues; i++) {
+ que = &vsi->queues[i];
+ rxr = &que->rxr;
+ txr = &que->txr;
+ if (rxr->base)
+ i40e_free_dma(&sc->hw, &rxr->dma);
+ if (txr->base)
+ i40e_free_dma(&sc->hw, &txr->dma);
+ }
+
+early:
+ return (error);
+}
+
+/*
+** This routine is run via an vlan config EVENT,
+** it enables us to use the HW Filter table since
+** we can get the vlan id. This just creates the
+** entry in the soft version of the VFTA, init will
+** repopulate the real table.
+*/
+static void
+ixlv_register_vlan(void *arg, struct ifnet *ifp, u16 vtag)
+{
+ struct ixl_vsi *vsi = ifp->if_softc;
+ struct ixlv_sc *sc = vsi->back;
+ struct ixlv_vlan_filter *v;
+
+
+ if (ifp->if_softc != arg) /* Not our event */
+ return;
+
+ if ((vtag == 0) || (vtag > 4095)) /* Invalid */
+ return;
+
+ /* Sanity check - make sure it doesn't already exist */
+ SLIST_FOREACH(v, sc->vlan_filters, next) {
+ if (v->vlan == vtag)
+ return;
+ }
+
+ mtx_lock(&sc->mtx);
+ ++vsi->num_vlans;
+ v = malloc(sizeof(struct ixlv_vlan_filter), M_DEVBUF, M_NOWAIT | M_ZERO);
+ SLIST_INSERT_HEAD(sc->vlan_filters, v, next);
+ v->vlan = vtag;
+ v->flags = IXL_FILTER_ADD;
+ sc->aq_required |= IXLV_FLAG_AQ_ADD_VLAN_FILTER;
+ mtx_unlock(&sc->mtx);
+ return;
+}
+
+/*
+** This routine is run via an vlan
+** unconfig EVENT, remove our entry
+** in the soft vfta.
+*/
+static void
+ixlv_unregister_vlan(void *arg, struct ifnet *ifp, u16 vtag)
+{
+ struct ixl_vsi *vsi = ifp->if_softc;
+ struct ixlv_sc *sc = vsi->back;
+ struct ixlv_vlan_filter *v;
+ int i = 0;
+
+ if (ifp->if_softc != arg)
+ return;
+
+ if ((vtag == 0) || (vtag > 4095)) /* Invalid */
+ return;
+
+ mtx_lock(&sc->mtx);
+ SLIST_FOREACH(v, sc->vlan_filters, next) {
+ if (v->vlan == vtag) {
+ v->flags = IXL_FILTER_DEL;
+ ++i;
+ --vsi->num_vlans;
+ }
+ }
+ if (i)
+ sc->aq_required |= IXLV_FLAG_AQ_DEL_VLAN_FILTER;
+ mtx_unlock(&sc->mtx);
+ return;
+}
+
+/*
+** Get a new filter and add it to the mac filter list.
+*/
+static struct ixlv_mac_filter *
+ixlv_get_mac_filter(struct ixlv_sc *sc)
+{
+ struct ixlv_mac_filter *f;
+
+ f = malloc(sizeof(struct ixlv_mac_filter), M_DEVBUF, M_NOWAIT | M_ZERO);
+ SLIST_INSERT_HEAD(sc->mac_filters, f, next);
+
+ return (f);
+}
+
+/*
+** Find the filter with matching MAC address
+*/
+static struct ixlv_mac_filter *
+ixlv_find_mac_filter(struct ixlv_sc *sc, u8 *macaddr)
+{
+ struct ixlv_mac_filter *f;
+ bool match = FALSE;
+
+ SLIST_FOREACH(f, sc->mac_filters, next) {
+ if (cmp_etheraddr(f->macaddr, macaddr)) {
+ match = TRUE;
+ break;
+ }
+ }
+
+ if (!match)
+ f = NULL;
+ return (f);
+}
+
+/*
+** Admin Queue interrupt handler
+*/
+static void
+ixlv_msix_adminq(void *arg)
+{
+ struct ixlv_sc *sc = arg;
+ struct i40e_hw *hw = &sc->hw;
+ u32 reg, mask;
+
+ reg = rd32(hw, I40E_VFINT_ICR01);
+ mask = rd32(hw, I40E_VFINT_ICR0_ENA1);
+
+ reg = rd32(hw, I40E_VFINT_DYN_CTL01);
+ reg |= I40E_PFINT_DYN_CTL0_CLEARPBA_MASK;
+ wr32(hw, I40E_VFINT_DYN_CTL01, reg);
+
+ /* re-enable interrupt causes */
+ wr32(hw, I40E_VFINT_ICR0_ENA1, mask);
+ wr32(hw, I40E_VFINT_DYN_CTL01, I40E_VFINT_DYN_CTL01_INTENA_MASK);
+
+ /* schedule task */
+ taskqueue_enqueue(sc->tq, &sc->aq_irq);
+ return;
+}
+
+void
+ixlv_enable_intr(struct ixl_vsi *vsi)
+{
+ struct i40e_hw *hw = vsi->hw;
+ struct ixl_queue *que = vsi->queues;
+
+ ixlv_enable_adminq_irq(hw);
+ for (int i = 0; i < vsi->num_queues; i++, que++)
+ ixlv_enable_queue_irq(hw, que->me);
+}
+
+void
+ixlv_disable_intr(struct ixl_vsi *vsi)
+{
+ struct i40e_hw *hw = vsi->hw;
+ struct ixl_queue *que = vsi->queues;
+
+ ixlv_disable_adminq_irq(hw);
+ for (int i = 0; i < vsi->num_queues; i++, que++)
+ ixlv_disable_queue_irq(hw, que->me);
+}
+
+
+static void
+ixlv_disable_adminq_irq(struct i40e_hw *hw)
+{
+ wr32(hw, I40E_VFINT_DYN_CTL01, 0);
+ wr32(hw, I40E_VFINT_ICR0_ENA1, 0);
+ /* flush */
+ rd32(hw, I40E_VFGEN_RSTAT);
+ return;
+}
+
+static void
+ixlv_enable_adminq_irq(struct i40e_hw *hw)
+{
+ wr32(hw, I40E_VFINT_DYN_CTL01,
+ I40E_VFINT_DYN_CTL01_INTENA_MASK |
+ I40E_VFINT_DYN_CTL01_ITR_INDX_MASK);
+ wr32(hw, I40E_VFINT_ICR0_ENA1, I40E_VFINT_ICR0_ENA_ADMINQ_MASK);
+ /* flush */
+ rd32(hw, I40E_VFGEN_RSTAT);
+ return;
+}
+
+static void
+ixlv_enable_queue_irq(struct i40e_hw *hw, int id)
+{
+ u32 reg;
+
+ reg = I40E_VFINT_DYN_CTLN1_INTENA_MASK |
+ I40E_VFINT_DYN_CTLN_CLEARPBA_MASK;
+ wr32(hw, I40E_VFINT_DYN_CTLN1(id), reg);
+}
+
+static void
+ixlv_disable_queue_irq(struct i40e_hw *hw, int id)
+{
+ wr32(hw, I40E_VFINT_DYN_CTLN1(id), 0);
+ rd32(hw, I40E_VFGEN_RSTAT);
+ return;
+}
+
+
+/*
+** Provide a update to the queue RX
+** interrupt moderation value.
+*/
+static void
+ixlv_set_queue_rx_itr(struct ixl_queue *que)
+{
+ struct ixl_vsi *vsi = que->vsi;
+ 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 (ixlv_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 = rx_itr & IXL_MAX_ITR;
+ wr32(hw, I40E_VFINT_ITRN1(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 = ixlv_rx_itr;
+ /* Update the hardware if needed */
+ if (rxr->itr != vsi->rx_itr_setting) {
+ rxr->itr = vsi->rx_itr_setting;
+ wr32(hw, I40E_VFINT_ITRN1(IXL_RX_ITR,
+ que->me), rxr->itr);
+ }
+ }
+ rxr->bytes = 0;
+ rxr->packets = 0;
+ return;
+}
+
+
+/*
+** Provide a update to the queue TX
+** interrupt moderation value.
+*/
+static void
+ixlv_set_queue_tx_itr(struct ixl_queue *que)
+{
+ struct ixl_vsi *vsi = que->vsi;
+ 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 (ixlv_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 = tx_itr & IXL_MAX_ITR;
+ wr32(hw, I40E_VFINT_ITRN1(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 = ixlv_tx_itr;
+ /* Update the hardware if needed */
+ if (txr->itr != vsi->tx_itr_setting) {
+ txr->itr = vsi->tx_itr_setting;
+ wr32(hw, I40E_VFINT_ITRN1(IXL_TX_ITR,
+ que->me), txr->itr);
+ }
+ }
+ txr->bytes = 0;
+ txr->packets = 0;
+ return;
+}
+
+
+/*
+**
+** MSIX Interrupt Handlers and Tasklets
+**
+*/
+static void
+ixlv_handle_que(void *context, int pending)
+{
+ struct ixl_queue *que = context;
+ struct ixl_vsi *vsi = que->vsi;
+ 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);
+ mtx_lock(&txr->mtx);
+ ixl_txeof(que);
+ if (!drbr_empty(ifp, txr->br))
+ ixl_mq_start_locked(ifp, txr);
+ mtx_unlock(&txr->mtx);
+ if (more) {
+ taskqueue_enqueue(que->tq, &que->task);
+ return;
+ }
+ }
+
+ /* Reenable this interrupt - hmmm */
+ ixlv_enable_queue_irq(hw, que->me);
+ return;
+}
+
+
+/*********************************************************************
+ *
+ * MSIX Queue Interrupt Service routine
+ *
+ **********************************************************************/
+static void
+ixlv_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;
+
+ /* Spurious interrupts are ignored */
+ if (!(vsi->ifp->if_drv_flags & IFF_DRV_RUNNING))
+ return;
+
+ ++que->irqs;
+
+ more_rx = ixl_rxeof(que, IXL_RX_LIMIT);
+
+ mtx_lock(&txr->mtx);
+ 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;
+ mtx_unlock(&txr->mtx);
+
+ ixlv_set_queue_rx_itr(que);
+ ixlv_set_queue_tx_itr(que);
+
+ if (more_tx || more_rx)
+ taskqueue_enqueue(que->tq, &que->task);
+ else
+ ixlv_enable_queue_irq(hw, que->me);
+
+ return;
+}
+
+
+/*********************************************************************
+ *
+ * Media Ioctl callback
+ *
+ * This routine is called whenever the user queries the status of
+ * the interface using ifconfig.
+ *
+ **********************************************************************/
+static void
+ixlv_media_status(struct ifnet * ifp, struct ifmediareq * ifmr)
+{
+ struct ixl_vsi *vsi = ifp->if_softc;
+ struct ixlv_sc *sc = vsi->back;
+
+ INIT_DBG_IF(ifp, "begin");
+
+ mtx_lock(&sc->mtx);
+
+ ixlv_update_link_status(sc);
+
+ ifmr->ifm_status = IFM_AVALID;
+ ifmr->ifm_active = IFM_ETHER;
+
+ if (!vsi->link_up) {
+ mtx_unlock(&sc->mtx);
+ INIT_DBG_IF(ifp, "end: link not up");
+ return;
+ }
+
+ ifmr->ifm_status |= IFM_ACTIVE;
+ /* Hardware is always full-duplex */
+ ifmr->ifm_active |= IFM_FDX;
+ mtx_unlock(&sc->mtx);
+ INIT_DBG_IF(ifp, "end");
+ return;
+}
+
+/*********************************************************************
+ *
+ * Media Ioctl callback
+ *
+ * This routine is called when the user changes speed/duplex using
+ * media/mediopt option with ifconfig.
+ *
+ **********************************************************************/
+static int
+ixlv_media_change(struct ifnet * ifp)
+{
+ struct ixl_vsi *vsi = ifp->if_softc;
+ struct ifmedia *ifm = &vsi->media;
+
+ INIT_DBG_IF(ifp, "begin");
+
+ if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER)
+ return (EINVAL);
+
+ INIT_DBG_IF(ifp, "end");
+ return (0);
+}
+
+
+/*********************************************************************
+ * Multicast Initialization
+ *
+ * This routine is called by init to reset a fresh state.
+ *
+ **********************************************************************/
+
+static void
+ixlv_init_multi(struct ixl_vsi *vsi)
+{
+ struct ixlv_mac_filter *f;
+ struct ixlv_sc *sc = vsi->back;
+ int mcnt = 0;
+
+ IOCTL_DBG_IF(vsi->ifp, "begin");
+
+ /* First clear any multicast filters */
+ SLIST_FOREACH(f, sc->mac_filters, next) {
+ if ((f->flags & IXL_FILTER_USED)
+ && (f->flags & IXL_FILTER_MC)) {
+ f->flags |= IXL_FILTER_DEL;
+ mcnt++;
+ }
+ }
+ if (mcnt > 0)
+ sc->aq_required |= IXLV_FLAG_AQ_DEL_MAC_FILTER;
+
+ IOCTL_DBG_IF(vsi->ifp, "end");
+}
+
+static void
+ixlv_add_multi(struct ixl_vsi *vsi)
+{
+ struct ifmultiaddr *ifma;
+ struct ifnet *ifp = vsi->ifp;
+ struct ixlv_sc *sc = vsi->back;
+ int mcnt = 0;
+
+ IOCTL_DBG_IF(ifp, "begin");
+
+ if_maddr_rlock(ifp);
+ /*
+ ** Get a count, to decide if we
+ ** simply use multicast promiscuous.
+ */
+ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ if (ifma->ifma_addr->sa_family != AF_LINK)
+ continue;
+ mcnt++;
+ }
+ if_maddr_runlock(ifp);
+
+ if (__predict_false(mcnt >= MAX_MULTICAST_ADDR)) {
+ /* delete all multicast filters */
+ ixlv_init_multi(vsi);
+ sc->promiscuous_flags |= I40E_FLAG_VF_MULTICAST_PROMISC;
+ sc->aq_required |= IXLV_FLAG_AQ_CONFIGURE_PROMISC;
+ IOCTL_DEBUGOUT("%s: end: too many filters", __func__);
+ return;
+ }
+
+ mcnt = 0;
+ if_maddr_rlock(ifp);
+ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ if (ifma->ifma_addr->sa_family != AF_LINK)
+ continue;
+ if (!ixlv_add_mac_filter(sc,
+ (u8*)LLADDR((struct sockaddr_dl *) ifma->ifma_addr),
+ IXL_FILTER_MC))
+ mcnt++;
+ }
+ if_maddr_runlock(ifp);
+ /*
+ ** Notify AQ task that sw filters need to be
+ ** added to hw list
+ */
+ if (mcnt > 0)
+ sc->aq_required |= IXLV_FLAG_AQ_ADD_MAC_FILTER;
+
+ IOCTL_DBG_IF(ifp, "end");
+}
+
+static void
+ixlv_del_multi(struct ixl_vsi *vsi)
+{
+ struct ixlv_mac_filter *f;
+ struct ifmultiaddr *ifma;
+ struct ifnet *ifp = vsi->ifp;
+ struct ixlv_sc *sc = vsi->back;
+ int mcnt = 0;
+ bool match = FALSE;
+
+ IOCTL_DBG_IF(ifp, "begin");
+
+ /* Search for removed multicast addresses */
+ if_maddr_rlock(ifp);
+ SLIST_FOREACH(f, sc->mac_filters, next) {
+ if ((f->flags & IXL_FILTER_USED)
+ && (f->flags & IXL_FILTER_MC)) {
+ /* check if mac address in filter is in sc's list */
+ match = FALSE;
+ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ if (ifma->ifma_addr->sa_family != AF_LINK)
+ continue;
+ u8 *mc_addr =
+ (u8 *)LLADDR((struct sockaddr_dl *)ifma->ifma_addr);
+ if (cmp_etheraddr(f->macaddr, mc_addr)) {
+ match = TRUE;
+ break;
+ }
+ }
+ /* if this filter is not in the sc's list, remove it */
+ if (match == FALSE && !(f->flags & IXL_FILTER_DEL)) {
+ f->flags |= IXL_FILTER_DEL;
+ mcnt++;
+ IOCTL_DBG_IF(ifp, "marked: " MAC_FORMAT,
+ MAC_FORMAT_ARGS(f->macaddr));
+ }
+ else if (match == FALSE)
+ IOCTL_DBG_IF(ifp, "exists: " MAC_FORMAT,
+ MAC_FORMAT_ARGS(f->macaddr));
+ }
+ }
+ if_maddr_runlock(ifp);
+
+ if (mcnt > 0)
+ sc->aq_required |= IXLV_FLAG_AQ_DEL_MAC_FILTER;
+
+ IOCTL_DBG_IF(ifp, "end");
+}
+
+/*********************************************************************
+ * Timer routine
+ *
+ * This routine checks for link status,updates statistics,
+ * and runs the watchdog check.
+ *
+ **********************************************************************/
+
+static void
+ixlv_local_timer(void *arg)
+{
+ struct ixlv_sc *sc = arg;
+ struct i40e_hw *hw = &sc->hw;
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct ixl_queue *que = vsi->queues;
+ device_t dev = sc->dev;
+ int hung = 0;
+ u32 mask, val, oldval;
+
+ mtx_assert(&sc->mtx, MA_OWNED);
+
+ /* If Reset is in progress just bail */
+ if (sc->init_state == IXLV_RESET_PENDING)
+ return;
+
+ /* Check for when PF triggers a VF reset */
+ val = rd32(hw, I40E_VFGEN_RSTAT) &
+ I40E_VFGEN_RSTAT_VFR_STATE_MASK;
+
+ if (val != I40E_VFR_VFACTIVE
+ && val != I40E_VFR_COMPLETED) {
+#ifdef IXL_DEBUG
+ device_printf(dev, "%s: reset in progress! (%d)\n",
+ __func__, val);
+#endif
+ return;
+ }
+
+ /* check for Admin queue errors */
+ val = rd32(hw, hw->aq.arq.len);
+ oldval = val;
+ if (val & I40E_VF_ARQLEN_ARQVFE_MASK) {
+ device_printf(dev, "ARQ VF Error detected\n");
+ val &= ~I40E_VF_ARQLEN_ARQVFE_MASK;
+ }
+ if (val & I40E_VF_ARQLEN_ARQOVFL_MASK) {
+ device_printf(dev, "ARQ Overflow Error detected\n");
+ val &= ~I40E_VF_ARQLEN_ARQOVFL_MASK;
+ }
+ if (val & I40E_VF_ARQLEN_ARQCRIT_MASK) {
+ device_printf(dev, "ARQ Critical Error detected\n");
+ val &= ~I40E_VF_ARQLEN_ARQCRIT_MASK;
+ }
+ if (oldval != val)
+ wr32(hw, hw->aq.arq.len, val);
+
+ val = rd32(hw, hw->aq.asq.len);
+ oldval = val;
+ if (val & I40E_VF_ATQLEN_ATQVFE_MASK) {
+ device_printf(dev, "ASQ VF Error detected\n");
+ val &= ~I40E_VF_ATQLEN_ATQVFE_MASK;
+ }
+ if (val & I40E_VF_ATQLEN_ATQOVFL_MASK) {
+ device_printf(dev, "ASQ Overflow Error detected\n");
+ val &= ~I40E_VF_ATQLEN_ATQOVFL_MASK;
+ }
+ if (val & I40E_VF_ATQLEN_ATQCRIT_MASK) {
+ device_printf(dev, "ASQ Critical Error detected\n");
+ val &= ~I40E_VF_ATQLEN_ATQCRIT_MASK;
+ }
+ if (oldval != val)
+ wr32(hw, hw->aq.asq.len, val);
+
+ /* clean and process any events */
+ taskqueue_enqueue(sc->tq, &sc->aq_irq);
+
+ /*
+ ** Check status on the queues for a hang
+ */
+ mask = (I40E_VFINT_DYN_CTLN_INTENA_MASK |
+ I40E_VFINT_DYN_CTLN_SWINT_TRIG_MASK);
+
+ for (int i = 0; i < vsi->num_queues; i++,que++) {
+ /* Any queues with outstanding work get a sw irq */
+ if (que->busy)
+ wr32(hw, I40E_VFINT_DYN_CTLN1(que->me), mask);
+ /*
+ ** Each time txeof runs without cleaning, but there
+ ** are uncleaned descriptors it increments busy. If
+ ** we get to 5 we declare it hung.
+ */
+ if (que->busy == IXL_QUEUE_HUNG) {
+ ++hung;
+ /* Mark the queue as inactive */
+ vsi->active_queues &= ~((u64)1 << que->me);
+ continue;
+ } else {
+ /* Check if we've come back from hung */
+ if ((vsi->active_queues & ((u64)1 << que->me)) == 0)
+ vsi->active_queues |= ((u64)1 << que->me);
+ }
+ if (que->busy >= IXL_MAX_TX_BUSY) {
+ device_printf(dev,"Warning queue %d "
+ "appears to be hung!\n", i);
+ que->busy = IXL_QUEUE_HUNG;
+ ++hung;
+ }
+ }
+ /* Only reset when all queues show hung */
+ if (hung == vsi->num_queues)
+ goto hung;
+ callout_reset(&sc->timer, hz, ixlv_local_timer, sc);
+ return;
+
+hung:
+ device_printf(dev, "Local Timer: TX HANG DETECTED - Resetting!!\n");
+ sc->init_state = IXLV_RESET_REQUIRED;
+ ixlv_init_locked(sc);
+}
+
+/*
+** Note: this routine updates the OS on the link state
+** the real check of the hardware only happens with
+** a link interrupt.
+*/
+static void
+ixlv_update_link_status(struct ixlv_sc *sc)
+{
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct ifnet *ifp = vsi->ifp;
+ device_t dev = sc->dev;
+
+ if (vsi->link_up){
+ if (vsi->link_active == FALSE) {
+ if (bootverbose)
+ device_printf(dev,"Link is Up, %d Gbps\n",
+ (vsi->link_speed == I40E_LINK_SPEED_40GB) ? 40:10);
+ vsi->link_active = TRUE;
+ if_link_state_change(ifp, LINK_STATE_UP);
+ }
+ } 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;
+ }
+ }
+
+ return;
+}
+
+/*********************************************************************
+ *
+ * This routine disables all traffic on the adapter by issuing a
+ * global reset on the MAC and deallocates TX/RX buffers.
+ *
+ **********************************************************************/
+
+static void
+ixlv_stop(struct ixlv_sc *sc)
+{
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+ INIT_DBG_IF(&sc->vsi->ifp, "begin");
+
+ sc->aq_required |= IXLV_FLAG_AQ_DISABLE_QUEUES;
+ callout_reset(&sc->aq_task, IXLV_CALLOUT_TIMO,
+ ixlv_sched_aq, sc);
+
+ /* Stop the local timer */
+ callout_stop(&sc->timer);
+
+ INIT_DBG_IF(&sc->vsi->ifp, "end");
+}
+
+
+/*********************************************************************
+ *
+ * Free all station queue structs.
+ *
+ **********************************************************************/
+static void
+ixlv_free_queues(struct ixl_vsi *vsi)
+{
+ struct ixlv_sc *sc = (struct ixlv_sc *)vsi->back;
+ struct ixl_queue *que = vsi->queues;
+
+ 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);
+ ixl_free_que_tx(que);
+ if (txr->base)
+ i40e_free_dma(&sc->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(&sc->hw, &rxr->dma);
+ IXL_RX_UNLOCK(rxr);
+ IXL_RX_LOCK_DESTROY(rxr);
+
+ }
+ free(vsi->queues, M_DEVBUF);
+}
+
+
+/*
+** ixlv_config_rss - setup RSS
+*/
+static void
+ixlv_config_rss(struct ixlv_sc *sc)
+{
+ struct i40e_hw *hw = &sc->hw;
+ struct ixl_vsi *vsi = &sc->vsi;
+ u32 lut = 0;
+ u64 set_hena, hena;
+ int i, j;
+
+ /* set up random bits */
+ static const u32 seed[I40E_VFQF_HKEY_MAX_INDEX + 1] = {
+ 0x794221b4, 0xbca0c5ab, 0x6cd5ebd9, 0x1ada6127,
+ 0x983b3aa1, 0x1c4e71eb, 0x7f6328b2, 0xfcdc0da0,
+ 0xc135cafa, 0x7a6f7e2d, 0xe7102d28, 0x163cd12e,
+ 0x4954b126 };
+
+ /* Fill out hash function seed */
+ for (i = 0; i <= I40E_VFQF_HKEY_MAX_INDEX; i++)
+ wr32(hw, I40E_VFQF_HKEY(i), seed[i]);
+
+ /* Enable PCTYPES for RSS: */
+ set_hena =
+ ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV4_UDP) |
+ ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV4_TCP) |
+ ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV4_SCTP) |
+ ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV4_OTHER) |
+ ((u64)1 << I40E_FILTER_PCTYPE_FRAG_IPV4) |
+ ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV6_UDP) |
+ ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV6_TCP) |
+ ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV6_SCTP) |
+ ((u64)1 << I40E_FILTER_PCTYPE_NONF_IPV6_OTHER) |
+ ((u64)1 << I40E_FILTER_PCTYPE_FRAG_IPV6) |
+ ((u64)1 << I40E_FILTER_PCTYPE_L2_PAYLOAD);
+
+ hena = (u64)rd32(hw, I40E_VFQF_HENA(0)) |
+ ((u64)rd32(hw, I40E_VFQF_HENA(1)) << 32);
+ hena |= set_hena;
+ wr32(hw, I40E_VFQF_HENA(0), (u32)hena);
+ wr32(hw, I40E_VFQF_HENA(1), (u32)(hena >> 32));
+
+ /* Populate the LUT with max no. of queues in round robin fashion */
+ for (i = j = 0; i < hw->func_caps.rss_table_size; i++, j++) {
+ if (j == vsi->num_queues)
+ j = 0;
+ /* lut = 4-byte sliding window of 4 lut entries */
+ lut = (lut << 8) | (j &
+ ((0x1 << hw->func_caps.rss_table_entry_width) - 1));
+ /* On i = 3, we have 4 entries in lut; write to the register */
+ if ((i & 3) == 3)
+ wr32(hw, I40E_VFQF_HLUT(i >> 2), lut);
+ }
+ ixl_flush(hw);
+}
+
+
+/*
+** This routine refreshes vlan filters, called by init
+** it scans the filter table and then updates the AQ
+*/
+static void
+ixlv_setup_vlan_filters(struct ixlv_sc *sc)
+{
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct ixlv_vlan_filter *f;
+ int cnt = 0;
+
+ if (vsi->num_vlans == 0)
+ return;
+ /*
+ ** Scan the filter table for vlan entries,
+ ** and if found call for the AQ update.
+ */
+ SLIST_FOREACH(f, sc->vlan_filters, next)
+ if (f->flags & IXL_FILTER_ADD)
+ cnt++;
+ if (cnt == 0)
+ return;
+
+ sc->aq_required |= IXLV_FLAG_AQ_ADD_VLAN_FILTER;
+ return;
+}
+
+
+/*
+** This routine adds new MAC filters to the sc's list;
+** these are later added in hardware by the periodic
+** aq task.
+*/
+static int
+ixlv_add_mac_filter(struct ixlv_sc *sc, u8 *macaddr, u16 flags)
+{
+ struct ixlv_mac_filter *f;
+ device_t dev = sc->dev;
+
+ /* Does one already exist? */
+ f = ixlv_find_mac_filter(sc, macaddr);
+ if (f != NULL) {
+ IDPRINTF(sc->vsi.ifp, "exists: " MAC_FORMAT,
+ MAC_FORMAT_ARGS(macaddr));
+ return (EEXIST);
+ }
+
+ /* If not, get a new empty filter */
+ f = ixlv_get_mac_filter(sc);
+ if (f == NULL) {
+ device_printf(dev, "%s: no filters available!!\n",
+ __func__);
+ return (ENOMEM);
+ }
+
+ IDPRINTF(sc->vsi.ifp, "marked: " MAC_FORMAT,
+ MAC_FORMAT_ARGS(macaddr));
+
+ bcopy(macaddr, f->macaddr, ETHER_ADDR_LEN);
+ f->flags |= (IXL_FILTER_ADD | IXL_FILTER_USED);
+ f->flags |= flags;
+ return (0);
+}
+
+/*
+** Tasklet handler for MSIX Adminq interrupts
+** - done outside interrupt context since it might sleep
+*/
+static void
+ixlv_do_adminq(void *context, int pending)
+{
+ struct ixlv_sc *sc = context;
+ struct i40e_hw *hw = &sc->hw;
+ struct i40e_arq_event_info event;
+ struct i40e_virtchnl_msg *v_msg;
+ i40e_status ret;
+ u16 result = 0;
+
+
+ event.buf_len = IXL_AQ_BUF_SZ;
+ event.msg_buf = malloc(event.buf_len,
+ M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (!event.msg_buf) {
+ printf("Unable to allocate adminq memory\n");
+ return;
+ }
+ v_msg = (struct i40e_virtchnl_msg *)&event.desc;
+
+ mtx_lock(&sc->mtx);
+ /* clean and process any events */
+ do {
+ ret = i40e_clean_arq_element(hw, &event, &result);
+ if (ret)
+ break;
+ ixlv_vc_completion(sc, v_msg->v_opcode,
+ v_msg->v_retval, event.msg_buf, event.msg_len);
+ if (result != 0)
+ bzero(event.msg_buf, IXL_AQ_BUF_SZ);
+ } while (result);
+
+ ixlv_enable_adminq_irq(hw);
+ free(event.msg_buf, M_DEVBUF);
+ mtx_unlock(&sc->mtx);
+ return;
+}
+
+/*
+** ixlv_sched_aq - Periodic scheduling tasklet
+**
+*/
+static void
+ixlv_sched_aq(void *context)
+{
+ struct ixlv_sc *sc = context;
+ struct ixl_vsi *vsi = &sc->vsi;
+
+ /* This is driven by a callout, don't spin */
+ if (!mtx_trylock(&sc->mtx))
+ goto done_nolock;
+
+ if (sc->init_state == IXLV_RESET_PENDING)
+ goto done;
+
+ /* Process requested admin queue tasks */
+ if (sc->aq_pending)
+ goto done;
+
+ if (sc->aq_required & IXLV_FLAG_AQ_MAP_VECTORS) {
+ ixlv_map_queues(sc);
+ goto done;
+ }
+
+ if (sc->aq_required & IXLV_FLAG_AQ_ADD_MAC_FILTER) {
+ ixlv_add_ether_filters(sc);
+ goto done;
+ }
+
+ if (sc->aq_required & IXLV_FLAG_AQ_ADD_VLAN_FILTER) {
+ ixlv_add_vlans(sc);
+ goto done;
+ }
+
+ if (sc->aq_required & IXLV_FLAG_AQ_DEL_MAC_FILTER) {
+ ixlv_del_ether_filters(sc);
+ goto done;
+ }
+
+ if (sc->aq_required & IXLV_FLAG_AQ_DEL_VLAN_FILTER) {
+ ixlv_del_vlans(sc);
+ goto done;
+ }
+
+ if (sc->aq_required & IXLV_FLAG_AQ_CONFIGURE_QUEUES) {
+ ixlv_configure_queues(sc);
+ goto done;
+ }
+
+ if (sc->aq_required & IXLV_FLAG_AQ_DISABLE_QUEUES) {
+ ixlv_disable_queues(sc);
+ goto done;
+ }
+
+ if (sc->aq_required & IXLV_FLAG_AQ_ENABLE_QUEUES) {
+ ixlv_enable_queues(sc);
+ goto done;
+ }
+
+ /* Do stats request only if no other AQ operations requested */
+ if (vsi->ifp->if_drv_flags & IFF_DRV_RUNNING)
+ ixlv_request_stats(sc);
+
+done:
+ mtx_unlock(&sc->mtx);
+done_nolock:
+ if (sc->aq_required) /* Reschedule */
+ callout_reset(&sc->aq_task, IXLV_CALLOUT_TIMO,
+ ixlv_sched_aq, sc);
+ else
+ callout_reset(&sc->aq_task, 2 * hz, ixlv_sched_aq, sc);
+}
+
+static void
+ixlv_add_stats_sysctls(struct ixlv_sc *sc)
+{
+ device_t dev = sc->dev;
+ struct ixl_vsi *vsi = &sc->vsi;
+ struct i40e_eth_stats *es = &vsi->eth_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);
+
+ struct sysctl_oid *vsi_node, *queue_node;
+ struct sysctl_oid_list *vsi_list, *queue_list;
+
+#define QUEUE_NAME_LEN 32
+ char queue_namebuf[QUEUE_NAME_LEN];
+
+ struct ixl_queue *queues = vsi->queues;
+ struct tx_ring *txr;
+ struct rx_ring *rxr;
+
+ /* Driver statistics */
+ SYSCTL_ADD_ULONG(ctx, child, OID_AUTO, "watchdog_events",
+ CTLFLAG_RD, &sc->watchdog_events,
+ "Watchdog timeouts");
+ SYSCTL_ADD_ULONG(ctx, child, OID_AUTO, "admin_irq",
+ CTLFLAG_RD, &sc->admin_irq,
+ "Admin Queue IRQ Handled");
+
+ /* VSI statistics */
+ vsi_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "vsi",
+ CTLFLAG_RD, NULL, "VSI-specific statistics");
+ vsi_list = SYSCTL_CHILDREN(vsi_node);
+
+ struct ixl_sysctl_info ctls[] =
+ {
+ {&es->rx_bytes, "good_octets_rcvd", "Good Octets Received"},
+ {&es->rx_unicast, "ucast_pkts_rcvd",
+ "Unicast Packets Received"},
+ {&es->rx_multicast, "mcast_pkts_rcvd",
+ "Multicast Packets Received"},
+ {&es->rx_broadcast, "bcast_pkts_rcvd",
+ "Broadcast Packets Received"},
+ {&es->rx_discards, "rx_discards", "Discarded RX packets"},
+ {&es->tx_bytes, "good_octets_txd", "Good Octets Transmitted"},
+ {&es->tx_unicast, "ucast_pkts_txd", "Unicast Packets Transmitted"},
+ {&es->tx_multicast, "mcast_pkts_txd",
+ "Multicast Packets Transmitted"},
+ {&es->tx_broadcast, "bcast_pkts_txd",
+ "Broadcast Packets Transmitted"},
+ {&es->tx_discards, "tx_discards", "Discarded TX packets"},
+ // end
+ {0,0,0}
+ };
+ struct ixl_sysctl_info *entry = ctls;
+ while (entry->stat != 0)
+ {
+ SYSCTL_ADD_UQUAD(ctx, child, OID_AUTO, entry->name,
+ CTLFLAG_RD, entry->stat,
+ entry->description);
+ entry++;
+ }
+
+ /* Queue statistics */
+ for (int q = 0; q < vsi->num_queues; q++) {
+ snprintf(queue_namebuf, QUEUE_NAME_LEN, "que%d", q);
+ queue_node = SYSCTL_ADD_NODE(ctx, vsi_list, OID_AUTO, queue_namebuf,
+ CTLFLAG_RD, NULL, "Queue Name");
+ queue_list = SYSCTL_CHILDREN(queue_node);
+
+ txr = &(queues[q].txr);
+ rxr = &(queues[q].rxr);
+
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "mbuf_defrag_failed",
+ CTLFLAG_RD, &(queues[q].mbuf_defrag_failed),
+ "m_defrag() failed");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "dropped",
+ CTLFLAG_RD, &(queues[q].dropped_pkts),
+ "Driver dropped packets");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "irqs",
+ CTLFLAG_RD, &(queues[q].irqs),
+ "irqs on this queue");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "tso_tx",
+ CTLFLAG_RD, &(queues[q].tso),
+ "TSO");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "tx_dma_setup",
+ CTLFLAG_RD, &(queues[q].tx_dma_setup),
+ "Driver tx dma failure in xmit");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "no_desc_avail",
+ CTLFLAG_RD, &(txr->no_desc),
+ "Queue No Descriptor Available");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "tx_packets",
+ CTLFLAG_RD, &(txr->total_packets),
+ "Queue Packets Transmitted");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "tx_bytes",
+ CTLFLAG_RD, &(txr->tx_bytes),
+ "Queue Bytes Transmitted");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "rx_packets",
+ CTLFLAG_RD, &(rxr->rx_packets),
+ "Queue Packets Received");
+ SYSCTL_ADD_UQUAD(ctx, queue_list, OID_AUTO, "rx_bytes",
+ CTLFLAG_RD, &(rxr->rx_bytes),
+ "Queue Bytes Received");
+ }
+}
+
+static void
+ixlv_init_filters(struct ixlv_sc *sc)
+{
+ sc->mac_filters = malloc(sizeof(struct ixlv_mac_filter),
+ M_DEVBUF, M_NOWAIT | M_ZERO);
+ SLIST_INIT(sc->mac_filters);
+ sc->vlan_filters = malloc(sizeof(struct ixlv_vlan_filter),
+ M_DEVBUF, M_NOWAIT | M_ZERO);
+ SLIST_INIT(sc->vlan_filters);
+ return;
+}
+
+static void
+ixlv_free_filters(struct ixlv_sc *sc)
+{
+ struct ixlv_mac_filter *f;
+ struct ixlv_vlan_filter *v;
+
+ while (!SLIST_EMPTY(sc->mac_filters)) {
+ f = SLIST_FIRST(sc->mac_filters);
+ SLIST_REMOVE_HEAD(sc->mac_filters, next);
+ free(f, M_DEVBUF);
+ }
+ while (!SLIST_EMPTY(sc->vlan_filters)) {
+ v = SLIST_FIRST(sc->vlan_filters);
+ SLIST_REMOVE_HEAD(sc->vlan_filters, next);
+ free(v, M_DEVBUF);
+ }
+ return;
+}
+
OpenPOWER on IntegriCloud