root/drivers/net/ethernet/huawei/hinic/hinic_port.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Huawei HiNIC PCI Express Linux driver
 * Copyright(c) 2017 Huawei Technologies Co., Ltd
 */

#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/errno.h>

#include "hinic_hw_if.h"
#include "hinic_hw_dev.h"
#include "hinic_port.h"
#include "hinic_dev.h"

enum mac_op {
        MAC_DEL,
        MAC_SET,
};

/**
 * change_mac - change(add or delete) mac address
 * @nic_dev: nic device
 * @addr: mac address
 * @vlan_id: vlan number to set with the mac
 * @op: add or delete the mac
 *
 * Return 0 - Success, negative - Failure
 **/
static int change_mac(struct hinic_dev *nic_dev, const u8 *addr,
                      u16 vlan_id, enum mac_op op)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_port_mac_cmd port_mac_cmd;
        struct hinic_hwif *hwif = hwdev->hwif;
        u16 out_size = sizeof(port_mac_cmd);
        struct pci_dev *pdev = hwif->pdev;
        enum hinic_port_cmd cmd;
        int err;

        if (op == MAC_SET)
                cmd = HINIC_PORT_CMD_SET_MAC;
        else
                cmd = HINIC_PORT_CMD_DEL_MAC;

        port_mac_cmd.func_idx = HINIC_HWIF_FUNC_IDX(hwif);
        port_mac_cmd.vlan_id = vlan_id;
        memcpy(port_mac_cmd.mac, addr, ETH_ALEN);

        err = hinic_port_msg_cmd(hwdev, cmd, &port_mac_cmd,
                                 sizeof(port_mac_cmd),
                                 &port_mac_cmd, &out_size);
        if (err || out_size != sizeof(port_mac_cmd) ||
            (port_mac_cmd.status &&
             (port_mac_cmd.status != HINIC_PF_SET_VF_ALREADY || !HINIC_IS_VF(hwif)) &&
             port_mac_cmd.status != HINIC_MGMT_STATUS_EXIST)) {
                dev_err(&pdev->dev, "Failed to change MAC, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, port_mac_cmd.status, out_size);
                return -EFAULT;
        }

        if (port_mac_cmd.status == HINIC_PF_SET_VF_ALREADY) {
                dev_warn(&pdev->dev, "PF has already set VF mac, ignore %s operation\n",
                         (op == MAC_SET) ? "set" : "del");
                return HINIC_PF_SET_VF_ALREADY;
        }

        if (cmd == HINIC_PORT_CMD_SET_MAC && port_mac_cmd.status ==
            HINIC_MGMT_STATUS_EXIST)
                dev_warn(&pdev->dev, "MAC is repeated, ignore set operation\n");

        return 0;
}

