/* 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. */ /** * @file iavf_vc_common.c * @brief Common virtchnl interface functions * * Contains functions implementing the virtchnl interface for connecting to * the PF driver. This file contains the functions which are common between * the legacy and iflib driver implementations. */ #include "iavf_vc_common.h" /* Static function decls */ static void iavf_handle_link_event(struct iavf_sc *sc, struct virtchnl_pf_event *vpe); /** * iavf_send_pf_msg - Send virtchnl message to PF device * @sc: device softc * @op: the op to send * @msg: message contents * @len: length of the message * * Send a message to the PF device over the virtchnl connection. Print * a status code if the message reports an error. * * @returns zero on success, or an error code on failure. */ int iavf_send_pf_msg(struct iavf_sc *sc, enum virtchnl_ops op, u8 *msg, u16 len) { struct iavf_hw *hw = &sc->hw; device_t dev = sc->dev; enum iavf_status status; int val_err; /* Validating message before sending it to the PF */ val_err = virtchnl_vc_validate_vf_msg(&sc->version, op, msg, len); if (val_err) device_printf(dev, "Error validating msg to PF for op %d," " msglen %d: error %d\n", op, len, val_err); if (!iavf_check_asq_alive(hw)) { if (op != VIRTCHNL_OP_GET_STATS) device_printf(dev, "Unable to send opcode %s to PF, " "ASQ is not alive\n", iavf_vc_opcode_str(op)); return (0); } if (op != VIRTCHNL_OP_GET_STATS) iavf_dbg_vc(sc, "Sending msg (op=%s[%d]) to PF\n", iavf_vc_opcode_str(op), op); status = iavf_aq_send_msg_to_pf(hw, op, IAVF_SUCCESS, msg, len, NULL); if (status && op != VIRTCHNL_OP_GET_STATS) device_printf(dev, "Unable to send opcode %s to PF, " "status %s, aq error %s\n", iavf_vc_opcode_str(op), iavf_stat_str(hw, status), iavf_aq_str(hw, hw->aq.asq_last_status)); return (status); } /** * iavf_send_api_ver - Send the API version we support to the PF * @sc: device softc * * Send API version admin queue message to the PF. The reply is not checked * in this function. * * @returns 0 if the message was successfully sent, or one of the * IAVF_ADMIN_QUEUE_ERROR_ statuses if not. */ int iavf_send_api_ver(struct iavf_sc *sc) { struct virtchnl_version_info vvi; vvi.major = VIRTCHNL_VERSION_MAJOR; vvi.minor = VIRTCHNL_VERSION_MINOR; return iavf_send_pf_msg(sc, VIRTCHNL_OP_VERSION, (u8 *)&vvi, sizeof(vvi)); } /** * iavf_verify_api_ver - Verify the PF supports our API version * @sc: device softc * * Compare API versions with the PF. Must be called after admin queue is * initialized. * * @returns 0 if API versions match, EIO if they do not, or * IAVF_ERR_ADMIN_QUEUE_NO_WORK if the admin queue is empty. */ int iavf_verify_api_ver(struct iavf_sc *sc) { struct virtchnl_version_info *pf_vvi; struct iavf_hw *hw = &sc->hw; struct iavf_arq_event_info event; enum iavf_status status; device_t dev = sc->dev; int error = 0; int retries = 0; event.buf_len = IAVF_AQ_BUF_SZ; event.msg_buf = (u8 *)malloc(event.buf_len, M_IAVF, M_WAITOK); for (;;) { if (++retries > IAVF_AQ_MAX_ERR) goto out_alloc; /* Initial delay here is necessary */ iavf_msec_pause(100); status = iavf_clean_arq_element(hw, &event, NULL); if (status == IAVF_ERR_ADMIN_QUEUE_NO_WORK) continue; else if (status) { error = EIO; goto out_alloc; } if ((enum virtchnl_ops)le32toh(event.desc.cookie_high) != VIRTCHNL_OP_VERSION) { iavf_dbg_vc(sc, "%s: Received unexpected op response: %d\n", __func__, le32toh(event.desc.cookie_high)); /* Don't stop looking for expected response */ continue; } status = (enum iavf_status)le32toh(event.desc.cookie_low); if (status) { error = EIO; goto out_alloc; } else break; } pf_vvi = (struct virtchnl_version_info *)event.msg_buf; if ((pf_vvi->major > VIRTCHNL_VERSION_MAJOR) || ((pf_vvi->major == VIRTCHNL_VERSION_MAJOR) && (pf_vvi->minor > VIRTCHNL_VERSION_MINOR))) { device_printf(dev, "Critical PF/VF API version mismatch!\n"); error = EIO; } else { sc->version.major = pf_vvi->major; sc->version.minor = pf_vvi->minor; } /* Log PF/VF api versions */ device_printf(dev, "PF API %d.%d / VF API %d.%d\n", pf_vvi->major, pf_vvi->minor, VIRTCHNL_VERSION_MAJOR, VIRTCHNL_VERSION_MINOR); out_alloc: free(event.msg_buf, M_IAVF); return (error); } /** * iavf_send_vf_config_msg - Send VF configuration request * @sc: device softc * * Send VF configuration request admin queue message to the PF. The reply * is not checked in this function. * * @returns 0 if the message was successfully sent, or one of the * IAVF_ADMIN_QUEUE_ERROR_ statuses if not. */ int iavf_send_vf_config_msg(struct iavf_sc *sc) { u32 caps; /* Support the base mode functionality, as well as advanced * speed reporting capability. */ caps = VF_BASE_MODE_OFFLOADS | VIRTCHNL_VF_CAP_ADV_LINK_SPEED; iavf_dbg_info(sc, "Sending offload flags: 0x%b\n", caps, IAVF_PRINTF_VF_OFFLOAD_FLAGS); if (sc->version.minor == VIRTCHNL_VERSION_MINOR_NO_VF_CAPS) return iavf_send_pf_msg(sc, VIRTCHNL_OP_GET_VF_RESOURCES, NULL, 0); else return iavf_send_pf_msg(sc, VIRTCHNL_OP_GET_VF_RESOURCES, (u8 *)&caps, sizeof(caps)); } /** * iavf_get_vf_config - Get the VF configuration from the PF * @sc: device softc * * Get VF configuration from PF and populate hw structure. Must be called after * admin queue is initialized. Busy waits until response is received from PF, * with maximum timeout. Response from PF is returned in the buffer for further * processing by the caller. * * @returns zero on success, or an error code on failure */ int iavf_get_vf_config(struct iavf_sc *sc) { struct iavf_hw *hw = &sc->hw; device_t dev = sc->dev; enum iavf_status status = IAVF_SUCCESS; struct iavf_arq_event_info event; u16 len; u32 retries = 0; int error = 0; /* Note this assumes a single VSI */ len = sizeof(struct virtchnl_vf_resource) + sizeof(struct virtchnl_vsi_resource); event.buf_len = len; event.msg_buf = (u8 *)malloc(event.buf_len, M_IAVF, M_WAITOK); for (;;) { status = iavf_clean_arq_element(hw, &event, NULL); if (status == IAVF_ERR_ADMIN_QUEUE_NO_WORK) { if (++retries <= IAVF_AQ_MAX_ERR) iavf_msec_pause(10); } else if ((enum virtchnl_ops)le32toh(event.desc.cookie_high) != VIRTCHNL_OP_GET_VF_RESOURCES) { iavf_dbg_vc(sc, "%s: Received a response from PF," " opcode %d, error %d", __func__, le32toh(event.desc.cookie_high), le32toh(event.desc.cookie_low)); retries++; continue; } else { status = (enum iavf_status)le32toh(event.desc.cookie_low); if (status) { device_printf(dev, "%s: Error returned from PF," " opcode %d, error %d\n", __func__, le32toh(event.desc.cookie_high), le32toh(event.desc.cookie_low)); error = EIO; goto out_alloc; } /* We retrieved the config message, with no errors */ break; } if (retries > IAVF_AQ_MAX_ERR) { iavf_dbg_vc(sc, "%s: Did not receive response after %d tries.", __func__, retries); error = ETIMEDOUT; goto out_alloc; } } memcpy(sc->vf_res, event.msg_buf, min(event.msg_len, len)); iavf_vf_parse_hw_config(hw, sc->vf_res); out_alloc: free(event.msg_buf, M_IAVF); return (error); } /** * iavf_enable_queues - Enable queues * @sc: device softc * * Request that the PF enable all of our queues. * * @remark the reply from the PF is not checked by this function. * * @returns zero */ int iavf_enable_queues(struct iavf_sc *sc) { struct virtchnl_queue_select vqs; struct iavf_vsi *vsi = &sc->vsi; vqs.vsi_id = sc->vsi_res->vsi_id; vqs.tx_queues = (1 << IAVF_NTXQS(vsi)) - 1; vqs.rx_queues = vqs.tx_queues; iavf_send_pf_msg(sc, VIRTCHNL_OP_ENABLE_QUEUES, (u8 *)&vqs, sizeof(vqs)); return (0); } /** * iavf_disable_queues - Disable queues * @sc: device softc * * Request that the PF disable all of our queues. * * @remark the reply from the PF is not checked by this function. * * @returns zero */ int iavf_disable_queues(struct iavf_sc *sc) { struct virtchnl_queue_select vqs; struct iavf_vsi *vsi = &sc->vsi; vqs.vsi_id = sc->vsi_res->vsi_id; vqs.tx_queues = (1 << IAVF_NTXQS(vsi)) - 1; vqs.rx_queues = vqs.tx_queues; iavf_send_pf_msg(sc, VIRTCHNL_OP_DISABLE_QUEUES, (u8 *)&vqs, sizeof(vqs)); return (0); } /** * iavf_add_vlans - Add VLAN filters * @sc: device softc * * Scan the Filter List looking for vlans that need * to be added, then create the data to hand to the AQ * for handling. * * @returns zero on success, or an error code on failure. */ int iavf_add_vlans(struct iavf_sc *sc) { struct virtchnl_vlan_filter_list *v; struct iavf_vlan_filter *f, *ftmp; device_t dev = sc->dev; int i = 0, cnt = 0; u32 len; /* Get count of VLAN filters to add */ SLIST_FOREACH(f, sc->vlan_filters, next) { if (f->flags & IAVF_FILTER_ADD) cnt++; } if (!cnt) /* no work... */ return (ENOENT); len = sizeof(struct virtchnl_vlan_filter_list) + (cnt * sizeof(u16)); if (len > IAVF_AQ_BUF_SZ) { device_printf(dev, "%s: Exceeded Max AQ Buf size\n", __func__); return (EFBIG); } v = (struct virtchnl_vlan_filter_list *)malloc(len, M_IAVF, M_NOWAIT | M_ZERO); if (!v) { device_printf(dev, "%s: unable to allocate memory\n", __func__); return (ENOMEM); } v->vsi_id = sc->vsi_res->vsi_id; v->num_elements = cnt; /* Scan the filter array */ SLIST_FOREACH_SAFE(f, sc->vlan_filters, next, ftmp) { if (f->flags & IAVF_FILTER_ADD) { bcopy(&f->vlan, &v->vlan_id[i], sizeof(u16)); f->flags = IAVF_FILTER_USED; i++; } if (i == cnt) break; } iavf_send_pf_msg(sc, VIRTCHNL_OP_ADD_VLAN, (u8 *)v, len); free(v, M_IAVF); /* add stats? */ return (0); } /** * iavf_del_vlans - Delete VLAN filters * @sc: device softc * * Scan the Filter Table looking for vlans that need * to be removed, then create the data to hand to the AQ * for handling. * * @returns zero on success, or an error code on failure. */ int iavf_del_vlans(struct iavf_sc *sc) { struct virtchnl_vlan_filter_list *v; struct iavf_vlan_filter *f, *ftmp; device_t dev = sc->dev; int i = 0, cnt = 0; u32 len; /* Get count of VLAN filters to delete */ SLIST_FOREACH(f, sc->vlan_filters, next) { if (f->flags & IAVF_FILTER_DEL) cnt++; } if (!cnt) /* no work... */ return (ENOENT); len = sizeof(struct virtchnl_vlan_filter_list) + (cnt * sizeof(u16)); if (len > IAVF_AQ_BUF_SZ) { device_printf(dev, "%s: Exceeded Max AQ Buf size\n", __func__); return (EFBIG); } v = (struct virtchnl_vlan_filter_list *) malloc(len, M_IAVF, M_NOWAIT | M_ZERO); if (!v) { device_printf(dev, "%s: unable to allocate memory\n", __func__); return (ENOMEM); } v->vsi_id = sc->vsi_res->vsi_id; v->num_elements = cnt; /* Scan the filter array */ SLIST_FOREACH_SAFE(f, sc->vlan_filters, next, ftmp) { if (f->flags & IAVF_FILTER_DEL) { bcopy(&f->vlan, &v->vlan_id[i], sizeof(u16)); i++; SLIST_REMOVE(sc->vlan_filters, f, iavf_vlan_filter, next); free(f, M_IAVF); } if (i == cnt) break; } iavf_send_pf_msg(sc, VIRTCHNL_OP_DEL_VLAN, (u8 *)v, len); free(v, M_IAVF); /* add stats? */ return (0); } /** * iavf_add_ether_filters - Add MAC filters * @sc: device softc * * This routine takes additions to the vsi filter * table and creates an Admin Queue call to create * the filters in the hardware. * * @returns zero on success, or an error code on failure. */ int iavf_add_ether_filters(struct iavf_sc *sc) { struct virtchnl_ether_addr_list *a; struct iavf_mac_filter *f; device_t dev = sc->dev; int len, j = 0, cnt = 0; int error; /* Get count of MAC addresses to add */ SLIST_FOREACH(f, sc->mac_filters, next) { if (f->flags & IAVF_FILTER_ADD) cnt++; } if (cnt == 0) { /* Should not happen... */ iavf_dbg_vc(sc, "%s: cnt == 0, exiting...\n", __func__); return (ENOENT); } len = sizeof(struct virtchnl_ether_addr_list) + (cnt * sizeof(struct virtchnl_ether_addr)); a = (struct virtchnl_ether_addr_list *) malloc(len, M_IAVF, M_NOWAIT | M_ZERO); if (a == NULL) { device_printf(dev, "%s: Failed to get memory for " "virtchnl_ether_addr_list\n", __func__); return (ENOMEM); } a->vsi_id = sc->vsi.id; a->num_elements = cnt; /* Scan the filter array */ SLIST_FOREACH(f, sc->mac_filters, next) { if (f->flags & IAVF_FILTER_ADD) { bcopy(f->macaddr, a->list[j].addr, ETHER_ADDR_LEN); f->flags &= ~IAVF_FILTER_ADD; j++; iavf_dbg_vc(sc, "%s: ADD: " MAC_FORMAT "\n", __func__, MAC_FORMAT_ARGS(f->macaddr)); } if (j == cnt) break; } iavf_dbg_vc(sc, "%s: len %d, j %d, cnt %d\n", __func__, len, j, cnt); error = iavf_send_pf_msg(sc, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)a, len); /* add stats? */ free(a, M_IAVF); return (error); } /** * iavf_del_ether_filters - Delete MAC filters * @sc: device softc * * This routine takes filters flagged for deletion in the * sc MAC filter list and creates an Admin Queue call * to delete those filters in the hardware. * * @returns zero on success, or an error code on failure. */ int iavf_del_ether_filters(struct iavf_sc *sc) { struct virtchnl_ether_addr_list *d; struct iavf_mac_filter *f, *f_temp; device_t dev = sc->dev; int len, j = 0, cnt = 0; /* Get count of MAC addresses to delete */ SLIST_FOREACH(f, sc->mac_filters, next) { if (f->flags & IAVF_FILTER_DEL) cnt++; } if (cnt == 0) { iavf_dbg_vc(sc, "%s: cnt == 0, exiting...\n", __func__); return (ENOENT); } len = sizeof(struct virtchnl_ether_addr_list) + (cnt * sizeof(struct virtchnl_ether_addr)); d = (struct virtchnl_ether_addr_list *) malloc(len, M_IAVF, M_NOWAIT | M_ZERO); if (d == NULL) { device_printf(dev, "%s: Failed to get memory for " "virtchnl_ether_addr_list\n", __func__); return (ENOMEM); } d->vsi_id = sc->vsi.id; d->num_elements = cnt; /* Scan the filter array */ SLIST_FOREACH_SAFE(f, sc->mac_filters, next, f_temp) { if (f->flags & IAVF_FILTER_DEL) { bcopy(f->macaddr, d->list[j].addr, ETHER_ADDR_LEN); iavf_dbg_vc(sc, "DEL: " MAC_FORMAT "\n", MAC_FORMAT_ARGS(f->macaddr)); j++; SLIST_REMOVE(sc->mac_filters, f, iavf_mac_filter, next); free(f, M_IAVF); } if (j == cnt) break; } iavf_send_pf_msg(sc, VIRTCHNL_OP_DEL_ETH_ADDR, (u8 *)d, len); /* add stats? */ free(d, M_IAVF); return (0); } /** * iavf_request_reset - Request a device reset * @sc: device softc * * Request that the PF reset this VF. No response is expected. * * @returns zero */ int iavf_request_reset(struct iavf_sc *sc) { /* ** Set the reset status to "in progress" before ** the request, this avoids any possibility of ** a mistaken early detection of completion. */ wr32(&sc->hw, IAVF_VFGEN_RSTAT, VIRTCHNL_VFR_INPROGRESS); iavf_send_pf_msg(sc, VIRTCHNL_OP_RESET_VF, NULL, 0); return (0); } /** * iavf_request_stats - Request VF stats * @sc: device softc * * Request the statistics for this VF's VSI from PF. * * @remark prints an error message on failure to obtain stats, but does not * return with an error code. * * @returns zero */ int iavf_request_stats(struct iavf_sc *sc) { struct virtchnl_queue_select vqs; int error = 0; vqs.vsi_id = sc->vsi_res->vsi_id; /* Low priority, we don't need to error check */ error = iavf_send_pf_msg(sc, VIRTCHNL_OP_GET_STATS, (u8 *)&vqs, sizeof(vqs)); if (error) device_printf(sc->dev, "Error sending stats request to PF: %d\n", error); return (0); } /** * iavf_update_stats_counters - Update driver statistics * @sc: device softc * @es: ethernet stats storage * * Updates driver's stats counters with VSI stats returned from PF. */ void iavf_update_stats_counters(struct iavf_sc *sc, struct iavf_eth_stats *es) { struct iavf_vsi *vsi = &sc->vsi; /* Update ifnet stats */ vsi->ipackets = es->rx_unicast + es->rx_multicast + es->rx_broadcast; vsi->opackets = es->tx_unicast + es->tx_multicast + es->tx_broadcast; vsi->ibytes = es->rx_bytes; vsi->obytes = es->tx_bytes; vsi->imcasts = es->rx_multicast; vsi->omcasts = es->tx_multicast; vsi->oerrors = es->tx_errors; vsi->iqdrops = es->rx_discards; vsi->oqdrops = es->tx_discards; vsi->noproto = es->rx_unknown_protocol; vsi->eth_stats = *es; } /** * iavf_config_rss_key - Configure RSS key over virtchnl * @sc: device softc * * Send a message to the PF to configure the RSS key using the virtchnl * interface. * * @remark this does not check the reply from the PF. * * @returns zero on success, or an error code on failure. */ int iavf_config_rss_key(struct iavf_sc *sc) { struct virtchnl_rss_key *rss_key_msg; int msg_len, key_length; u8 rss_seed[IAVF_RSS_KEY_SIZE]; #ifdef RSS /* Fetch the configured RSS key */ rss_getkey((uint8_t *) &rss_seed); #else iavf_get_default_rss_key((u32 *)rss_seed); #endif /* Send the fetched key */ key_length = IAVF_RSS_KEY_SIZE; msg_len = sizeof(struct virtchnl_rss_key) + (sizeof(u8) * key_length) - 1; rss_key_msg = (struct virtchnl_rss_key *) malloc(msg_len, M_IAVF, M_NOWAIT | M_ZERO); if (rss_key_msg == NULL) { device_printf(sc->dev, "Unable to allocate msg memory for RSS key msg.\n"); return (ENOMEM); } rss_key_msg->vsi_id = sc->vsi_res->vsi_id; rss_key_msg->key_len = key_length; bcopy(rss_seed, &rss_key_msg->key[0], key_length); iavf_dbg_vc(sc, "%s: vsi_id %d, key_len %d\n", __func__, rss_key_msg->vsi_id, rss_key_msg->key_len); iavf_send_pf_msg(sc, VIRTCHNL_OP_CONFIG_RSS_KEY, (u8 *)rss_key_msg, msg_len); free(rss_key_msg, M_IAVF); return (0); } /** * iavf_set_rss_hena - Configure the RSS HENA * @sc: device softc * * Configure the RSS HENA values by sending a virtchnl message to the PF * * @remark the reply from the PF is not checked by this function. * * @returns zero */ int iavf_set_rss_hena(struct iavf_sc *sc) { struct virtchnl_rss_hena hena; struct iavf_hw *hw = &sc->hw; if (hw->mac.type == IAVF_MAC_VF) hena.hena = IAVF_DEFAULT_RSS_HENA_AVF; else if (hw->mac.type == IAVF_MAC_X722_VF) hena.hena = IAVF_DEFAULT_RSS_HENA_X722; else hena.hena = IAVF_DEFAULT_RSS_HENA_BASE; iavf_send_pf_msg(sc, VIRTCHNL_OP_SET_RSS_HENA, (u8 *)&hena, sizeof(hena)); return (0); } /** * iavf_config_rss_lut - Configure RSS lookup table * @sc: device softc * * Configure the RSS lookup table by sending a virtchnl message to the PF. * * @remark the reply from the PF is not checked in this function. * * @returns zero on success, or an error code on failure. */ int iavf_config_rss_lut(struct iavf_sc *sc) { struct virtchnl_rss_lut *rss_lut_msg; int msg_len; u16 lut_length; u32 lut; int i, que_id; lut_length = IAVF_RSS_VSI_LUT_SIZE; msg_len = sizeof(struct virtchnl_rss_lut) + (lut_length * sizeof(u8)) - 1; rss_lut_msg = (struct virtchnl_rss_lut *) malloc(msg_len, M_IAVF, M_NOWAIT | M_ZERO); if (rss_lut_msg == NULL) { device_printf(sc->dev, "Unable to allocate msg memory for RSS lut msg.\n"); return (ENOMEM); } rss_lut_msg->vsi_id = sc->vsi_res->vsi_id; /* Each LUT entry is a max of 1 byte, so this is easy */ rss_lut_msg->lut_entries = lut_length; /* Populate the LUT with max no. of queues in round robin fashion */ for (i = 0; i < lut_length; i++) { #ifdef RSS /* * Fetch the RSS bucket id for the given indirection entry. * Cap it at the number of configured buckets (which is * num_queues.) */ que_id = rss_get_indirection_to_bucket(i); que_id = que_id % sc->vsi.num_rx_queues; #else que_id = i % sc->vsi.num_rx_queues; #endif lut = que_id & IAVF_RSS_VSI_LUT_ENTRY_MASK; rss_lut_msg->lut[i] = lut; } iavf_send_pf_msg(sc, VIRTCHNL_OP_CONFIG_RSS_LUT, (u8 *)rss_lut_msg, msg_len); free(rss_lut_msg, M_IAVF); return (0); } /** * iavf_config_promisc_mode - Configure promiscuous mode * @sc: device softc * * Configure the device into promiscuous mode by sending a virtchnl message to * the PF. * * @remark the reply from the PF is not checked in this function. * * @returns zero */ int iavf_config_promisc_mode(struct iavf_sc *sc) { struct virtchnl_promisc_info pinfo; pinfo.vsi_id = sc->vsi_res->vsi_id; pinfo.flags = sc->promisc_flags; iavf_send_pf_msg(sc, VIRTCHNL_OP_CONFIG_PROMISCUOUS_MODE, (u8 *)&pinfo, sizeof(pinfo)); return (0); } /** * iavf_vc_send_cmd - Convert request into virtchnl calls * @sc: device softc * @request: the requested command to run * * Send the proper virtchnl call based on the request value. * * @returns zero on success, or an error code on failure. Note that unknown * requests will return zero. */ int iavf_vc_send_cmd(struct iavf_sc *sc, uint32_t request) { switch (request) { case IAVF_FLAG_AQ_MAP_VECTORS: return iavf_map_queues(sc); case IAVF_FLAG_AQ_ADD_MAC_FILTER: return iavf_add_ether_filters(sc); case IAVF_FLAG_AQ_ADD_VLAN_FILTER: return iavf_add_vlans(sc); case IAVF_FLAG_AQ_DEL_MAC_FILTER: return iavf_del_ether_filters(sc); case IAVF_FLAG_AQ_DEL_VLAN_FILTER: return iavf_del_vlans(sc); case IAVF_FLAG_AQ_CONFIGURE_QUEUES: return iavf_configure_queues(sc); case IAVF_FLAG_AQ_DISABLE_QUEUES: return iavf_disable_queues(sc); case IAVF_FLAG_AQ_ENABLE_QUEUES: return iavf_enable_queues(sc); case IAVF_FLAG_AQ_CONFIG_RSS_KEY: return iavf_config_rss_key(sc); case IAVF_FLAG_AQ_SET_RSS_HENA: return iavf_set_rss_hena(sc); case IAVF_FLAG_AQ_CONFIG_RSS_LUT: return iavf_config_rss_lut(sc); case IAVF_FLAG_AQ_CONFIGURE_PROMISC: return iavf_config_promisc_mode(sc); } return (0); } /** * iavf_vc_get_op_chan - Get op channel for a request * @sc: device softc * @request: the request type * * @returns the op channel for the given request, or NULL if no channel is * used. */ void * iavf_vc_get_op_chan(struct iavf_sc *sc, uint32_t request) { switch (request) { case IAVF_FLAG_AQ_ENABLE_QUEUES: return (&sc->enable_queues_chan); case IAVF_FLAG_AQ_DISABLE_QUEUES: return (&sc->disable_queues_chan); default: return (NULL); } } /** * iavf_vc_stat_str - convert virtchnl status err code to a string * @hw: pointer to the HW structure * @stat_err: the status error code to convert * * @returns the human readable string representing the specified error code. **/ const char * iavf_vc_stat_str(struct iavf_hw *hw, enum virtchnl_status_code stat_err) { switch (stat_err) { case VIRTCHNL_STATUS_SUCCESS: return "OK"; case VIRTCHNL_ERR_PARAM: return "VIRTCHNL_ERR_PARAM"; case VIRTCHNL_STATUS_ERR_NO_MEMORY: return "VIRTCHNL_STATUS_ERR_NO_MEMORY"; case VIRTCHNL_STATUS_ERR_OPCODE_MISMATCH: return "VIRTCHNL_STATUS_ERR_OPCODE_MISMATCH"; case VIRTCHNL_STATUS_ERR_CQP_COMPL_ERROR: return "VIRTCHNL_STATUS_ERR_CQP_COMPL_ERROR"; case VIRTCHNL_STATUS_ERR_INVALID_VF_ID: return "VIRTCHNL_STATUS_ERR_INVALID_VF_ID"; case VIRTCHNL_STATUS_ERR_ADMIN_QUEUE_ERROR: return "VIRTCHNL_STATUS_ERR_ADMIN_QUEUE_ERROR"; case VIRTCHNL_STATUS_NOT_SUPPORTED: return "VIRTCHNL_STATUS_NOT_SUPPORTED"; } snprintf(hw->err_str, sizeof(hw->err_str), "%d", stat_err); return hw->err_str; } /** * iavf_adv_speed_to_ext_speed - Convert numeric speed to iavf speed enum * @adv_link_speed: link speed in Mb/s * * Converts the link speed from the "advanced" link speed virtchnl op into the * closest approximation of the internal iavf link speed, rounded down. * * @returns the link speed as an iavf_ext_link_speed enum value */ enum iavf_ext_link_speed iavf_adv_speed_to_ext_speed(u32 adv_link_speed) { if (adv_link_speed >= 100000) return IAVF_EXT_LINK_SPEED_100GB; if (adv_link_speed >= 50000) return IAVF_EXT_LINK_SPEED_50GB; if (adv_link_speed >= 40000) return IAVF_EXT_LINK_SPEED_40GB; if (adv_link_speed >= 25000) return IAVF_EXT_LINK_SPEED_25GB; if (adv_link_speed >= 20000) return IAVF_EXT_LINK_SPEED_20GB; if (adv_link_speed >= 10000) return IAVF_EXT_LINK_SPEED_10GB; if (adv_link_speed >= 5000) return IAVF_EXT_LINK_SPEED_5GB; if (adv_link_speed >= 2500) return IAVF_EXT_LINK_SPEED_2500MB; if (adv_link_speed >= 1000) return IAVF_EXT_LINK_SPEED_1000MB; if (adv_link_speed >= 100) return IAVF_EXT_LINK_SPEED_100MB; if (adv_link_speed >= 10) return IAVF_EXT_LINK_SPEED_10MB; return IAVF_EXT_LINK_SPEED_UNKNOWN; } /** * iavf_ext_speed_to_ifmedia - Convert internal iavf speed to ifmedia value * @link_speed: the link speed * * @remark this is sort of a hack, because we don't actually know what media * type the VF is running on. In an ideal world we might just report the media * type as "virtual" and have another mechanism for reporting the link * speed. * * @returns a suitable ifmedia type for the given link speed. */ u32 iavf_ext_speed_to_ifmedia(enum iavf_ext_link_speed link_speed) { switch (link_speed) { case IAVF_EXT_LINK_SPEED_100GB: return IFM_100G_SR4; case IAVF_EXT_LINK_SPEED_50GB: return IFM_50G_SR2; case IAVF_EXT_LINK_SPEED_40GB: return IFM_40G_SR4; case IAVF_EXT_LINK_SPEED_25GB: return IFM_25G_SR; case IAVF_EXT_LINK_SPEED_20GB: return IFM_20G_KR2; case IAVF_EXT_LINK_SPEED_10GB: return IFM_10G_SR; case IAVF_EXT_LINK_SPEED_5GB: return IFM_5000_T; case IAVF_EXT_LINK_SPEED_2500MB: return IFM_2500_T; case IAVF_EXT_LINK_SPEED_1000MB: return IFM_1000_T; case IAVF_EXT_LINK_SPEED_100MB: return IFM_100_TX; case IAVF_EXT_LINK_SPEED_10MB: return IFM_10_T; case IAVF_EXT_LINK_SPEED_UNKNOWN: default: return IFM_UNKNOWN; } } /** * iavf_vc_speed_to_ext_speed - Convert virtchnl speed enum to native iavf * driver speed representation. * @link_speed: link speed enum value * * @returns the link speed in the native iavf format. */ enum iavf_ext_link_speed iavf_vc_speed_to_ext_speed(enum virtchnl_link_speed link_speed) { switch (link_speed) { case VIRTCHNL_LINK_SPEED_40GB: return IAVF_EXT_LINK_SPEED_40GB; case VIRTCHNL_LINK_SPEED_25GB: return IAVF_EXT_LINK_SPEED_25GB; case VIRTCHNL_LINK_SPEED_20GB: return IAVF_EXT_LINK_SPEED_20GB; case VIRTCHNL_LINK_SPEED_10GB: return IAVF_EXT_LINK_SPEED_10GB; case VIRTCHNL_LINK_SPEED_1GB: return IAVF_EXT_LINK_SPEED_1000MB; case VIRTCHNL_LINK_SPEED_100MB: return IAVF_EXT_LINK_SPEED_100MB; case VIRTCHNL_LINK_SPEED_UNKNOWN: default: return IAVF_EXT_LINK_SPEED_UNKNOWN; } } /** * iavf_vc_speed_to_string - Convert virtchnl speed to a string * @link_speed: the speed to convert * * @returns string representing the link speed as reported by the virtchnl * interface. */ const char * iavf_vc_speed_to_string(enum virtchnl_link_speed link_speed) { return iavf_ext_speed_to_str(iavf_vc_speed_to_ext_speed(link_speed)); } /** * iavf_ext_speed_to_str - Convert iavf speed enum to string representation * @link_speed: link speed enum value * * XXX: This is an iavf-modified copy of ice_aq_speed_to_str() * * @returns the string representation of the given link speed. */ const char * iavf_ext_speed_to_str(enum iavf_ext_link_speed link_speed) { switch (link_speed) { case IAVF_EXT_LINK_SPEED_100GB: return "100 Gbps"; case IAVF_EXT_LINK_SPEED_50GB: return "50 Gbps"; case IAVF_EXT_LINK_SPEED_40GB: return "40 Gbps"; case IAVF_EXT_LINK_SPEED_25GB: return "25 Gbps"; case IAVF_EXT_LINK_SPEED_20GB: return "20 Gbps"; case IAVF_EXT_LINK_SPEED_10GB: return "10 Gbps"; case IAVF_EXT_LINK_SPEED_5GB: return "5 Gbps"; case IAVF_EXT_LINK_SPEED_2500MB: return "2.5 Gbps"; case IAVF_EXT_LINK_SPEED_1000MB: return "1 Gbps"; case IAVF_EXT_LINK_SPEED_100MB: return "100 Mbps"; case IAVF_EXT_LINK_SPEED_10MB: return "10 Mbps"; case IAVF_EXT_LINK_SPEED_UNKNOWN: default: return "Unknown"; } } /** * iavf_vc_opcode_str - Convert virtchnl opcode to string * @op: the virtchnl op code * * @returns the string representation of the given virtchnl op code */ const char * iavf_vc_opcode_str(uint16_t op) { switch (op) { case VIRTCHNL_OP_VERSION: return ("VERSION"); case VIRTCHNL_OP_RESET_VF: return ("RESET_VF"); case VIRTCHNL_OP_GET_VF_RESOURCES: return ("GET_VF_RESOURCES"); case VIRTCHNL_OP_CONFIG_TX_QUEUE: return ("CONFIG_TX_QUEUE"); case VIRTCHNL_OP_CONFIG_RX_QUEUE: return ("CONFIG_RX_QUEUE"); case VIRTCHNL_OP_CONFIG_VSI_QUEUES: return ("CONFIG_VSI_QUEUES"); case VIRTCHNL_OP_CONFIG_IRQ_MAP: return ("CONFIG_IRQ_MAP"); case VIRTCHNL_OP_ENABLE_QUEUES: return ("ENABLE_QUEUES"); case VIRTCHNL_OP_DISABLE_QUEUES: return ("DISABLE_QUEUES"); case VIRTCHNL_OP_ADD_ETH_ADDR: return ("ADD_ETH_ADDR"); case VIRTCHNL_OP_DEL_ETH_ADDR: return ("DEL_ETH_ADDR"); case VIRTCHNL_OP_ADD_VLAN: return ("ADD_VLAN"); case VIRTCHNL_OP_DEL_VLAN: return ("DEL_VLAN"); case VIRTCHNL_OP_CONFIG_PROMISCUOUS_MODE: return ("CONFIG_PROMISCUOUS_MODE"); case VIRTCHNL_OP_GET_STATS: return ("GET_STATS"); case VIRTCHNL_OP_RSVD: return ("RSVD"); case VIRTCHNL_OP_EVENT: return ("EVENT"); case VIRTCHNL_OP_CONFIG_RSS_KEY: return ("CONFIG_RSS_KEY"); case VIRTCHNL_OP_CONFIG_RSS_LUT: return ("CONFIG_RSS_LUT"); case VIRTCHNL_OP_GET_RSS_HENA_CAPS: return ("GET_RSS_HENA_CAPS"); case VIRTCHNL_OP_SET_RSS_HENA: return ("SET_RSS_HENA"); default: return ("UNKNOWN"); } } /** * iavf_vc_completion - Handle PF reply messages * @sc: device softc * @v_opcode: virtchnl op code * @v_retval: virtchnl return value * @msg: the message to send * @msglen: length of the msg buffer * * Asynchronous completion function for admin queue messages. Rather than busy * wait, we fire off our requests and assume that no errors will be returned. * This function handles the reply messages. */ void iavf_vc_completion(struct iavf_sc *sc, enum virtchnl_ops v_opcode, enum virtchnl_status_code v_retval, u8 *msg, u16 msglen __unused) { device_t dev = sc->dev; if (v_opcode != VIRTCHNL_OP_GET_STATS) iavf_dbg_vc(sc, "%s: opcode %s\n", __func__, iavf_vc_opcode_str(v_opcode)); if (v_opcode == VIRTCHNL_OP_EVENT) { struct virtchnl_pf_event *vpe = (struct virtchnl_pf_event *)msg; switch (vpe->event) { case VIRTCHNL_EVENT_LINK_CHANGE: iavf_handle_link_event(sc, vpe); break; case VIRTCHNL_EVENT_RESET_IMPENDING: device_printf(dev, "PF initiated reset!\n"); iavf_set_state(&sc->state, IAVF_STATE_RESET_PENDING); break; default: iavf_dbg_vc(sc, "Unknown event %d from AQ\n", vpe->event); break; } return; } /* Catch-all error response */ if (v_retval) { bool print_error = true; switch (v_opcode) { case VIRTCHNL_OP_ADD_ETH_ADDR: device_printf(dev, "WARNING: Error adding VF mac filter!\n"); device_printf(dev, "WARNING: Device may not receive traffic!\n"); break; case VIRTCHNL_OP_ENABLE_QUEUES: sc->enable_queues_chan = 1; wakeup_one(&sc->enable_queues_chan); break; case VIRTCHNL_OP_DISABLE_QUEUES: sc->disable_queues_chan = 1; wakeup_one(&sc->disable_queues_chan); /* This may fail, but it does not necessarily mean that * something is critically wrong. */ if (!(sc->dbg_mask & IAVF_DBG_VC)) print_error = false; break; default: break; } if (print_error) device_printf(dev, "%s: AQ returned error %s to our request %s!\n", __func__, iavf_vc_stat_str(&sc->hw, v_retval), iavf_vc_opcode_str(v_opcode)); return; } switch (v_opcode) { case VIRTCHNL_OP_GET_STATS: iavf_update_stats_counters(sc, (struct iavf_eth_stats *)msg); break; case VIRTCHNL_OP_ADD_ETH_ADDR: break; case VIRTCHNL_OP_DEL_ETH_ADDR: break; case VIRTCHNL_OP_CONFIG_PROMISCUOUS_MODE: break; case VIRTCHNL_OP_ADD_VLAN: break; case VIRTCHNL_OP_DEL_VLAN: break; case VIRTCHNL_OP_ENABLE_QUEUES: atomic_store_rel_32(&sc->queues_enabled, 1); sc->enable_queues_chan = 1; wakeup_one(&sc->enable_queues_chan); break; case VIRTCHNL_OP_DISABLE_QUEUES: atomic_store_rel_32(&sc->queues_enabled, 0); sc->disable_queues_chan = 1; wakeup_one(&sc->disable_queues_chan); break; case VIRTCHNL_OP_CONFIG_VSI_QUEUES: break; case VIRTCHNL_OP_CONFIG_IRQ_MAP: break; case VIRTCHNL_OP_CONFIG_RSS_KEY: break; case VIRTCHNL_OP_SET_RSS_HENA: break; case VIRTCHNL_OP_CONFIG_RSS_LUT: break; default: iavf_dbg_vc(sc, "Received unexpected message %s from PF.\n", iavf_vc_opcode_str(v_opcode)); break; } } /** * iavf_handle_link_event - Handle Link event virtchml message * @sc: device softc * @vpe: virtchnl PF link event structure * * Process a virtchnl PF link event and update the driver and stack status of * the link event. */ static void iavf_handle_link_event(struct iavf_sc *sc, struct virtchnl_pf_event *vpe) { MPASS(vpe->event == VIRTCHNL_EVENT_LINK_CHANGE); if (sc->vf_res->vf_cap_flags & VIRTCHNL_VF_CAP_ADV_LINK_SPEED) { iavf_dbg_vc(sc, "Link change (adv): status %d, speed %u\n", vpe->event_data.link_event_adv.link_status, vpe->event_data.link_event_adv.link_speed); sc->link_up = vpe->event_data.link_event_adv.link_status; sc->link_speed_adv = vpe->event_data.link_event_adv.link_speed; } else { iavf_dbg_vc(sc, "Link change: status %d, speed %x\n", vpe->event_data.link_event.link_status, vpe->event_data.link_event.link_speed); sc->link_up = vpe->event_data.link_event.link_status; sc->link_speed = vpe->event_data.link_event.link_speed; } iavf_update_link_status(sc); }