// 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/kernel.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>

#include "ossl_knl.h"
#include "hinic_hw.h"
#include "hinic_hw_mgmt.h"
#include "hinic_lld.h"
#include "hinic_nic_cfg.h"
#include "hinic_nic_dev.h"
#include "hinic_dcb.h"

#define DCB_HW_CFG_CHG		0
#define DCB_HW_CFG_NO_CHG	1
#define DCB_HW_CFG_ERR		2

#define DCB_CFG_CHG_PG_TX	0x1
#define DCB_CFG_CHG_PG_RX	0x2
#define DCB_CFG_CHG_PFC		0x4
#define DCB_CFG_CHG_UP_COS	0x8

u8 hinic_dcb_get_tc(struct hinic_dcb_config *dcb_cfg, int dir, u8 up)
{
	struct hinic_tc_cfg *tc_cfg = &dcb_cfg->tc_cfg[0];
	u8 tc = dcb_cfg->pg_tcs;

	if (!tc)
		return 0;

	for (tc--; tc; tc--) {
		if (BIT(up) & tc_cfg[tc].path[dir].up_map)
			break;
	}

	return tc;
}

#define UP_MAPPING(prio)	((u8)(1U << ((HINIC_DCB_UP_MAX - 1) - (prio))))

void hinic_dcb_config_init(struct hinic_nic_dev *nic_dev,
			   struct hinic_dcb_config *dcb_cfg)
{
	struct hinic_tc_cfg *tc;
	int i;

	memset(dcb_cfg->tc_cfg, 0, sizeof(dcb_cfg->tc_cfg));
	tc = &dcb_cfg->tc_cfg[0];
	/* All TC mapping to PG0 */
	for (i = 0; i < dcb_cfg->pg_tcs; i++) {
		tc = &dcb_cfg->tc_cfg[i];
		tc->path[HINIC_DCB_CFG_TX].pg_id = 0;
		tc->path[HINIC_DCB_CFG_TX].bw_pct = 100;
		tc->path[HINIC_DCB_CFG_TX].up_map = UP_MAPPING(i);
		tc->path[HINIC_DCB_CFG_RX].pg_id = 0;
		tc->path[HINIC_DCB_CFG_RX].bw_pct = 100;
		tc->path[HINIC_DCB_CFG_RX].up_map = UP_MAPPING(i);

		tc->pfc_en = false;
	}

	for (; i < HINIC_DCB_UP_MAX; i++) {
		tc->path[HINIC_DCB_CFG_TX].up_map |= UP_MAPPING(i);
		tc->path[HINIC_DCB_CFG_RX].up_map |= UP_MAPPING(i);
	}

	memset(dcb_cfg->bw_pct, 0, sizeof(dcb_cfg->bw_pct));
	/* Use PG0 in default, PG0's bw is 100% */
	dcb_cfg->bw_pct[HINIC_DCB_CFG_TX][0] = 100;
	dcb_cfg->bw_pct[HINIC_DCB_CFG_RX][0] = 100;
	dcb_cfg->pfc_state = false;
}

void hinic_init_ieee_settings(struct hinic_nic_dev *nic_dev)
{
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;
	struct ieee_ets *ets = &nic_dev->hinic_ieee_ets_default;
	struct ieee_pfc *pfc = &nic_dev->hinic_ieee_pfc;
	struct hinic_tc_attr *tc_attr;
	u8 i;

	memset(ets, 0x0, sizeof(struct ieee_ets));
	memset(&nic_dev->hinic_ieee_ets, 0x0, sizeof(struct ieee_ets));
	ets->ets_cap = dcb_cfg->pg_tcs;
	for (i = 0; i < HINIC_DCB_TC_MAX; i++) {
		tc_attr = &dcb_cfg->tc_cfg[i].path[HINIC_DCB_CFG_TX];
		ets->tc_tsa[i] = tc_attr->prio_type ?
			IEEE8021Q_TSA_STRICT : IEEE8021Q_TSA_ETS;
		ets->tc_tx_bw[i] = nic_dev->dcb_cfg.bw_pct[HINIC_DCB_CFG_TX][i];
		ets->tc_rx_bw[i] = nic_dev->dcb_cfg.bw_pct[HINIC_DCB_CFG_RX][i];
		ets->prio_tc[i] = hinic_dcb_get_tc(dcb_cfg,
						   HINIC_DCB_CFG_TX, i);
	}
	memcpy(&nic_dev->hinic_ieee_ets, ets, sizeof(struct ieee_ets));

	memset(pfc, 0x0, sizeof(struct ieee_pfc));
	pfc->pfc_cap = dcb_cfg->pfc_tcs;
	for (i = 0; i < dcb_cfg->pfc_tcs; i++) {
		if (dcb_cfg->tc_cfg[i].pfc_en)
			pfc->pfc_en |= (u8)BIT(i);
	}
}

static int hinic_set_up_cos_map(struct hinic_nic_dev *nic_dev,
				u8 num_cos, u8 *cos_up)
{
	u8 up_valid_bitmap, up_cos[HINIC_DCB_UP_MAX] = {0};
	u8 i;

	up_valid_bitmap = 0;
	for (i = 0; i < num_cos; i++) {
		if (cos_up[i] >= HINIC_DCB_UP_MAX) {
			hinic_info(nic_dev, drv, "Invalid up %d mapping to cos %d\n",
				   cos_up[i], i);
			return -EFAULT;
		}

		if (i > 0 && cos_up[i] >= cos_up[i - 1]) {
			hinic_info(nic_dev, drv,
				   "Invalid priority order, should be descending cos[%d]=%d, cos[%d]=%d\n",
				   i, cos_up[i], i - 1, cos_up[i - 1]);
			return -EINVAL;
		}

		up_valid_bitmap |= (u8)BIT(cos_up[i]);
		if (i == (num_cos - 1))
			up_cos[cos_up[i]] = nic_dev->default_cos_id;
		else
			up_cos[cos_up[i]] = i;	/* reverse up and cos */
	}

	for (i = 0; i < HINIC_DCB_UP_MAX; i++) {
		if (up_valid_bitmap & (u8)BIT(i))
			continue;

		up_cos[i] = nic_dev->default_cos_id;
	}

	nic_dev->up_valid_bitmap = up_valid_bitmap;
	memcpy(nic_dev->up_cos, up_cos, sizeof(up_cos));

	return hinic_sq_cos_mapping(nic_dev->netdev);
}

static int hinic_init_up_cos_map(struct hinic_nic_dev *nic_dev, u8 num_cos)
{
	u8 default_map[HINIC_DCB_COS_MAX] = {0};
	bool setted = false;
	u8 max_cos, cos_id, up;
	int err;

	max_cos = hinic_max_num_cos(nic_dev->hwdev);
	if (!max_cos || ((max_cos - 1) < nic_dev->default_cos_id)) {
		hinic_err(nic_dev, drv, "Max_cos is %d, default cos id %d\n",
			  max_cos, nic_dev->default_cos_id);
		return -EFAULT;
	}

	err = hinic_get_chip_cos_up_map(nic_dev->pdev, &setted, default_map);
	if (err) {
		hinic_err(nic_dev, drv, "Get chip cos_up map failed\n");
		return -EFAULT;
	}

	if (!setted) {
		/* Use (max_cos-1)~0 as default user priority and mapping
		 * to cos0~(max_cos-1)
		 */
		up = nic_dev->max_cos - 1;
		for (cos_id = 0; cos_id < nic_dev->max_cos; cos_id++, up--)
			default_map[cos_id] = up;
	}

	return hinic_set_up_cos_map(nic_dev, num_cos, default_map);
}