/**
 * hinic_port_add_mac - add mac address
 * @nic_dev: nic device
 * @addr: mac address
 * @vlan_id: vlan number to set with the mac
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_add_mac(struct hinic_dev *nic_dev,
                       const u8 *addr, u16 vlan_id)
{
        return change_mac(nic_dev, addr, vlan_id, MAC_SET);
}

/**
 * hinic_port_del_mac - remove mac address
 * @nic_dev: nic device
 * @addr: mac address
 * @vlan_id: vlan number that is connected to the mac
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_del_mac(struct hinic_dev *nic_dev, const u8 *addr,
                       u16 vlan_id)
{
        return change_mac(nic_dev, addr, vlan_id, MAC_DEL);
}

/**
 * hinic_port_get_mac - get the mac address of the nic device
 * @nic_dev: nic device
 * @addr: returned mac address
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_get_mac(struct hinic_dev *nic_dev, u8 *addr)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_port_mac_cmd port_mac_cmd;
        struct hinic_hwif *hwif = hwdev->hwif;
        u16 out_size = sizeof(port_mac_cmd);
        struct pci_dev *pdev = hwif->pdev;
        int err;

        port_mac_cmd.func_idx = HINIC_HWIF_FUNC_IDX(hwif);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_MAC,
                                 &port_mac_cmd, sizeof(port_mac_cmd),
                                 &port_mac_cmd, &out_size);
        if (err || out_size != sizeof(port_mac_cmd) || port_mac_cmd.status) {
                dev_err(&pdev->dev, "Failed to get mac, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, port_mac_cmd.status, out_size);
                return -EFAULT;
        }

        memcpy(addr, port_mac_cmd.mac, ETH_ALEN);
        return 0;
}

/**
 * hinic_port_set_mtu - set mtu
 * @nic_dev: nic device
 * @new_mtu: new mtu
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_set_mtu(struct hinic_dev *nic_dev, int new_mtu)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_port_mtu_cmd port_mtu_cmd;
        struct hinic_hwif *hwif = hwdev->hwif;
        u16 out_size = sizeof(port_mtu_cmd);
        struct pci_dev *pdev = hwif->pdev;
        int err;

        port_mtu_cmd.func_idx = HINIC_HWIF_FUNC_IDX(hwif);
        port_mtu_cmd.mtu = new_mtu;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_CHANGE_MTU,
                                 &port_mtu_cmd, sizeof(port_mtu_cmd),
                                 &port_mtu_cmd, &out_size);
        if (err || out_size != sizeof(port_mtu_cmd) || port_mtu_cmd.status) {
                dev_err(&pdev->dev, "Failed to set mtu, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, port_mtu_cmd.status, out_size);
                return -EFAULT;
        }

        return 0;
}

/**
 * hinic_port_add_vlan - add vlan to the nic device
 * @nic_dev: nic device
 * @vlan_id: the vlan number to add
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_add_vlan(struct hinic_dev *nic_dev, u16 vlan_id)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_port_vlan_cmd port_vlan_cmd;

        port_vlan_cmd.func_idx = HINIC_HWIF_FUNC_IDX(hwdev->hwif);
        port_vlan_cmd.vlan_id = vlan_id;

        return hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_ADD_VLAN,
                                  &port_vlan_cmd, sizeof(port_vlan_cmd),
                                  NULL, NULL);
}

/**
 * hinic_port_del_vlan - delete vlan from the nic device
 * @nic_dev: nic device
 * @vlan_id: the vlan number to delete
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_del_vlan(struct hinic_dev *nic_dev, u16 vlan_id)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_port_vlan_cmd port_vlan_cmd;

        port_vlan_cmd.func_idx = HINIC_HWIF_FUNC_IDX(hwdev->hwif);
        port_vlan_cmd.vlan_id = vlan_id;

        return hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_DEL_VLAN,
                                 &port_vlan_cmd, sizeof(port_vlan_cmd),
                                 NULL, NULL);
}

/**
 * hinic_port_set_rx_mode - set rx mode in the nic device
 * @nic_dev: nic device
 * @rx_mode: the rx mode to set
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_set_rx_mode(struct hinic_dev *nic_dev, u32 rx_mode)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_port_rx_mode_cmd rx_mode_cmd;

        rx_mode_cmd.func_idx = HINIC_HWIF_FUNC_IDX(hwdev->hwif);
        rx_mode_cmd.rx_mode = rx_mode;

        return hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_RX_MODE,
                                  &rx_mode_cmd, sizeof(rx_mode_cmd),
                                  NULL, NULL);
}

/**
 * hinic_port_link_state - get the link state
 * @nic_dev: nic device
 * @link_state: the returned link state
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_link_state(struct hinic_dev *nic_dev,
                          enum hinic_port_link_state *link_state)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct hinic_port_link_cmd link_cmd;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(link_cmd);
        int err;

        link_cmd.func_idx = HINIC_HWIF_FUNC_IDX(hwif);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_LINK_STATE,
                                 &link_cmd, sizeof(link_cmd),
                                 &link_cmd, &out_size);
        if (err || out_size != sizeof(link_cmd) || link_cmd.status) {
                dev_err(&pdev->dev, "Failed to get link state, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, link_cmd.status, out_size);
                return -EINVAL;
        }

        *link_state = link_cmd.state;
        return 0;
}

/**
 * hinic_port_set_state - set port state
 * @nic_dev: nic device
 * @state: the state to set
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_set_state(struct hinic_dev *nic_dev, enum hinic_port_state state)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_port_state_cmd port_state;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(port_state);
        int err;

        if (HINIC_IS_VF(hwdev->hwif))
                return 0;

        port_state.state = state;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_PORT_STATE,
                                 &port_state, sizeof(port_state),
                                 &port_state, &out_size);
        if (err || out_size != sizeof(port_state) || port_state.status) {
                dev_err(&pdev->dev, "Failed to set port state, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, port_state.status, out_size);
                return -EFAULT;
        }

        return 0;
}

/**
 * hinic_port_set_func_state- set func device state
 * @nic_dev: nic device
 * @state: the state to set
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_set_func_state(struct hinic_dev *nic_dev,
                              enum hinic_func_port_state state)
{
        struct hinic_port_func_state_cmd func_state;
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(func_state);
        int err;

        func_state.func_idx = HINIC_HWIF_FUNC_IDX(hwif);
        func_state.state = state;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_FUNC_STATE,
                                 &func_state, sizeof(func_state),
                                 &func_state, &out_size);
        if (err || out_size != sizeof(func_state) || func_state.status) {
                dev_err(&pdev->dev, "Failed to set port func state, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, func_state.status, out_size);
                return -EFAULT;
        }

        return 0;
}

/**
 * hinic_port_get_cap - get port capabilities
 * @nic_dev: nic device
 * @port_cap: returned port capabilities
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_get_cap(struct hinic_dev *nic_dev,
                       struct hinic_port_cap *port_cap)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(*port_cap);
        int err;

        port_cap->func_idx = HINIC_HWIF_FUNC_IDX(hwif);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_CAP,
                                 port_cap, sizeof(*port_cap),
                                 port_cap, &out_size);
        if (err || out_size != sizeof(*port_cap) || port_cap->status) {
                dev_err(&pdev->dev,
                        "Failed to get port capabilities, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, port_cap->status, out_size);
                return -EIO;
        }

        return 0;
}

/**
 * hinic_port_set_tso - set port tso configuration
 * @nic_dev: nic device
 * @state: the tso state to set
 *
 * Return 0 - Success, negative - Failure
 **/
int hinic_port_set_tso(struct hinic_dev *nic_dev, enum hinic_tso_state state)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct hinic_tso_config tso_cfg = {0};
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(tso_cfg);
        int err;

        tso_cfg.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        tso_cfg.tso_en = state;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_TSO,
                                 &tso_cfg, sizeof(tso_cfg),
                                 &tso_cfg, &out_size);
        if (err || out_size != sizeof(tso_cfg) || tso_cfg.status) {
                dev_err(&pdev->dev,
                        "Failed to set port tso, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, tso_cfg.status, out_size);
                return -EIO;
        }

        return 0;
}

