2026-01-29 22:25:33 +08:00

464 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Huawei HiNIC PCI Express Linux driver
* Copyright(c) 2017 Huawei Technologies Co., Ltd
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include "ossl_knl.h"
#include "hinic_hw.h"
#include "hinic_nic_cfg.h"
#include "hinic_nic_dev.h"
#include "hinic_sriov.h"
#include "hinic_lld.h"
int hinic_pci_sriov_disable(struct pci_dev *dev)
{
#ifdef CONFIG_PCI_IOV
struct hinic_sriov_info *sriov_info;
u16 tmp_vfs;
sriov_info = hinic_get_sriov_info_by_pcidev(dev);
/* if SR-IOV is already disabled then nothing will be done */
if (!sriov_info->sriov_enabled)
return 0;
if (test_and_set_bit(HINIC_SRIOV_DISABLE, &sriov_info->state)) {
nic_err(&sriov_info->pdev->dev,
"SR-IOV disable in process, please wait\n");
return -EPERM;
}
/* If our VFs are assigned we cannot shut down SR-IOV
* without causing issues, so just leave the hardware
* available but disabled
*/
if (pci_vfs_assigned(sriov_info->pdev)) {
clear_bit(HINIC_SRIOV_DISABLE, &sriov_info->state);
nic_warn(&sriov_info->pdev->dev, "Unloading driver while VFs are assigned - VFs will not be deallocated\n");
return -EPERM;
}
sriov_info->sriov_enabled = false;
/* disable iov and allow time for transactions to clear */
pci_disable_sriov(sriov_info->pdev);
tmp_vfs = (u16)sriov_info->num_vfs;
sriov_info->num_vfs = 0;
hinic_deinit_vf_hw(sriov_info->hwdev, OS_VF_ID_TO_HW(0),
OS_VF_ID_TO_HW(tmp_vfs - 1));
clear_bit(HINIC_SRIOV_DISABLE, &sriov_info->state);
#endif
return 0;
}
int hinic_pci_sriov_enable(struct pci_dev *dev, int num_vfs)
{
#ifdef CONFIG_PCI_IOV
struct hinic_sriov_info *sriov_info;
int err = 0;
int pre_existing_vfs = 0;
sriov_info = hinic_get_sriov_info_by_pcidev(dev);
if (test_and_set_bit(HINIC_SRIOV_ENABLE, &sriov_info->state)) {
nic_err(&sriov_info->pdev->dev,
"SR-IOV enable in process, please wait, num_vfs %d\n",
num_vfs);
return -EPERM;
}
pre_existing_vfs = pci_num_vf(sriov_info->pdev);
if (num_vfs > pci_sriov_get_totalvfs(sriov_info->pdev)) {
clear_bit(HINIC_SRIOV_ENABLE, &sriov_info->state);
return -ERANGE;
}
if (pre_existing_vfs && pre_existing_vfs != num_vfs) {
err = hinic_pci_sriov_disable(sriov_info->pdev);
if (err) {
clear_bit(HINIC_SRIOV_ENABLE, &sriov_info->state);
return err;
}
} else if (pre_existing_vfs == num_vfs) {
clear_bit(HINIC_SRIOV_ENABLE, &sriov_info->state);
return num_vfs;
}
err = hinic_init_vf_hw(sriov_info->hwdev, OS_VF_ID_TO_HW(0),
OS_VF_ID_TO_HW((u16)num_vfs - 1));
if (err) {
nic_err(&sriov_info->pdev->dev,
"Failed to init vf in hardware before enable sriov, error %d\n",
err);
clear_bit(HINIC_SRIOV_ENABLE, &sriov_info->state);
return err;
}
err = pci_enable_sriov(sriov_info->pdev, num_vfs);
if (err) {
nic_err(&sriov_info->pdev->dev,
"Failed to enable SR-IOV, error %d\n", err);
clear_bit(HINIC_SRIOV_ENABLE, &sriov_info->state);
return err;
}
sriov_info->sriov_enabled = true;
sriov_info->num_vfs = num_vfs;
clear_bit(HINIC_SRIOV_ENABLE, &sriov_info->state);
return num_vfs;
#else
return 0;
#endif
}
static bool hinic_is_support_sriov_configure(struct pci_dev *pdev)
{
enum hinic_init_state state = hinic_get_init_state(pdev);
struct hinic_sriov_info *sriov_info;
if (state < HINIC_INIT_STATE_NIC_INITED) {
nic_err(&pdev->dev, "NIC device not initialized, don't support to configure sriov\n");
return false;
}
sriov_info = hinic_get_sriov_info_by_pcidev(pdev);
if (FUNC_SRIOV_FIX_NUM_VF(sriov_info->hwdev)) {
nic_err(&pdev->dev, "Don't support to changed sriov configuration\n");
return false;
}
return true;
}
int hinic_pci_sriov_configure(struct pci_dev *dev, int num_vfs)
{
struct hinic_sriov_info *sriov_info;
if (!hinic_is_support_sriov_configure(dev))
return -EFAULT;
sriov_info = hinic_get_sriov_info_by_pcidev(dev);
if (test_bit(HINIC_FUNC_REMOVE, &sriov_info->state))
return -EFAULT;
if (!num_vfs)
return hinic_pci_sriov_disable(dev);
else
return hinic_pci_sriov_enable(dev, num_vfs);
}
int hinic_ndo_set_vf_mac(struct net_device *netdev, int vf, u8 *mac)
{
struct hinic_nic_dev *adapter = netdev_priv(netdev);
struct hinic_sriov_info *sriov_info;
int err;
if (!FUNC_SUPPORT_SET_VF_MAC_VLAN(adapter->hwdev)) {
nicif_err(adapter, drv, netdev,
"Current function don't support to set vf mac\n");
return -EOPNOTSUPP;
}
sriov_info = hinic_get_sriov_info_by_pcidev(adapter->pdev);
if (is_multicast_ether_addr(mac) || /*lint !e574*/
vf >= sriov_info->num_vfs) /*lint !e574*/
return -EINVAL;
err = hinic_set_vf_mac(sriov_info->hwdev, OS_VF_ID_TO_HW(vf), mac);
if (err) {
nicif_info(adapter, drv, netdev, "Failed to set MAC %pM on VF %d\n",
mac, vf);
return err;
}
if (is_zero_ether_addr(mac))
nicif_info(adapter, drv, netdev, "Removing MAC on VF %d\n", vf);
else
nicif_info(adapter, drv, netdev, "Setting MAC %pM on VF %d\n",
mac, vf);
nicif_info(adapter, drv, netdev, "Reload the VF driver to make this change effective\n");
return 0;
}
/*lint -save -e574 -e734*/
static int set_hw_vf_vlan(struct hinic_sriov_info *sriov_info,
u16 cur_vlanprio, int vf, u16 vlan, u8 qos)
{
int err = 0;
u16 old_vlan = cur_vlanprio & VLAN_VID_MASK;
if (vlan || qos) {
if (cur_vlanprio) {
err = hinic_kill_vf_vlan(sriov_info->hwdev,
OS_VF_ID_TO_HW(vf));
if (err) {
nic_err(&sriov_info->pdev->dev, "Failed to delete vf %d old vlan %d\n",
vf, old_vlan);
return err;
}
}
err = hinic_add_vf_vlan(sriov_info->hwdev,
OS_VF_ID_TO_HW(vf), vlan, qos);
if (err) {
nic_err(&sriov_info->pdev->dev, "Failed to add vf %d new vlan %d\n",
vf, vlan);
return err;
}
} else {
err = hinic_kill_vf_vlan(sriov_info->hwdev, OS_VF_ID_TO_HW(vf));
if (err) {
nic_err(&sriov_info->pdev->dev, "Failed to delete vf %d vlan %d\n",
vf, old_vlan);
return err;
}
}
return hinic_update_mac_vlan(sriov_info->hwdev, old_vlan, vlan,
OS_VF_ID_TO_HW(vf));
}
int hinic_ndo_set_vf_vlan(struct net_device *netdev, int vf, u16 vlan, u8 qos,
__be16 vlan_proto)
{
struct hinic_nic_dev *adapter = netdev_priv(netdev);
struct hinic_sriov_info *sriov_info;
u16 vlanprio, cur_vlanprio;
if (!FUNC_SUPPORT_SET_VF_MAC_VLAN(adapter->hwdev)) {
nicif_err(adapter, drv, netdev,
"Current function don't support to set vf vlan\n");
return -EOPNOTSUPP;
}
sriov_info = hinic_get_sriov_info_by_pcidev(adapter->pdev);
if (vf >= sriov_info->num_vfs || vlan > 4095 || qos > 7)
return -EINVAL;
if (vlan_proto != htons(ETH_P_8021Q))
return -EPROTONOSUPPORT;
vlanprio = vlan | qos << HINIC_VLAN_PRIORITY_SHIFT;
cur_vlanprio = hinic_vf_info_vlanprio(sriov_info->hwdev,
OS_VF_ID_TO_HW(vf));
/* duplicate request, so just return success */
if (vlanprio == cur_vlanprio)
return 0;
return set_hw_vf_vlan(sriov_info, cur_vlanprio, vf, vlan, qos);
}
int hinic_ndo_set_vf_spoofchk(struct net_device *netdev, int vf, bool setting)
{
struct hinic_nic_dev *adapter = netdev_priv(netdev);
struct hinic_sriov_info *sriov_info;
int err = 0;
bool cur_spoofchk;
sriov_info = hinic_get_sriov_info_by_pcidev(adapter->pdev);
if (vf >= sriov_info->num_vfs)
return -EINVAL;
cur_spoofchk = hinic_vf_info_spoofchk(sriov_info->hwdev,
OS_VF_ID_TO_HW(vf));
/* same request, so just return success */
if ((setting && cur_spoofchk) || (!setting && !cur_spoofchk))
return 0;
err = hinic_set_vf_spoofchk(sriov_info->hwdev,
OS_VF_ID_TO_HW(vf), setting);
if (!err) {
nicif_info(adapter, drv, netdev, "Set VF %d spoofchk %s\n",
vf, setting ? "on" : "off");
} else if (err == HINIC_MGMT_CMD_UNSUPPORTED) {
nicif_err(adapter, drv, netdev,
"Current firmware doesn't support to set vf spoofchk, need to upgrade latest firmware version\n");
err = -EOPNOTSUPP;
}
return err;
}
int hinic_ndo_set_vf_trust(struct net_device *netdev, int vf, bool setting)
{
struct hinic_nic_dev *adapter = netdev_priv(netdev);
struct hinic_sriov_info *sriov_info;
int err = 0;
bool cur_trust;
sriov_info = hinic_get_sriov_info_by_pcidev(adapter->pdev);
if (vf >= sriov_info->num_vfs)
return -EINVAL;
cur_trust = hinic_vf_info_trust(sriov_info->hwdev,
OS_VF_ID_TO_HW(vf));
/* same request, so just return success */
if ((setting && cur_trust) || (!setting && !cur_trust))
return 0;
err = hinic_set_vf_trust(sriov_info->hwdev,
OS_VF_ID_TO_HW(vf), setting);
if (!err)
nicif_info(adapter, drv, netdev, "Set VF %d trusted %s succeed\n",
vf, setting ? "on" : "off");
else
nicif_err(adapter, drv, netdev, "Failed set VF %d trusted %s\n",
vf, setting ? "on" : "off");
return err;
}
int hinic_ndo_get_vf_config(struct net_device *netdev,
int vf, struct ifla_vf_info *ivi)
{
struct hinic_nic_dev *adapter = netdev_priv(netdev);
struct hinic_sriov_info *sriov_info;
sriov_info = hinic_get_sriov_info_by_pcidev(adapter->pdev);
if (vf >= sriov_info->num_vfs)
return -EINVAL;
hinic_get_vf_config(sriov_info->hwdev, OS_VF_ID_TO_HW(vf), ivi);
return 0;
}
/**
* hinic_ndo_set_vf_link_state
* @netdev: network interface device structure
* @vf_id: VF identifier
* @link: required link state
* Return: 0 - success, negative - failure
* Set the link state of a specified VF, regardless of physical link state
*/
int hinic_ndo_set_vf_link_state(struct net_device *netdev, int vf_id, int link)
{
struct hinic_nic_dev *adapter = netdev_priv(netdev);
struct hinic_sriov_info *sriov_info;
static const char * const vf_link[] = {"auto", "enable", "disable"};
int err;
if (FUNC_FORCE_LINK_UP(adapter->hwdev)) {
nicif_err(adapter, drv, netdev,
"Current function don't support to set vf link state\n");
return -EOPNOTSUPP;
}
sriov_info = hinic_get_sriov_info_by_pcidev(adapter->pdev);
/* validate the request */
if (vf_id >= sriov_info->num_vfs) {
nicif_err(adapter, drv, netdev,
"Invalid VF Identifier %d\n", vf_id);
return -EINVAL;
}
err = hinic_set_vf_link_state(sriov_info->hwdev,
OS_VF_ID_TO_HW(vf_id), link);
if (!err)
nicif_info(adapter, drv, netdev, "Set VF %d link state: %s\n",
vf_id, vf_link[link]);
return err;
}
#define HINIC_TX_RATE_TABLE_FULL 12
int hinic_ndo_set_vf_bw(struct net_device *netdev,
int vf, int min_tx_rate, int max_tx_rate)
{
struct hinic_nic_dev *adapter = netdev_priv(netdev);
struct nic_port_info port_info = {0};
struct hinic_sriov_info *sriov_info;
u8 link_status = 0;
u32 speeds[] = {SPEED_10, SPEED_100, SPEED_1000, SPEED_10000,
SPEED_25000, SPEED_40000, SPEED_100000};
int err = 0;
if (!FUNC_SUPPORT_RATE_LIMIT(adapter->hwdev)) {
nicif_err(adapter, drv, netdev,
"Current function don't support to set vf rate limit\n");
return -EOPNOTSUPP;
}
sriov_info = hinic_get_sriov_info_by_pcidev(adapter->pdev);
/* verify VF is active */
if (vf >= sriov_info->num_vfs) {
nicif_err(adapter, drv, netdev, "VF number must be less than %d\n",
sriov_info->num_vfs);
return -EINVAL;
}
if (max_tx_rate < min_tx_rate) {
nicif_err(adapter, drv, netdev, "Invalid rate, max rate %d must greater than min rate %d\n",
max_tx_rate, min_tx_rate);
return -EINVAL;
}
err = hinic_get_link_state(adapter->hwdev, &link_status);
if (err) {
nicif_err(adapter, drv, netdev,
"Get link status failed when set vf tx rate\n");
return -EIO;
}
if (!link_status) {
nicif_err(adapter, drv, netdev,
"Link status must be up when set vf tx rate\n");
return -EINVAL;
}
err = hinic_get_port_info(adapter->hwdev, &port_info);
if (err || port_info.speed > LINK_SPEED_100GB)
return -EIO;
/* rate limit cannot be less than 0 and greater than link speed */
if (max_tx_rate < 0 || max_tx_rate > speeds[port_info.speed]) {
nicif_err(adapter, drv, netdev, "Set vf max tx rate must be in [0 - %d]\n",
speeds[port_info.speed]);
return -EINVAL;
}
err = hinic_set_vf_tx_rate(adapter->hwdev, OS_VF_ID_TO_HW(vf),
max_tx_rate, min_tx_rate);
if (err) {
nicif_err(adapter, drv, netdev,
"Unable to set VF %d max rate %d min rate %d%s\n",
vf, max_tx_rate, min_tx_rate,
err == HINIC_TX_RATE_TABLE_FULL ?
", tx rate profile is full" : "");
return -EIO;
}
nicif_info(adapter, drv, netdev,
"Set VF %d max tx rate %d min tx rate %d successfully\n",
vf, max_tx_rate, min_tx_rate);
return 0;
}
/*lint -restore*/