int hinic_dcb_init(struct hinic_nic_dev *nic_dev)
{
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;
	u8 num_cos, support_cos = 0, default_cos = 0;
	u8 i, cos_valid_bitmap;
	int err;

	if (HINIC_FUNC_IS_VF(nic_dev->hwdev))
		return 0;

	cos_valid_bitmap = hinic_cos_valid_bitmap(nic_dev->hwdev);
	if (!cos_valid_bitmap) {
		hinic_err(nic_dev, drv, "None cos supported\n");
		return -EFAULT;
	}

	for (i = 0; i < HINIC_DCB_COS_MAX; i++) {
		if (cos_valid_bitmap & BIT(i)) {
			support_cos++;
			default_cos = i; /* Find max cos id as default cos */
		}
	}

	hinic_info(nic_dev, drv, "Support num cos %d, default cos %d\n",
		   support_cos, default_cos);

	num_cos = (u8)(1U << ilog2(support_cos));
	if (num_cos != support_cos)
		hinic_info(nic_dev, drv, "Adjust num_cos from %d to %d\n",
			   support_cos, num_cos);

	nic_dev->dcbx_cap = 0;
	nic_dev->max_cos = num_cos;
	nic_dev->default_cos_id = default_cos;
	dcb_cfg->pfc_tcs  = nic_dev->max_cos;
	dcb_cfg->pg_tcs   = nic_dev->max_cos;
	err = hinic_init_up_cos_map(nic_dev, num_cos);
	if (err) {
		hinic_info(nic_dev, drv, "Initialize up_cos mapping failed\n");
		return -EFAULT;
	}

	hinic_dcb_config_init(nic_dev, dcb_cfg);

	nic_dev->dcb_changes = DCB_CFG_CHG_PFC | DCB_CFG_CHG_PG_TX |
			       DCB_CFG_CHG_PG_RX | DCB_CFG_CHG_UP_COS;
	nic_dev->dcbx_cap = DCB_CAP_DCBX_HOST | DCB_CAP_DCBX_VER_CEE;

	memcpy(&nic_dev->tmp_dcb_cfg, &nic_dev->dcb_cfg,
	       sizeof(nic_dev->tmp_dcb_cfg));
	memcpy(&nic_dev->save_dcb_cfg, &nic_dev->dcb_cfg,
	       sizeof(nic_dev->save_dcb_cfg));

	hinic_init_ieee_settings(nic_dev);

	sema_init(&nic_dev->dcb_sem, 1);

	return 0;
}

void hinic_set_prio_tc_map(struct hinic_nic_dev *nic_dev)
{
	struct net_device *netdev = nic_dev->netdev;
	u8 prio, tc;

	for (prio = 0; prio < HINIC_DCB_UP_MAX; prio++) {
		tc = nic_dev->up_cos[prio];
		if (tc == nic_dev->default_cos_id)
			tc = nic_dev->max_cos - 1;

		netdev_set_prio_tc_map(netdev, prio, tc);
	}
}

int hinic_setup_tc(struct net_device *netdev, u8 tc)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int err;

	if (!FUNC_SUPPORT_DCB(nic_dev->hwdev)) {
		nicif_err(nic_dev, drv, netdev,
			  "Current function don't support DCB\n");
		return -EOPNOTSUPP;
	}

	if (tc > nic_dev->dcb_cfg.pg_tcs) {
		nicif_err(nic_dev, drv, netdev, "Invalid num_tc: %d, max tc: %d\n",
			  tc, nic_dev->dcb_cfg.pg_tcs);
		return -EINVAL;
	}

	if (netif_running(netdev)) {
		err = hinic_close(netdev);
		if (err) {
			nicif_err(nic_dev, drv, netdev, "Failed to close device\n");
			return -EFAULT;
		}
	}

	if (tc) {
		if (tc & (tc - 1)) {
			nicif_err(nic_dev, drv, netdev,
				  "Invalid num_tc: %d, must be power of 2\n",
				  tc);
			return -EINVAL;
		}

		netdev_set_num_tc(netdev, tc);
		hinic_set_prio_tc_map(nic_dev);

		set_bit(HINIC_DCB_ENABLE, &nic_dev->flags);
	} else {
		netdev_reset_tc(netdev);

		clear_bit(HINIC_DCB_ENABLE, &nic_dev->flags);
	}

	hinic_sq_cos_mapping(netdev);

	if (netif_running(netdev)) {
		err = hinic_open(netdev);
		if (err) {
			nicif_err(nic_dev, drv, netdev, "Failed to open device\n");
			return -EFAULT;
		}
	} else {
		hinic_update_num_qps(netdev);
	}

	hinic_configure_dcb(netdev);

	return 0;
}

u8 hinic_setup_dcb_tool(struct net_device *netdev, u8 *dcb_en, bool wr_flag)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int err = 0;

	if (wr_flag) {
		if (nic_dev->max_qps < nic_dev->dcb_cfg.pg_tcs && *dcb_en) {
			nicif_err(nic_dev, drv, netdev,
				  "max_qps: %d is less than %d\n",
				  nic_dev->max_qps, nic_dev->dcb_cfg.pg_tcs);
			return 1;
		}
		if (*dcb_en)
			set_bit(HINIC_DCB_ENABLE, &nic_dev->flags);
		else
			clear_bit(HINIC_DCB_ENABLE, &nic_dev->flags);
		/*hinic_setup_tc need get the nic_mutex lock again */
		mutex_unlock(&nic_dev->nic_mutex);
		/*  kill the rtnl assert warning */
		rtnl_lock();
		err = hinic_setup_tc(netdev,
				     *dcb_en ? nic_dev->dcb_cfg.pg_tcs : 0);
		rtnl_unlock();
		mutex_lock(&nic_dev->nic_mutex);

		if (!err)
			nicif_info(nic_dev, drv, netdev, "%s DCB\n",
				   *dcb_en ? "Enable" : "Disable");
	} else {
		*dcb_en = (u8)test_bit(HINIC_DCB_ENABLE, &nic_dev->flags);
	}

	return !!err;
}

static u8 hinic_dcbnl_get_state(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	return !!test_bit(HINIC_DCB_ENABLE, &nic_dev->flags);
}

static u8 hinic_dcbnl_set_state(struct net_device *netdev, u8 state)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u8 curr_state = !!test_bit(HINIC_DCB_ENABLE, &nic_dev->flags);
	int err = 0;

	if (!(nic_dev->dcbx_cap & DCB_CAP_DCBX_VER_CEE))
		return 1;

	if (state == curr_state)
		return 0;

	if (nic_dev->max_qps < nic_dev->dcb_cfg.pg_tcs && state) {
		nicif_err(nic_dev, drv, netdev,
			  "max_qps: %d is less than %d\n",
			  nic_dev->max_qps, nic_dev->dcb_cfg.pg_tcs);
		return 1;
	}

	err = hinic_setup_tc(netdev, state ? nic_dev->dcb_cfg.pg_tcs : 0);
	if (!err)
		nicif_info(nic_dev, drv, netdev, "%s DCB\n",
			   state ? "Enable" : "Disable");

	return !!err;
}

static void hinic_dcbnl_get_perm_hw_addr(struct net_device *netdev,
					 u8 *perm_addr)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int err;

	memset(perm_addr, 0xff, MAX_ADDR_LEN);

	err = hinic_get_default_mac(nic_dev->hwdev, perm_addr);
	if (err)
		nicif_err(nic_dev, drv, netdev, "Failed to get default mac\n");
}

void hinic_dcbnl_set_ets_tc_tool(struct net_device *netdev, u8 tc[], bool flag)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_tc_cfg *cfg = nic_dev->tmp_dcb_cfg.tc_cfg;
	struct hinic_tc_cfg *tc_conf = nic_dev->dcb_cfg.tc_cfg;
	u8 i, tc_tmp, j;

	if (flag) {
		/*need to clear first */
		for (i = 0; i < HINIC_DCB_TC_MAX; i++) {
			cfg[i].path[HINIC_DCB_CFG_TX].up_map = 0;
			cfg[i].path[HINIC_DCB_CFG_RX].up_map = 0;
		}
		for (i = 0; i < HINIC_DCB_TC_MAX; i++) {
			tc_tmp = tc[i];
			cfg[tc_tmp].path[HINIC_DCB_CFG_TX].up_map |= (u8)BIT(i);
			cfg[tc_tmp].path[HINIC_DCB_CFG_RX].up_map |= (u8)BIT(i);
			cfg[tc_tmp].path[HINIC_DCB_CFG_TX].pg_id = (u8)tc_tmp;
			cfg[tc_tmp].path[HINIC_DCB_CFG_RX].pg_id = (u8)tc_tmp;
		}
	} else {
		for (i = 0; i < HINIC_DCB_TC_MAX; i++) {
			for (j = 0; j < HINIC_DCB_TC_MAX; j++) {
				if (tc_conf[i].path[HINIC_DCB_CFG_TX].up_map &
					(u8)BIT(j)) {
					tc[j] = i;
				}
			}
		}
	}
}