int hinic_set_rx_csum_offload(struct hinic_dev *nic_dev, u32 en)
{
        struct hinic_checksum_offload rx_csum_cfg = {0};
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        u16 out_size = sizeof(rx_csum_cfg);
        struct hinic_hwif *hwif;
        struct pci_dev *pdev;
        int err;

        if (!hwdev)
                return -EINVAL;

        hwif = hwdev->hwif;
        pdev = hwif->pdev;
        rx_csum_cfg.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        rx_csum_cfg.rx_csum_offload = en;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_RX_CSUM,
                                 &rx_csum_cfg, sizeof(rx_csum_cfg),
                                 &rx_csum_cfg, &out_size);
        if (err || !out_size || rx_csum_cfg.status) {
                dev_err(&pdev->dev,
                        "Failed to set rx csum offload, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, rx_csum_cfg.status, out_size);
                return -EIO;
        }

        return 0;
}

int hinic_set_rx_vlan_offload(struct hinic_dev *nic_dev, u8 en)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_vlan_cfg vlan_cfg;
        struct hinic_hwif *hwif;
        struct pci_dev *pdev;
        u16 out_size;
        int err;

        if (!hwdev)
                return -EINVAL;

        out_size = sizeof(vlan_cfg);
        hwif = hwdev->hwif;
        pdev = hwif->pdev;
        vlan_cfg.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        vlan_cfg.vlan_rx_offload = en;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_RX_VLAN_OFFLOAD,
                                 &vlan_cfg, sizeof(vlan_cfg),
                                 &vlan_cfg, &out_size);
        if (err || !out_size || vlan_cfg.status) {
                dev_err(&pdev->dev,
                        "Failed to set rx vlan offload, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, vlan_cfg.status, out_size);
                return -EINVAL;
        }

        return 0;
}

int hinic_set_vlan_fliter(struct hinic_dev *nic_dev, u32 en)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        struct hinic_vlan_filter vlan_filter;
        u16 out_size = sizeof(vlan_filter);
        int err;

        vlan_filter.func_idx = HINIC_HWIF_FUNC_IDX(hwif);
        vlan_filter.enable = en;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_VLAN_FILTER,
                                 &vlan_filter, sizeof(vlan_filter),
                                 &vlan_filter, &out_size);
        if (vlan_filter.status == HINIC_MGMT_CMD_UNSUPPORTED) {
                err = HINIC_MGMT_CMD_UNSUPPORTED;
        } else if ((err == HINIC_MBOX_VF_CMD_ERROR) &&
                           HINIC_IS_VF(hwif)) {
                err = HINIC_MGMT_CMD_UNSUPPORTED;
        } else if (err || !out_size || vlan_filter.status) {
                dev_err(&pdev->dev,
                        "Failed to set vlan filter, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, vlan_filter.status, out_size);
                err = -EINVAL;
        }

        return err;
}

int hinic_set_max_qnum(struct hinic_dev *nic_dev, u8 num_rqs)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct hinic_rq_num rq_num = { 0 };
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(rq_num);
        int err;

        rq_num.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        rq_num.num_rqs = num_rqs;
        rq_num.rq_depth = ilog2(nic_dev->rq_depth);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_RQ_IQ_MAP,
                                 &rq_num, sizeof(rq_num),
                                 &rq_num, &out_size);
        if (err || !out_size || rq_num.status) {
                dev_err(&pdev->dev,
                        "Failed to set rxq number, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, rq_num.status, out_size);
                return -EIO;
        }

        return 0;
}

static int hinic_set_rx_lro(struct hinic_dev *nic_dev, u8 ipv4_en, u8 ipv6_en,
                            u8 max_wqe_num)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_lro_config lro_cfg = { 0 };
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(lro_cfg);
        int err;

        lro_cfg.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        lro_cfg.lro_ipv4_en = ipv4_en;
        lro_cfg.lro_ipv6_en = ipv6_en;
        lro_cfg.lro_max_wqe_num = max_wqe_num;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_LRO,
                                 &lro_cfg, sizeof(lro_cfg),
                                 &lro_cfg, &out_size);
        if (err || !out_size || lro_cfg.status) {
                dev_err(&pdev->dev,
                        "Failed to set lro offload, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, lro_cfg.status, out_size);
                return -EIO;
        }

        return 0;
}

static int hinic_set_rx_lro_timer(struct hinic_dev *nic_dev, u32 timer_value)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_lro_timer lro_timer = { 0 };
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(lro_timer);
        int err;

        lro_timer.status = 0;
        lro_timer.type = 0;
        lro_timer.enable = 1;
        lro_timer.timer = timer_value;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_LRO_TIMER,
                                 &lro_timer, sizeof(lro_timer),
                                 &lro_timer, &out_size);
        if (lro_timer.status == 0xFF) {
                /* For this case, we think status (0xFF) is OK */
                lro_timer.status = 0;
                dev_dbg(&pdev->dev,
                        "Set lro timer not supported by the current FW version, it will be 1ms default\n");
        }

        if (err || !out_size || lro_timer.status) {
                dev_err(&pdev->dev,
                        "Failed to set lro timer, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, lro_timer.status, out_size);

                return -EIO;
        }

        return 0;
}

int hinic_set_rx_lro_state(struct hinic_dev *nic_dev, u8 lro_en,
                           u32 lro_timer, u32 wqe_num)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        u8 ipv4_en;
        u8 ipv6_en;
        int err;

        if (!hwdev)
                return -EINVAL;

        ipv4_en = lro_en ? 1 : 0;
        ipv6_en = lro_en ? 1 : 0;

        err = hinic_set_rx_lro(nic_dev, ipv4_en, ipv6_en, (u8)wqe_num);
        if (err)
                return err;

        if (HINIC_IS_VF(nic_dev->hwdev->hwif))
                return 0;

        err = hinic_set_rx_lro_timer(nic_dev, lro_timer);
        if (err)
                return err;

        return 0;
}

