905 lines
25 KiB
C
905 lines
25 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/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 "ossl_knl.h"
|
|
#include "hinic3_crm.h"
|
|
#include "hinic3_nic_cfg.h"
|
|
#include "hinic3_nic_dev.h"
|
|
|
|
#define MAX_NUM_OF_ETHTOOL_NTUPLE_RULES BIT(9)
|
|
struct hinic3_ethtool_rx_flow_rule {
|
|
struct list_head list;
|
|
struct ethtool_rx_flow_spec flow_spec;
|
|
};
|
|
|
|
static void tcam_translate_key_y(u8 *key_y, const u8 *src_input, const u8 *mask, u8 len)
|
|
{
|
|
u8 idx;
|
|
|
|
for (idx = 0; idx < len; idx++)
|
|
key_y[idx] = src_input[idx] & mask[idx];
|
|
}
|
|
|
|
static void tcam_translate_key_x(u8 *key_x, const u8 *key_y, const u8 *mask, u8 len)
|
|
{
|
|
u8 idx;
|
|
|
|
for (idx = 0; idx < len; idx++)
|
|
key_x[idx] = key_y[idx] ^ mask[idx];
|
|
}
|
|
|
|
static void tcam_key_calculate(struct tag_tcam_key *tcam_key,
|
|
struct nic_tcam_cfg_rule *fdir_tcam_rule)
|
|
{
|
|
tcam_translate_key_y(fdir_tcam_rule->key.y,
|
|
(u8 *)(&tcam_key->key_info),
|
|
(u8 *)(&tcam_key->key_mask), TCAM_FLOW_KEY_SIZE);
|
|
tcam_translate_key_x(fdir_tcam_rule->key.x, fdir_tcam_rule->key.y,
|
|
(u8 *)(&tcam_key->key_mask), TCAM_FLOW_KEY_SIZE);
|
|
}
|
|
|
|
#define TCAM_IPV4_TYPE 0
|
|
#define TCAM_IPV6_TYPE 1
|
|
|
|
static int hinic3_base_ipv4_parse(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
struct tag_tcam_key *tcam_key)
|
|
{
|
|
struct ethtool_tcpip4_spec *mask = &fs->m_u.tcp_ip4_spec;
|
|
struct ethtool_tcpip4_spec *val = &fs->h_u.tcp_ip4_spec;
|
|
u32 temp;
|
|
|
|
switch (mask->ip4src) {
|
|
case U32_MAX:
|
|
temp = ntohl(val->ip4src);
|
|
tcam_key->key_info.sipv4_h = high_16_bits(temp);
|
|
tcam_key->key_info.sipv4_l = low_16_bits(temp);
|
|
|
|
tcam_key->key_mask.sipv4_h = U16_MAX;
|
|
tcam_key->key_mask.sipv4_l = U16_MAX;
|
|
break;
|
|
case 0:
|
|
break;
|
|
|
|
default:
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "invalid src_ip mask\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (mask->ip4dst) {
|
|
case U32_MAX:
|
|
temp = ntohl(val->ip4dst);
|
|
tcam_key->key_info.dipv4_h = high_16_bits(temp);
|
|
tcam_key->key_info.dipv4_l = low_16_bits(temp);
|
|
|
|
tcam_key->key_mask.dipv4_h = U16_MAX;
|
|
tcam_key->key_mask.dipv4_l = U16_MAX;
|
|
break;
|
|
case 0:
|
|
break;
|
|
|
|
default:
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "invalid src_ip mask\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tcam_key->key_info.ip_type = TCAM_IPV4_TYPE;
|
|
tcam_key->key_mask.ip_type = TCAM_IP_TYPE_MASK;
|
|
|
|
tcam_key->key_info.function_id = hinic3_global_func_id(nic_dev->hwdev);
|
|
tcam_key->key_mask.function_id = TCAM_FUNC_ID_MASK;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_fdir_tcam_ipv4_l4_init(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
struct tag_tcam_key *tcam_key)
|
|
{
|
|
struct ethtool_tcpip4_spec *l4_mask = &fs->m_u.tcp_ip4_spec;
|
|
struct ethtool_tcpip4_spec *l4_val = &fs->h_u.tcp_ip4_spec;
|
|
int err;
|
|
|
|
err = hinic3_base_ipv4_parse(nic_dev, fs, tcam_key);
|
|
if (err)
|
|
return err;
|
|
|
|
tcam_key->key_info.dport = ntohs(l4_val->pdst);
|
|
tcam_key->key_mask.dport = l4_mask->pdst;
|
|
|
|
tcam_key->key_info.sport = ntohs(l4_val->psrc);
|
|
tcam_key->key_mask.sport = l4_mask->psrc;
|
|
|
|
if (fs->flow_type == TCP_V4_FLOW)
|
|
tcam_key->key_info.ip_proto = IPPROTO_TCP;
|
|
else
|
|
tcam_key->key_info.ip_proto = IPPROTO_UDP;
|
|
tcam_key->key_mask.ip_proto = U8_MAX;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_fdir_tcam_ipv4_init(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
struct tag_tcam_key *tcam_key)
|
|
{
|
|
struct ethtool_usrip4_spec *l3_mask = &fs->m_u.usr_ip4_spec;
|
|
struct ethtool_usrip4_spec *l3_val = &fs->h_u.usr_ip4_spec;
|
|
int err;
|
|
|
|
err = hinic3_base_ipv4_parse(nic_dev, fs, tcam_key);
|
|
if (err)
|
|
return err;
|
|
|
|
tcam_key->key_info.ip_proto = l3_val->proto;
|
|
tcam_key->key_mask.ip_proto = l3_mask->proto;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef UNSUPPORT_NTUPLE_IPV6
|
|
enum ipv6_parse_res {
|
|
IPV6_MASK_INVALID,
|
|
IPV6_MASK_ALL_MASK,
|
|
IPV6_MASK_ALL_ZERO,
|
|
};
|
|
|
|
enum ipv6_index {
|
|
IPV6_IDX0,
|
|
IPV6_IDX1,
|
|
IPV6_IDX2,
|
|
IPV6_IDX3,
|
|
};
|
|
|
|
static int ipv6_mask_parse(const u32 *ipv6_mask)
|
|
{
|
|
if (ipv6_mask[IPV6_IDX0] == 0 && ipv6_mask[IPV6_IDX1] == 0 &&
|
|
ipv6_mask[IPV6_IDX2] == 0 && ipv6_mask[IPV6_IDX3] == 0)
|
|
return IPV6_MASK_ALL_ZERO;
|
|
|
|
if (ipv6_mask[IPV6_IDX0] == U32_MAX &&
|
|
ipv6_mask[IPV6_IDX1] == U32_MAX &&
|
|
ipv6_mask[IPV6_IDX2] == U32_MAX && ipv6_mask[IPV6_IDX3] == U32_MAX)
|
|
return IPV6_MASK_ALL_MASK;
|
|
|
|
return IPV6_MASK_INVALID;
|
|
}
|
|
|
|
static int hinic3_base_ipv6_parse(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
struct tag_tcam_key *tcam_key)
|
|
{
|
|
struct ethtool_tcpip6_spec *mask = &fs->m_u.tcp_ip6_spec;
|
|
struct ethtool_tcpip6_spec *val = &fs->h_u.tcp_ip6_spec;
|
|
int parse_res;
|
|
u32 temp;
|
|
|
|
parse_res = ipv6_mask_parse((u32 *)mask->ip6src);
|
|
if (parse_res == IPV6_MASK_ALL_MASK) {
|
|
temp = ntohl(val->ip6src[IPV6_IDX0]);
|
|
tcam_key->key_info_ipv6.sipv6_key0 = high_16_bits(temp);
|
|
tcam_key->key_info_ipv6.sipv6_key1 = low_16_bits(temp);
|
|
temp = ntohl(val->ip6src[IPV6_IDX1]);
|
|
tcam_key->key_info_ipv6.sipv6_key2 = high_16_bits(temp);
|
|
tcam_key->key_info_ipv6.sipv6_key3 = low_16_bits(temp);
|
|
temp = ntohl(val->ip6src[IPV6_IDX2]);
|
|
tcam_key->key_info_ipv6.sipv6_key4 = high_16_bits(temp);
|
|
tcam_key->key_info_ipv6.sipv6_key5 = low_16_bits(temp);
|
|
temp = ntohl(val->ip6src[IPV6_IDX3]);
|
|
tcam_key->key_info_ipv6.sipv6_key6 = high_16_bits(temp);
|
|
tcam_key->key_info_ipv6.sipv6_key7 = low_16_bits(temp);
|
|
|
|
tcam_key->key_mask_ipv6.sipv6_key0 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.sipv6_key1 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.sipv6_key2 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.sipv6_key3 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.sipv6_key4 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.sipv6_key5 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.sipv6_key6 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.sipv6_key7 = U16_MAX;
|
|
} else if (parse_res == IPV6_MASK_INVALID) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "invalid src_ipv6 mask\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
parse_res = ipv6_mask_parse((u32 *)mask->ip6dst);
|
|
if (parse_res == IPV6_MASK_ALL_MASK) {
|
|
temp = ntohl(val->ip6dst[IPV6_IDX0]);
|
|
tcam_key->key_info_ipv6.dipv6_key0 = high_16_bits(temp);
|
|
tcam_key->key_info_ipv6.dipv6_key1 = low_16_bits(temp);
|
|
temp = ntohl(val->ip6dst[IPV6_IDX1]);
|
|
tcam_key->key_info_ipv6.dipv6_key2 = high_16_bits(temp);
|
|
tcam_key->key_info_ipv6.dipv6_key3 = low_16_bits(temp);
|
|
temp = ntohl(val->ip6dst[IPV6_IDX2]);
|
|
tcam_key->key_info_ipv6.dipv6_key4 = high_16_bits(temp);
|
|
tcam_key->key_info_ipv6.dipv6_key5 = low_16_bits(temp);
|
|
temp = ntohl(val->ip6dst[IPV6_IDX3]);
|
|
tcam_key->key_info_ipv6.dipv6_key6 = high_16_bits(temp);
|
|
tcam_key->key_info_ipv6.dipv6_key7 = low_16_bits(temp);
|
|
|
|
tcam_key->key_mask_ipv6.dipv6_key0 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.dipv6_key1 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.dipv6_key2 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.dipv6_key3 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.dipv6_key4 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.dipv6_key5 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.dipv6_key6 = U16_MAX;
|
|
tcam_key->key_mask_ipv6.dipv6_key7 = U16_MAX;
|
|
} else if (parse_res == IPV6_MASK_INVALID) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "invalid dst_ipv6 mask\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tcam_key->key_info_ipv6.ip_type = TCAM_IPV6_TYPE;
|
|
tcam_key->key_mask_ipv6.ip_type = TCAM_IP_TYPE_MASK;
|
|
|
|
tcam_key->key_info_ipv6.function_id =
|
|
hinic3_global_func_id(nic_dev->hwdev);
|
|
tcam_key->key_mask_ipv6.function_id = TCAM_FUNC_ID_MASK;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_fdir_tcam_ipv6_l4_init(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
struct tag_tcam_key *tcam_key)
|
|
{
|
|
struct ethtool_tcpip6_spec *l4_mask = &fs->m_u.tcp_ip6_spec;
|
|
struct ethtool_tcpip6_spec *l4_val = &fs->h_u.tcp_ip6_spec;
|
|
int err;
|
|
|
|
err = hinic3_base_ipv6_parse(nic_dev, fs, tcam_key);
|
|
if (err)
|
|
return err;
|
|
|
|
tcam_key->key_info_ipv6.dport = ntohs(l4_val->pdst);
|
|
tcam_key->key_mask_ipv6.dport = l4_mask->pdst;
|
|
|
|
tcam_key->key_info_ipv6.sport = ntohs(l4_val->psrc);
|
|
tcam_key->key_mask_ipv6.sport = l4_mask->psrc;
|
|
|
|
if (fs->flow_type == TCP_V6_FLOW)
|
|
tcam_key->key_info_ipv6.ip_proto = NEXTHDR_TCP;
|
|
else
|
|
tcam_key->key_info_ipv6.ip_proto = NEXTHDR_UDP;
|
|
tcam_key->key_mask_ipv6.ip_proto = U8_MAX;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_fdir_tcam_ipv6_init(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
struct tag_tcam_key *tcam_key)
|
|
{
|
|
struct ethtool_usrip6_spec *l3_mask = &fs->m_u.usr_ip6_spec;
|
|
struct ethtool_usrip6_spec *l3_val = &fs->h_u.usr_ip6_spec;
|
|
int err;
|
|
|
|
err = hinic3_base_ipv6_parse(nic_dev, fs, tcam_key);
|
|
if (err)
|
|
return err;
|
|
|
|
tcam_key->key_info_ipv6.ip_proto = l3_val->l4_proto;
|
|
tcam_key->key_mask_ipv6.ip_proto = l3_mask->l4_proto;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int hinic3_fdir_tcam_info_init(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
struct tag_tcam_key *tcam_key,
|
|
struct nic_tcam_cfg_rule *fdir_tcam_rule)
|
|
{
|
|
int err;
|
|
|
|
switch (fs->flow_type) {
|
|
case TCP_V4_FLOW:
|
|
case UDP_V4_FLOW:
|
|
err = hinic3_fdir_tcam_ipv4_l4_init(nic_dev, fs, tcam_key);
|
|
if (err)
|
|
return err;
|
|
break;
|
|
case IP_USER_FLOW:
|
|
err = hinic3_fdir_tcam_ipv4_init(nic_dev, fs, tcam_key);
|
|
if (err)
|
|
return err;
|
|
break;
|
|
#ifndef UNSUPPORT_NTUPLE_IPV6
|
|
case TCP_V6_FLOW:
|
|
case UDP_V6_FLOW:
|
|
err = hinic3_fdir_tcam_ipv6_l4_init(nic_dev, fs, tcam_key);
|
|
if (err)
|
|
return err;
|
|
break;
|
|
case IPV6_USER_FLOW:
|
|
err = hinic3_fdir_tcam_ipv6_init(nic_dev, fs, tcam_key);
|
|
if (err)
|
|
return err;
|
|
break;
|
|
#endif
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
tcam_key->key_info.tunnel_type = 0;
|
|
tcam_key->key_mask.tunnel_type = TCAM_TUNNEL_TYPE_MASK;
|
|
|
|
fdir_tcam_rule->data.qid = (u32)fs->ring_cookie;
|
|
tcam_key_calculate(tcam_key, fdir_tcam_rule);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void hinic3_flush_rx_flow_rule(struct hinic3_nic_dev *nic_dev)
|
|
{
|
|
struct hinic3_tcam_info *tcam_info = &nic_dev->tcam;
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule = NULL;
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule_tmp = NULL;
|
|
struct hinic3_tcam_filter *tcam_iter = NULL;
|
|
struct hinic3_tcam_filter *tcam_iter_tmp = NULL;
|
|
struct hinic3_tcam_dynamic_block *block = NULL;
|
|
struct hinic3_tcam_dynamic_block *block_tmp = NULL;
|
|
struct list_head *dynamic_list =
|
|
&tcam_info->tcam_dynamic_info.tcam_dynamic_list;
|
|
|
|
if (!list_empty(&tcam_info->tcam_list)) {
|
|
list_for_each_entry_safe(tcam_iter, tcam_iter_tmp,
|
|
&tcam_info->tcam_list,
|
|
tcam_filter_list) {
|
|
list_del(&tcam_iter->tcam_filter_list);
|
|
kfree(tcam_iter);
|
|
}
|
|
}
|
|
if (!list_empty(dynamic_list)) {
|
|
list_for_each_entry_safe(block, block_tmp, dynamic_list,
|
|
block_list) {
|
|
list_del(&block->block_list);
|
|
kfree(block);
|
|
}
|
|
}
|
|
|
|
if (!list_empty(&nic_dev->rx_flow_rule.rules)) {
|
|
list_for_each_entry_safe(eth_rule, eth_rule_tmp,
|
|
&nic_dev->rx_flow_rule.rules, list) {
|
|
list_del(ð_rule->list);
|
|
kfree(eth_rule);
|
|
}
|
|
}
|
|
|
|
if (HINIC3_SUPPORT_FDIR(nic_dev->hwdev)) {
|
|
hinic3_flush_tcam_rule(nic_dev->hwdev);
|
|
hinic3_set_fdir_tcam_rule_filter(nic_dev->hwdev, false);
|
|
}
|
|
}
|
|
|
|
static struct hinic3_tcam_dynamic_block *
|
|
hinic3_alloc_dynamic_block_resource(struct hinic3_nic_dev *nic_dev,
|
|
struct hinic3_tcam_info *tcam_info,
|
|
u16 dynamic_block_id)
|
|
{
|
|
struct hinic3_tcam_dynamic_block *dynamic_block_ptr = NULL;
|
|
|
|
dynamic_block_ptr = kzalloc(sizeof(*dynamic_block_ptr), GFP_KERNEL);
|
|
if (!dynamic_block_ptr)
|
|
return NULL;
|
|
|
|
dynamic_block_ptr->dynamic_block_id = dynamic_block_id;
|
|
list_add_tail(&dynamic_block_ptr->block_list,
|
|
&tcam_info->tcam_dynamic_info.tcam_dynamic_list);
|
|
|
|
tcam_info->tcam_dynamic_info.dynamic_block_cnt++;
|
|
|
|
return dynamic_block_ptr;
|
|
}
|
|
|
|
static void hinic3_free_dynamic_block_resource(struct hinic3_tcam_info *tcam_info,
|
|
struct hinic3_tcam_dynamic_block *block_ptr)
|
|
{
|
|
if (!block_ptr)
|
|
return;
|
|
|
|
list_del(&block_ptr->block_list);
|
|
kfree(block_ptr);
|
|
|
|
tcam_info->tcam_dynamic_info.dynamic_block_cnt--;
|
|
}
|
|
|
|
static struct hinic3_tcam_dynamic_block *
|
|
hinic3_dynamic_lookup_tcam_filter(struct hinic3_nic_dev *nic_dev,
|
|
struct nic_tcam_cfg_rule *fdir_tcam_rule,
|
|
const struct hinic3_tcam_info *tcam_info,
|
|
struct hinic3_tcam_filter *tcam_filter,
|
|
u16 *tcam_index)
|
|
{
|
|
struct hinic3_tcam_dynamic_block *tmp = NULL;
|
|
u16 index;
|
|
|
|
list_for_each_entry(tmp,
|
|
&tcam_info->tcam_dynamic_info.tcam_dynamic_list,
|
|
block_list)
|
|
if (tmp->dynamic_index_cnt < HINIC3_TCAM_DYNAMIC_BLOCK_SIZE)
|
|
break;
|
|
|
|
if (!tmp || tmp->dynamic_index_cnt >= HINIC3_TCAM_DYNAMIC_BLOCK_SIZE) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Fdir filter dynamic lookup for index failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
for (index = 0; index < HINIC3_TCAM_DYNAMIC_BLOCK_SIZE; index++)
|
|
if (tmp->dynamic_index_used[index] == 0)
|
|
break;
|
|
|
|
if (index == HINIC3_TCAM_DYNAMIC_BLOCK_SIZE) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "tcam block 0x%x supports filter rules is full\n",
|
|
tmp->dynamic_block_id);
|
|
return NULL;
|
|
}
|
|
|
|
tcam_filter->dynamic_block_id = tmp->dynamic_block_id;
|
|
tcam_filter->index = index;
|
|
*tcam_index = index;
|
|
|
|
fdir_tcam_rule->index = index +
|
|
HINIC3_PKT_TCAM_DYNAMIC_INDEX_START(tmp->dynamic_block_id);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
static int hinic3_add_tcam_filter(struct hinic3_nic_dev *nic_dev,
|
|
struct hinic3_tcam_filter *tcam_filter,
|
|
struct nic_tcam_cfg_rule *fdir_tcam_rule)
|
|
{
|
|
struct hinic3_tcam_info *tcam_info = &nic_dev->tcam;
|
|
struct hinic3_tcam_dynamic_block *dynamic_block_ptr = NULL;
|
|
struct hinic3_tcam_dynamic_block *tmp = NULL;
|
|
u16 block_cnt = tcam_info->tcam_dynamic_info.dynamic_block_cnt;
|
|
u16 tcam_block_index = 0;
|
|
int block_alloc_flag = 0;
|
|
u16 index = 0;
|
|
int err;
|
|
|
|
if (tcam_info->tcam_rule_nums >=
|
|
block_cnt * HINIC3_TCAM_DYNAMIC_BLOCK_SIZE) {
|
|
if (block_cnt >= (HINIC3_MAX_TCAM_FILTERS /
|
|
HINIC3_TCAM_DYNAMIC_BLOCK_SIZE)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Dynamic tcam block is full, alloc failed\n");
|
|
goto failed;
|
|
}
|
|
|
|
err = hinic3_alloc_tcam_block(nic_dev->hwdev,
|
|
&tcam_block_index);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Fdir filter dynamic tcam alloc block failed\n");
|
|
goto failed;
|
|
}
|
|
|
|
block_alloc_flag = 1;
|
|
|
|
dynamic_block_ptr =
|
|
hinic3_alloc_dynamic_block_resource(nic_dev, tcam_info,
|
|
tcam_block_index);
|
|
if (!dynamic_block_ptr) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Fdir filter dynamic alloc block memory failed\n");
|
|
goto block_alloc_failed;
|
|
}
|
|
}
|
|
|
|
tmp = hinic3_dynamic_lookup_tcam_filter(nic_dev,
|
|
fdir_tcam_rule, tcam_info,
|
|
tcam_filter, &index);
|
|
if (!tmp) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Dynamic lookup tcam filter failed\n");
|
|
goto lookup_tcam_index_failed;
|
|
}
|
|
|
|
err = hinic3_add_tcam_rule(nic_dev->hwdev, fdir_tcam_rule);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Fdir_tcam_rule add failed\n");
|
|
goto add_tcam_rules_failed;
|
|
}
|
|
|
|
nicif_info(nic_dev, drv, nic_dev->netdev,
|
|
"Add fdir tcam rule, function_id: 0x%x, tcam_block_id: %d, local_index: %d, global_index: %d, queue: %d, tcam_rule_nums: %d succeed\n",
|
|
hinic3_global_func_id(nic_dev->hwdev),
|
|
tcam_filter->dynamic_block_id, index, fdir_tcam_rule->index,
|
|
fdir_tcam_rule->data.qid, tcam_info->tcam_rule_nums + 1);
|
|
|
|
if (tcam_info->tcam_rule_nums == 0) {
|
|
err = hinic3_set_fdir_tcam_rule_filter(nic_dev->hwdev, true);
|
|
if (err)
|
|
goto enable_failed;
|
|
}
|
|
|
|
list_add_tail(&tcam_filter->tcam_filter_list, &tcam_info->tcam_list);
|
|
|
|
tmp->dynamic_index_used[index] = 1;
|
|
tmp->dynamic_index_cnt++;
|
|
|
|
tcam_info->tcam_rule_nums++;
|
|
|
|
return 0;
|
|
|
|
enable_failed:
|
|
hinic3_del_tcam_rule(nic_dev->hwdev, fdir_tcam_rule->index);
|
|
|
|
add_tcam_rules_failed:
|
|
lookup_tcam_index_failed:
|
|
if (block_alloc_flag == 1)
|
|
hinic3_free_dynamic_block_resource(tcam_info,
|
|
dynamic_block_ptr);
|
|
|
|
block_alloc_failed:
|
|
if (block_alloc_flag == 1)
|
|
hinic3_free_tcam_block(nic_dev->hwdev, &tcam_block_index);
|
|
|
|
failed:
|
|
return -EFAULT;
|
|
}
|
|
|
|
static int hinic3_del_tcam_filter(struct hinic3_nic_dev *nic_dev,
|
|
struct hinic3_tcam_filter *tcam_filter)
|
|
{
|
|
struct hinic3_tcam_info *tcam_info = &nic_dev->tcam;
|
|
u16 dynamic_block_id = tcam_filter->dynamic_block_id;
|
|
struct hinic3_tcam_dynamic_block *tmp = NULL;
|
|
u32 index = 0;
|
|
int err;
|
|
|
|
list_for_each_entry(tmp,
|
|
&tcam_info->tcam_dynamic_info.tcam_dynamic_list,
|
|
block_list) {
|
|
if (tmp->dynamic_block_id == dynamic_block_id)
|
|
break;
|
|
}
|
|
if (!tmp || tmp->dynamic_block_id != dynamic_block_id) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Fdir filter del dynamic lookup for block failed\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
index = HINIC3_PKT_TCAM_DYNAMIC_INDEX_START(tmp->dynamic_block_id) +
|
|
tcam_filter->index;
|
|
|
|
err = hinic3_del_tcam_rule(nic_dev->hwdev, index);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "fdir_tcam_rule del failed\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
nicif_info(nic_dev, drv, nic_dev->netdev,
|
|
"Del fdir_tcam_dynamic_rule function_id: 0x%x, tcam_block_id: %d, local_index: %d, global_index: %d, local_rules_nums: %d, global_rule_nums: %d succeed\n",
|
|
hinic3_global_func_id(nic_dev->hwdev), dynamic_block_id,
|
|
tcam_filter->index, index, tmp->dynamic_index_cnt - 1,
|
|
tcam_info->tcam_rule_nums - 1);
|
|
|
|
tmp->dynamic_index_used[tcam_filter->index] = 0;
|
|
tmp->dynamic_index_cnt--;
|
|
tcam_info->tcam_rule_nums--;
|
|
if (tmp->dynamic_index_cnt == 0) {
|
|
hinic3_free_tcam_block(nic_dev->hwdev, &dynamic_block_id);
|
|
hinic3_free_dynamic_block_resource(tcam_info, tmp);
|
|
}
|
|
|
|
if (tcam_info->tcam_rule_nums == 0)
|
|
hinic3_set_fdir_tcam_rule_filter(nic_dev->hwdev, false);
|
|
|
|
list_del(&tcam_filter->tcam_filter_list);
|
|
kfree(tcam_filter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline struct hinic3_tcam_filter *
|
|
hinic3_tcam_filter_lookup(const struct list_head *filter_list,
|
|
struct tag_tcam_key *key)
|
|
{
|
|
struct hinic3_tcam_filter *iter;
|
|
|
|
list_for_each_entry(iter, filter_list, tcam_filter_list) {
|
|
if (memcmp(key, &iter->tcam_key,
|
|
sizeof(struct tag_tcam_key)) == 0) {
|
|
return iter;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void del_ethtool_rule(struct hinic3_nic_dev *nic_dev,
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule)
|
|
{
|
|
list_del(ð_rule->list);
|
|
nic_dev->rx_flow_rule.tot_num_rules--;
|
|
|
|
kfree(eth_rule);
|
|
}
|
|
|
|
static int hinic3_remove_one_rule(struct hinic3_nic_dev *nic_dev,
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule)
|
|
{
|
|
struct hinic3_tcam_info *tcam_info = &nic_dev->tcam;
|
|
struct hinic3_tcam_filter *tcam_filter;
|
|
struct nic_tcam_cfg_rule fdir_tcam_rule;
|
|
struct tag_tcam_key tcam_key;
|
|
int err;
|
|
|
|
memset(&fdir_tcam_rule, 0, sizeof(fdir_tcam_rule));
|
|
memset(&tcam_key, 0, sizeof(tcam_key));
|
|
|
|
err = hinic3_fdir_tcam_info_init(nic_dev, ð_rule->flow_spec,
|
|
&tcam_key, &fdir_tcam_rule);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Init fdir info failed\n");
|
|
return err;
|
|
}
|
|
|
|
tcam_filter = hinic3_tcam_filter_lookup(&tcam_info->tcam_list,
|
|
&tcam_key);
|
|
if (!tcam_filter) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Filter does not exists\n");
|
|
return -EEXIST;
|
|
}
|
|
|
|
err = hinic3_del_tcam_filter(nic_dev, tcam_filter);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Delete tcam filter failed\n");
|
|
return err;
|
|
}
|
|
|
|
del_ethtool_rule(nic_dev, eth_rule);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void add_rule_to_list(struct hinic3_nic_dev *nic_dev,
|
|
struct hinic3_ethtool_rx_flow_rule *rule)
|
|
{
|
|
struct hinic3_ethtool_rx_flow_rule *iter = NULL;
|
|
struct list_head *head = &nic_dev->rx_flow_rule.rules;
|
|
|
|
list_for_each_entry(iter, &nic_dev->rx_flow_rule.rules, list) {
|
|
if (iter->flow_spec.location > rule->flow_spec.location)
|
|
break;
|
|
head = &iter->list;
|
|
}
|
|
nic_dev->rx_flow_rule.tot_num_rules++;
|
|
list_add(&rule->list, head);
|
|
}
|
|
|
|
static int hinic3_add_one_rule(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs)
|
|
{
|
|
struct nic_tcam_cfg_rule fdir_tcam_rule;
|
|
struct tag_tcam_key tcam_key;
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule = NULL;
|
|
struct hinic3_tcam_filter *tcam_filter = NULL;
|
|
struct hinic3_tcam_info *tcam_info = &nic_dev->tcam;
|
|
int err;
|
|
|
|
memset(&fdir_tcam_rule, 0, sizeof(fdir_tcam_rule));
|
|
memset(&tcam_key, 0, sizeof(tcam_key));
|
|
err = hinic3_fdir_tcam_info_init(nic_dev, fs, &tcam_key,
|
|
&fdir_tcam_rule);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Init fdir info failed\n");
|
|
return err;
|
|
}
|
|
|
|
tcam_filter = hinic3_tcam_filter_lookup(&tcam_info->tcam_list,
|
|
&tcam_key);
|
|
if (tcam_filter) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Filter exists\n");
|
|
return -EEXIST;
|
|
}
|
|
|
|
tcam_filter = kzalloc(sizeof(*tcam_filter), GFP_KERNEL);
|
|
if (!tcam_filter)
|
|
return -ENOMEM;
|
|
memcpy(&tcam_filter->tcam_key,
|
|
&tcam_key, sizeof(struct tag_tcam_key));
|
|
tcam_filter->queue = (u16)fdir_tcam_rule.data.qid;
|
|
|
|
err = hinic3_add_tcam_filter(nic_dev, tcam_filter, &fdir_tcam_rule);
|
|
if (err)
|
|
goto add_tcam_filter_fail;
|
|
|
|
/* driver save new rule filter */
|
|
eth_rule = kzalloc(sizeof(*eth_rule), GFP_KERNEL);
|
|
if (!eth_rule) {
|
|
err = -ENOMEM;
|
|
goto alloc_eth_rule_fail;
|
|
}
|
|
|
|
eth_rule->flow_spec = *fs;
|
|
add_rule_to_list(nic_dev, eth_rule);
|
|
|
|
return 0;
|
|
|
|
alloc_eth_rule_fail:
|
|
hinic3_del_tcam_filter(nic_dev, tcam_filter);
|
|
add_tcam_filter_fail:
|
|
kfree(tcam_filter);
|
|
return err;
|
|
}
|
|
|
|
static struct hinic3_ethtool_rx_flow_rule *
|
|
find_ethtool_rule(const struct hinic3_nic_dev *nic_dev, u32 location)
|
|
{
|
|
struct hinic3_ethtool_rx_flow_rule *iter = NULL;
|
|
|
|
list_for_each_entry(iter, &nic_dev->rx_flow_rule.rules, list) {
|
|
if (iter->flow_spec.location == location)
|
|
return iter;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int validate_flow(struct hinic3_nic_dev *nic_dev,
|
|
const struct ethtool_rx_flow_spec *fs)
|
|
{
|
|
if (fs->location >= MAX_NUM_OF_ETHTOOL_NTUPLE_RULES) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "loc exceed limit[0,%lu]\n",
|
|
MAX_NUM_OF_ETHTOOL_NTUPLE_RULES);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (fs->ring_cookie >= nic_dev->q_params.num_qps) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "action is larger than queue number %u\n",
|
|
nic_dev->q_params.num_qps);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fs->flow_type) {
|
|
case TCP_V4_FLOW:
|
|
case UDP_V4_FLOW:
|
|
case IP_USER_FLOW:
|
|
#ifndef UNSUPPORT_NTUPLE_IPV6
|
|
case TCP_V6_FLOW:
|
|
case UDP_V6_FLOW:
|
|
case IPV6_USER_FLOW:
|
|
#endif
|
|
break;
|
|
default:
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "flow type is not supported\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hinic3_ethtool_flow_replace(struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rx_flow_spec *fs)
|
|
{
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule = NULL;
|
|
struct ethtool_rx_flow_spec flow_spec_temp;
|
|
int loc_exit_flag = 0;
|
|
int err;
|
|
|
|
if (!HINIC3_SUPPORT_FDIR(nic_dev->hwdev)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Unsupported ntuple function\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
err = validate_flow(nic_dev, fs);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"flow is not valid %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
eth_rule = find_ethtool_rule(nic_dev, fs->location);
|
|
/* when location is same, delete old location rule. */
|
|
if (eth_rule) {
|
|
memcpy(&flow_spec_temp, ð_rule->flow_spec,
|
|
sizeof(struct ethtool_rx_flow_spec));
|
|
err = hinic3_remove_one_rule(nic_dev, eth_rule);
|
|
if (err)
|
|
return err;
|
|
|
|
loc_exit_flag = 1;
|
|
}
|
|
|
|
/* add new rule filter */
|
|
err = hinic3_add_one_rule(nic_dev, fs);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Add new rule filter failed\n");
|
|
if (loc_exit_flag)
|
|
hinic3_add_one_rule(nic_dev, &flow_spec_temp);
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hinic3_ethtool_flow_remove(struct hinic3_nic_dev *nic_dev, u32 location)
|
|
{
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule = NULL;
|
|
int err;
|
|
|
|
if (!HINIC3_SUPPORT_FDIR(nic_dev->hwdev)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Unsupported ntuple function\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (location >= MAX_NUM_OF_ETHTOOL_NTUPLE_RULES)
|
|
return -ENOSPC;
|
|
|
|
eth_rule = find_ethtool_rule(nic_dev, location);
|
|
if (!eth_rule)
|
|
return -ENOENT;
|
|
|
|
err = hinic3_remove_one_rule(nic_dev, eth_rule);
|
|
|
|
return err;
|
|
}
|
|
|
|
int hinic3_ethtool_get_flow(const struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rxnfc *info, u32 location)
|
|
{
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule = NULL;
|
|
|
|
if (!HINIC3_SUPPORT_FDIR(nic_dev->hwdev)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Unsupported ntuple function\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (location >= MAX_NUM_OF_ETHTOOL_NTUPLE_RULES)
|
|
return -EINVAL;
|
|
|
|
list_for_each_entry(eth_rule, &nic_dev->rx_flow_rule.rules, list) {
|
|
if (eth_rule->flow_spec.location == location) {
|
|
info->fs = eth_rule->flow_spec;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
int hinic3_ethtool_get_all_flows(const struct hinic3_nic_dev *nic_dev,
|
|
struct ethtool_rxnfc *info, u32 *rule_locs)
|
|
{
|
|
int idx = 0;
|
|
struct hinic3_ethtool_rx_flow_rule *eth_rule = NULL;
|
|
|
|
if (!HINIC3_SUPPORT_FDIR(nic_dev->hwdev)) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev, "Unsupported ntuple function\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
info->data = MAX_NUM_OF_ETHTOOL_NTUPLE_RULES;
|
|
list_for_each_entry(eth_rule, &nic_dev->rx_flow_rule.rules, list)
|
|
rule_locs[idx++] = eth_rule->flow_spec.location;
|
|
|
|
return info->rule_cnt == idx ? 0 : -ENOENT;
|
|
}
|
|
|
|
bool hinic3_validate_channel_setting_in_ntuple(const struct hinic3_nic_dev *nic_dev, u32 q_num)
|
|
{
|
|
struct hinic3_ethtool_rx_flow_rule *iter = NULL;
|
|
|
|
list_for_each_entry(iter, &nic_dev->rx_flow_rule.rules, list) {
|
|
if (iter->flow_spec.ring_cookie >= q_num) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"User defined filter %u assigns flow to queue %llu. Queue number %u is invalid\n",
|
|
iter->flow_spec.location, iter->flow_spec.ring_cookie, q_num);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|