void hinic_dcbnl_set_ets_pecent_tool(struct net_device *netdev,
				     u8 percent[], bool flag)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int i;

	if (flag) {
		for (i = 0; i < HINIC_DCB_COS_MAX; i++) {
			nic_dev->tmp_dcb_cfg.bw_pct[HINIC_DCB_CFG_TX][i] =
				percent[i];
			nic_dev->tmp_dcb_cfg.bw_pct[HINIC_DCB_CFG_RX][i] =
				percent[i];
		}
	} else {
		for (i = 0; i < HINIC_DCB_COS_MAX; i++)
			percent[i] =
			nic_dev->dcb_cfg.bw_pct[HINIC_DCB_CFG_TX][i];
	}
}

static void hinic_dcbnl_set_pg_tc_cfg_tx(struct net_device *netdev, int tc,
					 u8 prio, u8 pg_id, u8 bw_pct,
					 u8 up_map)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (tc > HINIC_DCB_TC_MAX - 1)
		return;

	if (prio != DCB_ATTR_VALUE_UNDEFINED)
		nic_dev->tmp_dcb_cfg.tc_cfg[tc].path[0].prio_type = prio;
	if (pg_id != DCB_ATTR_VALUE_UNDEFINED)
		nic_dev->tmp_dcb_cfg.tc_cfg[tc].path[0].pg_id = pg_id;
	if (bw_pct != DCB_ATTR_VALUE_UNDEFINED)
		nic_dev->tmp_dcb_cfg.tc_cfg[tc].path[0].bw_pct = bw_pct;
	/* if all priority mapping to the same tc,
	 * up_map is 0xFF, and it's a valid value
	 */
	nic_dev->tmp_dcb_cfg.tc_cfg[tc].path[0].up_map = up_map;
}

static void hinic_dcbnl_set_pg_bwg_cfg_tx(struct net_device *netdev, int bwg_id,
					  u8 bw_pct)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (bwg_id > HINIC_DCB_PG_MAX - 1)
		return;

	nic_dev->tmp_dcb_cfg.bw_pct[0][bwg_id] = bw_pct;
}

static void hinic_dcbnl_set_pg_tc_cfg_rx(struct net_device *netdev, int tc,
					 u8 prio, u8 pg_id, u8 bw_pct,
					 u8 up_map)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (tc > HINIC_DCB_TC_MAX - 1)
		return;

	if (prio != DCB_ATTR_VALUE_UNDEFINED)
		nic_dev->tmp_dcb_cfg.tc_cfg[tc].path[1].prio_type = prio;
	if (pg_id != DCB_ATTR_VALUE_UNDEFINED)
		nic_dev->tmp_dcb_cfg.tc_cfg[tc].path[1].pg_id = pg_id;
	if (bw_pct != DCB_ATTR_VALUE_UNDEFINED)
		nic_dev->tmp_dcb_cfg.tc_cfg[tc].path[1].bw_pct = bw_pct;

	nic_dev->tmp_dcb_cfg.tc_cfg[tc].path[1].up_map = up_map;
}

static void hinic_dcbnl_set_pg_bwg_cfg_rx(struct net_device *netdev, int bwg_id,
					  u8 bw_pct)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (bwg_id > HINIC_DCB_PG_MAX - 1)
		return;

	nic_dev->tmp_dcb_cfg.bw_pct[1][bwg_id] = bw_pct;
}

static void hinic_dcbnl_get_pg_tc_cfg_tx(struct net_device *netdev, int tc,
					 u8 *prio, u8 *pg_id, u8 *bw_pct,
					 u8 *up_map)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (tc > HINIC_DCB_TC_MAX - 1)
		return;

	*prio = nic_dev->dcb_cfg.tc_cfg[tc].path[0].prio_type;
	*pg_id = nic_dev->dcb_cfg.tc_cfg[tc].path[0].pg_id;
	*bw_pct = nic_dev->dcb_cfg.tc_cfg[tc].path[0].bw_pct;
	*up_map = nic_dev->dcb_cfg.tc_cfg[tc].path[0].up_map;
}

static void hinic_dcbnl_get_pg_bwg_cfg_tx(struct net_device *netdev, int bwg_id,
					  u8 *bw_pct)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (bwg_id > HINIC_DCB_PG_MAX - 1)
		return;

	*bw_pct = nic_dev->dcb_cfg.bw_pct[0][bwg_id];
}

static void hinic_dcbnl_get_pg_tc_cfg_rx(struct net_device *netdev, int tc,
					 u8 *prio, u8 *pg_id, u8 *bw_pct,
					 u8 *up_map)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (tc > HINIC_DCB_TC_MAX - 1)
		return;

	*prio = nic_dev->dcb_cfg.tc_cfg[tc].path[1].prio_type;
	*pg_id = nic_dev->dcb_cfg.tc_cfg[tc].path[1].pg_id;
	*bw_pct = nic_dev->dcb_cfg.tc_cfg[tc].path[1].bw_pct;
	*up_map = nic_dev->dcb_cfg.tc_cfg[tc].path[1].up_map;
}

static void hinic_dcbnl_get_pg_bwg_cfg_rx(struct net_device *netdev, int bwg_id,
					  u8 *bw_pct)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (bwg_id > HINIC_DCB_PG_MAX - 1)
		return;

	*bw_pct = nic_dev->dcb_cfg.bw_pct[1][bwg_id];
}

void hinic_dcbnl_set_pfc_cfg_tool(struct net_device *netdev, u8 setting)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u8 i;

	for (i = 0; i < HINIC_DCB_TC_MAX; i++) {
		nic_dev->tmp_dcb_cfg.tc_cfg[i].pfc_en = !!(setting & BIT(i));
		if (nic_dev->tmp_dcb_cfg.tc_cfg[i].pfc_en !=
			nic_dev->dcb_cfg.tc_cfg[i].pfc_en) {
			nic_dev->tmp_dcb_cfg.pfc_state = true;
		}
	}
}

void hinic_dcbnl_set_ets_strict_tool(struct net_device *netdev,
				     u8 *setting, bool flag)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_tc_cfg *cfg = nic_dev->tmp_dcb_cfg.tc_cfg;
	struct hinic_tc_cfg *conf = nic_dev->dcb_cfg.tc_cfg;
	u8 i;

	if (flag) {
		for (i = 0; i < HINIC_DCB_COS_MAX; i++) {
			cfg[i].path[HINIC_DCB_CFG_TX].prio_type =
				!!(*setting & BIT(i)) ? 2 : 0;
			cfg[i].path[HINIC_DCB_CFG_RX].prio_type =
				!!(*setting & BIT(i)) ? 2 : 0;
		}
	} else {
		for (i = 0; i < HINIC_DCB_COS_MAX; i++) {
			*setting = *setting |
				(u8)((u32)(!!(conf[i].path[0].prio_type)) << i);
		}
	}
}

void hinic_dcbnl_set_pfc_en_tool(struct net_device *netdev,
				 u8 *value, bool flag)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (flag)
		nic_dev->tmp_dcb_cfg.pfc_state = !!(*value);
	else
		*value = nic_dev->tmp_dcb_cfg.pfc_state;
}

void hinic_dcbnl_set_ets_en_tool(struct net_device *netdev,
				 u8 *value, bool flag)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (flag) {
		if (*value)
			set_bit(HINIC_ETS_ENABLE, &nic_dev->flags);
		else
			clear_bit(HINIC_ETS_ENABLE, &nic_dev->flags);
	} else {
		*value = (u8)test_bit(HINIC_ETS_ENABLE, &nic_dev->flags);
	}
}

static void hinic_dcbnl_set_pfc_cfg(struct net_device *netdev, int prio,
				    u8 setting)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	nic_dev->tmp_dcb_cfg.tc_cfg[prio].pfc_en = !!setting;
	if (nic_dev->tmp_dcb_cfg.tc_cfg[prio].pfc_en !=
	    nic_dev->dcb_cfg.tc_cfg[prio].pfc_en)
		nic_dev->tmp_dcb_cfg.pfc_state = true;
}

void hinic_dcbnl_get_pfc_cfg_tool(struct net_device *netdev, u8 *setting)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u8 i;

	for (i = 0; i < HINIC_DCB_TC_MAX; i++) {
		*setting = *setting |
			(u8)((u32)(nic_dev->dcb_cfg.tc_cfg[i].pfc_en) << i);
	}
}