int hinic_rss_set_indir_tbl(struct hinic_dev *nic_dev, u32 tmpl_idx,
                            const u32 *indir_table)
{
        struct hinic_rss_indirect_tbl *indir_tbl;
        struct hinic_func_to_io *func_to_io;
        struct hinic_cmdq_buf cmd_buf;
        struct hinic_hwdev *hwdev;
        struct hinic_hwif *hwif;
        struct pci_dev *pdev;
        u32 indir_size;
        u64 out_param;
        int err, i;
        u32 *temp;

        hwdev = nic_dev->hwdev;
        func_to_io = &hwdev->func_to_io;
        hwif = hwdev->hwif;
        pdev = hwif->pdev;

        err = hinic_alloc_cmdq_buf(&func_to_io->cmdqs, &cmd_buf);
        if (err) {
                dev_err(&pdev->dev, "Failed to allocate cmdq buf\n");
                return err;
        }

        cmd_buf.size = sizeof(*indir_tbl);

        indir_tbl = cmd_buf.buf;
        indir_tbl->group_index = cpu_to_be32(tmpl_idx);

        for (i = 0; i < HINIC_RSS_INDIR_SIZE; i++) {
                indir_tbl->entry[i] = indir_table[i];

                if (0x3 == (i & 0x3)) {
                        temp = (u32 *)&indir_tbl->entry[i - 3];
                        *temp = cpu_to_be32(*temp);
                }
        }

        /* cfg the rss indirect table by command queue */
        indir_size = HINIC_RSS_INDIR_SIZE / 2;
        indir_tbl->offset = 0;
        indir_tbl->size = cpu_to_be32(indir_size);

        err = hinic_cmdq_direct_resp(&func_to_io->cmdqs, HINIC_MOD_L2NIC,
                                     HINIC_UCODE_CMD_SET_RSS_INDIR_TABLE,
                                     &cmd_buf, &out_param);
        if (err || out_param != 0) {
                dev_err(&pdev->dev, "Failed to set rss indir table\n");
                err = -EFAULT;
                goto free_buf;
        }

        indir_tbl->offset = cpu_to_be32(indir_size);
        indir_tbl->size = cpu_to_be32(indir_size);
        memcpy(&indir_tbl->entry[0], &indir_tbl->entry[indir_size], indir_size);

        err = hinic_cmdq_direct_resp(&func_to_io->cmdqs, HINIC_MOD_L2NIC,
                                     HINIC_UCODE_CMD_SET_RSS_INDIR_TABLE,
                                     &cmd_buf, &out_param);
        if (err || out_param != 0) {
                dev_err(&pdev->dev, "Failed to set rss indir table\n");
                err = -EFAULT;
        }

free_buf:
        hinic_free_cmdq_buf(&func_to_io->cmdqs, &cmd_buf);

        return err;
}

