// 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*/