void hinic_dcbnl_get_tc_num_tool(struct net_device *netdev, u8 *tc_num)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	*tc_num = nic_dev->max_cos;
}

static void hinic_dcbnl_get_pfc_cfg(struct net_device *netdev, int prio,
				    u8 *setting)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	if (prio > HINIC_DCB_TC_MAX - 1)
		return;

	*setting = nic_dev->dcb_cfg.tc_cfg[prio].pfc_en;
}

static u8 hinic_dcbnl_getcap(struct net_device *netdev, int cap_id,
			     u8 *dcb_cap)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	switch (cap_id) {
	case DCB_CAP_ATTR_PG:
		*dcb_cap = true;
		break;
	case DCB_CAP_ATTR_PFC:
		*dcb_cap = true;
		break;
	case DCB_CAP_ATTR_UP2TC:
		*dcb_cap = false;
		break;
	case DCB_CAP_ATTR_PG_TCS:
		*dcb_cap = 0x80;
		break;
	case DCB_CAP_ATTR_PFC_TCS:
		*dcb_cap = 0x80;
		break;
	case DCB_CAP_ATTR_GSP:
		*dcb_cap = true;
		break;
	case DCB_CAP_ATTR_BCN:
		*dcb_cap = false;
		break;
	case DCB_CAP_ATTR_DCBX:
		*dcb_cap = nic_dev->dcbx_cap;
		break;
	default:
		*dcb_cap = false;
		break;
	}

	return 0;
}

static u8 hinic_sync_tc_cfg(struct hinic_tc_cfg *tc_dst,
			    struct hinic_tc_cfg *tc_src, int dir)
{
	u8 tc_dir_change = (dir == HINIC_DCB_CFG_TX) ?
			   DCB_CFG_CHG_PG_TX : DCB_CFG_CHG_PG_RX;
	u8 changes = 0;

	if (tc_dst->path[dir].prio_type != tc_src->path[dir].prio_type) {
		tc_dst->path[dir].prio_type = tc_src->path[dir].prio_type;
		changes |= tc_dir_change;
	}

	if (tc_dst->path[dir].pg_id != tc_src->path[dir].pg_id) {
		tc_dst->path[dir].pg_id = tc_src->path[dir].pg_id;
		changes |= tc_dir_change;
	}

	if (tc_dst->path[dir].bw_pct != tc_src->path[dir].bw_pct) {
		tc_dst->path[dir].bw_pct = tc_src->path[dir].bw_pct;
		changes |= tc_dir_change;
	}

	if (tc_dst->path[dir].up_map != tc_src->path[dir].up_map) {
		tc_dst->path[dir].up_map = tc_src->path[dir].up_map;
		changes |= (tc_dir_change | DCB_CFG_CHG_PFC);
	}

	return changes;
}

static u8 hinic_sync_dcb_cfg(struct hinic_nic_dev *nic_dev)
{
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;
	struct hinic_dcb_config *tmp_dcb_cfg = &nic_dev->tmp_dcb_cfg;
	struct hinic_tc_cfg *tc_dst, *tc_src;
	u8 changes = 0;
	int i;

	for (i = 0; i < HINIC_DCB_UP_MAX; i++) {
		tc_src = &tmp_dcb_cfg->tc_cfg[i];
		tc_dst = &dcb_cfg->tc_cfg[i];

		changes |= hinic_sync_tc_cfg(tc_dst, tc_src, HINIC_DCB_CFG_TX);
		changes |= hinic_sync_tc_cfg(tc_dst, tc_src, HINIC_DCB_CFG_RX);
	}

	for (i = 0; i < HINIC_DCB_PG_MAX; i++) {
		if (dcb_cfg->bw_pct[HINIC_DCB_CFG_TX][i] !=
			tmp_dcb_cfg->bw_pct[HINIC_DCB_CFG_TX][i]) {
			dcb_cfg->bw_pct[HINIC_DCB_CFG_TX][i] =
				tmp_dcb_cfg->bw_pct[HINIC_DCB_CFG_TX][i];
			changes |= DCB_CFG_CHG_PG_TX;
		}

		if (dcb_cfg->bw_pct[HINIC_DCB_CFG_RX][i] !=
			tmp_dcb_cfg->bw_pct[HINIC_DCB_CFG_RX][i]) {
			dcb_cfg->bw_pct[HINIC_DCB_CFG_RX][i] =
				tmp_dcb_cfg->bw_pct[HINIC_DCB_CFG_RX][i];
			changes |= DCB_CFG_CHG_PG_RX;
		}
	}

	for (i = 0; i < HINIC_DCB_UP_MAX; i++) {
		if (dcb_cfg->tc_cfg[i].pfc_en !=
			tmp_dcb_cfg->tc_cfg[i].pfc_en) {
			dcb_cfg->tc_cfg[i].pfc_en =
				tmp_dcb_cfg->tc_cfg[i].pfc_en;
			changes |= DCB_CFG_CHG_PFC;
		}
	}

	if (dcb_cfg->pfc_state != tmp_dcb_cfg->pfc_state) {
		dcb_cfg->pfc_state = tmp_dcb_cfg->pfc_state;
		changes |= DCB_CFG_CHG_PFC;
	}

	return changes;
}

static void hinic_dcb_get_pfc_map(struct hinic_nic_dev *nic_dev,
				  struct hinic_dcb_config *dcb_cfg, u8 *pfc_map)
{
	u8 i, up;
	u8 pfc_en = 0, outof_range_pfc = 0;

	for (i = 0; i < dcb_cfg->pfc_tcs; i++) {
		up = (HINIC_DCB_UP_MAX - 1) - i;
		if (dcb_cfg->tc_cfg[up].pfc_en)
			*pfc_map |= (u8)BIT(up);
	}

	for (i = 0; i < HINIC_DCB_UP_MAX; i++) {
		up = (HINIC_DCB_UP_MAX - 1) - i;
		if (dcb_cfg->tc_cfg[up].pfc_en)
			pfc_en |= (u8)BIT(up);
	}

	*pfc_map = pfc_en & nic_dev->up_valid_bitmap;
	outof_range_pfc = pfc_en & (~nic_dev->up_valid_bitmap);

	if (outof_range_pfc)
		hinic_info(nic_dev, drv,
			   "PFC setting out of range, 0x%x will be ignored\n",
			   outof_range_pfc);
}

static bool is_cos_in_use(u8 cos, u8 up_valid_bitmap, u8 *up_cos)
{
	u32 i;

	for (i = 0; i < HINIC_DCB_UP_MAX; i++) {
		if (!(up_valid_bitmap & BIT(i)))
			continue;

		if (cos == up_cos[i])
			return true;
	}

	return false;
}

static void hinic_dcb_adjust_up_bw(struct hinic_nic_dev *nic_dev, u8 *up_pgid,
				   u8 *up_bw)
{
	u8 tmp_cos, pg_id;
	u16 bw_all;
	u8 bw_remain, cos_cnt;

	for (pg_id = 0; pg_id < HINIC_DCB_PG_MAX; pg_id++) {
		bw_all = 0;
		cos_cnt = 0;
		/* Find all up mapping to the same pg */
		for (tmp_cos = 0; tmp_cos < HINIC_DCB_UP_MAX; tmp_cos++) {
			if (!is_cos_in_use(tmp_cos, nic_dev->up_valid_bitmap,
					   nic_dev->up_cos))
				continue;

			if (up_pgid[tmp_cos] == pg_id) {
				bw_all += up_bw[tmp_cos];
				cos_cnt++;
			}
		}

		if (bw_all <= 100 || !cos_cnt)
			continue;

		/* Calculate up percent of bandwidth group, The sum of
		 * percentages for priorities in the same priority group
		 * must be 100
		 */
		bw_remain = 100 % cos_cnt;
		for (tmp_cos = 0; tmp_cos < HINIC_DCB_UP_MAX; tmp_cos++) {
			if (!is_cos_in_use(tmp_cos, nic_dev->up_valid_bitmap,
					   nic_dev->up_cos))
				continue;

			if (up_pgid[tmp_cos] == pg_id) {
				up_bw[tmp_cos] =
					(u8)(100 * up_bw[tmp_cos] / bw_all +
					(u8)!!bw_remain);
				if (bw_remain)
					bw_remain--;
			}
		}
	}
}