int hinic_rss_get_indir_tbl(struct hinic_dev *nic_dev, u32 tmpl_idx,
                            u32 *indir_table)
{
        struct hinic_rss_indir_table rss_cfg = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(rss_cfg);
        int err = 0, i;

        rss_cfg.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        rss_cfg.template_id = tmpl_idx;

        err = hinic_port_msg_cmd(hwdev,
                                 HINIC_PORT_CMD_GET_RSS_TEMPLATE_INDIR_TBL,
                                 &rss_cfg, sizeof(rss_cfg), &rss_cfg,
                                 &out_size);
        if (err || !out_size || rss_cfg.status) {
                dev_err(&pdev->dev, "Failed to get indir table, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, rss_cfg.status, out_size);
                return -EINVAL;
        }

        hinic_be32_to_cpu(rss_cfg.indir, HINIC_RSS_INDIR_SIZE);
        for (i = 0; i < HINIC_RSS_INDIR_SIZE; i++)
                indir_table[i] = rss_cfg.indir[i];

        return 0;
}

int hinic_set_rss_type(struct hinic_dev *nic_dev, u32 tmpl_idx,
                       struct hinic_rss_type rss_type)
{
        struct hinic_rss_context_tbl *ctx_tbl;
        struct hinic_func_to_io *func_to_io;
        struct hinic_cmdq_buf cmd_buf;
        struct hinic_hwdev *hwdev;
        struct hinic_hwif *hwif;
        struct pci_dev *pdev;
        u64 out_param;
        u32 ctx = 0;
        int err;

        hwdev = nic_dev->hwdev;
        func_to_io = &hwdev->func_to_io;
        hwif = hwdev->hwif;
        pdev = hwif->pdev;

        err = hinic_alloc_cmdq_buf(&func_to_io->cmdqs, &cmd_buf);
        if (err) {
                dev_err(&pdev->dev, "Failed to allocate cmd buf\n");
                return -ENOMEM;
        }

        ctx |=  HINIC_RSS_TYPE_SET(1, VALID) |
                HINIC_RSS_TYPE_SET(rss_type.ipv4, IPV4) |
                HINIC_RSS_TYPE_SET(rss_type.ipv6, IPV6) |
                HINIC_RSS_TYPE_SET(rss_type.ipv6_ext, IPV6_EXT) |
                HINIC_RSS_TYPE_SET(rss_type.tcp_ipv4, TCP_IPV4) |
                HINIC_RSS_TYPE_SET(rss_type.tcp_ipv6, TCP_IPV6) |
                HINIC_RSS_TYPE_SET(rss_type.tcp_ipv6_ext, TCP_IPV6_EXT) |
                HINIC_RSS_TYPE_SET(rss_type.udp_ipv4, UDP_IPV4) |
                HINIC_RSS_TYPE_SET(rss_type.udp_ipv6, UDP_IPV6);

        cmd_buf.size = sizeof(struct hinic_rss_context_tbl);

        ctx_tbl = (struct hinic_rss_context_tbl *)cmd_buf.buf;
        ctx_tbl->group_index = cpu_to_be32(tmpl_idx);
        ctx_tbl->offset = 0;
        ctx_tbl->size = sizeof(u32);
        ctx_tbl->size = cpu_to_be32(ctx_tbl->size);
        ctx_tbl->rsvd = 0;
        ctx_tbl->ctx = cpu_to_be32(ctx);

        /* cfg the rss context table by command queue */
        err = hinic_cmdq_direct_resp(&func_to_io->cmdqs, HINIC_MOD_L2NIC,
                                     HINIC_UCODE_CMD_SET_RSS_CONTEXT_TABLE,
                                     &cmd_buf, &out_param);

        hinic_free_cmdq_buf(&func_to_io->cmdqs, &cmd_buf);

        if (err || out_param != 0) {
                dev_err(&pdev->dev, "Failed to set rss context table, err: %d\n",
                        err);
                return -EFAULT;
        }

        return 0;
}

int hinic_get_rss_type(struct hinic_dev *nic_dev, u32 tmpl_idx,
                       struct hinic_rss_type *rss_type)
{
        struct hinic_rss_context_table ctx_tbl = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        u16 out_size = sizeof(ctx_tbl);
        struct hinic_hwif *hwif;
        struct pci_dev *pdev;
        int err;

        if (!hwdev || !rss_type)
                return -EINVAL;

        hwif = hwdev->hwif;
        pdev = hwif->pdev;

        ctx_tbl.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        ctx_tbl.template_id = tmpl_idx;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_RSS_CTX_TBL,
                                 &ctx_tbl, sizeof(ctx_tbl),
                                 &ctx_tbl, &out_size);
        if (err || !out_size || ctx_tbl.status) {
                dev_err(&pdev->dev, "Failed to get hash type, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, ctx_tbl.status, out_size);
                return -EINVAL;
        }

        rss_type->ipv4          = HINIC_RSS_TYPE_GET(ctx_tbl.context, IPV4);
        rss_type->ipv6          = HINIC_RSS_TYPE_GET(ctx_tbl.context, IPV6);
        rss_type->ipv6_ext      = HINIC_RSS_TYPE_GET(ctx_tbl.context, IPV6_EXT);
        rss_type->tcp_ipv4      = HINIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV4);
        rss_type->tcp_ipv6      = HINIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV6);
        rss_type->tcp_ipv6_ext  = HINIC_RSS_TYPE_GET(ctx_tbl.context,
                                                     TCP_IPV6_EXT);
        rss_type->udp_ipv4      = HINIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV4);
        rss_type->udp_ipv6      = HINIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV6);

        return 0;
}

int hinic_rss_set_template_tbl(struct hinic_dev *nic_dev, u32 template_id,
                               const u8 *temp)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct hinic_rss_key rss_key = { 0 };
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(rss_key);
        int err;

        rss_key.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        rss_key.template_id = template_id;
        memcpy(rss_key.key, temp, HINIC_RSS_KEY_SIZE);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_RSS_TEMPLATE_TBL,
                                 &rss_key, sizeof(rss_key),
                                 &rss_key, &out_size);
        if (err || !out_size || rss_key.status) {
                dev_err(&pdev->dev,
                        "Failed to set rss hash key, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, rss_key.status, out_size);
                return -EINVAL;
        }

        return 0;
}

int hinic_rss_get_template_tbl(struct hinic_dev *nic_dev, u32 tmpl_idx,
                               u8 *temp)
{
        struct hinic_rss_template_key temp_key = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        u16 out_size = sizeof(temp_key);
        struct hinic_hwif *hwif;
        struct pci_dev *pdev;
        int err;

        if (!hwdev || !temp)
                return -EINVAL;

        hwif = hwdev->hwif;
        pdev = hwif->pdev;

        temp_key.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        temp_key.template_id = tmpl_idx;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_RSS_TEMPLATE_TBL,
                                 &temp_key, sizeof(temp_key),
                                 &temp_key, &out_size);
        if (err || !out_size || temp_key.status) {
                dev_err(&pdev->dev, "Failed to set hash key, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, temp_key.status, out_size);
                return -EINVAL;
        }

        memcpy(temp, temp_key.key, HINIC_RSS_KEY_SIZE);

        return 0;
}

int hinic_rss_set_hash_engine(struct hinic_dev *nic_dev, u8 template_id,
                              u8 type)
{
        struct hinic_rss_engine_type rss_engine = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(rss_engine);
        int err;

        rss_engine.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        rss_engine.hash_engine = type;
        rss_engine.template_id = template_id;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_RSS_HASH_ENGINE,
                                 &rss_engine, sizeof(rss_engine),
                                 &rss_engine, &out_size);
        if (err || !out_size || rss_engine.status) {
                dev_err(&pdev->dev,
                        "Failed to set hash engine, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, rss_engine.status, out_size);
                return -EINVAL;
        }

        return 0;
}

