Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/iavf/iavf_lib.c
- This file was added.
/* SPDX-License-Identifier: BSD-3-Clause */ | |||||
/* Copyright (c) 2021, 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$*/ | |||||
/** | |||||
* @file iavf_lib.c | |||||
* @brief library code common to both legacy and iflib | |||||
* | |||||
* Contains functions common to the iflib and legacy drivers. Includes | |||||
* hardware initialization and control functions, as well as sysctl handlers | |||||
* for the sysctls which are shared between the legacy and iflib drivers. | |||||
*/ | |||||
#include "iavf_iflib.h" | |||||
#include "iavf_vc_common.h" | |||||
static void iavf_init_hw(struct iavf_hw *hw, device_t dev); | |||||
static u_int iavf_mc_filter_apply(void *arg, struct sockaddr_dl *sdl, u_int cnt); | |||||
/** | |||||
* iavf_msec_pause - Pause for at least the specified number of milliseconds | |||||
* @msecs: number of milliseconds to pause for | |||||
* | |||||
* Pause execution of the current thread for a specified number of | |||||
* milliseconds. Used to enforce minimum delay times when waiting for various | |||||
* hardware events. | |||||
*/ | |||||
void | |||||
iavf_msec_pause(int msecs) | |||||
{ | |||||
pause("iavf_msec_pause", MSEC_2_TICKS(msecs)); | |||||
} | |||||
/** | |||||
* iavf_get_default_rss_key - Get the default RSS key for this driver | |||||
* @key: output parameter to store the key in | |||||
* | |||||
* Copies the driver's default RSS key into the provided key variable. | |||||
* | |||||
* @pre assumes that key is not NULL and has at least IAVF_RSS_KEY_SIZE | |||||
* storage space. | |||||
*/ | |||||
void | |||||
iavf_get_default_rss_key(u32 *key) | |||||
{ | |||||
MPASS(key != NULL); | |||||
u32 rss_seed[IAVF_RSS_KEY_SIZE_REG] = {0x41b01687, | |||||
0x183cfd8c, 0xce880440, 0x580cbc3c, | |||||
0x35897377, 0x328b25e1, 0x4fa98922, | |||||
0xb7d90c14, 0xd5bad70d, 0xcd15a2c1, | |||||
0x0, 0x0, 0x0}; | |||||
bcopy(rss_seed, key, IAVF_RSS_KEY_SIZE); | |||||
} | |||||
/** | |||||
* iavf_allocate_pci_resources_common - Allocate PCI resources | |||||
* @sc: the private device softc pointer | |||||
* | |||||
* @pre sc->dev is set | |||||
* | |||||
* Allocates the common PCI resources used by the driver. | |||||
* | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_allocate_pci_resources_common(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_hw *hw = &sc->hw; | |||||
device_t dev = sc->dev; | |||||
int rid; | |||||
/* Map PCI BAR0 */ | |||||
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: PCI memory\n"); | |||||
return (ENXIO); | |||||
} | |||||
iavf_init_hw(hw, dev); | |||||
/* Save off register access information */ | |||||
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->osdep.flush_reg = IAVF_VFGEN_RSTAT; | |||||
sc->osdep.dev = dev; | |||||
sc->hw.hw_addr = (u8 *)&sc->osdep.mem_bus_space_handle; | |||||
sc->hw.back = &sc->osdep; | |||||
return (0); | |||||
} | |||||
/** | |||||
* iavf_init_hw - Initialize the device HW | |||||
* @hw: device hardware structure | |||||
* @dev: the stack device_t pointer | |||||
* | |||||
* Attach helper function. Gathers information about the (virtual) hardware | |||||
* for use elsewhere in the driver. | |||||
*/ | |||||
static void | |||||
iavf_init_hw(struct iavf_hw *hw, device_t dev) | |||||
{ | |||||
/* 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); | |||||
} | |||||
/** | |||||
* iavf_sysctl_current_speed - Sysctl to display the current device speed | |||||
* @oidp: syctl oid pointer | |||||
* @arg1: pointer to the device softc typecasted to void * | |||||
* @arg2: unused sysctl argument | |||||
* @req: sysctl request structure | |||||
* | |||||
* Reads the current speed reported from the physical device into a string for | |||||
* display by the current_speed sysctl. | |||||
* | |||||
* @returns zero or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_sysctl_current_speed(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct iavf_sc *sc = (struct iavf_sc *)arg1; | |||||
int error = 0; | |||||
UNREFERENCED_PARAMETER(arg2); | |||||
if (iavf_driver_is_detaching(sc)) | |||||
return (ESHUTDOWN); | |||||
if (IAVF_CAP_ADV_LINK_SPEED(sc)) | |||||
error = sysctl_handle_string(oidp, | |||||
__DECONST(char *, iavf_ext_speed_to_str(iavf_adv_speed_to_ext_speed(sc->link_speed_adv))), | |||||
8, req); | |||||
else | |||||
error = sysctl_handle_string(oidp, | |||||
__DECONST(char *, iavf_vc_speed_to_string(sc->link_speed)), | |||||
8, req); | |||||
return (error); | |||||
} | |||||
/** | |||||
* iavf_reset_complete - Wait for a device reset to complete | |||||
* @hw: pointer to the hardware structure | |||||
* | |||||
* Reads the reset registers and waits until they indicate that a device reset | |||||
* is complete. | |||||
* | |||||
* @pre this function may call pause() and must not be called from a context | |||||
* that cannot sleep. | |||||
* | |||||
* @returns zero on success, or EBUSY if it times out waiting for reset. | |||||
*/ | |||||
int | |||||
iavf_reset_complete(struct iavf_hw *hw) | |||||
{ | |||||
u32 reg; | |||||
/* Wait up to ~10 seconds */ | |||||
for (int i = 0; i < 100; i++) { | |||||
reg = rd32(hw, IAVF_VFGEN_RSTAT) & | |||||
IAVF_VFGEN_RSTAT_VFR_STATE_MASK; | |||||
if ((reg == VIRTCHNL_VFR_VFACTIVE) || | |||||
(reg == VIRTCHNL_VFR_COMPLETED)) | |||||
return (0); | |||||
iavf_msec_pause(100); | |||||
} | |||||
return (EBUSY); | |||||
} | |||||
/** | |||||
* iavf_setup_vc - Setup virtchnl communication | |||||
* @sc: device private softc | |||||
* | |||||
* iavf_attach() helper function. Initializes the admin queue and attempts to | |||||
* establish contact with the PF by retrying the initial "API version" message | |||||
* several times or until the PF responds. | |||||
* | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_setup_vc(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_hw *hw = &sc->hw; | |||||
device_t dev = sc->dev; | |||||
int error = 0, ret_error = 0, asq_retries = 0; | |||||
bool send_api_ver_retried = 0; | |||||
/* Need to set these AQ parameters before initializing AQ */ | |||||
hw->aq.num_arq_entries = IAVF_AQ_LEN; | |||||
hw->aq.num_asq_entries = IAVF_AQ_LEN; | |||||
hw->aq.arq_buf_size = IAVF_AQ_BUF_SZ; | |||||
hw->aq.asq_buf_size = IAVF_AQ_BUF_SZ; | |||||
for (int i = 0; i < IAVF_AQ_MAX_ERR; i++) { | |||||
/* Initialize admin queue */ | |||||
error = iavf_init_adminq(hw); | |||||
if (error) { | |||||
device_printf(dev, "%s: init_adminq failed: %d\n", | |||||
__func__, error); | |||||
ret_error = 1; | |||||
continue; | |||||
} | |||||
iavf_dbg_init(sc, "Initialized Admin Queue; starting" | |||||
" send_api_ver attempt %d", i+1); | |||||
retry_send: | |||||
/* Send VF's API version */ | |||||
error = iavf_send_api_ver(sc); | |||||
if (error) { | |||||
iavf_shutdown_adminq(hw); | |||||
ret_error = 2; | |||||
device_printf(dev, "%s: unable to send api" | |||||
" version to PF on attempt %d, error %d\n", | |||||
__func__, i+1, error); | |||||
} | |||||
asq_retries = 0; | |||||
while (!iavf_asq_done(hw)) { | |||||
if (++asq_retries > IAVF_AQ_MAX_ERR) { | |||||
iavf_shutdown_adminq(hw); | |||||
device_printf(dev, "Admin Queue timeout " | |||||
"(waiting for send_api_ver), %d more tries...\n", | |||||
IAVF_AQ_MAX_ERR - (i + 1)); | |||||
ret_error = 3; | |||||
break; | |||||
} | |||||
iavf_msec_pause(10); | |||||
} | |||||
if (asq_retries > IAVF_AQ_MAX_ERR) | |||||
continue; | |||||
iavf_dbg_init(sc, "Sent API version message to PF"); | |||||
/* Verify that the VF accepts the PF's API version */ | |||||
error = iavf_verify_api_ver(sc); | |||||
if (error == ETIMEDOUT) { | |||||
if (!send_api_ver_retried) { | |||||
/* Resend message, one more time */ | |||||
send_api_ver_retried = true; | |||||
device_printf(dev, | |||||
"%s: Timeout while verifying API version on first" | |||||
" try!\n", __func__); | |||||
goto retry_send; | |||||
} else { | |||||
device_printf(dev, | |||||
"%s: Timeout while verifying API version on second" | |||||
" try!\n", __func__); | |||||
ret_error = 4; | |||||
break; | |||||
} | |||||
} | |||||
if (error) { | |||||
device_printf(dev, | |||||
"%s: Unable to verify API version," | |||||
" error %d\n", __func__, error); | |||||
ret_error = 5; | |||||
} | |||||
break; | |||||
} | |||||
if (ret_error >= 4) | |||||
iavf_shutdown_adminq(hw); | |||||
return (ret_error); | |||||
} | |||||
/** | |||||
* iavf_reset - Requests a VF reset from the PF. | |||||
* @sc: device private softc | |||||
* | |||||
* @pre Requires the VF's Admin Queue to be initialized. | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_reset(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_hw *hw = &sc->hw; | |||||
device_t dev = sc->dev; | |||||
int error = 0; | |||||
/* Ask the PF to reset us if we are initiating */ | |||||
if (!iavf_test_state(&sc->state, IAVF_STATE_RESET_PENDING)) | |||||
iavf_request_reset(sc); | |||||
iavf_msec_pause(100); | |||||
error = iavf_reset_complete(hw); | |||||
if (error) { | |||||
device_printf(dev, "%s: VF reset failed\n", | |||||
__func__); | |||||
return (error); | |||||
} | |||||
pci_enable_busmaster(dev); | |||||
error = iavf_shutdown_adminq(hw); | |||||
if (error) { | |||||
device_printf(dev, "%s: shutdown_adminq failed: %d\n", | |||||
__func__, error); | |||||
return (error); | |||||
} | |||||
error = iavf_init_adminq(hw); | |||||
if (error) { | |||||
device_printf(dev, "%s: init_adminq failed: %d\n", | |||||
__func__, error); | |||||
return (error); | |||||
} | |||||
/* IFLIB: This is called only in the iflib driver */ | |||||
iavf_enable_adminq_irq(hw); | |||||
return (0); | |||||
} | |||||
/** | |||||
* iavf_enable_admin_irq - Enable the administrative interrupt | |||||
* @hw: pointer to the hardware structure | |||||
* | |||||
* Writes to registers to enable the administrative interrupt cause, in order | |||||
* to handle non-queue related interrupt events. | |||||
*/ | |||||
void | |||||
iavf_enable_adminq_irq(struct iavf_hw *hw) | |||||
{ | |||||
wr32(hw, IAVF_VFINT_DYN_CTL01, | |||||
IAVF_VFINT_DYN_CTL01_INTENA_MASK | | |||||
IAVF_VFINT_DYN_CTL01_CLEARPBA_MASK | | |||||
IAVF_VFINT_DYN_CTL01_ITR_INDX_MASK); | |||||
wr32(hw, IAVF_VFINT_ICR0_ENA1, IAVF_VFINT_ICR0_ENA1_ADMINQ_MASK); | |||||
/* flush */ | |||||
rd32(hw, IAVF_VFGEN_RSTAT); | |||||
} | |||||
/** | |||||
* iavf_disable_admin_irq - Disable the administrative interrupt cause | |||||
* @hw: pointer to the hardware structure | |||||
* | |||||
* Writes to registers to disable the administrative interrupt cause. | |||||
*/ | |||||
void | |||||
iavf_disable_adminq_irq(struct iavf_hw *hw) | |||||
{ | |||||
wr32(hw, IAVF_VFINT_DYN_CTL01, 0); | |||||
wr32(hw, IAVF_VFINT_ICR0_ENA1, 0); | |||||
iavf_flush(hw); | |||||
} | |||||
/** | |||||
* iavf_vf_config - Configure this VF over the virtchnl | |||||
* @sc: device private softc | |||||
* | |||||
* iavf_attach() helper function. Asks the PF for this VF's configuration, and | |||||
* saves the information if it receives it. | |||||
* | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_vf_config(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_hw *hw = &sc->hw; | |||||
device_t dev = sc->dev; | |||||
int bufsz, error = 0, ret_error = 0; | |||||
int asq_retries, retried = 0; | |||||
retry_config: | |||||
error = iavf_send_vf_config_msg(sc); | |||||
if (error) { | |||||
device_printf(dev, | |||||
"%s: Unable to send VF config request, attempt %d," | |||||
" error %d\n", __func__, retried + 1, error); | |||||
ret_error = 2; | |||||
} | |||||
asq_retries = 0; | |||||
while (!iavf_asq_done(hw)) { | |||||
if (++asq_retries > IAVF_AQ_MAX_ERR) { | |||||
device_printf(dev, "%s: Admin Queue timeout " | |||||
"(waiting for send_vf_config_msg), attempt %d\n", | |||||
__func__, retried + 1); | |||||
ret_error = 3; | |||||
goto fail; | |||||
} | |||||
iavf_msec_pause(10); | |||||
} | |||||
iavf_dbg_init(sc, "Sent VF config message to PF, attempt %d\n", | |||||
retried + 1); | |||||
if (!sc->vf_res) { | |||||
bufsz = sizeof(struct virtchnl_vf_resource) + | |||||
(IAVF_MAX_VF_VSI * sizeof(struct virtchnl_vsi_resource)); | |||||
sc->vf_res = (struct virtchnl_vf_resource *)malloc(bufsz, M_IAVF, M_NOWAIT); | |||||
if (!sc->vf_res) { | |||||
device_printf(dev, | |||||
"%s: Unable to allocate memory for VF configuration" | |||||
" message from PF on attempt %d\n", __func__, retried + 1); | |||||
ret_error = 1; | |||||
goto fail; | |||||
} | |||||
} | |||||
/* Check for VF config response */ | |||||
error = iavf_get_vf_config(sc); | |||||
if (error == ETIMEDOUT) { | |||||
/* The 1st time we timeout, send the configuration message again */ | |||||
if (!retried) { | |||||
retried++; | |||||
goto retry_config; | |||||
} | |||||
device_printf(dev, | |||||
"%s: iavf_get_vf_config() timed out waiting for a response\n", | |||||
__func__); | |||||
} | |||||
if (error) { | |||||
device_printf(dev, | |||||
"%s: Unable to get VF configuration from PF after %d tries!\n", | |||||
__func__, retried + 1); | |||||
ret_error = 4; | |||||
} | |||||
goto done; | |||||
fail: | |||||
free(sc->vf_res, M_IAVF); | |||||
done: | |||||
return (ret_error); | |||||
} | |||||
/** | |||||
* iavf_print_device_info - Print some device parameters at attach | |||||
* @sc: device private softc | |||||
* | |||||
* Log a message about this virtual device's capabilities at attach time. | |||||
*/ | |||||
void | |||||
iavf_print_device_info(struct iavf_sc *sc) | |||||
{ | |||||
device_t dev = sc->dev; | |||||
device_printf(dev, | |||||
"VSIs %d, QPs %d, MSI-X %d, RSS sizes: key %d lut %d\n", | |||||
sc->vf_res->num_vsis, | |||||
sc->vf_res->num_queue_pairs, | |||||
sc->vf_res->max_vectors, | |||||
sc->vf_res->rss_key_size, | |||||
sc->vf_res->rss_lut_size); | |||||
iavf_dbg_info(sc, "Capabilities=%b\n", | |||||
sc->vf_res->vf_cap_flags, IAVF_PRINTF_VF_OFFLOAD_FLAGS); | |||||
} | |||||
/** | |||||
* iavf_get_vsi_res_from_vf_res - Get VSI parameters and info for this VF | |||||
* @sc: device private softc | |||||
* | |||||
* Get the VSI parameters and information from the general VF resource info | |||||
* received by the physical device. | |||||
* | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_get_vsi_res_from_vf_res(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_vsi *vsi = &sc->vsi; | |||||
device_t dev = sc->dev; | |||||
sc->vsi_res = NULL; | |||||
for (int i = 0; i < sc->vf_res->num_vsis; i++) { | |||||
/* XXX: We only use the first VSI we find */ | |||||
if (sc->vf_res->vsi_res[i].vsi_type == IAVF_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__); | |||||
return (EIO); | |||||
} | |||||
vsi->id = sc->vsi_res->vsi_id; | |||||
return (0); | |||||
} | |||||
/** | |||||
* iavf_set_mac_addresses - Set the MAC address for this interface | |||||
* @sc: device private softc | |||||
* | |||||
* Set the permanent MAC address field in the HW structure. If a MAC address | |||||
* has not yet been set for this device by the physical function, generate one | |||||
* randomly. | |||||
*/ | |||||
void | |||||
iavf_set_mac_addresses(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_hw *hw = &sc->hw; | |||||
device_t dev = sc->dev; | |||||
u8 addr[ETHER_ADDR_LEN]; | |||||
/* If no mac address was assigned just make a random one */ | |||||
if (ETHER_IS_ZERO(hw->mac.addr)) { | |||||
arc4rand(&addr, sizeof(addr), 0); | |||||
addr[0] &= 0xFE; | |||||
addr[0] |= 0x02; | |||||
memcpy(hw->mac.addr, addr, sizeof(addr)); | |||||
device_printf(dev, "Generated random MAC address\n"); | |||||
} | |||||
memcpy(hw->mac.perm_addr, hw->mac.addr, ETHER_ADDR_LEN); | |||||
} | |||||
/** | |||||
* iavf_init_filters - Initialize filter structures | |||||
* @sc: device private softc | |||||
* | |||||
* Initialize the MAC and VLAN filter list heads. | |||||
* | |||||
* @remark this is intended to be called only once during the device attach | |||||
* process. | |||||
* | |||||
* @pre Because it uses M_WAITOK, this function should only be called in | |||||
* a context that is safe to sleep. | |||||
*/ | |||||
void | |||||
iavf_init_filters(struct iavf_sc *sc) | |||||
{ | |||||
sc->mac_filters = (struct mac_list *)malloc(sizeof(struct iavf_mac_filter), | |||||
M_IAVF, M_WAITOK | M_ZERO); | |||||
SLIST_INIT(sc->mac_filters); | |||||
sc->vlan_filters = (struct vlan_list *)malloc(sizeof(struct iavf_vlan_filter), | |||||
M_IAVF, M_WAITOK | M_ZERO); | |||||
SLIST_INIT(sc->vlan_filters); | |||||
} | |||||
/** | |||||
* iavf_free_filters - Release filter lists | |||||
* @sc: device private softc | |||||
* | |||||
* Free the MAC and VLAN filter lists. | |||||
* | |||||
* @remark this is intended to be called only once during the device detach | |||||
* process. | |||||
*/ | |||||
void | |||||
iavf_free_filters(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_mac_filter *f; | |||||
struct iavf_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_IAVF); | |||||
} | |||||
free(sc->mac_filters, M_IAVF); | |||||
while (!SLIST_EMPTY(sc->vlan_filters)) { | |||||
v = SLIST_FIRST(sc->vlan_filters); | |||||
SLIST_REMOVE_HEAD(sc->vlan_filters, next); | |||||
free(v, M_IAVF); | |||||
} | |||||
free(sc->vlan_filters, M_IAVF); | |||||
} | |||||
/** | |||||
* iavf_add_device_sysctls_common - Initialize common device sysctls | |||||
* @sc: device private softc | |||||
* | |||||
* Setup sysctls common to both the iflib and legacy drivers. | |||||
*/ | |||||
void | |||||
iavf_add_device_sysctls_common(struct iavf_sc *sc) | |||||
{ | |||||
device_t dev = sc->dev; | |||||
struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); | |||||
struct sysctl_oid_list *ctx_list = | |||||
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)); | |||||
SYSCTL_ADD_PROC(ctx, ctx_list, | |||||
OID_AUTO, "current_speed", CTLTYPE_STRING | CTLFLAG_RD, | |||||
sc, 0, iavf_sysctl_current_speed, "A", "Current Port Speed"); | |||||
SYSCTL_ADD_PROC(ctx, ctx_list, | |||||
OID_AUTO, "tx_itr", CTLTYPE_INT | CTLFLAG_RW, | |||||
sc, 0, iavf_sysctl_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, | |||||
sc, 0, iavf_sysctl_rx_itr, "I", | |||||
"Immediately set RX ITR value for all queues"); | |||||
SYSCTL_ADD_UQUAD(ctx, ctx_list, | |||||
OID_AUTO, "admin_irq", CTLFLAG_RD, | |||||
&sc->admin_irq, "Admin Queue IRQ Handled"); | |||||
} | |||||
/** | |||||
* iavf_add_debug_sysctls_common - Initialize common debug sysctls | |||||
* @sc: device private softc | |||||
* @debug_list: pionter to debug sysctl node | |||||
* | |||||
* Setup sysctls used for debugging the device driver into the debug sysctl | |||||
* node. | |||||
*/ | |||||
void | |||||
iavf_add_debug_sysctls_common(struct iavf_sc *sc, struct sysctl_oid_list *debug_list) | |||||
{ | |||||
device_t dev = sc->dev; | |||||
struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); | |||||
SYSCTL_ADD_UINT(ctx, debug_list, | |||||
OID_AUTO, "shared_debug_mask", CTLFLAG_RW, | |||||
&sc->hw.debug_mask, 0, "Shared code debug message level"); | |||||
SYSCTL_ADD_UINT(ctx, debug_list, | |||||
OID_AUTO, "core_debug_mask", CTLFLAG_RW, | |||||
(unsigned int *)&sc->dbg_mask, 0, "Non-shared code debug message level"); | |||||
SYSCTL_ADD_PROC(ctx, debug_list, | |||||
OID_AUTO, "filter_list", CTLTYPE_STRING | CTLFLAG_RD, | |||||
sc, 0, iavf_sysctl_sw_filter_list, "A", "SW Filter List"); | |||||
} | |||||
/** | |||||
* iavf_sysctl_tx_itr - Sysctl to set the Tx ITR value | |||||
* @oidp: sysctl oid pointer | |||||
* @arg1: pointer to the device softc | |||||
* @arg2: unused sysctl argument | |||||
* @req: sysctl req pointer | |||||
* | |||||
* On read, returns the Tx ITR value for all of the VF queues. On write, | |||||
* update the Tx ITR registers with the new Tx ITR value. | |||||
* | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_sysctl_tx_itr(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct iavf_sc *sc = (struct iavf_sc *)arg1; | |||||
device_t dev = sc->dev; | |||||
int requested_tx_itr; | |||||
int error = 0; | |||||
UNREFERENCED_PARAMETER(arg2); | |||||
if (iavf_driver_is_detaching(sc)) | |||||
return (ESHUTDOWN); | |||||
requested_tx_itr = sc->tx_itr; | |||||
error = sysctl_handle_int(oidp, &requested_tx_itr, 0, req); | |||||
if ((error) || (req->newptr == NULL)) | |||||
return (error); | |||||
if (requested_tx_itr < 0 || requested_tx_itr > IAVF_MAX_ITR) { | |||||
device_printf(dev, | |||||
"Invalid TX itr value; value must be between 0 and %d\n", | |||||
IAVF_MAX_ITR); | |||||
return (EINVAL); | |||||
} | |||||
sc->tx_itr = requested_tx_itr; | |||||
iavf_configure_tx_itr(sc); | |||||
return (error); | |||||
} | |||||
/** | |||||
* iavf_sysctl_rx_itr - Sysctl to set the Rx ITR value | |||||
* @oidp: sysctl oid pointer | |||||
* @arg1: pointer to the device softc | |||||
* @arg2: unused sysctl argument | |||||
* @req: sysctl req pointer | |||||
* | |||||
* On read, returns the Rx ITR value for all of the VF queues. On write, | |||||
* update the ITR registers with the new Rx ITR value. | |||||
* | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_sysctl_rx_itr(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct iavf_sc *sc = (struct iavf_sc *)arg1; | |||||
device_t dev = sc->dev; | |||||
int requested_rx_itr; | |||||
int error = 0; | |||||
UNREFERENCED_PARAMETER(arg2); | |||||
if (iavf_driver_is_detaching(sc)) | |||||
return (ESHUTDOWN); | |||||
requested_rx_itr = sc->rx_itr; | |||||
error = sysctl_handle_int(oidp, &requested_rx_itr, 0, req); | |||||
if ((error) || (req->newptr == NULL)) | |||||
return (error); | |||||
if (requested_rx_itr < 0 || requested_rx_itr > IAVF_MAX_ITR) { | |||||
device_printf(dev, | |||||
"Invalid RX itr value; value must be between 0 and %d\n", | |||||
IAVF_MAX_ITR); | |||||
return (EINVAL); | |||||
} | |||||
sc->rx_itr = requested_rx_itr; | |||||
iavf_configure_rx_itr(sc); | |||||
return (error); | |||||
} | |||||
/** | |||||
* iavf_configure_tx_itr - Configure the Tx ITR | |||||
* @sc: device private softc | |||||
* | |||||
* Updates the ITR registers with a new Tx ITR setting. | |||||
*/ | |||||
void | |||||
iavf_configure_tx_itr(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_hw *hw = &sc->hw; | |||||
struct iavf_vsi *vsi = &sc->vsi; | |||||
struct iavf_tx_queue *que = vsi->tx_queues; | |||||
vsi->tx_itr_setting = sc->tx_itr; | |||||
for (int i = 0; i < IAVF_NTXQS(vsi); i++, que++) { | |||||
struct tx_ring *txr = &que->txr; | |||||
wr32(hw, IAVF_VFINT_ITRN1(IAVF_TX_ITR, i), | |||||
vsi->tx_itr_setting); | |||||
txr->itr = vsi->tx_itr_setting; | |||||
txr->latency = IAVF_AVE_LATENCY; | |||||
} | |||||
} | |||||
/** | |||||
* iavf_configure_rx_itr - Configure the Rx ITR | |||||
* @sc: device private softc | |||||
* | |||||
* Updates the ITR registers with a new Rx ITR setting. | |||||
*/ | |||||
void | |||||
iavf_configure_rx_itr(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_hw *hw = &sc->hw; | |||||
struct iavf_vsi *vsi = &sc->vsi; | |||||
struct iavf_rx_queue *que = vsi->rx_queues; | |||||
vsi->rx_itr_setting = sc->rx_itr; | |||||
for (int i = 0; i < IAVF_NRXQS(vsi); i++, que++) { | |||||
struct rx_ring *rxr = &que->rxr; | |||||
wr32(hw, IAVF_VFINT_ITRN1(IAVF_RX_ITR, i), | |||||
vsi->rx_itr_setting); | |||||
rxr->itr = vsi->rx_itr_setting; | |||||
rxr->latency = IAVF_AVE_LATENCY; | |||||
} | |||||
} | |||||
/** | |||||
* iavf_create_debug_sysctl_tree - Create a debug sysctl node | |||||
* @sc: device private softc | |||||
* | |||||
* Create a sysctl node meant to hold sysctls used to print debug information. | |||||
* Mark it as CTLFLAG_SKIP so that these sysctls do not show up in the | |||||
* "sysctl -a" output. | |||||
* | |||||
* @returns a pointer to the created sysctl node. | |||||
*/ | |||||
struct sysctl_oid_list * | |||||
iavf_create_debug_sysctl_tree(struct iavf_sc *sc) | |||||
{ | |||||
device_t dev = sc->dev; | |||||
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; | |||||
debug_node = SYSCTL_ADD_NODE(ctx, ctx_list, | |||||
OID_AUTO, "debug", CTLFLAG_RD | CTLFLAG_SKIP, NULL, "Debug Sysctls"); | |||||
return (SYSCTL_CHILDREN(debug_node)); | |||||
} | |||||
/** | |||||
* iavf_add_vsi_sysctls - Add sysctls for a given VSI | |||||
* @dev: device pointer | |||||
* @vsi: pointer to the VSI | |||||
* @ctx: sysctl context to add to | |||||
* @sysctl_name: name of the sysctl node (containing the VSI number) | |||||
* | |||||
* Adds a new sysctl node for holding specific sysctls for the given VSI. | |||||
*/ | |||||
void | |||||
iavf_add_vsi_sysctls(device_t dev, struct iavf_vsi *vsi, | |||||
struct sysctl_ctx_list *ctx, const char *sysctl_name) | |||||
{ | |||||
struct sysctl_oid *tree; | |||||
struct sysctl_oid_list *child; | |||||
struct sysctl_oid_list *vsi_list; | |||||
tree = device_get_sysctl_tree(dev); | |||||
child = SYSCTL_CHILDREN(tree); | |||||
vsi->vsi_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, sysctl_name, | |||||
CTLFLAG_RD, NULL, "VSI Number"); | |||||
vsi_list = SYSCTL_CHILDREN(vsi->vsi_node); | |||||
iavf_add_sysctls_eth_stats(ctx, vsi_list, &vsi->eth_stats); | |||||
} | |||||
/** | |||||
* iavf_sysctl_sw_filter_list - Dump software filters | |||||
* @oidp: sysctl oid pointer | |||||
* @arg1: pointer to the device softc | |||||
* @arg2: unused sysctl argument | |||||
* @req: sysctl req pointer | |||||
* | |||||
* On read, generates a string which lists the MAC and VLAN filters added to | |||||
* this virtual device. Useful for debugging to see whether or not the | |||||
* expected filters have been configured by software. | |||||
* | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_sysctl_sw_filter_list(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct iavf_sc *sc = (struct iavf_sc *)arg1; | |||||
struct iavf_mac_filter *f; | |||||
struct iavf_vlan_filter *v; | |||||
device_t dev = sc->dev; | |||||
int ftl_len, ftl_counter = 0, error = 0; | |||||
struct sbuf *buf; | |||||
UNREFERENCED_2PARAMETER(arg2, oidp); | |||||
if (iavf_driver_is_detaching(sc)) | |||||
return (ESHUTDOWN); | |||||
buf = sbuf_new_for_sysctl(NULL, NULL, 128, req); | |||||
if (!buf) { | |||||
device_printf(dev, "Could not allocate sbuf for output.\n"); | |||||
return (ENOMEM); | |||||
} | |||||
sbuf_printf(buf, "\n"); | |||||
/* Print MAC filters */ | |||||
sbuf_printf(buf, "MAC Filters:\n"); | |||||
ftl_len = 0; | |||||
SLIST_FOREACH(f, sc->mac_filters, next) | |||||
ftl_len++; | |||||
if (ftl_len < 1) | |||||
sbuf_printf(buf, "(none)\n"); | |||||
else { | |||||
SLIST_FOREACH(f, sc->mac_filters, next) { | |||||
sbuf_printf(buf, | |||||
MAC_FORMAT ", flags %#06x\n", | |||||
MAC_FORMAT_ARGS(f->macaddr), f->flags); | |||||
} | |||||
} | |||||
/* Print VLAN filters */ | |||||
sbuf_printf(buf, "VLAN Filters:\n"); | |||||
ftl_len = 0; | |||||
SLIST_FOREACH(v, sc->vlan_filters, next) | |||||
ftl_len++; | |||||
if (ftl_len < 1) | |||||
sbuf_printf(buf, "(none)"); | |||||
else { | |||||
SLIST_FOREACH(v, sc->vlan_filters, next) { | |||||
sbuf_printf(buf, | |||||
"%d, flags %#06x", | |||||
v->vlan, v->flags); | |||||
/* don't print '\n' for last entry */ | |||||
if (++ftl_counter != ftl_len) | |||||
sbuf_printf(buf, "\n"); | |||||
} | |||||
} | |||||
error = sbuf_finish(buf); | |||||
if (error) | |||||
device_printf(dev, "Error finishing sbuf: %d\n", error); | |||||
sbuf_delete(buf); | |||||
return (error); | |||||
} | |||||
/** | |||||
* iavf_media_status_common - Get media status for this device | |||||
* @sc: device softc pointer | |||||
* @ifmr: ifmedia request structure | |||||
* | |||||
* Report the media status for this device into the given ifmr structure. | |||||
*/ | |||||
void | |||||
iavf_media_status_common(struct iavf_sc *sc, struct ifmediareq *ifmr) | |||||
{ | |||||
enum iavf_ext_link_speed ext_speed; | |||||
iavf_update_link_status(sc); | |||||
ifmr->ifm_status = IFM_AVALID; | |||||
ifmr->ifm_active = IFM_ETHER; | |||||
if (!sc->link_up) | |||||
return; | |||||
ifmr->ifm_status |= IFM_ACTIVE; | |||||
/* Hardware is always full-duplex */ | |||||
ifmr->ifm_active |= IFM_FDX; | |||||
/* Based on the link speed reported by the PF over the AdminQ, choose a | |||||
* PHY type to report. This isn't 100% correct since we don't really | |||||
* know the underlying PHY type of the PF, but at least we can report | |||||
* a valid link speed... | |||||
*/ | |||||
if (IAVF_CAP_ADV_LINK_SPEED(sc)) | |||||
ext_speed = iavf_adv_speed_to_ext_speed(sc->link_speed_adv); | |||||
else | |||||
ext_speed = iavf_vc_speed_to_ext_speed(sc->link_speed); | |||||
ifmr->ifm_active |= iavf_ext_speed_to_ifmedia(ext_speed); | |||||
} | |||||
/** | |||||
* iavf_media_change_common - Change the media type for this device | |||||
* @ifp: ifnet structure | |||||
* | |||||
* @returns ENODEV because changing the media and speed is not supported. | |||||
*/ | |||||
int | |||||
iavf_media_change_common(struct ifnet *ifp) | |||||
{ | |||||
if_printf(ifp, "Changing speed is not supported\n"); | |||||
return (ENODEV); | |||||
} | |||||
/** | |||||
* iavf_set_initial_baudrate - Set the initial device baudrate | |||||
* @ifp: ifnet structure | |||||
* | |||||
* Set the baudrate for this ifnet structure to the expected initial value of | |||||
* 40Gbps. This maybe updated to a lower baudrate after the physical function | |||||
* reports speed to us over the virtchnl interface. | |||||
*/ | |||||
void | |||||
iavf_set_initial_baudrate(struct ifnet *ifp) | |||||
{ | |||||
#if __FreeBSD_version >= 1100000 | |||||
if_setbaudrate(ifp, IF_Gbps(40)); | |||||
#else | |||||
if_initbaudrate(ifp, IF_Gbps(40)); | |||||
#endif | |||||
} | |||||
/** | |||||
* iavf_add_sysctls_eth_stats - Add ethernet statistics sysctls | |||||
* @ctx: the sysctl ctx to add to | |||||
* @child: the node to add the sysctls to | |||||
* @eth_stats: ethernet stats structure | |||||
* | |||||
* Creates sysctls that report the values of the provided ethernet stats | |||||
* structure. | |||||
*/ | |||||
void | |||||
iavf_add_sysctls_eth_stats(struct sysctl_ctx_list *ctx, | |||||
struct sysctl_oid_list *child, | |||||
struct iavf_eth_stats *eth_stats) | |||||
{ | |||||
struct iavf_sysctl_info ctls[] = | |||||
{ | |||||
{ð_stats->rx_bytes, "good_octets_rcvd", "Good Octets Received"}, | |||||
{ð_stats->rx_unicast, "ucast_pkts_rcvd", | |||||
"Unicast Packets Received"}, | |||||
{ð_stats->rx_multicast, "mcast_pkts_rcvd", | |||||
"Multicast Packets Received"}, | |||||
{ð_stats->rx_broadcast, "bcast_pkts_rcvd", | |||||
"Broadcast Packets Received"}, | |||||
{ð_stats->rx_discards, "rx_discards", "Discarded RX packets"}, | |||||
{ð_stats->rx_unknown_protocol, "rx_unknown_proto", | |||||
"RX unknown protocol packets"}, | |||||
{ð_stats->tx_bytes, "good_octets_txd", "Good Octets Transmitted"}, | |||||
{ð_stats->tx_unicast, "ucast_pkts_txd", "Unicast Packets Transmitted"}, | |||||
{ð_stats->tx_multicast, "mcast_pkts_txd", | |||||
"Multicast Packets Transmitted"}, | |||||
{ð_stats->tx_broadcast, "bcast_pkts_txd", | |||||
"Broadcast Packets Transmitted"}, | |||||
{ð_stats->tx_errors, "tx_errors", "TX packet errors"}, | |||||
// end | |||||
{0,0,0} | |||||
}; | |||||
struct iavf_sysctl_info *entry = ctls; | |||||
while (entry->stat != 0) | |||||
{ | |||||
SYSCTL_ADD_UQUAD(ctx, child, OID_AUTO, entry->name, | |||||
CTLFLAG_RD, entry->stat, | |||||
entry->description); | |||||
entry++; | |||||
} | |||||
} | |||||
/** | |||||
* iavf_max_vc_speed_to_value - Convert link speed to IF speed value | |||||
* @link_speeds: bitmap of supported link speeds | |||||
* | |||||
* @returns the link speed value for the highest speed reported in the | |||||
* link_speeds bitmap. | |||||
*/ | |||||
u64 | |||||
iavf_max_vc_speed_to_value(u8 link_speeds) | |||||
{ | |||||
if (link_speeds & VIRTCHNL_LINK_SPEED_40GB) | |||||
return IF_Gbps(40); | |||||
if (link_speeds & VIRTCHNL_LINK_SPEED_25GB) | |||||
return IF_Gbps(25); | |||||
if (link_speeds & VIRTCHNL_LINK_SPEED_20GB) | |||||
return IF_Gbps(20); | |||||
if (link_speeds & VIRTCHNL_LINK_SPEED_10GB) | |||||
return IF_Gbps(10); | |||||
if (link_speeds & VIRTCHNL_LINK_SPEED_1GB) | |||||
return IF_Gbps(1); | |||||
if (link_speeds & VIRTCHNL_LINK_SPEED_100MB) | |||||
return IF_Mbps(100); | |||||
else | |||||
/* Minimum supported link speed */ | |||||
return IF_Mbps(100); | |||||
} | |||||
/** | |||||
* iavf_config_rss_reg - Configure RSS using registers | |||||
* @sc: device private softc | |||||
* | |||||
* Configures RSS for this function using the device registers. Called if the | |||||
* PF does not support configuring RSS over the virtchnl interface. | |||||
*/ | |||||
void | |||||
iavf_config_rss_reg(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_hw *hw = &sc->hw; | |||||
struct iavf_vsi *vsi = &sc->vsi; | |||||
u32 lut = 0; | |||||
u64 set_hena = 0, hena; | |||||
int i, j, que_id; | |||||
u32 rss_seed[IAVF_RSS_KEY_SIZE_REG]; | |||||
#ifdef RSS | |||||
u32 rss_hash_config; | |||||
#endif | |||||
/* Don't set up RSS if using a single queue */ | |||||
if (IAVF_NRXQS(vsi) == 1) { | |||||
wr32(hw, IAVF_VFQF_HENA(0), 0); | |||||
wr32(hw, IAVF_VFQF_HENA(1), 0); | |||||
iavf_flush(hw); | |||||
return; | |||||
} | |||||
#ifdef RSS | |||||
/* Fetch the configured RSS key */ | |||||
rss_getkey((uint8_t *) &rss_seed); | |||||
#else | |||||
iavf_get_default_rss_key(rss_seed); | |||||
#endif | |||||
/* Fill out hash function seed */ | |||||
for (i = 0; i < IAVF_RSS_KEY_SIZE_REG; i++) | |||||
wr32(hw, IAVF_VFQF_HKEY(i), rss_seed[i]); | |||||
/* Enable PCTYPES for RSS: */ | |||||
#ifdef RSS | |||||
rss_hash_config = rss_gethashconfig(); | |||||
if (rss_hash_config & RSS_HASHTYPE_RSS_IPV4) | |||||
set_hena |= ((u64)1 << IAVF_FILTER_PCTYPE_NONF_IPV4_OTHER); | |||||
if (rss_hash_config & RSS_HASHTYPE_RSS_TCP_IPV4) | |||||
set_hena |= ((u64)1 << IAVF_FILTER_PCTYPE_NONF_IPV4_TCP); | |||||
if (rss_hash_config & RSS_HASHTYPE_RSS_UDP_IPV4) | |||||
set_hena |= ((u64)1 << IAVF_FILTER_PCTYPE_NONF_IPV4_UDP); | |||||
if (rss_hash_config & RSS_HASHTYPE_RSS_IPV6) | |||||
set_hena |= ((u64)1 << IAVF_FILTER_PCTYPE_NONF_IPV6_OTHER); | |||||
if (rss_hash_config & RSS_HASHTYPE_RSS_IPV6_EX) | |||||
set_hena |= ((u64)1 << IAVF_FILTER_PCTYPE_FRAG_IPV6); | |||||
if (rss_hash_config & RSS_HASHTYPE_RSS_TCP_IPV6) | |||||
set_hena |= ((u64)1 << IAVF_FILTER_PCTYPE_NONF_IPV6_TCP); | |||||
if (rss_hash_config & RSS_HASHTYPE_RSS_UDP_IPV6) | |||||
set_hena |= ((u64)1 << IAVF_FILTER_PCTYPE_NONF_IPV6_UDP); | |||||
#else | |||||
set_hena = IAVF_DEFAULT_RSS_HENA_XL710; | |||||
#endif | |||||
hena = (u64)rd32(hw, IAVF_VFQF_HENA(0)) | | |||||
((u64)rd32(hw, IAVF_VFQF_HENA(1)) << 32); | |||||
hena |= set_hena; | |||||
wr32(hw, IAVF_VFQF_HENA(0), (u32)hena); | |||||
wr32(hw, IAVF_VFQF_HENA(1), (u32)(hena >> 32)); | |||||
/* Populate the LUT with max no. of queues in round robin fashion */ | |||||
for (i = 0, j = 0; i < IAVF_RSS_VSI_LUT_SIZE; i++, j++) { | |||||
if (j == IAVF_NRXQS(vsi)) | |||||
j = 0; | |||||
#ifdef RSS | |||||
/* | |||||
* Fetch the RSS bucket id for the given indirection entry. | |||||
* Cap it at the number of configured buckets (which is | |||||
* num_rx_queues.) | |||||
*/ | |||||
que_id = rss_get_indirection_to_bucket(i); | |||||
que_id = que_id % IAVF_NRXQS(vsi); | |||||
#else | |||||
que_id = j; | |||||
#endif | |||||
/* lut = 4-byte sliding window of 4 lut entries */ | |||||
lut = (lut << 8) | (que_id & IAVF_RSS_VF_LUT_ENTRY_MASK); | |||||
/* On i = 3, we have 4 entries in lut; write to the register */ | |||||
if ((i & 3) == 3) { | |||||
wr32(hw, IAVF_VFQF_HLUT(i >> 2), lut); | |||||
iavf_dbg_rss(sc, "%s: HLUT(%2d): %#010x", __func__, | |||||
i, lut); | |||||
} | |||||
} | |||||
iavf_flush(hw); | |||||
} | |||||
/** | |||||
* iavf_config_rss_pf - Configure RSS using PF virtchnl messages | |||||
* @sc: device private softc | |||||
* | |||||
* Configure RSS by sending virtchnl messages to the PF. | |||||
*/ | |||||
void | |||||
iavf_config_rss_pf(struct iavf_sc *sc) | |||||
{ | |||||
iavf_send_vc_msg(sc, IAVF_FLAG_AQ_CONFIG_RSS_KEY); | |||||
iavf_send_vc_msg(sc, IAVF_FLAG_AQ_SET_RSS_HENA); | |||||
iavf_send_vc_msg(sc, IAVF_FLAG_AQ_CONFIG_RSS_LUT); | |||||
} | |||||
/** | |||||
* iavf_config_rss - setup RSS | |||||
* @sc: device private softc | |||||
* | |||||
* Configures RSS using the method determined by capability flags in the VF | |||||
* resources structure sent from the PF over the virtchnl interface. | |||||
* | |||||
* @remark RSS keys and table are cleared on VF reset. | |||||
*/ | |||||
void | |||||
iavf_config_rss(struct iavf_sc *sc) | |||||
{ | |||||
if (sc->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_RSS_REG) { | |||||
iavf_dbg_info(sc, "Setting up RSS using VF registers...\n"); | |||||
iavf_config_rss_reg(sc); | |||||
} else if (sc->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_RSS_PF) { | |||||
iavf_dbg_info(sc, "Setting up RSS using messages to PF...\n"); | |||||
iavf_config_rss_pf(sc); | |||||
} else | |||||
device_printf(sc->dev, "VF does not support RSS capability sent by PF.\n"); | |||||
} | |||||
/** | |||||
* iavf_config_promisc - setup promiscuous mode | |||||
* @sc: device private softc | |||||
* @flags: promiscuous flags to configure | |||||
* | |||||
* Request that promiscuous modes be enabled from the PF | |||||
* | |||||
* @returns zero on success, or an error code on failure. | |||||
*/ | |||||
int | |||||
iavf_config_promisc(struct iavf_sc *sc, int flags) | |||||
{ | |||||
struct ifnet *ifp = sc->vsi.ifp; | |||||
sc->promisc_flags = 0; | |||||
if (flags & IFF_ALLMULTI || | |||||
if_llmaddr_count(ifp) == MAX_MULTICAST_ADDR) | |||||
sc->promisc_flags |= FLAG_VF_MULTICAST_PROMISC; | |||||
if (flags & IFF_PROMISC) | |||||
sc->promisc_flags |= FLAG_VF_UNICAST_PROMISC; | |||||
iavf_send_vc_msg(sc, IAVF_FLAG_AQ_CONFIGURE_PROMISC); | |||||
return (0); | |||||
} | |||||
/** | |||||
* iavf_mc_filter_apply - Program a MAC filter for this VF | |||||
* @arg: pointer to the device softc | |||||
* @sdl: MAC multicast address | |||||
* @cnt: unused parameter | |||||
* | |||||
* Program a MAC address multicast filter for this device. Intended | |||||
* to be used with the map-like function if_foreach_llmaddr(). | |||||
* | |||||
* @returns 1 on success, or 0 on failure | |||||
*/ | |||||
static u_int | |||||
iavf_mc_filter_apply(void *arg, struct sockaddr_dl *sdl, u_int cnt __unused) | |||||
{ | |||||
struct iavf_sc *sc = (struct iavf_sc *)arg; | |||||
int error; | |||||
error = iavf_add_mac_filter(sc, (u8*)LLADDR(sdl), IAVF_FILTER_MC); | |||||
return (!error); | |||||
} | |||||
/** | |||||
* iavf_init_multi - Initialize multicast address filters | |||||
* @sc: device private softc | |||||
* | |||||
* Called during initialization to reset multicast address filters to a known | |||||
* fresh state by deleting all currently active filters. | |||||
*/ | |||||
void | |||||
iavf_init_multi(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_mac_filter *f; | |||||
int mcnt = 0; | |||||
/* First clear any multicast filters */ | |||||
SLIST_FOREACH(f, sc->mac_filters, next) { | |||||
if ((f->flags & IAVF_FILTER_USED) | |||||
&& (f->flags & IAVF_FILTER_MC)) { | |||||
f->flags |= IAVF_FILTER_DEL; | |||||
mcnt++; | |||||
} | |||||
} | |||||
if (mcnt > 0) | |||||
iavf_send_vc_msg(sc, IAVF_FLAG_AQ_DEL_MAC_FILTER); | |||||
} | |||||
/** | |||||
* iavf_multi_set - Set multicast filters | |||||
* @sc: device private softc | |||||
* | |||||
* Set multicast MAC filters for this device. If there are too many filters, | |||||
* this will request the device to go into multicast promiscuous mode instead. | |||||
*/ | |||||
void | |||||
iavf_multi_set(struct iavf_sc *sc) | |||||
{ | |||||
if_t ifp = sc->vsi.ifp; | |||||
int mcnt = 0; | |||||
IOCTL_DEBUGOUT("iavf_multi_set: begin"); | |||||
mcnt = if_llmaddr_count(ifp); | |||||
if (__predict_false(mcnt == MAX_MULTICAST_ADDR)) { | |||||
/* Delete MC filters and enable mulitcast promisc instead */ | |||||
iavf_init_multi(sc); | |||||
sc->promisc_flags |= FLAG_VF_MULTICAST_PROMISC; | |||||
iavf_send_vc_msg(sc, IAVF_FLAG_AQ_CONFIGURE_PROMISC); | |||||
return; | |||||
} | |||||
/* If there aren't too many filters, delete existing MC filters */ | |||||
iavf_init_multi(sc); | |||||
/* And (re-)install filters for all mcast addresses */ | |||||
mcnt = if_foreach_llmaddr(ifp, iavf_mc_filter_apply, sc); | |||||
if (mcnt > 0) | |||||
iavf_send_vc_msg(sc, IAVF_FLAG_AQ_ADD_MAC_FILTER); | |||||
} | |||||
/** | |||||
* iavf_add_mac_filter - Add a MAC filter to the sc MAC list | |||||
* @sc: device private softc | |||||
* @macaddr: MAC address to add | |||||
* @flags: filter flags | |||||
* | |||||
* Add a new MAC filter to the softc MAC filter list. These will later be sent | |||||
* to the physical function (and ultimately hardware) via the virtchnl | |||||
* interface. | |||||
* | |||||
* @returns zero on success, EEXIST if the filter already exists, and ENOMEM | |||||
* if we ran out of memory allocating the filter structure. | |||||
*/ | |||||
int | |||||
iavf_add_mac_filter(struct iavf_sc *sc, u8 *macaddr, u16 flags) | |||||
{ | |||||
struct iavf_mac_filter *f; | |||||
/* Does one already exist? */ | |||||
f = iavf_find_mac_filter(sc, macaddr); | |||||
if (f != NULL) { | |||||
iavf_dbg_filter(sc, "exists: " MAC_FORMAT "\n", | |||||
MAC_FORMAT_ARGS(macaddr)); | |||||
return (EEXIST); | |||||
} | |||||
/* If not, get a new empty filter */ | |||||
f = iavf_get_mac_filter(sc); | |||||
if (f == NULL) { | |||||
device_printf(sc->dev, "%s: no filters available!!\n", | |||||
__func__); | |||||
return (ENOMEM); | |||||
} | |||||
iavf_dbg_filter(sc, "marked: " MAC_FORMAT "\n", | |||||
MAC_FORMAT_ARGS(macaddr)); | |||||
bcopy(macaddr, f->macaddr, ETHER_ADDR_LEN); | |||||
f->flags |= (IAVF_FILTER_ADD | IAVF_FILTER_USED); | |||||
f->flags |= flags; | |||||
return (0); | |||||
} | |||||
/** | |||||
* iavf_find_mac_filter - Find a MAC filter with the given address | |||||
* @sc: device private softc | |||||
* @macaddr: the MAC address to find | |||||
* | |||||
* Finds the filter structure in the MAC filter list with the corresponding | |||||
* MAC address. | |||||
* | |||||
* @returns a pointer to the filter structure, or NULL if no such filter | |||||
* exists in the list yet. | |||||
*/ | |||||
struct iavf_mac_filter * | |||||
iavf_find_mac_filter(struct iavf_sc *sc, u8 *macaddr) | |||||
{ | |||||
struct iavf_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); | |||||
} | |||||
/** | |||||
* iavf_get_mac_filter - Get a new MAC address filter | |||||
* @sc: device private softc | |||||
* | |||||
* Allocates a new filter structure and inserts it into the MAC filter list. | |||||
* | |||||
* @post the caller must fill in the structure details after calling this | |||||
* function, but does not need to insert it into the linked list. | |||||
* | |||||
* @returns a pointer to the new filter structure, or NULL of we failed to | |||||
* allocate it. | |||||
*/ | |||||
struct iavf_mac_filter * | |||||
iavf_get_mac_filter(struct iavf_sc *sc) | |||||
{ | |||||
struct iavf_mac_filter *f; | |||||
f = (struct iavf_mac_filter *)malloc(sizeof(struct iavf_mac_filter), | |||||
M_IAVF, M_NOWAIT | M_ZERO); | |||||
if (f) | |||||
SLIST_INSERT_HEAD(sc->mac_filters, f, next); | |||||
return (f); | |||||
} | |||||
/** | |||||
* iavf_baudrate_from_link_speed - Convert link speed to baudrate | |||||
* @sc: device private softc | |||||
* | |||||
* @post The link_speed_adv field is in Mbps, so it is multipled by | |||||
* 1,000,000 before it's returned. | |||||
* | |||||
* @returns the adapter link speed in bits/sec | |||||
*/ | |||||
u64 | |||||
iavf_baudrate_from_link_speed(struct iavf_sc *sc) | |||||
{ | |||||
if (sc->vf_res->vf_cap_flags & VIRTCHNL_VF_CAP_ADV_LINK_SPEED) | |||||
return (sc->link_speed_adv * IAVF_ADV_LINK_SPEED_SCALE); | |||||
else | |||||
return iavf_max_vc_speed_to_value(sc->link_speed); | |||||
} | |||||
/** | |||||
* iavf_add_vlan_filter - Add a VLAN filter to the softc VLAN list | |||||
* @sc: device private softc | |||||
* @vtag: the VLAN id to filter | |||||
* | |||||
* Allocate a new VLAN filter structure and insert it into the VLAN list. | |||||
*/ | |||||
void | |||||
iavf_add_vlan_filter(struct iavf_sc *sc, u16 vtag) | |||||
{ | |||||
struct iavf_vlan_filter *v; | |||||
v = (struct iavf_vlan_filter *)malloc(sizeof(struct iavf_vlan_filter), | |||||
M_IAVF, M_WAITOK | M_ZERO); | |||||
SLIST_INSERT_HEAD(sc->vlan_filters, v, next); | |||||
v->vlan = vtag; | |||||
v->flags = IAVF_FILTER_ADD; | |||||
} | |||||
/** | |||||
* iavf_mark_del_vlan_filter - Mark a given VLAN id for deletion | |||||
* @sc: device private softc | |||||
* @vtag: the VLAN id to delete | |||||
* | |||||
* Marks all VLAN filters matching the given vtag for deletion. | |||||
* | |||||
* @returns the number of filters marked for deletion. | |||||
* | |||||
* @remark the filters are not removed immediately, but will be removed from | |||||
* the list by another function that synchronizes over the virtchnl interface. | |||||
*/ | |||||
int | |||||
iavf_mark_del_vlan_filter(struct iavf_sc *sc, u16 vtag) | |||||
{ | |||||
struct iavf_vlan_filter *v; | |||||
int i = 0; | |||||
SLIST_FOREACH(v, sc->vlan_filters, next) { | |||||
if (v->vlan == vtag) { | |||||
v->flags = IAVF_FILTER_DEL; | |||||
++i; | |||||
} | |||||
} | |||||
return (i); | |||||
} | |||||
/** | |||||
* iavf_update_msix_devinfo - Fix MSIX values for pci_msix_count() | |||||
* @dev: pointer to kernel device | |||||
* | |||||
* Fix cached MSI-X control register information. This is a workaround | |||||
* for an issue where VFs spawned in non-passthrough mode on FreeBSD | |||||
* will have their PCI information cached before the PF driver | |||||
* finishes updating their PCI information. | |||||
* | |||||
* @pre Must be called before pci_msix_count() | |||||
*/ | |||||
void | |||||
iavf_update_msix_devinfo(device_t dev) | |||||
{ | |||||
struct pci_devinfo *dinfo; | |||||
u32 msix_ctrl; | |||||
dinfo = (struct pci_devinfo *)device_get_ivars(dev); | |||||
/* We can hardcode this offset since we know the device */ | |||||
msix_ctrl = pci_read_config(dev, 0x70 + PCIR_MSIX_CTRL, 2); | |||||
dinfo->cfg.msix.msix_ctrl = msix_ctrl; | |||||
dinfo->cfg.msix.msix_msgnum = (msix_ctrl & PCIM_MSIXCTRL_TABLE_SIZE) + 1; | |||||
} | |||||
/** | |||||
* iavf_disable_queues_with_retries - Send PF multiple DISABLE_QUEUES messages | |||||
* @sc: device softc | |||||
* | |||||
* Send a virtual channel message to the PF to DISABLE_QUEUES, but resend it up | |||||
* to IAVF_MAX_DIS_Q_RETRY times if the response says that it wasn't | |||||
* successful. This is intended to workaround a bug that can appear on the PF. | |||||
*/ | |||||
void | |||||
iavf_disable_queues_with_retries(struct iavf_sc *sc) | |||||
{ | |||||
bool in_detach = iavf_driver_is_detaching(sc); | |||||
int max_attempts = IAVF_MAX_DIS_Q_RETRY; | |||||
int msg_count = 0; | |||||
/* While the driver is detaching, it doesn't care if the queue | |||||
* disable finishes successfully or not. Just send one message | |||||
* to just notify the PF driver. | |||||
*/ | |||||
if (in_detach) | |||||
max_attempts = 1; | |||||
while ((msg_count < max_attempts) && | |||||
atomic_load_acq_32(&sc->queues_enabled)) { | |||||
msg_count++; | |||||
iavf_send_vc_msg_sleep(sc, IAVF_FLAG_AQ_DISABLE_QUEUES); | |||||
} | |||||
/* Possibly print messages about retry attempts and issues */ | |||||
if (msg_count > 1) | |||||
iavf_dbg_vc(sc, "DISABLE_QUEUES messages sent: %d\n", | |||||
msg_count); | |||||
if (!in_detach && msg_count >= max_attempts) | |||||
device_printf(sc->dev, "%s: DISABLE_QUEUES may have failed\n", | |||||
__func__); | |||||
} |