static void hinic_dcb_dump_configuration(struct hinic_nic_dev *nic_dev,
					 u8 *up_tc, u8 *up_pgid, u8 *up_bw,
					 u8 *pg_bw, u8 *up_strict, u8 *bw_pct)
{
	u8 i;
	u8 cos;

	for (i = 0; i < HINIC_DCB_UP_MAX; i++) {
		if (!(nic_dev->up_valid_bitmap & BIT(i)))
			continue;

		cos = nic_dev->up_cos[i];
		hinic_info(nic_dev, drv,
			   "up: %d, cos: %d, tc: %d, pgid: %d, bw: %d, tsa: %d\n",
			   i, cos, up_tc[cos], up_pgid[cos], up_bw[cos],
			   up_strict[cos]);
	}

	for (i = 0; i < HINIC_DCB_PG_MAX; i++)
		hinic_info(nic_dev, drv, "pgid: %d, bw: %d\n", i, pg_bw[i]);
}

/* Ucode thread timeout is 210ms, must be lagger then 210ms */
#define HINIC_WAIT_PORT_IO_STOP		250

static int hinic_stop_port_traffic_flow(struct hinic_nic_dev *nic_dev)
{
	int err = 0;

	down(&nic_dev->dcb_sem);

	if (nic_dev->disable_port_cnt++ != 0)
		goto out;

	err = hinic_force_port_disable(nic_dev);
	if (err) {
		hinic_err(nic_dev, drv, "Failed to disable port\n");
		goto set_port_err;
	}

	err = hinic_set_port_funcs_state(nic_dev->hwdev, false);
	if (err) {
		hinic_err(nic_dev, drv,
			  "Failed to disable all functions in port\n");
		goto set_port_funcs_err;
	}

	hinic_info(nic_dev, drv, "Stop port traffic flow\n");

	goto out;

set_port_funcs_err:
	hinic_force_set_port_state(nic_dev, !!netif_running(nic_dev->netdev));

set_port_err:
out:
	if (err)
		nic_dev->disable_port_cnt--;

	up(&nic_dev->dcb_sem);

	return err;
}

static int hinic_start_port_traffic_flow(struct hinic_nic_dev *nic_dev)
{
	int err;

	down(&nic_dev->dcb_sem);

	nic_dev->disable_port_cnt--;
	if (nic_dev->disable_port_cnt > 0) {
		up(&nic_dev->dcb_sem);
		return 0;
	}

	nic_dev->disable_port_cnt = 0;
	up(&nic_dev->dcb_sem);

	err = hinic_force_set_port_state(nic_dev,
					 !!netif_running(nic_dev->netdev));
	if (err)
		hinic_err(nic_dev, drv, "Failed to disable port\n");

	err = hinic_set_port_funcs_state(nic_dev->hwdev, true);
	if (err)
		hinic_err(nic_dev, drv,
			  "Failed to disable all functions in port\n");

	hinic_info(nic_dev, drv, "Start port traffic flow\n");

	return err;
}

static int __set_hw_cos_up_map(struct hinic_nic_dev *nic_dev)
{
	u8 cos, cos_valid_bitmap, cos_up_map[HINIC_DCB_COS_MAX] = {0};
	u8 i;
	int err;

	cos_valid_bitmap = 0;
	for (i = 0; i < HINIC_DCB_UP_MAX; i++) {
		if (!(nic_dev->up_valid_bitmap & BIT(i)))
			continue;

		cos = nic_dev->up_cos[i];
		cos_up_map[cos] = i;
		cos_valid_bitmap |= (u8)BIT(cos);
	}

	err = hinic_dcb_set_cos_up_map(nic_dev->hwdev, cos_valid_bitmap,
				       cos_up_map);
	if (err) {
		hinic_info(nic_dev, drv, "Set cos_up map failed\n");
		return err;
	}

	return 0;
}

static int __set_hw_ets(struct hinic_nic_dev *nic_dev)
{
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;
	struct ieee_ets *my_ets = &nic_dev->hinic_ieee_ets;
	struct hinic_tc_attr *tc_attr;
	u8 up_tc[HINIC_DCB_UP_MAX] = {0};
	u8 up_pgid[HINIC_DCB_UP_MAX] = {0};
	u8 up_bw[HINIC_DCB_UP_MAX] = {0};
	u8 pg_bw[HINIC_DCB_UP_MAX] = {0};
	u8 up_strict[HINIC_DCB_UP_MAX] = {0};
	u8 i, tc, cos;
	int err;

	for (i = 0; i < HINIC_DCB_UP_MAX; i++) {
		if (!(nic_dev->up_valid_bitmap & BIT(i)))
			continue;

		cos = nic_dev->up_cos[i];
		if ((nic_dev->dcbx_cap & DCB_CAP_DCBX_VER_IEEE)) {
			up_tc[cos] = my_ets->prio_tc[i];
			up_pgid[cos] = my_ets->prio_tc[i];
			up_bw[cos] = 100;
			up_strict[i] =
				(my_ets->tc_tsa[cos] == IEEE8021Q_TSA_STRICT) ?
				HINIC_DCB_TSA_TC_SP : HINIC_DCB_TSA_TC_DWRR;

		} else {
			tc = hinic_dcb_get_tc(dcb_cfg, HINIC_DCB_CFG_TX, i);
			tc_attr = &dcb_cfg->tc_cfg[tc].path[HINIC_DCB_CFG_TX];
			up_tc[cos] = tc;
			up_pgid[cos] = tc_attr->pg_id;
			up_bw[cos] = tc_attr->bw_pct;
			up_strict[cos] = tc_attr->prio_type ?
				HINIC_DCB_TSA_TC_SP : HINIC_DCB_TSA_TC_DWRR;
		}
	}

	hinic_dcb_adjust_up_bw(nic_dev, up_pgid, up_bw);

	if (nic_dev->dcbx_cap & DCB_CAP_DCBX_VER_IEEE) {
		for (i = 0; i < HINIC_DCB_PG_MAX; i++)
			pg_bw[i] = my_ets->tc_tx_bw[i];
	} else {
		for (i = 0; i < HINIC_DCB_PG_MAX; i++)
			pg_bw[i] = dcb_cfg->bw_pct[HINIC_DCB_CFG_TX][i];
	}

	if (test_bit(HINIC_DCB_ENABLE, &nic_dev->flags))
		hinic_dcb_dump_configuration(nic_dev, up_tc, up_pgid,
					     up_bw, pg_bw, up_strict,
					     pg_bw);

	err = hinic_dcb_set_ets(nic_dev->hwdev, up_tc, pg_bw, up_pgid,
				up_bw, up_strict);
	if (err) {
		hinic_err(nic_dev, drv, "Failed to set ets with mode: %d\n",
			  nic_dev->dcbx_cap);
		return err;
	}

	hinic_info(nic_dev, drv, "Set ets to hw done with mode: %d\n",
		   nic_dev->dcbx_cap);

	return 0;
}

u8 hinic_dcbnl_set_ets_tool(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u8 state = DCB_HW_CFG_CHG;
	int err;

	nic_dev->dcb_changes |= hinic_sync_dcb_cfg(nic_dev);
	if (!nic_dev->dcb_changes)
		return DCB_HW_CFG_CHG;

	err = hinic_stop_port_traffic_flow(nic_dev);
	if (err)
		return DCB_HW_CFG_ERR;
	/* wait all traffic flow stopped */
	if (netdev->reg_state == NETREG_REGISTERED)
		msleep(HINIC_WAIT_PORT_IO_STOP);

	if (nic_dev->dcb_changes & DCB_CFG_CHG_UP_COS) {
		err = __set_hw_cos_up_map(nic_dev);
		if (err) {
			hinic_info(nic_dev, drv,
				   "Set cos_up map to hardware failed\n");
			state = DCB_HW_CFG_ERR;
			goto out;
		}

		nic_dev->dcb_changes &= (~DCB_CFG_CHG_UP_COS);
	}

	if (nic_dev->dcb_changes & (DCB_CFG_CHG_PG_TX | DCB_CFG_CHG_PG_RX)) {
		err = __set_hw_ets(nic_dev);
		if (err) {
			state = DCB_HW_CFG_ERR;
			goto out;
		}

		nic_dev->dcb_changes &=
				(~(DCB_CFG_CHG_PG_TX | DCB_CFG_CHG_PG_RX));
	}

out:
	hinic_start_port_traffic_flow(nic_dev);

	return state;
}