int hinic_rss_get_hash_engine(struct hinic_dev *nic_dev, u8 tmpl_idx, u8 *type)
{
        struct hinic_rss_engine_type hash_type = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        u16 out_size = sizeof(hash_type);
        struct hinic_hwif *hwif;
        struct pci_dev *pdev;
        int err;

        if (!hwdev || !type)
                return -EINVAL;

        hwif = hwdev->hwif;
        pdev = hwif->pdev;

        hash_type.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        hash_type.template_id = tmpl_idx;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_RSS_HASH_ENGINE,
                                 &hash_type, sizeof(hash_type),
                                 &hash_type, &out_size);
        if (err || !out_size || hash_type.status) {
                dev_err(&pdev->dev, "Failed to get hash engine, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, hash_type.status, out_size);
                return -EINVAL;
        }

        *type = hash_type.hash_engine;
        return 0;
}

int hinic_rss_cfg(struct hinic_dev *nic_dev, u8 rss_en, u8 template_id)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_rss_config rss_cfg = { 0 };
        struct hinic_hwif *hwif = hwdev->hwif;
        struct pci_dev *pdev = hwif->pdev;
        u16 out_size = sizeof(rss_cfg);
        int err;

        rss_cfg.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        rss_cfg.rss_en = rss_en;
        rss_cfg.template_id = template_id;
        rss_cfg.rq_priority_number = 0;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_RSS_CFG,
                                 &rss_cfg, sizeof(rss_cfg),
                                 &rss_cfg, &out_size);
        if (err || !out_size || rss_cfg.status) {
                dev_err(&pdev->dev,
                        "Failed to set rss cfg, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, rss_cfg.status, out_size);
                return -EINVAL;
        }

        return 0;
}

int hinic_rss_template_alloc(struct hinic_dev *nic_dev, u8 *tmpl_idx)
{
        struct hinic_rss_template_mgmt template_mgmt = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        u16 out_size = sizeof(template_mgmt);
        struct pci_dev *pdev = hwif->pdev;
        int err;

        template_mgmt.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        template_mgmt.cmd = NIC_RSS_CMD_TEMP_ALLOC;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_RSS_TEMP_MGR,
                                 &template_mgmt, sizeof(template_mgmt),
                                 &template_mgmt, &out_size);
        if (err || !out_size || template_mgmt.status) {
                dev_err(&pdev->dev, "Failed to alloc rss template, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, template_mgmt.status, out_size);
                return -EINVAL;
        }

        *tmpl_idx = template_mgmt.template_id;

        return 0;
}

int hinic_rss_template_free(struct hinic_dev *nic_dev, u8 tmpl_idx)
{
        struct hinic_rss_template_mgmt template_mgmt = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        u16 out_size = sizeof(template_mgmt);
        struct pci_dev *pdev = hwif->pdev;
        int err;

        template_mgmt.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        template_mgmt.template_id = tmpl_idx;
        template_mgmt.cmd = NIC_RSS_CMD_TEMP_FREE;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_RSS_TEMP_MGR,
                                 &template_mgmt, sizeof(template_mgmt),
                                 &template_mgmt, &out_size);
        if (err || !out_size || template_mgmt.status) {
                dev_err(&pdev->dev, "Failed to free rss template, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, template_mgmt.status, out_size);
                return -EINVAL;
        }

        return 0;
}

int hinic_get_vport_stats(struct hinic_dev *nic_dev,
                          struct hinic_vport_stats *stats)
{
        struct hinic_cmd_vport_stats vport_stats = { 0 };
        struct hinic_port_stats_info stats_info = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        u16 out_size = sizeof(vport_stats);
        struct pci_dev *pdev = hwif->pdev;
        int err;

        stats_info.stats_version = HINIC_PORT_STATS_VERSION;
        stats_info.func_id = HINIC_HWIF_FUNC_IDX(hwif);
        stats_info.stats_size = sizeof(vport_stats);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_VPORT_STAT,
                                 &stats_info, sizeof(stats_info),
                                 &vport_stats, &out_size);
        if (err || !out_size || vport_stats.status) {
                dev_err(&pdev->dev,
                        "Failed to get function statistics, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, vport_stats.status, out_size);
                return -EFAULT;
        }

        memcpy(stats, &vport_stats.stats, sizeof(*stats));
        return 0;
}

int hinic_get_phy_port_stats(struct hinic_dev *nic_dev,
                             struct hinic_phy_port_stats *stats)
{
        struct hinic_port_stats_info stats_info = { 0 };
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_hwif *hwif = hwdev->hwif;
        struct hinic_port_stats *port_stats;
        u16 out_size = sizeof(*port_stats);
        struct pci_dev *pdev = hwif->pdev;
        int err;

        port_stats = kzalloc_obj(*port_stats);
        if (!port_stats)
                return -ENOMEM;

        stats_info.stats_version = HINIC_PORT_STATS_VERSION;
        stats_info.stats_size = sizeof(*port_stats);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_PORT_STATISTICS,
                                 &stats_info, sizeof(stats_info),
                                 port_stats, &out_size);
        if (err || !out_size || port_stats->status) {
                dev_err(&pdev->dev,
                        "Failed to get port statistics, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, port_stats->status, out_size);
                err = -EINVAL;
                goto out;
        }

        memcpy(stats, &port_stats->stats, sizeof(*stats));

out:
        kfree(port_stats);

        return err;
}

int hinic_get_mgmt_version(struct hinic_dev *nic_dev, u8 *mgmt_ver)
{
        struct hinic_hwdev *hwdev = nic_dev->hwdev;
        struct hinic_version_info up_ver = {0};
        u16 out_size = sizeof(up_ver);
        struct hinic_hwif *hwif;
        struct pci_dev *pdev;
        int err;

        if (!hwdev)
                return -EINVAL;

        hwif = hwdev->hwif;
        pdev = hwif->pdev;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_MGMT_VERSION,
                                 &up_ver, sizeof(up_ver), &up_ver,
                                 &out_size);
        if (err || !out_size || up_ver.status) {
                dev_err(&pdev->dev,
                        "Failed to get mgmt version, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, up_ver.status, out_size);
                return -EINVAL;
        }

        snprintf(mgmt_ver, HINIC_MGMT_VERSION_MAX_LEN, "%s", up_ver.ver);

        return 0;
}

