// SPDX-License-Identifier: GPL-2.0-only
/* Huawei HiNIC PCI Express Linux driver
* Copyright(c) 2017 Huawei Technologies Co., Ltd
*/
#include <linux/pci.h>
#include <linux/if_vlan.h>
#include <linux/interrupt.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include "hinic_hw_dev.h"
#include "hinic_dev.h"
#include "hinic_hw_mbox.h"
#include "hinic_hw_cmdq.h"
#include "hinic_port.h"
#include "hinic_sriov.h"
static unsigned char set_vf_link_state;
module_param(set_vf_link_state, byte, 0444);
MODULE_PARM_DESC(set_vf_link_state, "Set vf link state, 0 represents link auto, 1 represents link always up, 2 represents link always down. - default is 0.");
#define HINIC_VLAN_PRIORITY_SHIFT 13
#define HINIC_ADD_VLAN_IN_MAC 0x8000
static int hinic_set_mac(struct hinic_hwdev *hwdev, const u8 *mac_addr,
u16 vlan_id, u16 func_id)
{
struct hinic_port_mac_cmd mac_info = {0};
u16 out_size = sizeof(mac_info);
int err;
mac_info.func_idx = func_id;
mac_info.vlan_id = vlan_id;
memcpy(mac_info.mac, mac_addr, ETH_ALEN);
err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_MAC, &mac_info,
sizeof(mac_info), &mac_info, &out_size);
if (err || out_size != sizeof(mac_info) ||
(mac_info.status && mac_info.status != HINIC_PF_SET_VF_ALREADY &&
mac_info.status != HINIC_MGMT_STATUS_EXIST)) {
dev_err(&hwdev->func_to_io.hwif->pdev->dev, "Failed to change MAC, ret = %d\n",
mac_info.status);
return -EFAULT;
}
return 0;
}
static void hinic_notify_vf_link_status(struct hinic_hwdev *hwdev, u16 vf_id,
u8 link_status)
{
struct vf_data_storage *vf_infos = hwdev->func_to_io.vf_infos;
struct hinic_port_link_status link = {0};
u16 out_size = sizeof(link);
int err;
if (vf_infos[HW_VF_ID_TO_OS(vf_id)].registered) {
link.link = link_status;
link.func_id = hinic_glb_pf_vf_offset(hwdev->hwif) + vf_id;
err = hinic_mbox_to_vf(hwdev, HINIC_MOD_L2NIC,
vf_id, HINIC_PORT_CMD_LINK_STATUS_REPORT,
&link, sizeof(link),
&link, &out_size, 0);
if (err || !out_size || link.status)
dev_err(&hwdev->hwif->pdev->dev,
"Send link change event to VF %d failed, err: %d, status: 0x%x, out_size: 0x%x\n",
HW_VF_ID_TO_OS(vf_id), err,
link.status, out_size);
}
}
/* send link change event mbox msg to active vfs under the pf */
void hinic_notify_all_vfs_link_changed(struct hinic_hwdev *hwdev,
u8 link_status)
{
struct hinic_func_to_io *nic_io = &hwdev->func_to_io;
u16 i;
nic_io->link_status = link_status;
for (i = 1; i <= nic_io->max_vfs; i++) {
if (!nic_io->vf_infos[HW_VF_ID_TO_OS(i)].link_forced)
hinic_notify_vf_link_status(hwdev, i, link_status);
}
}
static u16 hinic_vf_info_vlanprio(struct hinic_hwdev *hwdev, int vf_id)
{
struct hinic_func_to_io *nic_io = &hwdev->func_to_io;
u16 pf_vlan, vlanprio;
u8 pf_qos;
pf_vlan = nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_vlan;
pf_qos = nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_qos;
vlanprio = pf_vlan | pf_qos << HINIC_VLAN_PRIORITY_SHIFT;
return vlanprio;
}
static int hinic_set_vf_vlan(struct hinic_hwdev *hwdev, bool add, u16 vid,
u8 qos, int vf_id)
{
struct hinic_vf_vlan_config vf_vlan = {0};
u16 out_size = sizeof(vf_vlan);
int err;
u8 cmd;
/* VLAN 0 is a special case, don't allow it to be removed */
if (!vid && !add)
return 0;
vf_vlan.func_id = hinic_glb_pf_vf_offset(hwdev->hwif) + vf_id;
vf_vlan.vlan_id = vid;
vf_vlan.qos