static int hinic_dcbnl_set_df_ieee_cfg(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct ieee_ets *ets_default = &nic_dev->hinic_ieee_ets_default;
	struct ieee_pfc *my_pfc = &nic_dev->hinic_ieee_pfc;
	struct ieee_ets *my_ets = &nic_dev->hinic_ieee_ets;
	struct ieee_pfc pfc = {0};
	int err1 = 0;
	int err2 = 0;
	u8 flag = 0;

	if (!(nic_dev->dcbx_cap & DCB_CAP_DCBX_VER_IEEE))
		return 0;

	if (memcmp(my_ets, ets_default, sizeof(struct ieee_ets)))
		flag |= (u8)BIT(0);

	if (my_pfc->pfc_en)
		flag |= (u8)BIT(1);
	if (!flag)
		return 0;

	err1 = hinic_stop_port_traffic_flow(nic_dev);
	if (err1)
		return err1;
	if (netdev->reg_state == NETREG_REGISTERED)
		msleep(HINIC_WAIT_PORT_IO_STOP);

	if (flag & BIT(0)) {
		memcpy(my_ets, ets_default, sizeof(struct ieee_ets));
		err1 = __set_hw_ets(nic_dev);
	}
	if (flag & BIT(1)) {
		my_pfc->pfc_en = 0;
		err2 = hinic_dcb_set_pfc(nic_dev->hwdev, false, pfc.pfc_en);
		if (err2)
			nicif_err(nic_dev, drv, netdev, "Failed to set pfc\n");
	}

	hinic_start_port_traffic_flow(nic_dev);

	return (err1 || err2) ? -EINVAL : 0;
}

u8 hinic_dcbnl_set_pfc_tool(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;
	u8 state = DCB_HW_CFG_CHG;
	int err;

	nic_dev->dcb_changes |= hinic_sync_dcb_cfg(nic_dev);
	if (!nic_dev->dcb_changes)
		return DCB_HW_CFG_CHG;

	if (nic_dev->dcb_changes & DCB_CFG_CHG_PFC) {
		u8 pfc_map = 0;

		hinic_dcb_get_pfc_map(nic_dev, dcb_cfg, &pfc_map);
		err = hinic_dcb_set_pfc(nic_dev->hwdev, dcb_cfg->pfc_state,
					pfc_map);
		if (err) {
			hinic_info(nic_dev, drv, "Failed to %s PFC\n",
				   dcb_cfg->pfc_state ? "enable" : "disable");
			state = DCB_HW_CFG_ERR;
			goto out;
		}

		if (dcb_cfg->pfc_state)
			hinic_info(nic_dev, drv, "Set PFC: 0x%x to hw done\n",
				   pfc_map);
		else
			hinic_info(nic_dev, drv, "Disable PFC, enable tx/rx pause\n");

		nic_dev->dcb_changes &= (~DCB_CFG_CHG_PFC);
	}
out:

	return state;
}

u8 hinic_dcbnl_set_all(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;
	u8 state = DCB_HW_CFG_CHG;
	int err;

	if (!(nic_dev->dcbx_cap & DCB_CAP_DCBX_VER_CEE))
		return DCB_HW_CFG_ERR;

	nic_dev->dcb_changes |= hinic_sync_dcb_cfg(nic_dev);
	if (!nic_dev->dcb_changes)
		return DCB_HW_CFG_NO_CHG;

	err = hinic_stop_port_traffic_flow(nic_dev);
	if (err)
		return DCB_HW_CFG_ERR;
	/* wait all traffic flow stopped */
	if (netdev->reg_state == NETREG_REGISTERED)
		msleep(HINIC_WAIT_PORT_IO_STOP);

	if (nic_dev->dcb_changes & DCB_CFG_CHG_UP_COS) {
		err = __set_hw_cos_up_map(nic_dev);
		if (err) {
			hinic_info(nic_dev, drv,
				   "Set cos_up map to hardware failed\n");
			state = DCB_HW_CFG_ERR;
			goto out;
		}

		nic_dev->dcb_changes &= (~DCB_CFG_CHG_UP_COS);
	}

	if (nic_dev->dcb_changes & (DCB_CFG_CHG_PG_TX | DCB_CFG_CHG_PG_RX)) {
		err = __set_hw_ets(nic_dev);
		if (err) {
			state = DCB_HW_CFG_ERR;
			goto out;
		}

		nic_dev->dcb_changes &=
				(~(DCB_CFG_CHG_PG_TX | DCB_CFG_CHG_PG_RX));
	}

	if (nic_dev->dcb_changes & DCB_CFG_CHG_PFC) {
		u8 pfc_map = 0;

		hinic_dcb_get_pfc_map(nic_dev, dcb_cfg, &pfc_map);
		err = hinic_dcb_set_pfc(nic_dev->hwdev, dcb_cfg->pfc_state,
					pfc_map);
		if (err) {
			hinic_info(nic_dev, drv, "Failed to %s PFC\n",
				   dcb_cfg->pfc_state ? "enable" : "disable");
			state = DCB_HW_CFG_ERR;
			goto out;
		}

		if (dcb_cfg->pfc_state)
			hinic_info(nic_dev, drv, "Set PFC: 0x%x to hw done\n",
				   pfc_map);
		else
			hinic_info(nic_dev, drv, "Disable PFC, enable tx/rx pause\n");

		nic_dev->dcb_changes &= (~DCB_CFG_CHG_PFC);
	}

out:
	hinic_start_port_traffic_flow(nic_dev);

	return state;
}

static int hinic_dcbnl_ieee_get_ets(struct net_device *netdev,
				    struct ieee_ets *ets)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct ieee_ets *my_ets = &nic_dev->hinic_ieee_ets;

	ets->ets_cap = my_ets->ets_cap;
	memcpy(ets->tc_tx_bw, my_ets->tc_tx_bw, sizeof(ets->tc_tx_bw));
	memcpy(ets->tc_rx_bw, my_ets->tc_rx_bw, sizeof(ets->tc_rx_bw));
	memcpy(ets->prio_tc, my_ets->prio_tc, sizeof(ets->prio_tc));
	memcpy(ets->tc_tsa, my_ets->tc_tsa, sizeof(ets->tc_tsa));

	return 0;
}

static int hinic_dcbnl_ieee_set_ets(struct net_device *netdev,
				    struct ieee_ets *ets)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;
	struct ieee_ets *my_ets = &nic_dev->hinic_ieee_ets;
	struct ieee_ets back_ets;
	int err, i;
	u8 max_tc = 0;
	u16 total_bw = 0;

	if (!(nic_dev->dcbx_cap & DCB_CAP_DCBX_VER_IEEE))
		return -EINVAL;

	if (!memcmp(ets->tc_tx_bw, my_ets->tc_tx_bw, sizeof(ets->tc_tx_bw)) &&
	    !memcmp(ets->tc_rx_bw, my_ets->tc_rx_bw, sizeof(ets->tc_rx_bw)) &&
	    !memcmp(ets->prio_tc, my_ets->prio_tc, sizeof(ets->prio_tc)) &&
	    !memcmp(ets->tc_tsa, my_ets->tc_tsa, sizeof(ets->tc_tsa)))
		return 0;

	for (i = 0; i < HINIC_DCB_TC_MAX; i++)
		total_bw += ets->tc_tx_bw[i];
	if (!total_bw)
		return -EINVAL;

	for (i = 0; i < dcb_cfg->pg_tcs; i++) {
		if (ets->prio_tc[i] > max_tc)
			max_tc = ets->prio_tc[i];
	}
	if (max_tc)
		max_tc++;

	if (max_tc > dcb_cfg->pg_tcs)
		return -EINVAL;

	max_tc = max_tc ? dcb_cfg->pg_tcs : 0;
	memcpy(&back_ets, my_ets, sizeof(struct ieee_ets));
	memcpy(my_ets->tc_tx_bw, ets->tc_tx_bw, sizeof(ets->tc_tx_bw));
	memcpy(my_ets->tc_rx_bw, ets->tc_rx_bw, sizeof(ets->tc_rx_bw));
	memcpy(my_ets->prio_tc, ets->prio_tc, sizeof(ets->prio_tc));
	memcpy(my_ets->tc_tsa, ets->tc_tsa, sizeof(ets->tc_tsa));

	if (max_tc != netdev_get_num_tc(netdev)) {
		err = hinic_setup_tc(netdev, max_tc);
		if (err) {
			nicif_err(nic_dev, drv, netdev,
				  "Failed to setup tc with max_tc: %d, err: %d\n",
				  max_tc, err);
			memcpy(my_ets, &back_ets, sizeof(struct ieee_ets));
			return err;
		}
	}

	err = hinic_stop_port_traffic_flow(nic_dev);
	if (err)
		return err;
	if (netdev->reg_state == NETREG_REGISTERED)
		msleep(HINIC_WAIT_PORT_IO_STOP);

	err = __set_hw_ets(nic_dev);

	hinic_start_port_traffic_flow(nic_dev);

	return err;
}