int hinic_get_link_mode(struct hinic_hwdev *hwdev,
                        struct hinic_link_mode_cmd *link_mode)
{
        u16 out_size;
        int err;

        if (!hwdev || !link_mode)
                return -EINVAL;

        link_mode->func_id = HINIC_HWIF_FUNC_IDX(hwdev->hwif);
        out_size = sizeof(*link_mode);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_LINK_MODE,
                                 link_mode, sizeof(*link_mode),
                                 link_mode, &out_size);
        if (err || !out_size || link_mode->status) {
                dev_err(&hwdev->hwif->pdev->dev,
                        "Failed to get link mode, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, link_mode->status, out_size);
                return -EIO;
        }

        return 0;
}

int hinic_set_autoneg(struct hinic_hwdev *hwdev, bool enable)
{
        struct hinic_set_autoneg_cmd autoneg = {0};
        u16 out_size = sizeof(autoneg);
        int err;

        if (!hwdev)
                return -EINVAL;

        autoneg.func_id = HINIC_HWIF_FUNC_IDX(hwdev->hwif);
        autoneg.enable = enable;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_AUTONEG,
                                 &autoneg, sizeof(autoneg),
                                 &autoneg, &out_size);
        if (err || !out_size || autoneg.status) {
                dev_err(&hwdev->hwif->pdev->dev, "Failed to %s autoneg, err: %d, status: 0x%x, out size: 0x%x\n",
                        enable ? "enable" : "disable", err, autoneg.status,
                        out_size);
                return -EIO;
        }

        return 0;
}

int hinic_set_speed(struct hinic_hwdev *hwdev, enum nic_speed_level speed)
{
        struct hinic_speed_cmd speed_info = {0};
        u16 out_size = sizeof(speed_info);
        int err;

        if (!hwdev)
                return -EINVAL;

        speed_info.func_id = HINIC_HWIF_FUNC_IDX(hwdev->hwif);
        speed_info.speed = speed;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_SPEED,
                                 &speed_info, sizeof(speed_info),
                                 &speed_info, &out_size);
        if (err || !out_size || speed_info.status) {
                dev_err(&hwdev->hwif->pdev->dev,
                        "Failed to set speed, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, speed_info.status, out_size);
                return -EIO;
        }

        return 0;
}

int hinic_set_link_settings(struct hinic_hwdev *hwdev,
                            struct hinic_link_ksettings_info *info)
{
        u16 out_size = sizeof(*info);
        int err;

        err = hinic_hilink_msg_cmd(hwdev, HINIC_HILINK_CMD_SET_LINK_SETTINGS,
                                   info, sizeof(*info), info, &out_size);
        if ((info->status != HINIC_MGMT_CMD_UNSUPPORTED &&
             info->status) || err || !out_size) {
                dev_err(&hwdev->hwif->pdev->dev,
                        "Failed to set link settings, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, info->status, out_size);
                return -EFAULT;
        }

        return info->status;
}

int hinic_get_hw_pause_info(struct hinic_hwdev *hwdev,
                            struct hinic_pause_config *pause_info)
{
        u16 out_size = sizeof(*pause_info);
        int err;

        pause_info->func_id = HINIC_HWIF_FUNC_IDX(hwdev->hwif);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_PAUSE_INFO,
                                 pause_info, sizeof(*pause_info),
                                 pause_info, &out_size);
        if (err || !out_size || pause_info->status) {
                dev_err(&hwdev->hwif->pdev->dev, "Failed to get pause info, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, pause_info->status, out_size);
                return -EIO;
        }

        return 0;
}

int hinic_set_hw_pause_info(struct hinic_hwdev *hwdev,
                            struct hinic_pause_config *pause_info)
{
        u16 out_size = sizeof(*pause_info);
        int err;

        pause_info->func_id = HINIC_HWIF_FUNC_IDX(hwdev->hwif);

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_PAUSE_INFO,
                                 pause_info, sizeof(*pause_info),
                                 pause_info, &out_size);
        if (err || !out_size || pause_info->status) {
                dev_err(&hwdev->hwif->pdev->dev, "Failed to set pause info, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, pause_info->status, out_size);
                return -EIO;
        }

        return 0;
}

int hinic_dcb_set_pfc(struct hinic_hwdev *hwdev, u8 pfc_en, u8 pfc_bitmap)
{
        struct hinic_nic_cfg *nic_cfg = &hwdev->func_to_io.nic_cfg;
        struct hinic_set_pfc pfc = {0};
        u16 out_size = sizeof(pfc);
        int err;

        if (HINIC_IS_VF(hwdev->hwif))
                return 0;

        mutex_lock(&nic_cfg->cfg_mutex);

        pfc.func_id = HINIC_HWIF_FUNC_IDX(hwdev->hwif);
        pfc.pfc_bitmap = pfc_bitmap;
        pfc.pfc_en = pfc_en;

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_PFC,
                                 &pfc, sizeof(pfc), &pfc, &out_size);
        if (err || pfc.status || !out_size) {
                dev_err(&hwdev->hwif->pdev->dev, "Failed to %s pfc, err: %d, status: 0x%x, out size: 0x%x\n",
                        pfc_en ? "enable" : "disable", err, pfc.status,
                        out_size);
                mutex_unlock(&nic_cfg->cfg_mutex);
                return -EIO;
        }

