972 lines
24 KiB
C
972 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright(c) 2021 Huawei Technologies Co., Ltd */
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/device.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/types.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/dcbnl.h>
|
|
|
|
#include "ossl_knl.h"
|
|
#include "hinic3_crm.h"
|
|
#include "hinic3_nic_cfg.h"
|
|
#include "hinic3_nic_dev.h"
|
|
#include "hinic3_hw.h"
|
|
#include "hinic3_rss.h"
|
|
|
|
/*lint -e806*/
|
|
static u16 num_qps;
|
|
module_param(num_qps, ushort, 0444);
|
|
MODULE_PARM_DESC(num_qps, "Number of Queue Pairs (default=0)");
|
|
|
|
#define MOD_PARA_VALIDATE_NUM_QPS(nic_dev, num_qps, out_qps) do { \
|
|
if ((num_qps) > (nic_dev)->max_qps) \
|
|
nic_warn(&(nic_dev)->pdev->dev, \
|
|
"Module Parameter %s value %u is out of range, " \
|
|
"Maximum value for the device: %u, using %u\n", \
|
|
#num_qps, num_qps, (nic_dev)->max_qps, \
|
|
(nic_dev)->max_qps); \
|
|
if ((num_qps) > (nic_dev)->max_qps) \
|
|
(out_qps) = (nic_dev)->max_qps; \
|
|
else if ((num_qps) > 0) \
|
|
(out_qps) = (num_qps); \
|
|
} while (0)
|
|
|
|
/* In rx, iq means cos */
|
|
static u8 hinic3_get_iqmap_by_tc(const u8 *prio_tc, u8 num_iq, u8 tc)
|
|
{
|
|
u8 i, map = 0;
|
|
|
|
for (i = 0; i < num_iq; i++) {
|
|
if (prio_tc[i] == tc)
|
|
map |= (u8)(1U << ((num_iq - 1) - i));
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
static u8 hinic3_get_tcid_by_rq(const u32 *indir_tbl, u8 num_tcs, u16 rq_id)
|
|
{
|
|
u16 tc_group_size;
|
|
int i;
|
|
u8 temp_num_tcs = num_tcs;
|
|
|
|
if (!num_tcs)
|
|
temp_num_tcs = 1;
|
|
|
|
tc_group_size = NIC_RSS_INDIR_SIZE / temp_num_tcs;
|
|
for (i = 0; i < NIC_RSS_INDIR_SIZE; i++) {
|
|
if (indir_tbl[i] == rq_id)
|
|
return (u8)(i / tc_group_size);
|
|
}
|
|
|
|
return 0xFF; /* Invalid TC */
|
|
}
|
|
|
|
static int hinic3_get_rq2iq_map(struct hinic3_nic_dev *nic_dev,
|
|
u16 num_rq, u8 num_tcs, u8 *prio_tc, u8 cos_num,
|
|
u32 *indir_tbl, u8 *map, u32 map_size)
|
|
{
|
|
u16 qid;
|
|
u8 tc_id;
|
|
u8 temp_num_tcs = num_tcs;
|
|
|
|
if (!num_tcs)
|
|
temp_num_tcs = 1;
|
|
|
|
if (num_rq > map_size) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Rq number(%u) exceed max map qid(%u)\n",
|
|
num_rq, map_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cos_num < HINIC_NUM_IQ_PER_FUNC) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Cos number(%u) less then map qid(%d)\n",
|
|
cos_num, HINIC_NUM_IQ_PER_FUNC);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (qid = 0; qid < num_rq; qid++) {
|
|
tc_id = hinic3_get_tcid_by_rq(indir_tbl, temp_num_tcs, qid);
|
|
map[qid] = hinic3_get_iqmap_by_tc(prio_tc,
|
|
HINIC_NUM_IQ_PER_FUNC, tc_id);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hinic3_fillout_indir_tbl(struct hinic3_nic_dev *nic_dev, u8 num_cos, u32 *indir)
|
|
{
|
|
u16 k, group_size, start_qid = 0, qp_num = 0;
|
|
int i = 0;
|
|
u8 j, cur_cos = 0, default_cos;
|
|
u8 valid_cos_map = hinic3_get_dev_valid_cos_map(nic_dev);
|
|
|
|
if (num_cos == 0) {
|
|
for (i = 0; i < NIC_RSS_INDIR_SIZE; i++)
|
|
indir[i] = i % nic_dev->q_params.num_qps;
|
|
} else {
|
|
group_size = NIC_RSS_INDIR_SIZE / num_cos;
|
|
|
|
for (j = 0; j < num_cos; j++) {
|
|
while (cur_cos < NIC_DCB_COS_MAX &&
|
|
nic_dev->hw_dcb_cfg.cos_qp_num[cur_cos] == 0)
|
|
cur_cos++;
|
|
|
|
if (cur_cos >= NIC_DCB_COS_MAX) {
|
|
if (BIT(nic_dev->hw_dcb_cfg.default_cos) & valid_cos_map)
|
|
default_cos = nic_dev->hw_dcb_cfg.default_cos;
|
|
else
|
|
default_cos = (u8)fls(valid_cos_map) - 1;
|
|
|
|
start_qid = nic_dev->hw_dcb_cfg.cos_qp_offset[default_cos];
|
|
qp_num = nic_dev->hw_dcb_cfg.cos_qp_num[default_cos];
|
|
} else {
|
|
start_qid = nic_dev->hw_dcb_cfg.cos_qp_offset[cur_cos];
|
|
qp_num = nic_dev->hw_dcb_cfg.cos_qp_num[cur_cos];
|
|
}
|
|
|
|
for (k = 0; k < group_size; k++)
|
|
indir[i++] = start_qid + k % qp_num;
|
|
|
|
cur_cos++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*lint -e528*/
|
|
int hinic3_rss_init(struct hinic3_nic_dev *nic_dev, u8 *rq2iq_map, u32 map_size, u8 dcb_en)
|
|
{
|
|
struct net_device *netdev = nic_dev->netdev;
|
|
u8 i, cos_num;
|
|
u8 cos_map[NIC_DCB_UP_MAX] = {0};
|
|
u8 cfg_map[NIC_DCB_UP_MAX] = {0};
|
|
int err;
|
|
|
|
if (dcb_en) {
|
|
cos_num = hinic3_get_dev_user_cos_num(nic_dev);
|
|
|
|
if (nic_dev->hw_dcb_cfg.trust == 0) {
|
|
memcpy(cfg_map, nic_dev->hw_dcb_cfg.pcp2cos, sizeof(cfg_map));
|
|
} else if (nic_dev->hw_dcb_cfg.trust == 1) {
|
|
for (i = 0; i < NIC_DCB_UP_MAX; i++)
|
|
cfg_map[i] = nic_dev->hw_dcb_cfg.dscp2cos[i * NIC_DCB_DSCP_NUM];
|
|
}
|
|
#define COS_CHANGE_OFFSET 4
|
|
for (i = 0; i < COS_CHANGE_OFFSET; i++)
|
|
cos_map[COS_CHANGE_OFFSET + i] = cfg_map[i];
|
|
|
|
for (i = 0; i < COS_CHANGE_OFFSET; i++)
|
|
cos_map[i] = cfg_map[NIC_DCB_UP_MAX - (i + 1)];
|
|
|
|
while (cos_num & (cos_num - 1))
|
|
cos_num++;
|
|
} else {
|
|
cos_num = 0;
|
|
}
|
|
|
|
err = hinic3_set_hw_rss_parameters(netdev, 1, cos_num, cos_map, dcb_en);
|
|
if (err)
|
|
return err;
|
|
|
|
err = hinic3_get_rq2iq_map(nic_dev, nic_dev->q_params.num_qps, cos_num, cos_map,
|
|
NIC_DCB_UP_MAX, nic_dev->rss_indir, rq2iq_map, map_size);
|
|
if (err)
|
|
nicif_err(nic_dev, drv, netdev, "Failed to get rq map\n");
|
|
return err;
|
|
}
|
|
|
|
/*lint -e528*/
|
|
void hinic3_rss_deinit(struct hinic3_nic_dev *nic_dev)
|
|
{
|
|
u8 cos_map[NIC_DCB_UP_MAX] = {0};
|
|
|
|
hinic3_rss_cfg(nic_dev->hwdev, 0, 0, cos_map, 1);
|
|
}
|
|
|
|
void hinic3_init_rss_parameters(struct net_device *netdev)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
nic_dev->rss_hash_engine = HINIC3_RSS_HASH_ENGINE_TYPE_XOR;
|
|
nic_dev->rss_type.tcp_ipv6_ext = 1;
|
|
nic_dev->rss_type.ipv6_ext = 1;
|
|
nic_dev->rss_type.tcp_ipv6 = 1;
|
|
nic_dev->rss_type.ipv6 = 1;
|
|
nic_dev->rss_type.tcp_ipv4 = 1;
|
|
nic_dev->rss_type.ipv4 = 1;
|
|
nic_dev->rss_type.udp_ipv6 = 1;
|
|
nic_dev->rss_type.udp_ipv4 = 1;
|
|
}
|
|
|
|
void hinic3_clear_rss_config(struct hinic3_nic_dev *nic_dev)
|
|
{
|
|
kfree(nic_dev->rss_hkey);
|
|
nic_dev->rss_hkey = NULL;
|
|
|
|
kfree(nic_dev->rss_indir);
|
|
nic_dev->rss_indir = NULL;
|
|
}
|
|
|
|
void hinic3_set_default_rss_indir(struct net_device *netdev)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
set_bit(HINIC3_RSS_DEFAULT_INDIR, &nic_dev->flags);
|
|
}
|
|
|
|
static void hinic3_maybe_reconfig_rss_indir(struct net_device *netdev, u8 dcb_en)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int i;
|
|
|
|
/* if dcb is enabled, user can not config rss indir table */
|
|
if (dcb_en) {
|
|
nicif_info(nic_dev, drv, netdev, "DCB is enabled, set default rss indir\n");
|
|
goto discard_user_rss_indir;
|
|
}
|
|
|
|
for (i = 0; i < NIC_RSS_INDIR_SIZE; i++) {
|
|
if (nic_dev->rss_indir[i] >= nic_dev->q_params.num_qps)
|
|
goto discard_user_rss_indir;
|
|
}
|
|
|
|
return;
|
|
|
|
discard_user_rss_indir:
|
|
hinic3_set_default_rss_indir(netdev);
|
|
}
|
|
|
|
static void decide_num_qps(struct hinic3_nic_dev *nic_dev)
|
|
{
|
|
u16 tmp_num_qps = nic_dev->max_qps;
|
|
u16 num_cpus = 0;
|
|
int i, node;
|
|
|
|
if (nic_dev->nic_cap.default_num_queues != 0 &&
|
|
nic_dev->nic_cap.default_num_queues < nic_dev->max_qps)
|
|
tmp_num_qps = nic_dev->nic_cap.default_num_queues;
|
|
|
|
MOD_PARA_VALIDATE_NUM_QPS(nic_dev, num_qps, tmp_num_qps);
|
|
|
|
for (i = 0; i < (int)num_online_cpus(); i++) {
|
|
node = (int)cpu_to_node(i);
|
|
if (node == dev_to_node(&nic_dev->pdev->dev))
|
|
num_cpus++;
|
|
}
|
|
|
|
if (!num_cpus)
|
|
num_cpus = (u16)num_online_cpus();
|
|
|
|
nic_dev->q_params.num_qps = (u16)min_t(u16, tmp_num_qps, num_cpus);
|
|
}
|
|
|
|
static void copy_value_to_rss_hkey(struct hinic3_nic_dev *nic_dev,
|
|
const u8 *hkey)
|
|
{
|
|
u32 i;
|
|
u32 *rss_hkey = (u32 *)nic_dev->rss_hkey;
|
|
|
|
memcpy(nic_dev->rss_hkey, hkey, NIC_RSS_KEY_SIZE);
|
|
|
|
/* make a copy of the key, and convert it to Big Endian */
|
|
for (i = 0; i < NIC_RSS_KEY_SIZE / sizeof(u32); i++)
|
|
nic_dev->rss_hkey_be[i] = cpu_to_be32(rss_hkey[i]);
|
|
}
|
|
|
|
static int alloc_rss_resource(struct hinic3_nic_dev *nic_dev)
|
|
{
|
|
u8 default_rss_key[NIC_RSS_KEY_SIZE] = {
|
|
0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
|
|
0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
|
|
0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
|
|
0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
|
|
0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa};
|
|
|
|
/* We request double spaces for the hash key,
|
|
* the second one holds the key of Big Edian
|
|
* format.
|
|
*/
|
|
nic_dev->rss_hkey =
|
|
kzalloc(NIC_RSS_KEY_SIZE *
|
|
HINIC3_RSS_KEY_RSV_NUM, GFP_KERNEL);
|
|
if (!nic_dev->rss_hkey) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Failed to alloc memory for rss_hkey\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* The second space is for big edian hash key */
|
|
nic_dev->rss_hkey_be = (u32 *)(nic_dev->rss_hkey +
|
|
NIC_RSS_KEY_SIZE);
|
|
copy_value_to_rss_hkey(nic_dev, (u8 *)default_rss_key);
|
|
|
|
nic_dev->rss_indir = kzalloc(sizeof(u32) * NIC_RSS_INDIR_SIZE, GFP_KERNEL);
|
|
if (!nic_dev->rss_indir) {
|
|
kfree(nic_dev->rss_hkey);
|
|
nic_dev->rss_hkey = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
set_bit(HINIC3_RSS_DEFAULT_INDIR, &nic_dev->flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*lint -e528*/
|
|
void hinic3_try_to_enable_rss(struct hinic3_nic_dev *nic_dev)
|
|
{
|
|
u8 cos_map[NIC_DCB_UP_MAX] = {0};
|
|
int err = 0;
|
|
|
|
if (!nic_dev)
|
|
return;
|
|
|
|
nic_dev->max_qps = hinic3_func_max_nic_qnum(nic_dev->hwdev);
|
|
if (nic_dev->max_qps <= 1 || !HINIC3_SUPPORT_RSS(nic_dev->hwdev))
|
|
goto set_q_params;
|
|
|
|
err = alloc_rss_resource(nic_dev);
|
|
if (err) {
|
|
nic_dev->max_qps = 1;
|
|
goto set_q_params;
|
|
}
|
|
|
|
set_bit(HINIC3_RSS_ENABLE, &nic_dev->flags);
|
|
nic_dev->max_qps = hinic3_func_max_nic_qnum(nic_dev->hwdev);
|
|
|
|
decide_num_qps(nic_dev);
|
|
|
|
hinic3_init_rss_parameters(nic_dev->netdev);
|
|
err = hinic3_set_hw_rss_parameters(nic_dev->netdev, 0, 0, cos_map,
|
|
test_bit(HINIC3_DCB_ENABLE, &nic_dev->flags) ? 1 : 0);
|
|
if (err) {
|
|
nic_err(&nic_dev->pdev->dev, "Failed to set hardware rss parameters\n");
|
|
|
|
hinic3_clear_rss_config(nic_dev);
|
|
nic_dev->max_qps = 1;
|
|
goto set_q_params;
|
|
}
|
|
return;
|
|
|
|
set_q_params:
|
|
clear_bit(HINIC3_RSS_ENABLE, &nic_dev->flags);
|
|
nic_dev->q_params.num_qps = nic_dev->max_qps;
|
|
}
|
|
|
|
static int hinic3_config_rss_hw_resource(struct hinic3_nic_dev *nic_dev,
|
|
u32 *indir_tbl)
|
|
{
|
|
int err;
|
|
|
|
err = hinic3_rss_set_indir_tbl(nic_dev->hwdev, indir_tbl);
|
|
if (err)
|
|
return err;
|
|
|
|
err = hinic3_set_rss_type(nic_dev->hwdev, nic_dev->rss_type);
|
|
if (err)
|
|
return err;
|
|
|
|
return hinic3_rss_set_hash_engine(nic_dev->hwdev,
|
|
nic_dev->rss_hash_engine);
|
|
}
|
|
|
|
int hinic3_set_hw_rss_parameters(struct net_device *netdev, u8 rss_en,
|
|
u8 cos_num, u8 *cos_map, u8 dcb_en)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int err;
|
|
|
|
/* RSS key */
|
|
err = hinic3_rss_set_hash_key(nic_dev->hwdev, nic_dev->rss_hkey);
|
|
if (err)
|
|
return err;
|
|
|
|
hinic3_maybe_reconfig_rss_indir(netdev, dcb_en);
|
|
|
|
if (test_bit(HINIC3_RSS_DEFAULT_INDIR, &nic_dev->flags))
|
|
hinic3_fillout_indir_tbl(nic_dev, cos_num, nic_dev->rss_indir);
|
|
|
|
err = hinic3_config_rss_hw_resource(nic_dev, nic_dev->rss_indir);
|
|
if (err)
|
|
return err;
|
|
|
|
err = hinic3_rss_cfg(nic_dev->hwdev, rss_en, cos_num, cos_map,
|
|
nic_dev->q_params.num_qps);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* for ethtool */
|
|
static int set_l4_rss_hash_ops(const struct ethtool_rxnfc *cmd,
|
|
struct nic_rss_type *rss_type)
|
|
{
|
|
u8 rss_l4_en = 0;
|
|
|
|
switch (cmd->data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)) {
|
|
case 0:
|
|
rss_l4_en = 0;
|
|
break;
|
|
case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
|
|
rss_l4_en = 1;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (cmd->flow_type) {
|
|
case TCP_V4_FLOW:
|
|
rss_type->tcp_ipv4 = rss_l4_en;
|
|
break;
|
|
case TCP_V6_FLOW:
|
|
rss_type->tcp_ipv6 = rss_l4_en;
|
|
break;
|
|
case UDP_V4_FLOW:
|
|
rss_type->udp_ipv4 = rss_l4_en;
|
|
break;
|
|
case UDP_V6_FLOW:
|
|
rss_type->udp_ipv6 = rss_l4_en;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int update_rss_hash_opts(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rxnfc *cmd,
|
|
struct nic_rss_type *rss_type)
|
|
{
|
|
int err;
|
|
|
|
switch (cmd->flow_type) {
|
|
case TCP_V4_FLOW:
|
|
case TCP_V6_FLOW:
|
|
case UDP_V4_FLOW:
|
|
case UDP_V6_FLOW:
|
|
err = set_l4_rss_hash_ops(cmd, rss_type);
|
|
if (err)
|
|
return err;
|
|
|
|
break;
|
|
case IPV4_FLOW:
|
|
rss_type->ipv4 = 1;
|
|
break;
|
|
case IPV6_FLOW:
|
|
rss_type->ipv6 = 1;
|
|
break;
|
|
default:
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Unsupported flow type\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_set_rss_hash_opts(struct hinic3_nic_dev *nic_dev, struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct nic_rss_type *rss_type = &nic_dev->rss_type;
|
|
int err;
|
|
|
|
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
|
|
cmd->data = 0;
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"RSS is disable, not support to set flow-hash\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* RSS does not support anything other than hashing
|
|
* to queues on src and dst IPs and ports
|
|
*/
|
|
if (cmd->data & ~(RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 |
|
|
RXH_L4_B_2_3))
|
|
return -EINVAL;
|
|
|
|
/* We need at least the IP SRC and DEST fields for hashing */
|
|
if (!(cmd->data & RXH_IP_SRC) || !(cmd->data & RXH_IP_DST))
|
|
return -EINVAL;
|
|
|
|
err = hinic3_get_rss_type(nic_dev->hwdev, rss_type);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Failed to get rss type\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
err = update_rss_hash_opts(nic_dev, cmd, rss_type);
|
|
if (err)
|
|
return err;
|
|
|
|
err = hinic3_set_rss_type(nic_dev->hwdev, *rss_type);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Failed to set rss type\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
nicif_info(nic_dev, drv, nic_dev->netdev, "Set rss hash options success\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void convert_rss_type(u8 rss_opt, struct ethtool_rxnfc *cmd)
|
|
{
|
|
if (rss_opt)
|
|
cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
|
|
}
|
|
|
|
static int hinic3_convert_rss_type(struct hinic3_nic_dev *nic_dev,
|
|
struct nic_rss_type *rss_type,
|
|
struct ethtool_rxnfc *cmd)
|
|
{
|
|
cmd->data = RXH_IP_SRC | RXH_IP_DST;
|
|
switch (cmd->flow_type) {
|
|
case TCP_V4_FLOW:
|
|
convert_rss_type(rss_type->tcp_ipv4, cmd);
|
|
break;
|
|
case TCP_V6_FLOW:
|
|
convert_rss_type(rss_type->tcp_ipv6, cmd);
|
|
break;
|
|
case UDP_V4_FLOW:
|
|
convert_rss_type(rss_type->udp_ipv4, cmd);
|
|
break;
|
|
case UDP_V6_FLOW:
|
|
convert_rss_type(rss_type->udp_ipv6, cmd);
|
|
break;
|
|
case IPV4_FLOW:
|
|
case IPV6_FLOW:
|
|
break;
|
|
default:
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Unsupported flow type\n");
|
|
cmd->data = 0;
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_get_rss_hash_opts(struct hinic3_nic_dev *nic_dev, struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct nic_rss_type rss_type = {0};
|
|
int err;
|
|
|
|
cmd->data = 0;
|
|
|
|
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags))
|
|
return 0;
|
|
|
|
err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Failed to get rss type\n");
|
|
return err;
|
|
}
|
|
|
|
return hinic3_convert_rss_type(nic_dev, &rss_type, cmd);
|
|
}
|
|
|
|
int hinic3_get_rxnfc(struct net_device *netdev,
|
|
struct ethtool_rxnfc *cmd, u32 *rule_locs)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int err = 0;
|
|
|
|
switch (cmd->cmd) {
|
|
case ETHTOOL_GRXRINGS:
|
|
cmd->data = nic_dev->q_params.num_qps;
|
|
break;
|
|
case ETHTOOL_GRXCLSRLCNT:
|
|
cmd->rule_cnt = (u32)nic_dev->rx_flow_rule.tot_num_rules;
|
|
break;
|
|
case ETHTOOL_GRXCLSRULE:
|
|
err = hinic3_ethtool_get_flow(nic_dev, cmd, cmd->fs.location);
|
|
break;
|
|
case ETHTOOL_GRXCLSRLALL:
|
|
err = hinic3_ethtool_get_all_flows(nic_dev, cmd, rule_locs);
|
|
break;
|
|
case ETHTOOL_GRXFH:
|
|
err = hinic3_get_rss_hash_opts(nic_dev, cmd);
|
|
break;
|
|
default:
|
|
err = -EOPNOTSUPP;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int err = 0;
|
|
|
|
switch (cmd->cmd) {
|
|
case ETHTOOL_SRXFH:
|
|
err = hinic3_set_rss_hash_opts(nic_dev, cmd);
|
|
break;
|
|
case ETHTOOL_SRXCLSRLINS:
|
|
err = hinic3_ethtool_flow_replace(nic_dev, &cmd->fs);
|
|
break;
|
|
case ETHTOOL_SRXCLSRLDEL:
|
|
err = hinic3_ethtool_flow_remove(nic_dev, cmd->fs.location);
|
|
break;
|
|
default:
|
|
err = -EOPNOTSUPP;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static u16 hinic3_max_channels(struct hinic3_nic_dev *nic_dev)
|
|
{
|
|
u8 tcs = (u8)netdev_get_num_tc(nic_dev->netdev);
|
|
|
|
return tcs ? nic_dev->max_qps / tcs : nic_dev->max_qps;
|
|
}
|
|
|
|
static u16 hinic3_curr_channels(struct hinic3_nic_dev *nic_dev)
|
|
{
|
|
if (netif_running(nic_dev->netdev))
|
|
return nic_dev->q_params.num_qps ?
|
|
nic_dev->q_params.num_qps : 1;
|
|
else
|
|
return (u16)min_t(u16, hinic3_max_channels(nic_dev),
|
|
nic_dev->q_params.num_qps);
|
|
}
|
|
|
|
void hinic3_get_channels(struct net_device *netdev,
|
|
struct ethtool_channels *channels)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
channels->max_rx = 0;
|
|
channels->max_tx = 0;
|
|
channels->max_other = 0;
|
|
/* report maximum channels */
|
|
channels->max_combined = hinic3_max_channels(nic_dev);
|
|
channels->rx_count = 0;
|
|
channels->tx_count = 0;
|
|
channels->other_count = 0;
|
|
/* report flow director queues as maximum channels */
|
|
channels->combined_count = hinic3_curr_channels(nic_dev);
|
|
}
|
|
|
|
static int hinic3_validate_channel_parameter(struct net_device *netdev,
|
|
const struct ethtool_channels *channels)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
u16 max_channel = hinic3_max_channels(nic_dev);
|
|
unsigned int count = channels->combined_count;
|
|
|
|
if (!count) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Unsupported combined_count=0\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (channels->tx_count || channels->rx_count || channels->other_count) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Setting rx/tx/other count not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (count > max_channel) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Combined count %u exceed limit %u\n", count,
|
|
max_channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void change_num_channel_reopen_handler(struct hinic3_nic_dev *nic_dev,
|
|
const void *priv_data)
|
|
{
|
|
hinic3_set_default_rss_indir(nic_dev->netdev);
|
|
}
|
|
|
|
int hinic3_set_channels(struct net_device *netdev,
|
|
struct ethtool_channels *channels)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic3_dyna_txrxq_params q_params = {0};
|
|
unsigned int count = channels->combined_count;
|
|
int err;
|
|
u8 user_cos_num = hinic3_get_dev_user_cos_num(nic_dev);
|
|
|
|
if (hinic3_validate_channel_parameter(netdev, channels))
|
|
return -EINVAL;
|
|
|
|
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"This function don't support RSS, only support 1 queue pair\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (test_bit(HINIC3_DCB_ENABLE, &nic_dev->flags)) {
|
|
if (count < user_cos_num) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"DCB is on, channels num should more than valid cos num:%u\n",
|
|
user_cos_num);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
if (HINIC3_SUPPORT_FDIR(nic_dev->hwdev) &&
|
|
!hinic3_validate_channel_setting_in_ntuple(nic_dev, count))
|
|
return -EOPNOTSUPP;
|
|
|
|
nicif_info(nic_dev, drv, netdev, "Set max combined queue number from %u to %u\n",
|
|
nic_dev->q_params.num_qps, count);
|
|
|
|
if (netif_running(netdev)) {
|
|
q_params = nic_dev->q_params;
|
|
q_params.num_qps = (u16)count;
|
|
q_params.txqs_res = NULL;
|
|
q_params.rxqs_res = NULL;
|
|
q_params.irq_cfg = NULL;
|
|
|
|
nicif_info(nic_dev, drv, netdev, "Restarting channel\n");
|
|
err = hinic3_change_channel_settings(nic_dev, &q_params,
|
|
change_num_channel_reopen_handler, NULL);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to change channel settings\n");
|
|
return -EFAULT;
|
|
}
|
|
} else {
|
|
/* Discard user configured rss */
|
|
hinic3_set_default_rss_indir(netdev);
|
|
nic_dev->q_params.num_qps = (u16)count;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
u32 hinic3_get_rxfh_indir_size(struct net_device *netdev)
|
|
{
|
|
return NIC_RSS_INDIR_SIZE;
|
|
}
|
|
#endif
|
|
|
|
static int set_rss_rxfh(struct net_device *netdev, const u32 *indir,
|
|
const u8 *key)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int err;
|
|
|
|
if (indir) {
|
|
err = hinic3_rss_set_indir_tbl(nic_dev->hwdev, indir);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Failed to set rss indir table\n");
|
|
return -EFAULT;
|
|
}
|
|
clear_bit(HINIC3_RSS_DEFAULT_INDIR, &nic_dev->flags);
|
|
|
|
memcpy(nic_dev->rss_indir, indir,
|
|
sizeof(u32) * NIC_RSS_INDIR_SIZE);
|
|
nicif_info(nic_dev, drv, netdev, "Change rss indir success\n");
|
|
}
|
|
|
|
if (key) {
|
|
err = hinic3_rss_set_hash_key(nic_dev->hwdev, key);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to set rss key\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
copy_value_to_rss_hkey(nic_dev, key);
|
|
nicif_info(nic_dev, drv, netdev, "Change rss key success\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(ETHTOOL_GRSSH) && defined(ETHTOOL_SRSSH)
|
|
u32 hinic3_get_rxfh_key_size(struct net_device *netdev)
|
|
{
|
|
return NIC_RSS_KEY_SIZE;
|
|
}
|
|
|
|
#ifdef HAVE_RXFH_HASHFUNC
|
|
int hinic3_get_rxfh(struct net_device *netdev, u32 *indir, u8 *key, u8 *hfunc)
|
|
#else
|
|
int hinic3_get_rxfh(struct net_device *netdev, u32 *indir, u8 *key)
|
|
#endif
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int err = 0;
|
|
|
|
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Rss is disable\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
#ifdef HAVE_RXFH_HASHFUNC
|
|
if (hfunc)
|
|
*hfunc = nic_dev->rss_hash_engine ?
|
|
ETH_RSS_HASH_TOP : ETH_RSS_HASH_XOR;
|
|
#endif
|
|
|
|
if (indir) {
|
|
err = hinic3_rss_get_indir_tbl(nic_dev->hwdev, indir);
|
|
if (err)
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (key)
|
|
memcpy(key, nic_dev->rss_hkey, NIC_RSS_KEY_SIZE);
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef HAVE_RXFH_HASHFUNC
|
|
int hinic3_set_rxfh(struct net_device *netdev, const u32 *indir, const u8 *key,
|
|
const u8 hfunc)
|
|
#else
|
|
#ifdef HAVE_RXFH_NONCONST
|
|
int hinic3_set_rxfh(struct net_device *netdev, u32 *indir, u8 *key)
|
|
#else
|
|
int hinic3_set_rxfh(struct net_device *netdev, const u32 *indir, const u8 *key)
|
|
#endif
|
|
#endif /* HAVE_RXFH_HASHFUNC */
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int err = 0;
|
|
|
|
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Not support to set rss parameters when rss is disable\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (test_bit(HINIC3_DCB_ENABLE, &nic_dev->flags) && indir) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Not support to set indir when DCB is enabled\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
#ifdef HAVE_RXFH_HASHFUNC
|
|
if (hfunc != ETH_RSS_HASH_NO_CHANGE) {
|
|
if (hfunc != ETH_RSS_HASH_TOP && hfunc != ETH_RSS_HASH_XOR) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Not support to set hfunc type except TOP and XOR\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
nic_dev->rss_hash_engine = (hfunc == ETH_RSS_HASH_XOR) ?
|
|
HINIC3_RSS_HASH_ENGINE_TYPE_XOR :
|
|
HINIC3_RSS_HASH_ENGINE_TYPE_TOEP;
|
|
err = hinic3_rss_set_hash_engine(nic_dev->hwdev,
|
|
nic_dev->rss_hash_engine);
|
|
if (err)
|
|
return -EFAULT;
|
|
|
|
nicif_info(nic_dev, drv, netdev,
|
|
"Change hfunc to RSS_HASH_%s success\n",
|
|
(hfunc == ETH_RSS_HASH_XOR) ? "XOR" : "TOP");
|
|
}
|
|
#endif
|
|
err = set_rss_rxfh(netdev, indir, key);
|
|
|
|
return err;
|
|
}
|
|
|
|
#else /* !(defined(ETHTOOL_GRSSH) && defined(ETHTOOL_SRSSH)) */
|
|
|
|
#ifdef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
int hinic3_get_rxfh_indir(struct net_device *netdev,
|
|
struct ethtool_rxfh_indir *indir1)
|
|
#else
|
|
int hinic3_get_rxfh_indir(struct net_device *netdev, u32 *indir)
|
|
#endif
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int err = 0;
|
|
#ifdef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
u32 *indir = NULL;
|
|
|
|
/* In a low version kernel(eg:suse 11.2), call the interface twice.
|
|
* First call to get the size value,
|
|
* and second call to get the rxfh indir according to the size value.
|
|
*/
|
|
if (indir1->size == 0) {
|
|
indir1->size = NIC_RSS_INDIR_SIZE;
|
|
return 0;
|
|
}
|
|
|
|
if (indir1->size < NIC_RSS_INDIR_SIZE) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Failed to get rss indir, rss size(%d) is more than system rss size(%u).\n",
|
|
NIC_RSS_INDIR_SIZE, indir1->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
indir = indir1->ring_index;
|
|
#endif
|
|
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Rss is disable\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (indir)
|
|
err = hinic3_rss_get_indir_tbl(nic_dev->hwdev, indir);
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
int hinic3_set_rxfh_indir(struct net_device *netdev,
|
|
const struct ethtool_rxfh_indir *indir1)
|
|
#else
|
|
int hinic3_set_rxfh_indir(struct net_device *netdev, const u32 *indir)
|
|
#endif
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
#ifdef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
const u32 *indir = NULL;
|
|
|
|
if (indir1->size != NIC_RSS_INDIR_SIZE) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Failed to set rss indir, rss size(%d) is more than system rss size(%u).\n",
|
|
NIC_RSS_INDIR_SIZE, indir1->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
indir = indir1->ring_index;
|
|
#endif
|
|
|
|
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Not support to set rss indir when rss is disable\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (test_bit(HINIC3_DCB_ENABLE, &nic_dev->flags) && indir) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Not support to set indir when DCB is enabled\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return set_rss_rxfh(netdev, indir, NULL);
|
|
}
|
|
|
|
#endif /* defined(ETHTOOL_GRSSH) && defined(ETHTOOL_SRSSH) */
|
|
|