static int hinic_dcbnl_ieee_get_pfc(struct net_device *netdev,
				    struct ieee_pfc *pfc)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct ieee_pfc *my_pfc = &nic_dev->hinic_ieee_pfc;

	pfc->pfc_en = my_pfc->pfc_en;
	pfc->pfc_cap = my_pfc->pfc_cap;

	return 0;
}

static int hinic_dcbnl_ieee_set_pfc(struct net_device *netdev,
				    struct ieee_pfc *pfc)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;
	struct ieee_pfc *my_pfc = &nic_dev->hinic_ieee_pfc;
	struct ieee_ets *my_ets = &nic_dev->hinic_ieee_ets;
	int err, i;
	u8 pfc_map, max_tc;
	u8 outof_range_pfc = 0;
	bool pfc_en;

	if (!(nic_dev->dcbx_cap & DCB_CAP_DCBX_VER_IEEE))
		return -EINVAL;

	if (my_pfc->pfc_en == pfc->pfc_en)
		return 0;

	pfc_map = pfc->pfc_en & nic_dev->up_valid_bitmap;
	outof_range_pfc = pfc->pfc_en & (~nic_dev->up_valid_bitmap);
	if (outof_range_pfc)
		nicif_info(nic_dev, drv, netdev,
			   "pfc setting out of range, 0x%x will be ignored\n",
			   outof_range_pfc);

	err = hinic_stop_port_traffic_flow(nic_dev);
	if (err)
		return err;
	if (netdev->reg_state == NETREG_REGISTERED)
		msleep(HINIC_WAIT_PORT_IO_STOP);

	pfc_en = pfc_map ? true : false;
	max_tc = 0;
	for (i = 0; i < dcb_cfg->pg_tcs; i++) {
		if (my_ets->prio_tc[i] > max_tc)
			max_tc = my_ets->prio_tc[i];
	}
	pfc_en = max_tc ? pfc_en : false;

	err = hinic_dcb_set_pfc(nic_dev->hwdev, pfc_en, pfc_map);
	if (err) {
		hinic_info(nic_dev, drv,
			   "Failed to set pfc to hw with pfc_map: 0x%x err: %d\n",
			   pfc_map, err);
		hinic_start_port_traffic_flow(nic_dev);
		return err;
	}

	hinic_start_port_traffic_flow(nic_dev);
	my_pfc->pfc_en = pfc->pfc_en;
	hinic_info(nic_dev, drv,
		   "Set pfc successfully with pfc_map: 0x%x, pfc_en: %d\n",
		   pfc_map, pfc_en);

	return 0;
}

static int hinic_dcbnl_getnumtcs(struct net_device *netdev, int tcid, u8 *num)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_dcb_config *dcb_cfg = &nic_dev->dcb_cfg;

	if (!test_bit(HINIC_DCB_ENABLE, &nic_dev->flags))
		return -EINVAL;

	switch (tcid) {
	case DCB_NUMTCS_ATTR_PG:
		*num = dcb_cfg->pg_tcs;
		break;
	case DCB_NUMTCS_ATTR_PFC:
		*num = dcb_cfg->pfc_tcs;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int hinic_dcbnl_setnumtcs(struct net_device *netdev, int tcid, u8 num)
{
	return -EINVAL;
}

static u8 hinic_dcbnl_getpfcstate(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	return (u8)nic_dev->dcb_cfg.pfc_state;
}

static void hinic_dcbnl_setpfcstate(struct net_device *netdev, u8 state)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	nic_dev->tmp_dcb_cfg.pfc_state = !!state;
}

static u8 hinic_dcbnl_getdcbx(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	return nic_dev->dcbx_cap;
}

static u8 hinic_dcbnl_setdcbx(struct net_device *netdev, u8 mode)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int err;

	if (((mode & DCB_CAP_DCBX_VER_IEEE) && (mode & DCB_CAP_DCBX_VER_CEE)) ||
	    ((mode & DCB_CAP_DCBX_LLD_MANAGED) &&
	    (!(mode & DCB_CAP_DCBX_HOST)))) {
		nicif_info(nic_dev, drv, netdev,
			   "Set dcbx failed with invalid mode: %d\n", mode);
		return 1;
	}

	if (nic_dev->dcbx_cap == mode)
		return 0;
	nic_dev->dcbx_cap = mode;

	if (mode & DCB_CAP_DCBX_VER_CEE) {
		u8 mask = DCB_CFG_CHG_PFC | DCB_CFG_CHG_PG_TX |
			DCB_CFG_CHG_PG_RX;
		nic_dev->dcb_changes |= mask;
		hinic_dcbnl_set_all(netdev);
	} else if (mode & DCB_CAP_DCBX_VER_IEEE) {
		if (netdev_get_num_tc(netdev)) {
			err = hinic_setup_tc(netdev, 0);
			if (err) {
				nicif_err(nic_dev, drv, netdev,
					  "Failed to setup tc with mode: %d\n",
					  mode);
				return 1;
			}
		}

		hinic_dcbnl_set_df_ieee_cfg(netdev);
		hinic_force_port_relink(nic_dev->hwdev);
	} else {
		err = hinic_setup_tc(netdev, 0);
		if (err) {
			nicif_err(nic_dev, drv, netdev,
				  "Failed to setup tc with mode: %d\n", mode);
			return 1;
		}
	}
	nicif_info(nic_dev, drv, netdev, "Change dcbx mode to 0x%x\n", mode);

	return 0;
}

const struct dcbnl_rtnl_ops hinic_dcbnl_ops = {
	/* IEEE 802.1Qaz std */
	.ieee_getets	= hinic_dcbnl_ieee_get_ets,
	.ieee_setets	= hinic_dcbnl_ieee_set_ets,
	.ieee_getpfc	= hinic_dcbnl_ieee_get_pfc,
	.ieee_setpfc	= hinic_dcbnl_ieee_set_pfc,

	/* CEE std */
	.getstate	= hinic_dcbnl_get_state,
	.setstate	= hinic_dcbnl_set_state,
	.getpermhwaddr	= hinic_dcbnl_get_perm_hw_addr,
	.setpgtccfgtx	= hinic_dcbnl_set_pg_tc_cfg_tx,
	.setpgbwgcfgtx	= hinic_dcbnl_set_pg_bwg_cfg_tx,
	.setpgtccfgrx	= hinic_dcbnl_set_pg_tc_cfg_rx,
	.setpgbwgcfgrx	= hinic_dcbnl_set_pg_bwg_cfg_rx,
	.getpgtccfgtx	= hinic_dcbnl_get_pg_tc_cfg_tx,
	.getpgbwgcfgtx	= hinic_dcbnl_get_pg_bwg_cfg_tx,
	.getpgtccfgrx	= hinic_dcbnl_get_pg_tc_cfg_rx,
	.getpgbwgcfgrx	= hinic_dcbnl_get_pg_bwg_cfg_rx,
	.setpfccfg	= hinic_dcbnl_set_pfc_cfg,
	.getpfccfg	= hinic_dcbnl_get_pfc_cfg,
	.setall		= hinic_dcbnl_set_all,
	.getcap		= hinic_dcbnl_getcap,
	.getnumtcs	= hinic_dcbnl_getnumtcs,
	.setnumtcs	= hinic_dcbnl_setnumtcs,
	.getpfcstate	= hinic_dcbnl_getpfcstate,
	.setpfcstate	= hinic_dcbnl_setpfcstate,

	/* DCBX configuration */
	.getdcbx	= hinic_dcbnl_getdcbx,
	.setdcbx	= hinic_dcbnl_setdcbx,
};