        /* pause settings is opposite from pfc */
        nic_cfg->rx_pause = pfc_en ? 0 : 1;
        nic_cfg->tx_pause = pfc_en ? 0 : 1;

        mutex_unlock(&nic_cfg->cfg_mutex);

        return 0;
}

int hinic_set_loopback_mode(struct hinic_hwdev *hwdev, u32 mode, u32 enable)
{
        struct hinic_port_loopback lb = {0};
        u16 out_size = sizeof(lb);
        int err;

        lb.mode = mode;
        lb.en = enable;

        if (mode < LOOP_MODE_MIN || mode > LOOP_MODE_MAX) {
                dev_err(&hwdev->hwif->pdev->dev,
                        "Invalid loopback mode %d to set\n", mode);
                return -EINVAL;
        }

        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_LOOPBACK_MODE,
                                 &lb, sizeof(lb), &lb, &out_size);
        if (err || !out_size || lb.status) {
                dev_err(&hwdev->hwif->pdev->dev,
                        "Failed to set loopback mode %d en %d, err: %d, status: 0x%x, out size: 0x%x\n",
                        mode, enable, err, lb.status, out_size);
                return -EIO;
        }

        return 0;
}

static int _set_led_status(struct hinic_hwdev *hwdev, u8 port,
                           enum hinic_led_type type,
                           enum hinic_led_mode mode, u8 reset)
{
        struct hinic_led_info led_info = {0};
        u16 out_size = sizeof(led_info);
        struct hinic_pfhwdev *pfhwdev;
        int err;

        pfhwdev = container_of(hwdev, struct hinic_pfhwdev, hwdev);

        led_info.port = port;
        led_info.reset = reset;

        led_info.type = type;
        led_info.mode = mode;

        err = hinic_msg_to_mgmt(&pfhwdev->pf_to_mgmt, HINIC_MOD_COMM,
                                HINIC_COMM_CMD_SET_LED_STATUS,
                                &led_info, sizeof(led_info),
                                &led_info, &out_size, HINIC_MGMT_MSG_SYNC);
        if (err || led_info.status || !out_size) {
                dev_err(&hwdev->hwif->pdev->dev, "Failed to set led status, err: %d, status: 0x%x, out size: 0x%x\n",
                        err, led_info.status, out_size);
                return -EIO;
        }

        return 0;
}

int hinic_set_led_status(struct hinic_hwdev *hwdev, u8 port,
                         enum hinic_led_type type, enum hinic_led_mode mode)
{
        if (!hwdev)
                return -EINVAL;

        return _set_led_status(hwdev, port, type, mode, 0);
}

int hinic_reset_led_status(struct hinic_hwdev *hwdev, u8 port)
{
        int err;

        if (!hwdev)
                return -EINVAL;

        err = _set_led_status(hwdev, port, HINIC_LED_TYPE_INVALID,
                              HINIC_LED_MODE_INVALID, 1);
        if (err)
                dev_err(&hwdev->hwif->pdev->dev,
                        "Failed to reset led status\n");

        return err;
}

static bool hinic_if_sfp_absent(struct hinic_hwdev *hwdev)
{
        struct hinic_cmd_get_light_module_abs sfp_abs = {0};
        u16 out_size = sizeof(sfp_abs);
        u8 port_id = hwdev->port_id;
        int err;

        sfp_abs.port_id = port_id;
        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_SFP_ABS,
                                 &sfp_abs, sizeof(sfp_abs), &sfp_abs,
                                 &out_size);
        if (sfp_abs.status || err || !out_size) {
                dev_err(&hwdev->hwif->pdev->dev,
                        "Failed to get port%d sfp absent status, err: %d, status: 0x%x, out size: 0x%x\n",
                        port_id, err, sfp_abs.status, out_size);
                return true;
        }

        return ((sfp_abs.abs_status == 0) ? false : true);
}

int hinic_get_sfp_eeprom(struct hinic_hwdev *hwdev, u8 *data, u16 *len)
{
        struct hinic_cmd_get_std_sfp_info sfp_info = {0};
        u16 out_size = sizeof(sfp_info);
        u8 port_id;
        int err;

        if (!hwdev || !data || !len)
                return -EINVAL;

        port_id = hwdev->port_id;

        if (hinic_if_sfp_absent(hwdev))
                return -ENXIO;

        sfp_info.port_id = port_id;
        err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_STD_SFP_INFO,
                                 &sfp_info, sizeof(sfp_info), &sfp_info,
                                 &out_size);
        if (sfp_info.status || err || !out_size) {
                dev_err(&hwdev->hwif->pdev->dev,
                        "Failed to get port%d sfp eeprom information, err: %d, status: 0x%x, out size: 0x%x\n",
                        port_id, err, sfp_info.status, out_size);
                return -EIO;
        }

        *len = min_t(u16, sfp_info.eeprom_len, STD_SFP_INFO_MAX_SIZE);
        memcpy(data, sfp_info.sfp_info, STD_SFP_INFO_MAX_SIZE);

        return 0;
}

int hinic_get_sfp_type(struct hinic_hwdev *hwdev, u8 *data0, u8 *data1)
{
        u8 sfp_data[STD_SFP_INFO_MAX_SIZE];
        u16 len;
        int err;

        if (hinic_if_sfp_absent(hwdev))
                return -ENXIO;

        err = hinic_get_sfp_eeprom(hwdev, sfp_data, &len);
        if (err)
                return err;

        *data0 = sfp_data[0];
        *data1 = sfp_data[1];

        return 0;
}