int hinic_dcb_reset_hw_config(struct hinic_nic_dev *nic_dev)
{
	struct net_device *netdev = nic_dev->netdev;
	u8 state;

	hinic_dcb_config_init(nic_dev, &nic_dev->tmp_dcb_cfg);
	state = hinic_dcbnl_set_all(netdev);
	if (state == DCB_HW_CFG_ERR)
		return -EFAULT;

	if (state == DCB_HW_CFG_CHG)
		hinic_info(nic_dev, drv,
			   "Reset hardware DCB configuration done\n");

	return 0;
}

void hinic_configure_dcb(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int err;

	if (test_bit(HINIC_DCB_ENABLE, &nic_dev->flags)) {
		memcpy(&nic_dev->tmp_dcb_cfg, &nic_dev->save_dcb_cfg,
		       sizeof(nic_dev->tmp_dcb_cfg));
		hinic_dcbnl_set_all(netdev);
	} else {
		memcpy(&nic_dev->save_dcb_cfg, &nic_dev->tmp_dcb_cfg,
		       sizeof(nic_dev->save_dcb_cfg));
		err = hinic_dcb_reset_hw_config(nic_dev);
		if (err)
			nicif_warn(nic_dev, drv, netdev,
				   "Failed to reset hw dcb configuration\n");
	}
}

static bool __is_cos_up_map_change(struct hinic_nic_dev *nic_dev, u8 *cos_up)
{
	u8 cos, up;

	for (cos = 0; cos < nic_dev->max_cos; cos++) {
		up = cos_up[cos];
		if (BIT(up) != (nic_dev->up_valid_bitmap & BIT(up)))
			return true;
	}

	return false;
}

int __set_cos_up_map(struct hinic_nic_dev *nic_dev, u8 *cos_up)
{
	struct net_device *netdev;
	u8 state;
	int err = 0;

	if (!nic_dev || !cos_up)
		return -EINVAL;

	netdev = nic_dev->netdev;

	if (test_and_set_bit(HINIC_DCB_UP_COS_SETTING, &nic_dev->dcb_flags)) {
		nicif_err(nic_dev, drv, netdev,
			  "Cos_up map setting in inprocess, please try again later\n");
		return -EFAULT;
	}

	nicif_info(nic_dev, drv, netdev, "Set cos2up: %d%d%d%d%d%d%d%d\n",
		   cos_up[0], cos_up[1], cos_up[2], cos_up[3],
		   cos_up[4], cos_up[5], cos_up[6], cos_up[7]);

	if (!__is_cos_up_map_change(nic_dev, cos_up)) {
		nicif_err(nic_dev, drv, netdev,
			  "Same mapping, don't need to change anything\n");
		err = 0;
		goto out;
	}

	err = hinic_set_up_cos_map(nic_dev, nic_dev->max_cos, cos_up);
	if (err) {
		err = -EFAULT;
		goto out;
	}

	nic_dev->dcb_changes = DCB_CFG_CHG_PG_TX | DCB_CFG_CHG_PG_RX |
			       DCB_CFG_CHG_PFC | DCB_CFG_CHG_UP_COS;

	if (test_bit(HINIC_DCB_ENABLE, &nic_dev->flags)) {
		/* Change map in kernel */
		hinic_set_prio_tc_map(nic_dev);

		state = hinic_dcbnl_set_all(netdev);
		if (state == DCB_HW_CFG_ERR) {
			nicif_err(nic_dev, drv, netdev,
				  "Reconfig dcb to hw failed\n");
			err = -EFAULT;
		}
	}

out:
	clear_bit(HINIC_DCB_UP_COS_SETTING, &nic_dev->dcb_flags);

	return err;
}

int hinic_get_num_cos(struct hinic_nic_dev *nic_dev, u8 *num_cos)
{
	if (!nic_dev || !num_cos)
		return -EINVAL;

	*num_cos = nic_dev->max_cos;

	return 0;
}

int hinic_get_cos_up_map(struct hinic_nic_dev *nic_dev, u8 *num_cos,
			 u8 *cos_up)
{
	u8 up, cos;

	if (!nic_dev || !cos_up)
		return -EINVAL;

	for (cos = 0; cos < HINIC_DCB_COS_MAX; cos++) {
		for (up = 0; up < HINIC_DCB_UP_MAX; up++) {
			if (!(nic_dev->up_valid_bitmap & BIT(up)))
				continue;

			if (nic_dev->up_cos[up] == cos ||
			    nic_dev->up_cos[up] == nic_dev->default_cos_id)
				cos_up[cos] = up;
		}
	}

	*num_cos = nic_dev->max_cos;

	return 0;
}

static int __stop_port_flow(void *uld_array[], u32 num_dev)
{
	struct hinic_nic_dev *tmp_dev;
	u32 i, idx;
	int err;

	for (idx = 0; idx < num_dev; idx++) {
		tmp_dev = (struct hinic_nic_dev *)uld_array[idx];
		err = hinic_stop_port_traffic_flow(tmp_dev);
		if (err) {
			nicif_err(tmp_dev, drv, tmp_dev->netdev,
				  "Stop port traffic flow failed\n");
			goto stop_port_err;
		}
	}

	/* wait all traffic flow stopped */
	msleep(HINIC_WAIT_PORT_IO_STOP);

	return 0;

stop_port_err:
	for (i = 0; i < idx; i++) {
		tmp_dev = (struct hinic_nic_dev *)uld_array[i];
		hinic_start_port_traffic_flow(tmp_dev);
	}

	return err;
}

static void __start_port_flow(void *uld_array[], u32 num_dev)
{
	struct hinic_nic_dev *tmp_dev;
	u32 idx;

	for (idx = 0; idx < num_dev; idx++) {
		tmp_dev = (struct hinic_nic_dev *)uld_array[idx];
		hinic_start_port_traffic_flow(tmp_dev);
	}
}

/* for hinicadm tool, need to chang all port of the chip */
int hinic_set_cos_up_map(struct hinic_nic_dev *nic_dev, u8 *cos_up)
{
	void *uld_array[HINIC_MAX_PF_NUM];
	struct hinic_nic_dev *tmp_dev;
	u8 num_cos, old_cos_up[HINIC_DCB_COS_MAX] = {0};
	u32 i, idx, num_dev = 0;
	int err, rollback_err;

	/* Save old map, in case of set failed */
	err = hinic_get_cos_up_map(nic_dev, &num_cos, old_cos_up);
	if (err || !num_cos) {
		nicif_err(nic_dev, drv, nic_dev->netdev,
			  "Get old cos_up map failed\n");
		return -EFAULT;
	}

	if (!memcmp(cos_up, old_cos_up, sizeof(u8) * num_cos)) {
		nicif_info(nic_dev, drv, nic_dev->netdev,
			   "Same cos2up map, don't need to change anything\n");
		return 0;
	}

	/* Get all pf of this chip */
	err = hinic_get_pf_uld_array(nic_dev->pdev, &num_dev, uld_array);
	if (err) {
		nicif_err(nic_dev, drv, nic_dev->netdev,
			  "Get all pf private handle failed\n");
		return -EFAULT;
	}

	err = __stop_port_flow(uld_array, num_dev);
	if (err)
		return -EFAULT;

	for (idx = 0; idx < num_dev; idx++) {
		tmp_dev = (struct hinic_nic_dev *)uld_array[idx];
		err = __set_cos_up_map(tmp_dev, cos_up);
		if (err) {
			nicif_err(tmp_dev, drv, tmp_dev->netdev,
				  "Set cos_up map to hw failed\n");
			goto set_err;
		}
	}

	__start_port_flow(uld_array, num_dev);

	hinic_set_chip_cos_up_map(nic_dev->pdev, cos_up);

	return 0;

set_err:
	/* undo all settings */
	for (i = 0; i < idx; i++) {
		tmp_dev = (struct hinic_nic_dev *)uld_array[i];
		rollback_err = __set_cos_up_map(tmp_dev, old_cos_up);
		if (rollback_err)
			nicif_err(tmp_dev, drv, tmp_dev->netdev,
				  "Undo cos_up map to hw failed\n");
	}

	__start_port_flow(uld_array, num_dev);

	return err;
}
