2594 lines
72 KiB
C
2594 lines
72 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
// Copyright (c) 2026 Hisilicon Limited.
|
|
|
|
#include <linux/ethtool.h>
|
|
#include <net/flow_offload.h>
|
|
#include <net/vxlan.h>
|
|
#include "hclge_fd.h"
|
|
#include "hclge_main.h"
|
|
|
|
static const struct key_info meta_data_key_info[] = {
|
|
{ PACKET_TYPE_ID, 6 },
|
|
{ IP_FRAGEMENT, 1 },
|
|
{ ROCE_TYPE, 1 },
|
|
{ NEXT_KEY, 5 },
|
|
{ VLAN_NUMBER, 2 },
|
|
{ SRC_VPORT, 12 },
|
|
{ DST_VPORT, 12 },
|
|
{ TUNNEL_PACKET, 1 },
|
|
};
|
|
|
|
static const struct key_info tuple_key_info[] = {
|
|
{ OUTER_DST_MAC, 48, KEY_OPT_MAC, -1, -1 },
|
|
{ OUTER_SRC_MAC, 48, KEY_OPT_MAC, -1, -1 },
|
|
{ OUTER_VLAN_TAG_FST, 16, KEY_OPT_LE16, -1, -1 },
|
|
{ OUTER_VLAN_TAG_SEC, 16, KEY_OPT_LE16, -1, -1 },
|
|
{ OUTER_ETH_TYPE, 16, KEY_OPT_LE16, -1, -1 },
|
|
{ OUTER_L2_RSV, 16, KEY_OPT_LE16, -1, -1 },
|
|
{ OUTER_IP_TOS, 8, KEY_OPT_U8, -1, -1 },
|
|
{ OUTER_IP_PROTO, 8, KEY_OPT_U8, -1, -1 },
|
|
{ OUTER_SRC_IP, 32, KEY_OPT_IP, -1, -1 },
|
|
{ OUTER_DST_IP, 32, KEY_OPT_IP, -1, -1 },
|
|
{ OUTER_L3_RSV, 16, KEY_OPT_LE16, -1, -1 },
|
|
{ OUTER_SRC_PORT, 16, KEY_OPT_LE16, -1, -1 },
|
|
{ OUTER_DST_PORT, 16, KEY_OPT_LE16, -1, -1 },
|
|
{ OUTER_L4_RSV, 32, KEY_OPT_LE32, -1, -1 },
|
|
{ OUTER_TUN_VNI, 24, KEY_OPT_VNI,
|
|
offsetof(struct hclge_fd_rule, tuples.outer_tun_vni),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.outer_tun_vni) },
|
|
{ OUTER_TUN_FLOW_ID, 8, KEY_OPT_U8, -1, -1 },
|
|
{ INNER_DST_MAC, 48, KEY_OPT_MAC,
|
|
offsetof(struct hclge_fd_rule, tuples.dst_mac),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.dst_mac) },
|
|
{ INNER_SRC_MAC, 48, KEY_OPT_MAC,
|
|
offsetof(struct hclge_fd_rule, tuples.src_mac),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.src_mac) },
|
|
{ INNER_VLAN_TAG_FST, 16, KEY_OPT_LE16,
|
|
offsetof(struct hclge_fd_rule, tuples.vlan_tag1),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.vlan_tag1) },
|
|
{ INNER_VLAN_TAG_SEC, 16, KEY_OPT_LE16, -1, -1 },
|
|
{ INNER_ETH_TYPE, 16, KEY_OPT_LE16,
|
|
offsetof(struct hclge_fd_rule, tuples.ether_proto),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.ether_proto) },
|
|
{ INNER_L2_RSV, 16, KEY_OPT_LE16,
|
|
offsetof(struct hclge_fd_rule, tuples.l2_user_def),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.l2_user_def) },
|
|
{ INNER_IP_TOS, 8, KEY_OPT_U8,
|
|
offsetof(struct hclge_fd_rule, tuples.ip_tos),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.ip_tos) },
|
|
{ INNER_IP_PROTO, 8, KEY_OPT_U8,
|
|
offsetof(struct hclge_fd_rule, tuples.ip_proto),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.ip_proto) },
|
|
{ INNER_SRC_IP, 32, KEY_OPT_IP,
|
|
offsetof(struct hclge_fd_rule, tuples.src_ip),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.src_ip) },
|
|
{ INNER_DST_IP, 32, KEY_OPT_IP,
|
|
offsetof(struct hclge_fd_rule, tuples.dst_ip),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.dst_ip) },
|
|
{ INNER_L3_RSV, 16, KEY_OPT_LE16,
|
|
offsetof(struct hclge_fd_rule, tuples.l3_user_def),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.l3_user_def) },
|
|
{ INNER_SRC_PORT, 16, KEY_OPT_LE16,
|
|
offsetof(struct hclge_fd_rule, tuples.src_port),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.src_port) },
|
|
{ INNER_DST_PORT, 16, KEY_OPT_LE16,
|
|
offsetof(struct hclge_fd_rule, tuples.dst_port),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.dst_port) },
|
|
{ INNER_L4_RSV, 32, KEY_OPT_LE32,
|
|
offsetof(struct hclge_fd_rule, tuples.l4_user_def),
|
|
offsetof(struct hclge_fd_rule, tuples_mask.l4_user_def) },
|
|
};
|
|
|
|
static void hclge_sync_fd_state(struct hclge_dev *hdev)
|
|
{
|
|
if (hlist_empty(&hdev->fd_rule_list))
|
|
hdev->fd_active_type = HCLGE_FD_RULE_NONE;
|
|
}
|
|
|
|
static void hclge_fd_inc_rule_cnt(struct hclge_dev *hdev, u16 location)
|
|
{
|
|
if (!test_bit(location, hdev->fd_bmap)) {
|
|
set_bit(location, hdev->fd_bmap);
|
|
hdev->hclge_fd_rule_num++;
|
|
}
|
|
}
|
|
|
|
static void hclge_fd_dec_rule_cnt(struct hclge_dev *hdev, u16 location)
|
|
{
|
|
if (test_bit(location, hdev->fd_bmap)) {
|
|
clear_bit(location, hdev->fd_bmap);
|
|
hdev->hclge_fd_rule_num--;
|
|
}
|
|
}
|
|
|
|
static void hclge_fd_free_node(struct hclge_dev *hdev,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
hlist_del(&rule->rule_node);
|
|
kfree(rule);
|
|
hclge_sync_fd_state(hdev);
|
|
}
|
|
|
|
static void hclge_update_fd_rule_node(struct hclge_dev *hdev,
|
|
struct hclge_fd_rule *old_rule,
|
|
struct hclge_fd_rule *new_rule,
|
|
enum HCLGE_FD_NODE_STATE state)
|
|
{
|
|
switch (state) {
|
|
case HCLGE_FD_TO_ADD:
|
|
case HCLGE_FD_ACTIVE:
|
|
/* 1) if the new state is TO_ADD, just replace the old rule
|
|
* with the same location, no matter its state, because the
|
|
* new rule will be configured to the hardware.
|
|
* 2) if the new state is ACTIVE, it means the new rule
|
|
* has been configured to the hardware, so just replace
|
|
* the old rule node with the same location.
|
|
* 3) for it doesn't add a new node to the list, so it's
|
|
* unnecessary to update the rule number and fd_bmap.
|
|
*/
|
|
new_rule->rule_node.next = old_rule->rule_node.next;
|
|
new_rule->rule_node.pprev = old_rule->rule_node.pprev;
|
|
memcpy(old_rule, new_rule, sizeof(*old_rule));
|
|
kfree(new_rule);
|
|
break;
|
|
case HCLGE_FD_DELETED:
|
|
hclge_fd_dec_rule_cnt(hdev, old_rule->location);
|
|
hclge_fd_free_node(hdev, old_rule);
|
|
break;
|
|
case HCLGE_FD_TO_DEL:
|
|
/* if new request is TO_DEL, and old rule is existent
|
|
* 1) the state of old rule is TO_DEL, we need do nothing,
|
|
* because we delete rule by location, other rule content
|
|
* is unnecessary.
|
|
* 2) the state of old rule is ACTIVE, we need to change its
|
|
* state to TO_DEL, so the rule will be deleted when periodic
|
|
* task being scheduled.
|
|
* 3) the state of old rule is TO_ADD, it means the rule hasn't
|
|
* been added to hardware, so we just delete the rule node from
|
|
* fd_rule_list directly.
|
|
*/
|
|
if (old_rule->state == HCLGE_FD_TO_ADD) {
|
|
hclge_fd_dec_rule_cnt(hdev, old_rule->location);
|
|
hclge_fd_free_node(hdev, old_rule);
|
|
return;
|
|
}
|
|
old_rule->state = HCLGE_FD_TO_DEL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct hclge_fd_rule *hclge_find_fd_rule(struct hlist_head *hlist,
|
|
u16 location,
|
|
struct hclge_fd_rule **parent)
|
|
{
|
|
struct hclge_fd_rule *rule;
|
|
struct hlist_node *node;
|
|
|
|
hlist_for_each_entry_safe(rule, node, hlist, rule_node) {
|
|
if (rule->location == location)
|
|
return rule;
|
|
else if (rule->location > location)
|
|
return NULL;
|
|
/* record the parent node, use to keep the nodes in fd_rule_list
|
|
* in ascend order.
|
|
*/
|
|
*parent = rule;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* insert fd rule node in ascend order according to rule->location */
|
|
static void hclge_fd_insert_rule_node(struct hlist_head *hlist,
|
|
struct hclge_fd_rule *rule,
|
|
struct hclge_fd_rule *parent)
|
|
{
|
|
INIT_HLIST_NODE(&rule->rule_node);
|
|
|
|
if (parent)
|
|
hlist_add_behind(&rule->rule_node, &parent->rule_node);
|
|
else
|
|
hlist_add_head(&rule->rule_node, hlist);
|
|
}
|
|
|
|
static int hclge_fd_set_user_def_cmd(struct hclge_dev *hdev,
|
|
struct hclge_fd_user_def_cfg *cfg)
|
|
{
|
|
struct hclge_fd_user_def_cfg_cmd *req;
|
|
struct hclge_desc desc;
|
|
u16 data = 0;
|
|
int ret;
|
|
|
|
hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_FD_USER_DEF_OP, false);
|
|
|
|
req = (struct hclge_fd_user_def_cfg_cmd *)desc.data;
|
|
|
|
hnae3_set_bit(data, HCLGE_FD_USER_DEF_EN_B, cfg[0].ref_cnt > 0);
|
|
hnae3_set_field(data, HCLGE_FD_USER_DEF_OFT_M,
|
|
HCLGE_FD_USER_DEF_OFT_S, cfg[0].offset);
|
|
req->ol2_cfg = cpu_to_le16(data);
|
|
|
|
data = 0;
|
|
hnae3_set_bit(data, HCLGE_FD_USER_DEF_EN_B, cfg[1].ref_cnt > 0);
|
|
hnae3_set_field(data, HCLGE_FD_USER_DEF_OFT_M,
|
|
HCLGE_FD_USER_DEF_OFT_S, cfg[1].offset);
|
|
req->ol3_cfg = cpu_to_le16(data);
|
|
|
|
data = 0;
|
|
hnae3_set_bit(data, HCLGE_FD_USER_DEF_EN_B, cfg[2].ref_cnt > 0);
|
|
hnae3_set_field(data, HCLGE_FD_USER_DEF_OFT_M,
|
|
HCLGE_FD_USER_DEF_OFT_S, cfg[2].offset);
|
|
req->ol4_cfg = cpu_to_le16(data);
|
|
|
|
ret = hclge_cmd_send(&hdev->hw, &desc, 1);
|
|
if (ret)
|
|
dev_err(&hdev->pdev->dev,
|
|
"failed to set fd user def data, ret= %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static void hclge_sync_fd_user_def_cfg(struct hclge_dev *hdev, bool locked)
|
|
{
|
|
int ret;
|
|
|
|
if (!test_and_clear_bit(HCLGE_STATE_FD_USER_DEF_CHANGED, &hdev->state))
|
|
return;
|
|
|
|
if (!locked)
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
|
|
ret = hclge_fd_set_user_def_cmd(hdev, hdev->fd_cfg.user_def_cfg);
|
|
if (ret)
|
|
set_bit(HCLGE_STATE_FD_USER_DEF_CHANGED, &hdev->state);
|
|
|
|
if (!locked)
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
}
|
|
|
|
static int hclge_fd_check_user_def_refcnt(struct hclge_dev *hdev,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
struct hlist_head *hlist = &hdev->fd_rule_list;
|
|
struct hclge_fd_rule *fd_rule, *parent = NULL;
|
|
struct hclge_fd_user_def_info *info, *old_info;
|
|
struct hclge_fd_user_def_cfg *cfg;
|
|
|
|
if (!rule || rule->rule_type != HCLGE_FD_EP_ACTIVE ||
|
|
rule->ep.user_def.layer == HCLGE_FD_USER_DEF_NONE)
|
|
return 0;
|
|
|
|
/* for valid layer is start from 1, so need minus 1 to get the cfg */
|
|
cfg = &hdev->fd_cfg.user_def_cfg[rule->ep.user_def.layer - 1];
|
|
info = &rule->ep.user_def;
|
|
|
|
if (!cfg->ref_cnt || cfg->offset == info->offset)
|
|
return 0;
|
|
|
|
if (cfg->ref_cnt > 1)
|
|
goto error;
|
|
|
|
fd_rule = hclge_find_fd_rule(hlist, rule->location, &parent);
|
|
if (fd_rule) {
|
|
old_info = &fd_rule->ep.user_def;
|
|
if (info->layer == old_info->layer)
|
|
return 0;
|
|
}
|
|
|
|
error:
|
|
dev_err(&hdev->pdev->dev,
|
|
"No available offset for layer%d fd rule, each layer only support one user def offset.\n",
|
|
info->layer + 1);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
static void hclge_fd_inc_user_def_refcnt(struct hclge_dev *hdev,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
struct hclge_fd_user_def_cfg *cfg;
|
|
|
|
if (!rule || rule->rule_type != HCLGE_FD_EP_ACTIVE ||
|
|
rule->ep.user_def.layer == HCLGE_FD_USER_DEF_NONE)
|
|
return;
|
|
|
|
cfg = &hdev->fd_cfg.user_def_cfg[rule->ep.user_def.layer - 1];
|
|
if (!cfg->ref_cnt) {
|
|
cfg->offset = rule->ep.user_def.offset;
|
|
set_bit(HCLGE_STATE_FD_USER_DEF_CHANGED, &hdev->state);
|
|
}
|
|
cfg->ref_cnt++;
|
|
}
|
|
|
|
static void hclge_fd_dec_user_def_refcnt(struct hclge_dev *hdev,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
struct hclge_fd_user_def_cfg *cfg;
|
|
|
|
if (!rule || rule->rule_type != HCLGE_FD_EP_ACTIVE ||
|
|
rule->ep.user_def.layer == HCLGE_FD_USER_DEF_NONE)
|
|
return;
|
|
|
|
cfg = &hdev->fd_cfg.user_def_cfg[rule->ep.user_def.layer - 1];
|
|
if (!cfg->ref_cnt)
|
|
return;
|
|
|
|
cfg->ref_cnt--;
|
|
if (!cfg->ref_cnt) {
|
|
cfg->offset = 0;
|
|
set_bit(HCLGE_STATE_FD_USER_DEF_CHANGED, &hdev->state);
|
|
}
|
|
}
|
|
|
|
static void hclge_update_fd_list(struct hclge_dev *hdev,
|
|
enum HCLGE_FD_NODE_STATE state, u16 location,
|
|
struct hclge_fd_rule *new_rule)
|
|
{
|
|
struct hlist_head *hlist = &hdev->fd_rule_list;
|
|
struct hclge_fd_rule *fd_rule, *parent = NULL;
|
|
|
|
fd_rule = hclge_find_fd_rule(hlist, location, &parent);
|
|
if (fd_rule) {
|
|
hclge_fd_dec_user_def_refcnt(hdev, fd_rule);
|
|
if (state == HCLGE_FD_ACTIVE)
|
|
hclge_fd_inc_user_def_refcnt(hdev, new_rule);
|
|
hclge_sync_fd_user_def_cfg(hdev, true);
|
|
|
|
hclge_update_fd_rule_node(hdev, fd_rule, new_rule, state);
|
|
return;
|
|
}
|
|
|
|
/* it's unlikely to fail here, because we have checked the rule
|
|
* exist before.
|
|
*/
|
|
if (unlikely(state == HCLGE_FD_TO_DEL || state == HCLGE_FD_DELETED)) {
|
|
dev_warn(&hdev->pdev->dev,
|
|
"failed to delete fd rule %u, it's inexistent\n",
|
|
location);
|
|
return;
|
|
}
|
|
|
|
hclge_fd_inc_user_def_refcnt(hdev, new_rule);
|
|
hclge_sync_fd_user_def_cfg(hdev, true);
|
|
|
|
hclge_fd_insert_rule_node(hlist, new_rule, parent);
|
|
hclge_fd_inc_rule_cnt(hdev, new_rule->location);
|
|
|
|
if (state == HCLGE_FD_TO_ADD) {
|
|
set_bit(HCLGE_STATE_FD_TBL_CHANGED, &hdev->state);
|
|
hclge_task_schedule(hdev, 0);
|
|
}
|
|
}
|
|
|
|
static int hclge_get_fd_mode(struct hclge_dev *hdev, u8 *fd_mode)
|
|
{
|
|
struct hclge_get_fd_mode_cmd *req;
|
|
struct hclge_desc desc;
|
|
int ret;
|
|
|
|
hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_FD_MODE_CTRL, true);
|
|
|
|
req = (struct hclge_get_fd_mode_cmd *)desc.data;
|
|
|
|
ret = hclge_cmd_send(&hdev->hw, &desc, 1);
|
|
if (ret) {
|
|
dev_err(&hdev->pdev->dev, "get fd mode fail, ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
*fd_mode = req->mode;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hclge_get_fd_allocation(struct hclge_dev *hdev,
|
|
u32 *stage1_entry_num,
|
|
u32 *stage2_entry_num,
|
|
u16 *stage1_counter_num,
|
|
u16 *stage2_counter_num)
|
|
{
|
|
struct hclge_get_fd_allocation_cmd *req;
|
|
struct hclge_desc desc;
|
|
int ret;
|
|
|
|
hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_FD_GET_ALLOCATION, true);
|
|
|
|
req = (struct hclge_get_fd_allocation_cmd *)desc.data;
|
|
|
|
ret = hclge_cmd_send(&hdev->hw, &desc, 1);
|
|
if (ret) {
|
|
dev_err(&hdev->pdev->dev, "query fd allocation fail, ret=%d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
*stage1_entry_num = le32_to_cpu(req->stage1_entry_num);
|
|
*stage2_entry_num = le32_to_cpu(req->stage2_entry_num);
|
|
*stage1_counter_num = le16_to_cpu(req->stage1_counter_num);
|
|
*stage2_counter_num = le16_to_cpu(req->stage2_counter_num);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hclge_set_fd_key_config(struct hclge_dev *hdev,
|
|
enum HCLGE_FD_STAGE stage_num)
|
|
{
|
|
struct hclge_set_fd_key_config_cmd *req;
|
|
struct hclge_fd_key_cfg *stage;
|
|
struct hclge_desc desc;
|
|
int ret;
|
|
|
|
hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_FD_KEY_CONFIG, false);
|
|
|
|
req = (struct hclge_set_fd_key_config_cmd *)desc.data;
|
|
stage = &hdev->fd_cfg.key_cfg[stage_num];
|
|
req->stage = stage_num;
|
|
req->key_select = stage->key_sel;
|
|
req->inner_sipv6_word_en = stage->inner_sipv6_word_en;
|
|
req->inner_dipv6_word_en = stage->inner_dipv6_word_en;
|
|
req->outer_sipv6_word_en = stage->outer_sipv6_word_en;
|
|
req->outer_dipv6_word_en = stage->outer_dipv6_word_en;
|
|
req->tuple_mask = cpu_to_le32(~stage->tuple_active);
|
|
req->meta_data_mask = cpu_to_le32(~stage->meta_data_active);
|
|
|
|
ret = hclge_cmd_send(&hdev->hw, &desc, 1);
|
|
if (ret)
|
|
dev_err(&hdev->pdev->dev, "set fd key fail, ret=%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void hclge_fd_disable_user_def(struct hclge_dev *hdev)
|
|
{
|
|
struct hclge_fd_user_def_cfg *cfg = hdev->fd_cfg.user_def_cfg;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
memset(cfg, 0, sizeof(hdev->fd_cfg.user_def_cfg));
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
|
|
hclge_fd_set_user_def_cmd(hdev, cfg);
|
|
}
|
|
|
|
int hclge_init_fd_config(struct hclge_dev *hdev)
|
|
{
|
|
#define LOW_2_WORDS 0x03
|
|
struct hclge_fd_key_cfg *key_cfg;
|
|
int ret;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return 0;
|
|
|
|
ret = hclge_get_fd_mode(hdev, &hdev->fd_cfg.fd_mode);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (hdev->fd_cfg.fd_mode) {
|
|
case HCLGE_FD_MODE_DEPTH_2K_WIDTH_400B_STAGE_1:
|
|
hdev->fd_cfg.max_key_length = MAX_KEY_LENGTH;
|
|
break;
|
|
case HCLGE_FD_MODE_DEPTH_4K_WIDTH_200B_STAGE_1:
|
|
hdev->fd_cfg.max_key_length = MAX_KEY_LENGTH / 2;
|
|
break;
|
|
default:
|
|
dev_err(&hdev->pdev->dev,
|
|
"Unsupported flow director mode %u\n",
|
|
hdev->fd_cfg.fd_mode);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
key_cfg = &hdev->fd_cfg.key_cfg[HCLGE_FD_STAGE_1];
|
|
key_cfg->key_sel = HCLGE_FD_KEY_BASE_ON_TUPLE;
|
|
key_cfg->inner_sipv6_word_en = LOW_2_WORDS;
|
|
key_cfg->inner_dipv6_word_en = LOW_2_WORDS;
|
|
key_cfg->outer_sipv6_word_en = 0;
|
|
key_cfg->outer_dipv6_word_en = 0;
|
|
|
|
key_cfg->tuple_active = BIT(INNER_VLAN_TAG_FST) | BIT(INNER_ETH_TYPE) |
|
|
BIT(INNER_IP_PROTO) | BIT(INNER_IP_TOS) |
|
|
BIT(INNER_SRC_IP) | BIT(INNER_DST_IP) |
|
|
BIT(INNER_SRC_PORT) | BIT(INNER_DST_PORT);
|
|
|
|
/* If use max 400bit key, we can support tuples for ether type */
|
|
if (hdev->fd_cfg.fd_mode == HCLGE_FD_MODE_DEPTH_2K_WIDTH_400B_STAGE_1) {
|
|
key_cfg->tuple_active |= BIT(INNER_DST_MAC) |
|
|
BIT(INNER_SRC_MAC) |
|
|
BIT(OUTER_TUN_VNI);
|
|
if (hdev->ae_dev->dev_version >= HNAE3_DEVICE_VERSION_V3)
|
|
key_cfg->tuple_active |= HCLGE_FD_TUPLE_USER_DEF_TUPLES;
|
|
}
|
|
|
|
/* roce_type is used to filter roce frames
|
|
* dst_vport is used to specify the rule
|
|
*/
|
|
key_cfg->meta_data_active = BIT(ROCE_TYPE) | BIT(DST_VPORT);
|
|
|
|
ret = hclge_get_fd_allocation(hdev,
|
|
&hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1],
|
|
&hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_2],
|
|
&hdev->fd_cfg.cnt_num[HCLGE_FD_STAGE_1],
|
|
&hdev->fd_cfg.cnt_num[HCLGE_FD_STAGE_2]);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return hclge_set_fd_key_config(hdev, HCLGE_FD_STAGE_1);
|
|
}
|
|
|
|
static int hclge_fd_tcam_config(struct hclge_dev *hdev, u8 stage, bool sel_x,
|
|
int loc, u8 *key, bool is_add)
|
|
{
|
|
struct hclge_fd_tcam_config_1_cmd *req1;
|
|
struct hclge_fd_tcam_config_2_cmd *req2;
|
|
struct hclge_fd_tcam_config_3_cmd *req3;
|
|
struct hclge_desc desc[3];
|
|
int ret;
|
|
|
|
hclge_cmd_setup_basic_desc(&desc[0], HCLGE_OPC_FD_TCAM_OP, false);
|
|
desc[0].flag |= cpu_to_le16(HCLGE_COMM_CMD_FLAG_NEXT);
|
|
hclge_cmd_setup_basic_desc(&desc[1], HCLGE_OPC_FD_TCAM_OP, false);
|
|
desc[1].flag |= cpu_to_le16(HCLGE_COMM_CMD_FLAG_NEXT);
|
|
hclge_cmd_setup_basic_desc(&desc[2], HCLGE_OPC_FD_TCAM_OP, false);
|
|
|
|
req1 = (struct hclge_fd_tcam_config_1_cmd *)desc[0].data;
|
|
req2 = (struct hclge_fd_tcam_config_2_cmd *)desc[1].data;
|
|
req3 = (struct hclge_fd_tcam_config_3_cmd *)desc[2].data;
|
|
|
|
req1->stage = stage;
|
|
req1->xy_sel = sel_x ? 1 : 0;
|
|
hnae3_set_bit(req1->port_info, HCLGE_FD_EPORT_SW_EN_B, 0);
|
|
req1->index = cpu_to_le32(loc);
|
|
req1->entry_vld = sel_x ? is_add : 0;
|
|
|
|
if (key) {
|
|
memcpy(req1->tcam_data, &key[0], sizeof(req1->tcam_data));
|
|
memcpy(req2->tcam_data, &key[sizeof(req1->tcam_data)],
|
|
sizeof(req2->tcam_data));
|
|
memcpy(req3->tcam_data, &key[sizeof(req1->tcam_data) +
|
|
sizeof(req2->tcam_data)], sizeof(req3->tcam_data));
|
|
}
|
|
|
|
ret = hclge_cmd_send(&hdev->hw, desc, 3);
|
|
if (ret)
|
|
dev_err(&hdev->pdev->dev,
|
|
"config tcam key fail, ret=%d\n",
|
|
ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hclge_fd_ad_config(struct hclge_dev *hdev, u8 stage, int loc,
|
|
struct hclge_fd_ad_data *action)
|
|
{
|
|
struct hnae3_ae_dev *ae_dev = pci_get_drvdata(hdev->pdev);
|
|
struct hclge_fd_ad_config_cmd *req;
|
|
struct hclge_desc desc;
|
|
u64 ad_data = 0;
|
|
int ret;
|
|
|
|
hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_FD_AD_OP, false);
|
|
|
|
req = (struct hclge_fd_ad_config_cmd *)desc.data;
|
|
req->index = cpu_to_le32(loc);
|
|
req->stage = stage;
|
|
|
|
hnae3_set_bit(ad_data, HCLGE_FD_AD_WR_RULE_ID_B,
|
|
action->write_rule_id_to_bd);
|
|
hnae3_set_field(ad_data, HCLGE_FD_AD_RULE_ID_M, HCLGE_FD_AD_RULE_ID_S,
|
|
action->rule_id);
|
|
if (test_bit(HNAE3_DEV_SUPPORT_FD_FORWARD_TC_B, ae_dev->caps)) {
|
|
hnae3_set_bit(ad_data, HCLGE_FD_AD_TC_OVRD_B,
|
|
action->override_tc);
|
|
hnae3_set_field(ad_data, HCLGE_FD_AD_TC_SIZE_M,
|
|
HCLGE_FD_AD_TC_SIZE_S, (u32)action->tc_size);
|
|
}
|
|
hnae3_set_bit(ad_data, HCLGE_FD_AD_QID_H_B,
|
|
action->queue_id >= HCLGE_TQP_MAX_SIZE_DEV_V2 ? 1 : 0);
|
|
hnae3_set_bit(ad_data, HCLGE_FD_AD_COUNTER_NUM_H_B,
|
|
action->counter_id >= HCLGE_FD_COUNTER_MAX_SIZE_DEV_V2 ?
|
|
1 : 0);
|
|
ad_data <<= 32;
|
|
hnae3_set_bit(ad_data, HCLGE_FD_AD_DROP_B, action->drop_packet);
|
|
hnae3_set_bit(ad_data, HCLGE_FD_AD_DIRECT_QID_B,
|
|
action->forward_to_direct_queue);
|
|
hnae3_set_field(ad_data, HCLGE_FD_AD_QID_L_M, HCLGE_FD_AD_QID_L_S,
|
|
action->queue_id);
|
|
hnae3_set_bit(ad_data, HCLGE_FD_AD_USE_COUNTER_B, action->use_counter);
|
|
hnae3_set_field(ad_data, HCLGE_FD_AD_COUNTER_NUM_L_M,
|
|
HCLGE_FD_AD_COUNTER_NUM_L_S, action->counter_id);
|
|
hnae3_set_bit(ad_data, HCLGE_FD_AD_NXT_STEP_B, action->use_next_stage);
|
|
hnae3_set_field(ad_data, HCLGE_FD_AD_NXT_KEY_M, HCLGE_FD_AD_NXT_KEY_S,
|
|
action->next_input_key);
|
|
|
|
req->ad_data = cpu_to_le64(ad_data);
|
|
ret = hclge_cmd_send(&hdev->hw, &desc, 1);
|
|
if (ret)
|
|
dev_err(&hdev->pdev->dev, "fd ad config fail, ret=%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool hclge_fd_convert_tuple(u32 tuple_bit, u8 *key_x, u8 *key_y,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
int offset, moffset, ip_offset;
|
|
enum HCLGE_FD_KEY_OPT key_opt;
|
|
u16 tmp_x_s, tmp_y_s;
|
|
u32 tmp_x_l, tmp_y_l;
|
|
u8 *p = (u8 *)rule;
|
|
__le32 le_x, le_y;
|
|
int i;
|
|
|
|
if (rule->unused_tuple & BIT(tuple_bit))
|
|
return true;
|
|
|
|
key_opt = tuple_key_info[tuple_bit].key_opt;
|
|
offset = tuple_key_info[tuple_bit].offset;
|
|
moffset = tuple_key_info[tuple_bit].moffset;
|
|
|
|
switch (key_opt) {
|
|
case KEY_OPT_U8:
|
|
calc_x(*key_x, p[offset], p[moffset]);
|
|
calc_y(*key_y, p[offset], p[moffset]);
|
|
|
|
return true;
|
|
case KEY_OPT_LE16:
|
|
calc_x(tmp_x_s, *(u16 *)(&p[offset]), *(u16 *)(&p[moffset]));
|
|
calc_y(tmp_y_s, *(u16 *)(&p[offset]), *(u16 *)(&p[moffset]));
|
|
*(__le16 *)key_x = cpu_to_le16(tmp_x_s);
|
|
*(__le16 *)key_y = cpu_to_le16(tmp_y_s);
|
|
|
|
return true;
|
|
case KEY_OPT_LE32:
|
|
calc_x(tmp_x_l, *(u32 *)(&p[offset]), *(u32 *)(&p[moffset]));
|
|
calc_y(tmp_y_l, *(u32 *)(&p[offset]), *(u32 *)(&p[moffset]));
|
|
*(__le32 *)key_x = cpu_to_le32(tmp_x_l);
|
|
*(__le32 *)key_y = cpu_to_le32(tmp_y_l);
|
|
|
|
return true;
|
|
case KEY_OPT_MAC:
|
|
for (i = 0; i < ETH_ALEN; i++) {
|
|
calc_x(key_x[ETH_ALEN - 1 - i], p[offset + i],
|
|
p[moffset + i]);
|
|
calc_y(key_y[ETH_ALEN - 1 - i], p[offset + i],
|
|
p[moffset + i]);
|
|
}
|
|
|
|
return true;
|
|
case KEY_OPT_IP:
|
|
ip_offset = IPV4_INDEX * sizeof(u32);
|
|
calc_x(tmp_x_l, *(u32 *)(&p[offset + ip_offset]),
|
|
*(u32 *)(&p[moffset + ip_offset]));
|
|
calc_y(tmp_y_l, *(u32 *)(&p[offset + ip_offset]),
|
|
*(u32 *)(&p[moffset + ip_offset]));
|
|
*(__le32 *)key_x = cpu_to_le32(tmp_x_l);
|
|
*(__le32 *)key_y = cpu_to_le32(tmp_y_l);
|
|
|
|
return true;
|
|
case KEY_OPT_VNI:
|
|
calc_x(tmp_x_l, *(u32 *)(&p[offset]), *(u32 *)(&p[moffset]));
|
|
calc_y(tmp_y_l, *(u32 *)(&p[offset]), *(u32 *)(&p[moffset]));
|
|
le_x = cpu_to_le32(tmp_x_l);
|
|
le_y = cpu_to_le32(tmp_y_l);
|
|
memcpy(key_x, &le_x, HCLGE_VNI_LENGTH);
|
|
memcpy(key_y, &le_y, HCLGE_VNI_LENGTH);
|
|
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void hclge_fd_convert_meta_data(struct hclge_fd_key_cfg *key_cfg,
|
|
__le32 *key_x, __le32 *key_y,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
u32 tuple_bit, meta_data = 0, tmp_x, tmp_y, port_number;
|
|
u8 cur_pos = 0, tuple_size, shift_bits;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < MAX_META_DATA; i++) {
|
|
tuple_size = meta_data_key_info[i].key_length;
|
|
tuple_bit = key_cfg->meta_data_active & BIT(i);
|
|
|
|
switch (tuple_bit) {
|
|
case BIT(ROCE_TYPE):
|
|
hnae3_set_bit(meta_data, cur_pos, NIC_PACKET);
|
|
cur_pos += tuple_size;
|
|
break;
|
|
case BIT(DST_VPORT):
|
|
port_number = hclge_get_port_number(HOST_PORT, 0,
|
|
rule->vf_id, 0);
|
|
hnae3_set_field(meta_data,
|
|
GENMASK(cur_pos + tuple_size, cur_pos),
|
|
cur_pos, port_number);
|
|
cur_pos += tuple_size;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
calc_x(tmp_x, meta_data, 0xFFFFFFFF);
|
|
calc_y(tmp_y, meta_data, 0xFFFFFFFF);
|
|
shift_bits = sizeof(meta_data) * 8 - cur_pos;
|
|
|
|
*key_x = cpu_to_le32(tmp_x << shift_bits);
|
|
*key_y = cpu_to_le32(tmp_y << shift_bits);
|
|
}
|
|
|
|
/* A complete key is combined with meta data key and tuple key.
|
|
* Meta data key is stored at the MSB region, and tuple key is stored at
|
|
* the LSB region, unused bits will be filled 0.
|
|
*/
|
|
static int hclge_config_key(struct hclge_dev *hdev, u8 stage,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
struct hclge_fd_key_cfg *key_cfg = &hdev->fd_cfg.key_cfg[stage];
|
|
u8 key_x[MAX_KEY_BYTES], key_y[MAX_KEY_BYTES];
|
|
u8 *cur_key_x, *cur_key_y;
|
|
u8 meta_data_region;
|
|
u8 tuple_size;
|
|
int ret;
|
|
u32 i;
|
|
|
|
memset(key_x, 0, sizeof(key_x));
|
|
memset(key_y, 0, sizeof(key_y));
|
|
cur_key_x = key_x;
|
|
cur_key_y = key_y;
|
|
|
|
for (i = 0; i < MAX_TUPLE; i++) {
|
|
bool tuple_valid;
|
|
|
|
tuple_size = tuple_key_info[i].key_length / 8;
|
|
if (!(key_cfg->tuple_active & BIT(i)))
|
|
continue;
|
|
|
|
tuple_valid = hclge_fd_convert_tuple(i, cur_key_x,
|
|
cur_key_y, rule);
|
|
if (tuple_valid) {
|
|
cur_key_x += tuple_size;
|
|
cur_key_y += tuple_size;
|
|
}
|
|
}
|
|
|
|
meta_data_region = hdev->fd_cfg.max_key_length / 8 -
|
|
MAX_META_DATA_LENGTH / 8;
|
|
|
|
hclge_fd_convert_meta_data(key_cfg,
|
|
(__le32 *)(key_x + meta_data_region),
|
|
(__le32 *)(key_y + meta_data_region),
|
|
rule);
|
|
|
|
ret = hclge_fd_tcam_config(hdev, stage, false, rule->location, key_y,
|
|
true);
|
|
if (ret) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"fd key_y config fail, loc=%u, ret=%d\n",
|
|
rule->queue_id, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = hclge_fd_tcam_config(hdev, stage, true, rule->location, key_x,
|
|
true);
|
|
if (ret)
|
|
dev_err(&hdev->pdev->dev,
|
|
"fd key_x config fail, loc=%u, ret=%d\n",
|
|
rule->queue_id, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int hclge_config_action(struct hclge_dev *hdev, u8 stage,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
struct hclge_vport *vport = hdev->vport;
|
|
struct hnae3_knic_private_info *kinfo = &vport->nic.kinfo;
|
|
struct hclge_fd_ad_data ad_data;
|
|
|
|
memset(&ad_data, 0, sizeof(struct hclge_fd_ad_data));
|
|
ad_data.ad_id = rule->location;
|
|
|
|
if (rule->action == HCLGE_FD_ACTION_DROP_PACKET) {
|
|
ad_data.drop_packet = true;
|
|
} else if (rule->action == HCLGE_FD_ACTION_SELECT_TC) {
|
|
ad_data.override_tc = true;
|
|
ad_data.queue_id =
|
|
kinfo->tc_info.tqp_offset[rule->cls_flower.tc];
|
|
ad_data.tc_size =
|
|
ilog2(kinfo->tc_info.tqp_count[rule->cls_flower.tc]);
|
|
} else {
|
|
ad_data.forward_to_direct_queue = true;
|
|
ad_data.queue_id = rule->queue_id;
|
|
}
|
|
|
|
if (hdev->fd_cfg.cnt_num[HCLGE_FD_STAGE_1]) {
|
|
ad_data.use_counter = true;
|
|
ad_data.counter_id = rule->vf_id %
|
|
hdev->fd_cfg.cnt_num[HCLGE_FD_STAGE_1];
|
|
} else {
|
|
ad_data.use_counter = false;
|
|
ad_data.counter_id = 0;
|
|
}
|
|
|
|
ad_data.use_next_stage = false;
|
|
ad_data.next_input_key = 0;
|
|
|
|
ad_data.write_rule_id_to_bd = true;
|
|
ad_data.rule_id = rule->location;
|
|
|
|
return hclge_fd_ad_config(hdev, stage, ad_data.ad_id, &ad_data);
|
|
}
|
|
|
|
static int hclge_fd_check_tcpip4_tuple(struct ethtool_tcpip4_spec *spec,
|
|
u32 *unused_tuple)
|
|
{
|
|
if (!spec || !unused_tuple)
|
|
return -EINVAL;
|
|
|
|
*unused_tuple |= BIT(INNER_SRC_MAC) | BIT(INNER_DST_MAC);
|
|
|
|
if (!spec->ip4src)
|
|
*unused_tuple |= BIT(INNER_SRC_IP);
|
|
|
|
if (!spec->ip4dst)
|
|
*unused_tuple |= BIT(INNER_DST_IP);
|
|
|
|
if (!spec->psrc)
|
|
*unused_tuple |= BIT(INNER_SRC_PORT);
|
|
|
|
if (!spec->pdst)
|
|
*unused_tuple |= BIT(INNER_DST_PORT);
|
|
|
|
if (!spec->tos)
|
|
*unused_tuple |= BIT(INNER_IP_TOS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_fd_check_ip4_tuple(struct ethtool_usrip4_spec *spec,
|
|
u32 *unused_tuple)
|
|
{
|
|
if (!spec || !unused_tuple)
|
|
return -EINVAL;
|
|
|
|
*unused_tuple |= BIT(INNER_SRC_MAC) | BIT(INNER_DST_MAC) |
|
|
BIT(INNER_SRC_PORT) | BIT(INNER_DST_PORT);
|
|
|
|
if (!spec->ip4src)
|
|
*unused_tuple |= BIT(INNER_SRC_IP);
|
|
|
|
if (!spec->ip4dst)
|
|
*unused_tuple |= BIT(INNER_DST_IP);
|
|
|
|
if (!spec->tos)
|
|
*unused_tuple |= BIT(INNER_IP_TOS);
|
|
|
|
if (!spec->proto)
|
|
*unused_tuple |= BIT(INNER_IP_PROTO);
|
|
|
|
if (spec->l4_4_bytes)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (spec->ip_ver != ETH_RX_NFC_IP4)
|
|
return -EOPNOTSUPP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_fd_check_tcpip6_tuple(struct ethtool_tcpip6_spec *spec,
|
|
u32 *unused_tuple)
|
|
{
|
|
if (!spec || !unused_tuple)
|
|
return -EINVAL;
|
|
|
|
*unused_tuple |= BIT(INNER_SRC_MAC) | BIT(INNER_DST_MAC);
|
|
|
|
/* check whether src/dst ip address used */
|
|
if (ipv6_addr_any((struct in6_addr *)spec->ip6src))
|
|
*unused_tuple |= BIT(INNER_SRC_IP);
|
|
|
|
if (ipv6_addr_any((struct in6_addr *)spec->ip6dst))
|
|
*unused_tuple |= BIT(INNER_DST_IP);
|
|
|
|
if (!spec->psrc)
|
|
*unused_tuple |= BIT(INNER_SRC_PORT);
|
|
|
|
if (!spec->pdst)
|
|
*unused_tuple |= BIT(INNER_DST_PORT);
|
|
|
|
if (!spec->tclass)
|
|
*unused_tuple |= BIT(INNER_IP_TOS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_fd_check_ip6_tuple(struct ethtool_usrip6_spec *spec,
|
|
u32 *unused_tuple)
|
|
{
|
|
if (!spec || !unused_tuple)
|
|
return -EINVAL;
|
|
|
|
*unused_tuple |= BIT(INNER_SRC_MAC) | BIT(INNER_DST_MAC) |
|
|
BIT(INNER_SRC_PORT) | BIT(INNER_DST_PORT);
|
|
|
|
/* check whether src/dst ip address used */
|
|
if (ipv6_addr_any((struct in6_addr *)spec->ip6src))
|
|
*unused_tuple |= BIT(INNER_SRC_IP);
|
|
|
|
if (ipv6_addr_any((struct in6_addr *)spec->ip6dst))
|
|
*unused_tuple |= BIT(INNER_DST_IP);
|
|
|
|
if (!spec->l4_proto)
|
|
*unused_tuple |= BIT(INNER_IP_PROTO);
|
|
|
|
if (!spec->tclass)
|
|
*unused_tuple |= BIT(INNER_IP_TOS);
|
|
|
|
if (spec->l4_4_bytes)
|
|
return -EOPNOTSUPP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_fd_check_ether_tuple(struct ethhdr *spec, u32 *unused_tuple)
|
|
{
|
|
if (!spec || !unused_tuple)
|
|
return -EINVAL;
|
|
|
|
*unused_tuple |= BIT(INNER_SRC_IP) | BIT(INNER_DST_IP) |
|
|
BIT(INNER_SRC_PORT) | BIT(INNER_DST_PORT) |
|
|
BIT(INNER_IP_TOS) | BIT(INNER_IP_PROTO);
|
|
|
|
if (is_zero_ether_addr(spec->h_source))
|
|
*unused_tuple |= BIT(INNER_SRC_MAC);
|
|
|
|
if (is_zero_ether_addr(spec->h_dest))
|
|
*unused_tuple |= BIT(INNER_DST_MAC);
|
|
|
|
if (!spec->h_proto)
|
|
*unused_tuple |= BIT(INNER_ETH_TYPE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_fd_check_ext_tuple(struct hclge_dev *hdev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
u32 *unused_tuple)
|
|
{
|
|
if (fs->flow_type & FLOW_EXT) {
|
|
if (fs->h_ext.vlan_etype) {
|
|
dev_err(&hdev->pdev->dev, "vlan-etype is not supported!\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!fs->h_ext.vlan_tci)
|
|
*unused_tuple |= BIT(INNER_VLAN_TAG_FST);
|
|
|
|
if (fs->m_ext.vlan_tci &&
|
|
be16_to_cpu(fs->h_ext.vlan_tci) >= VLAN_N_VID) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"failed to config vlan_tci, invalid vlan_tci: %u, max is %d.\n",
|
|
ntohs(fs->h_ext.vlan_tci), VLAN_N_VID - 1);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
*unused_tuple |= BIT(INNER_VLAN_TAG_FST);
|
|
}
|
|
|
|
if (fs->flow_type & FLOW_MAC_EXT) {
|
|
if (hdev->fd_cfg.fd_mode !=
|
|
HCLGE_FD_MODE_DEPTH_2K_WIDTH_400B_STAGE_1) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"FLOW_MAC_EXT is not supported in current fd mode!\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (is_zero_ether_addr(fs->h_ext.h_dest))
|
|
*unused_tuple |= BIT(INNER_DST_MAC);
|
|
else
|
|
*unused_tuple &= ~BIT(INNER_DST_MAC);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_fd_get_user_def_layer(u32 flow_type, u32 *unused_tuple,
|
|
struct hclge_fd_user_def_info *info)
|
|
{
|
|
switch (flow_type) {
|
|
case ETHER_FLOW:
|
|
info->layer = HCLGE_FD_USER_DEF_L2;
|
|
*unused_tuple &= ~BIT(INNER_L2_RSV);
|
|
break;
|
|
case IP_USER_FLOW:
|
|
case IPV6_USER_FLOW:
|
|
info->layer = HCLGE_FD_USER_DEF_L3;
|
|
*unused_tuple &= ~BIT(INNER_L3_RSV);
|
|
break;
|
|
case TCP_V4_FLOW:
|
|
case UDP_V4_FLOW:
|
|
case TCP_V6_FLOW:
|
|
case UDP_V6_FLOW:
|
|
info->layer = HCLGE_FD_USER_DEF_L4;
|
|
*unused_tuple &= ~BIT(INNER_L4_RSV);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool hclge_fd_is_user_def_all_masked(struct ethtool_rx_flow_spec *fs)
|
|
{
|
|
return be32_to_cpu(fs->m_ext.data[1] | fs->m_ext.data[0]) == 0;
|
|
}
|
|
|
|
static int hclge_fd_parse_user_def_field(struct hclge_dev *hdev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
u32 *unused_tuple,
|
|
struct hclge_fd_user_def_info *info)
|
|
{
|
|
u32 tuple_active = hdev->fd_cfg.key_cfg[HCLGE_FD_STAGE_1].tuple_active;
|
|
u32 flow_type = fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT);
|
|
u16 data, offset, data_mask, offset_mask;
|
|
int ret;
|
|
|
|
info->layer = HCLGE_FD_USER_DEF_NONE;
|
|
*unused_tuple |= HCLGE_FD_TUPLE_USER_DEF_TUPLES;
|
|
|
|
if (!(fs->flow_type & FLOW_EXT) || hclge_fd_is_user_def_all_masked(fs))
|
|
return 0;
|
|
|
|
/* user-def data from ethtool is 64 bit value, the bit0~15 is used
|
|
* for data, and bit32~47 is used for offset.
|
|
*/
|
|
data = be32_to_cpu(fs->h_ext.data[1]) & HCLGE_FD_USER_DEF_DATA;
|
|
data_mask = be32_to_cpu(fs->m_ext.data[1]) & HCLGE_FD_USER_DEF_DATA;
|
|
offset = be32_to_cpu(fs->h_ext.data[0]) & HCLGE_FD_USER_DEF_OFFSET;
|
|
offset_mask = be32_to_cpu(fs->m_ext.data[0]) & HCLGE_FD_USER_DEF_OFFSET;
|
|
|
|
if (!(tuple_active & HCLGE_FD_TUPLE_USER_DEF_TUPLES)) {
|
|
dev_err(&hdev->pdev->dev, "user-def bytes are not supported\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (offset > HCLGE_FD_MAX_USER_DEF_OFFSET) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"user-def offset[%u] should be no more than %u\n",
|
|
offset, HCLGE_FD_MAX_USER_DEF_OFFSET);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (offset_mask != HCLGE_FD_USER_DEF_OFFSET_UNMASK) {
|
|
dev_err(&hdev->pdev->dev, "user-def offset can't be masked\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = hclge_fd_get_user_def_layer(flow_type, unused_tuple, info);
|
|
if (ret) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"unsupported flow type for user-def bytes, ret = %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
info->data = data;
|
|
info->data_mask = data_mask;
|
|
info->offset = offset;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_fd_check_spec(struct hclge_dev *hdev,
|
|
struct ethtool_rx_flow_spec *fs,
|
|
u32 *unused_tuple,
|
|
struct hclge_fd_user_def_info *info)
|
|
{
|
|
u32 flow_type;
|
|
int ret;
|
|
|
|
if (fs->location >= hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1]) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"failed to config fd rules, invalid rule location: %u, max is %u\n.",
|
|
fs->location,
|
|
hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1] - 1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = hclge_fd_parse_user_def_field(hdev, fs, unused_tuple, info);
|
|
if (ret)
|
|
return ret;
|
|
|
|
flow_type = fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT);
|
|
switch (flow_type) {
|
|
case SCTP_V4_FLOW:
|
|
case TCP_V4_FLOW:
|
|
case UDP_V4_FLOW:
|
|
ret = hclge_fd_check_tcpip4_tuple(&fs->h_u.tcp_ip4_spec,
|
|
unused_tuple);
|
|
break;
|
|
case IP_USER_FLOW:
|
|
ret = hclge_fd_check_ip4_tuple(&fs->h_u.usr_ip4_spec,
|
|
unused_tuple);
|
|
break;
|
|
case SCTP_V6_FLOW:
|
|
case TCP_V6_FLOW:
|
|
case UDP_V6_FLOW:
|
|
ret = hclge_fd_check_tcpip6_tuple(&fs->h_u.tcp_ip6_spec,
|
|
unused_tuple);
|
|
break;
|
|
case IPV6_USER_FLOW:
|
|
ret = hclge_fd_check_ip6_tuple(&fs->h_u.usr_ip6_spec,
|
|
unused_tuple);
|
|
break;
|
|
case ETHER_FLOW:
|
|
if (hdev->fd_cfg.fd_mode !=
|
|
HCLGE_FD_MODE_DEPTH_2K_WIDTH_400B_STAGE_1) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"ETHER_FLOW is not supported in current fd mode!\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
ret = hclge_fd_check_ether_tuple(&fs->h_u.ether_spec,
|
|
unused_tuple);
|
|
break;
|
|
default:
|
|
dev_err(&hdev->pdev->dev,
|
|
"unsupported protocol type, protocol type = %#x\n",
|
|
flow_type);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (ret) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"failed to check flow union tuple, ret = %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
return hclge_fd_check_ext_tuple(hdev, fs, unused_tuple);
|
|
}
|
|
|
|
static void hclge_fd_get_tcpip4_tuple(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule, u8 ip_proto)
|
|
{
|
|
rule->tuples.src_ip[IPV4_INDEX] =
|
|
be32_to_cpu(fs->h_u.tcp_ip4_spec.ip4src);
|
|
rule->tuples_mask.src_ip[IPV4_INDEX] =
|
|
be32_to_cpu(fs->m_u.tcp_ip4_spec.ip4src);
|
|
|
|
rule->tuples.dst_ip[IPV4_INDEX] =
|
|
be32_to_cpu(fs->h_u.tcp_ip4_spec.ip4dst);
|
|
rule->tuples_mask.dst_ip[IPV4_INDEX] =
|
|
be32_to_cpu(fs->m_u.tcp_ip4_spec.ip4dst);
|
|
|
|
rule->tuples.src_port = be16_to_cpu(fs->h_u.tcp_ip4_spec.psrc);
|
|
rule->tuples_mask.src_port = be16_to_cpu(fs->m_u.tcp_ip4_spec.psrc);
|
|
|
|
rule->tuples.dst_port = be16_to_cpu(fs->h_u.tcp_ip4_spec.pdst);
|
|
rule->tuples_mask.dst_port = be16_to_cpu(fs->m_u.tcp_ip4_spec.pdst);
|
|
|
|
rule->tuples.ip_tos = fs->h_u.tcp_ip4_spec.tos;
|
|
rule->tuples_mask.ip_tos = fs->m_u.tcp_ip4_spec.tos;
|
|
|
|
rule->tuples.ether_proto = ETH_P_IP;
|
|
rule->tuples_mask.ether_proto = 0xFFFF;
|
|
|
|
rule->tuples.ip_proto = ip_proto;
|
|
rule->tuples_mask.ip_proto = 0xFF;
|
|
}
|
|
|
|
static void hclge_fd_get_ip4_tuple(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
rule->tuples.src_ip[IPV4_INDEX] =
|
|
be32_to_cpu(fs->h_u.usr_ip4_spec.ip4src);
|
|
rule->tuples_mask.src_ip[IPV4_INDEX] =
|
|
be32_to_cpu(fs->m_u.usr_ip4_spec.ip4src);
|
|
|
|
rule->tuples.dst_ip[IPV4_INDEX] =
|
|
be32_to_cpu(fs->h_u.usr_ip4_spec.ip4dst);
|
|
rule->tuples_mask.dst_ip[IPV4_INDEX] =
|
|
be32_to_cpu(fs->m_u.usr_ip4_spec.ip4dst);
|
|
|
|
rule->tuples.ip_tos = fs->h_u.usr_ip4_spec.tos;
|
|
rule->tuples_mask.ip_tos = fs->m_u.usr_ip4_spec.tos;
|
|
|
|
rule->tuples.ip_proto = fs->h_u.usr_ip4_spec.proto;
|
|
rule->tuples_mask.ip_proto = fs->m_u.usr_ip4_spec.proto;
|
|
|
|
rule->tuples.ether_proto = ETH_P_IP;
|
|
rule->tuples_mask.ether_proto = 0xFFFF;
|
|
}
|
|
|
|
static void hclge_fd_get_tcpip6_tuple(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule, u8 ip_proto)
|
|
{
|
|
ipv6_addr_be32_to_cpu(rule->tuples.src_ip,
|
|
fs->h_u.tcp_ip6_spec.ip6src);
|
|
ipv6_addr_be32_to_cpu(rule->tuples_mask.src_ip,
|
|
fs->m_u.tcp_ip6_spec.ip6src);
|
|
|
|
ipv6_addr_be32_to_cpu(rule->tuples.dst_ip,
|
|
fs->h_u.tcp_ip6_spec.ip6dst);
|
|
ipv6_addr_be32_to_cpu(rule->tuples_mask.dst_ip,
|
|
fs->m_u.tcp_ip6_spec.ip6dst);
|
|
|
|
rule->tuples.src_port = be16_to_cpu(fs->h_u.tcp_ip6_spec.psrc);
|
|
rule->tuples_mask.src_port = be16_to_cpu(fs->m_u.tcp_ip6_spec.psrc);
|
|
|
|
rule->tuples.dst_port = be16_to_cpu(fs->h_u.tcp_ip6_spec.pdst);
|
|
rule->tuples_mask.dst_port = be16_to_cpu(fs->m_u.tcp_ip6_spec.pdst);
|
|
|
|
rule->tuples.ether_proto = ETH_P_IPV6;
|
|
rule->tuples_mask.ether_proto = 0xFFFF;
|
|
|
|
rule->tuples.ip_tos = fs->h_u.tcp_ip6_spec.tclass;
|
|
rule->tuples_mask.ip_tos = fs->m_u.tcp_ip6_spec.tclass;
|
|
|
|
rule->tuples.ip_proto = ip_proto;
|
|
rule->tuples_mask.ip_proto = 0xFF;
|
|
}
|
|
|
|
static void hclge_fd_get_ip6_tuple(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
ipv6_addr_be32_to_cpu(rule->tuples.src_ip,
|
|
fs->h_u.usr_ip6_spec.ip6src);
|
|
ipv6_addr_be32_to_cpu(rule->tuples_mask.src_ip,
|
|
fs->m_u.usr_ip6_spec.ip6src);
|
|
|
|
ipv6_addr_be32_to_cpu(rule->tuples.dst_ip,
|
|
fs->h_u.usr_ip6_spec.ip6dst);
|
|
ipv6_addr_be32_to_cpu(rule->tuples_mask.dst_ip,
|
|
fs->m_u.usr_ip6_spec.ip6dst);
|
|
|
|
rule->tuples.ip_proto = fs->h_u.usr_ip6_spec.l4_proto;
|
|
rule->tuples_mask.ip_proto = fs->m_u.usr_ip6_spec.l4_proto;
|
|
|
|
rule->tuples.ip_tos = fs->h_u.tcp_ip6_spec.tclass;
|
|
rule->tuples_mask.ip_tos = fs->m_u.tcp_ip6_spec.tclass;
|
|
|
|
rule->tuples.ether_proto = ETH_P_IPV6;
|
|
rule->tuples_mask.ether_proto = 0xFFFF;
|
|
}
|
|
|
|
static void hclge_fd_get_ether_tuple(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
ether_addr_copy(rule->tuples.src_mac, fs->h_u.ether_spec.h_source);
|
|
ether_addr_copy(rule->tuples_mask.src_mac, fs->m_u.ether_spec.h_source);
|
|
|
|
ether_addr_copy(rule->tuples.dst_mac, fs->h_u.ether_spec.h_dest);
|
|
ether_addr_copy(rule->tuples_mask.dst_mac, fs->m_u.ether_spec.h_dest);
|
|
|
|
rule->tuples.ether_proto = be16_to_cpu(fs->h_u.ether_spec.h_proto);
|
|
rule->tuples_mask.ether_proto = be16_to_cpu(fs->m_u.ether_spec.h_proto);
|
|
}
|
|
|
|
static void hclge_fd_get_user_def_tuple(struct hclge_fd_user_def_info *info,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
switch (info->layer) {
|
|
case HCLGE_FD_USER_DEF_L2:
|
|
rule->tuples.l2_user_def = info->data;
|
|
rule->tuples_mask.l2_user_def = info->data_mask;
|
|
break;
|
|
case HCLGE_FD_USER_DEF_L3:
|
|
rule->tuples.l3_user_def = info->data;
|
|
rule->tuples_mask.l3_user_def = info->data_mask;
|
|
break;
|
|
case HCLGE_FD_USER_DEF_L4:
|
|
rule->tuples.l4_user_def = (u32)info->data << 16;
|
|
rule->tuples_mask.l4_user_def = (u32)info->data_mask << 16;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
rule->ep.user_def = *info;
|
|
}
|
|
|
|
static int hclge_fd_get_tuple(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule,
|
|
struct hclge_fd_user_def_info *info)
|
|
{
|
|
u32 flow_type = fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT);
|
|
|
|
switch (flow_type) {
|
|
case SCTP_V4_FLOW:
|
|
hclge_fd_get_tcpip4_tuple(fs, rule, IPPROTO_SCTP);
|
|
break;
|
|
case TCP_V4_FLOW:
|
|
hclge_fd_get_tcpip4_tuple(fs, rule, IPPROTO_TCP);
|
|
break;
|
|
case UDP_V4_FLOW:
|
|
hclge_fd_get_tcpip4_tuple(fs, rule, IPPROTO_UDP);
|
|
break;
|
|
case IP_USER_FLOW:
|
|
hclge_fd_get_ip4_tuple(fs, rule);
|
|
break;
|
|
case SCTP_V6_FLOW:
|
|
hclge_fd_get_tcpip6_tuple(fs, rule, IPPROTO_SCTP);
|
|
break;
|
|
case TCP_V6_FLOW:
|
|
hclge_fd_get_tcpip6_tuple(fs, rule, IPPROTO_TCP);
|
|
break;
|
|
case UDP_V6_FLOW:
|
|
hclge_fd_get_tcpip6_tuple(fs, rule, IPPROTO_UDP);
|
|
break;
|
|
case IPV6_USER_FLOW:
|
|
hclge_fd_get_ip6_tuple(fs, rule);
|
|
break;
|
|
case ETHER_FLOW:
|
|
hclge_fd_get_ether_tuple(fs, rule);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (fs->flow_type & FLOW_EXT) {
|
|
rule->tuples.vlan_tag1 = be16_to_cpu(fs->h_ext.vlan_tci);
|
|
rule->tuples_mask.vlan_tag1 = be16_to_cpu(fs->m_ext.vlan_tci);
|
|
hclge_fd_get_user_def_tuple(info, rule);
|
|
}
|
|
|
|
if (fs->flow_type & FLOW_MAC_EXT) {
|
|
ether_addr_copy(rule->tuples.dst_mac, fs->h_ext.h_dest);
|
|
ether_addr_copy(rule->tuples_mask.dst_mac, fs->m_ext.h_dest);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_fd_config_rule(struct hclge_dev *hdev,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
int ret;
|
|
|
|
ret = hclge_config_action(hdev, HCLGE_FD_STAGE_1, rule);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return hclge_config_key(hdev, HCLGE_FD_STAGE_1, rule);
|
|
}
|
|
|
|
static int hclge_add_fd_entry_common(struct hclge_dev *hdev,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
int ret;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
|
|
if (hdev->fd_active_type != rule->rule_type &&
|
|
(hdev->fd_active_type == HCLGE_FD_TC_FLOWER_ACTIVE ||
|
|
hdev->fd_active_type == HCLGE_FD_EP_ACTIVE)) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"mode conflict(new type %d, active type %d), please delete existent rules first\n",
|
|
rule->rule_type, hdev->fd_active_type);
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = hclge_fd_check_user_def_refcnt(hdev, rule);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = hclge_clear_arfs_rules(hdev);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = hclge_fd_config_rule(hdev, rule);
|
|
if (ret)
|
|
goto out;
|
|
|
|
rule->state = HCLGE_FD_ACTIVE;
|
|
hdev->fd_active_type = rule->rule_type;
|
|
hclge_update_fd_list(hdev, rule->state, rule->location, rule);
|
|
|
|
out:
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return ret;
|
|
}
|
|
|
|
bool hclge_is_cls_flower_active(struct hnae3_handle *handle)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
|
|
return hdev->fd_active_type == HCLGE_FD_TC_FLOWER_ACTIVE;
|
|
}
|
|
|
|
static int hclge_fd_parse_ring_cookie(struct hclge_dev *hdev, u64 ring_cookie,
|
|
u16 *vport_id, u8 *action, u16 *queue_id)
|
|
{
|
|
struct hclge_vport *vport = hdev->vport;
|
|
|
|
if (ring_cookie == RX_CLS_FLOW_DISC) {
|
|
*action = HCLGE_FD_ACTION_DROP_PACKET;
|
|
} else {
|
|
u32 ring = ethtool_get_flow_spec_ring(ring_cookie);
|
|
u8 vf = ethtool_get_flow_spec_ring_vf(ring_cookie);
|
|
u16 tqps;
|
|
|
|
/* To keep consistent with user's configuration, minus 1 when
|
|
* printing 'vf', because vf id from ethtool is added 1 for vf.
|
|
*/
|
|
if (vf > hdev->num_req_vfs) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"Error: vf id (%u) should be less than %u\n",
|
|
vf - 1U, hdev->num_req_vfs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*vport_id = vf ? hdev->vport[vf].vport_id : vport->vport_id;
|
|
tqps = hdev->vport[vf].nic.kinfo.num_tqps;
|
|
|
|
if (ring >= tqps) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"Error: queue id (%u) > max tqp num (%u)\n",
|
|
ring, tqps - 1U);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*action = HCLGE_FD_ACTION_SELECT_QUEUE;
|
|
*queue_id = ring;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hclge_add_fd_entry(struct hnae3_handle *handle, struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
struct hclge_fd_user_def_info info;
|
|
u16 dst_vport_id = 0, q_index = 0;
|
|
struct ethtool_rx_flow_spec *fs;
|
|
struct hclge_fd_rule *rule;
|
|
u32 unused = 0;
|
|
u8 action;
|
|
int ret;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev)) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"flow table director is not supported\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!hdev->fd_en) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"please enable flow director first\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
fs = (struct ethtool_rx_flow_spec *)&cmd->fs;
|
|
|
|
ret = hclge_fd_check_spec(hdev, fs, &unused, &info);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = hclge_fd_parse_ring_cookie(hdev, fs->ring_cookie, &dst_vport_id,
|
|
&action, &q_index);
|
|
if (ret)
|
|
return ret;
|
|
|
|
rule = kzalloc_obj(*rule);
|
|
if (!rule)
|
|
return -ENOMEM;
|
|
|
|
ret = hclge_fd_get_tuple(fs, rule, &info);
|
|
if (ret) {
|
|
kfree(rule);
|
|
return ret;
|
|
}
|
|
|
|
rule->flow_type = fs->flow_type;
|
|
rule->location = fs->location;
|
|
rule->unused_tuple = unused;
|
|
rule->vf_id = dst_vport_id;
|
|
rule->queue_id = q_index;
|
|
rule->action = action;
|
|
rule->rule_type = HCLGE_FD_EP_ACTIVE;
|
|
|
|
ret = hclge_add_fd_entry_common(hdev, rule);
|
|
if (ret)
|
|
kfree(rule);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int hclge_del_fd_entry(struct hnae3_handle *handle, struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
struct ethtool_rx_flow_spec *fs;
|
|
int ret;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return -EOPNOTSUPP;
|
|
|
|
fs = (struct ethtool_rx_flow_spec *)&cmd->fs;
|
|
|
|
if (fs->location >= hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1])
|
|
return -EINVAL;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
if (hdev->fd_active_type == HCLGE_FD_TC_FLOWER_ACTIVE ||
|
|
!test_bit(fs->location, hdev->fd_bmap)) {
|
|
dev_err(&hdev->pdev->dev,
|
|
"Delete fail, rule %u is inexistent\n", fs->location);
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return -ENOENT;
|
|
}
|
|
|
|
ret = hclge_fd_tcam_config(hdev, HCLGE_FD_STAGE_1, true, fs->location,
|
|
NULL, false);
|
|
if (ret)
|
|
goto out;
|
|
|
|
hclge_update_fd_list(hdev, HCLGE_FD_DELETED, fs->location, NULL);
|
|
|
|
out:
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return ret;
|
|
}
|
|
|
|
static void hclge_clear_fd_rules_in_list(struct hclge_dev *hdev,
|
|
bool clear_list)
|
|
{
|
|
struct hclge_fd_rule *rule;
|
|
struct hlist_node *node;
|
|
u16 location;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
|
|
for_each_set_bit(location, hdev->fd_bmap,
|
|
hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1])
|
|
hclge_fd_tcam_config(hdev, HCLGE_FD_STAGE_1, true, location,
|
|
NULL, false);
|
|
|
|
if (clear_list) {
|
|
hlist_for_each_entry_safe(rule, node, &hdev->fd_rule_list,
|
|
rule_node) {
|
|
hlist_del(&rule->rule_node);
|
|
kfree(rule);
|
|
}
|
|
hdev->fd_active_type = HCLGE_FD_RULE_NONE;
|
|
hdev->hclge_fd_rule_num = 0;
|
|
bitmap_zero(hdev->fd_bmap,
|
|
hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1]);
|
|
}
|
|
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
}
|
|
|
|
void hclge_del_all_fd_entries(struct hclge_dev *hdev)
|
|
{
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return;
|
|
|
|
hclge_clear_fd_rules_in_list(hdev, true);
|
|
hclge_fd_disable_user_def(hdev);
|
|
}
|
|
|
|
int hclge_restore_fd_entries(struct hnae3_handle *handle)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
struct hclge_fd_rule *rule;
|
|
struct hlist_node *node;
|
|
|
|
/* Return ok here, because reset error handling will check this
|
|
* return value. If error is returned here, the reset process will
|
|
* fail.
|
|
*/
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return 0;
|
|
|
|
/* if fd is disabled, should not restore it when reset */
|
|
if (!hdev->fd_en)
|
|
return 0;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
hlist_for_each_entry_safe(rule, node, &hdev->fd_rule_list, rule_node) {
|
|
if (rule->state == HCLGE_FD_ACTIVE)
|
|
rule->state = HCLGE_FD_TO_ADD;
|
|
}
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
set_bit(HCLGE_STATE_FD_TBL_CHANGED, &hdev->state);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hclge_get_fd_rule_cnt(struct hnae3_handle *handle,
|
|
struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev) ||
|
|
hclge_is_cls_flower_active(handle))
|
|
return -EOPNOTSUPP;
|
|
|
|
cmd->rule_cnt = hdev->hclge_fd_rule_num;
|
|
cmd->data = hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hclge_fd_get_tcpip4_info(struct hclge_fd_rule *rule,
|
|
struct ethtool_tcpip4_spec *spec,
|
|
struct ethtool_tcpip4_spec *spec_mask)
|
|
{
|
|
spec->ip4src = cpu_to_be32(rule->tuples.src_ip[IPV4_INDEX]);
|
|
spec_mask->ip4src = rule->unused_tuple & BIT(INNER_SRC_IP) ?
|
|
0 : cpu_to_be32(rule->tuples_mask.src_ip[IPV4_INDEX]);
|
|
|
|
spec->ip4dst = cpu_to_be32(rule->tuples.dst_ip[IPV4_INDEX]);
|
|
spec_mask->ip4dst = rule->unused_tuple & BIT(INNER_DST_IP) ?
|
|
0 : cpu_to_be32(rule->tuples_mask.dst_ip[IPV4_INDEX]);
|
|
|
|
spec->psrc = cpu_to_be16(rule->tuples.src_port);
|
|
spec_mask->psrc = rule->unused_tuple & BIT(INNER_SRC_PORT) ?
|
|
0 : cpu_to_be16(rule->tuples_mask.src_port);
|
|
|
|
spec->pdst = cpu_to_be16(rule->tuples.dst_port);
|
|
spec_mask->pdst = rule->unused_tuple & BIT(INNER_DST_PORT) ?
|
|
0 : cpu_to_be16(rule->tuples_mask.dst_port);
|
|
|
|
spec->tos = rule->tuples.ip_tos;
|
|
spec_mask->tos = rule->unused_tuple & BIT(INNER_IP_TOS) ?
|
|
0 : rule->tuples_mask.ip_tos;
|
|
}
|
|
|
|
static void hclge_fd_get_ip4_info(struct hclge_fd_rule *rule,
|
|
struct ethtool_usrip4_spec *spec,
|
|
struct ethtool_usrip4_spec *spec_mask)
|
|
{
|
|
spec->ip4src = cpu_to_be32(rule->tuples.src_ip[IPV4_INDEX]);
|
|
spec_mask->ip4src = rule->unused_tuple & BIT(INNER_SRC_IP) ?
|
|
0 : cpu_to_be32(rule->tuples_mask.src_ip[IPV4_INDEX]);
|
|
|
|
spec->ip4dst = cpu_to_be32(rule->tuples.dst_ip[IPV4_INDEX]);
|
|
spec_mask->ip4dst = rule->unused_tuple & BIT(INNER_DST_IP) ?
|
|
0 : cpu_to_be32(rule->tuples_mask.dst_ip[IPV4_INDEX]);
|
|
|
|
spec->tos = rule->tuples.ip_tos;
|
|
spec_mask->tos = rule->unused_tuple & BIT(INNER_IP_TOS) ?
|
|
0 : rule->tuples_mask.ip_tos;
|
|
|
|
spec->proto = rule->tuples.ip_proto;
|
|
spec_mask->proto = rule->unused_tuple & BIT(INNER_IP_PROTO) ?
|
|
0 : rule->tuples_mask.ip_proto;
|
|
|
|
spec->ip_ver = ETH_RX_NFC_IP4;
|
|
}
|
|
|
|
static void hclge_fd_get_tcpip6_info(struct hclge_fd_rule *rule,
|
|
struct ethtool_tcpip6_spec *spec,
|
|
struct ethtool_tcpip6_spec *spec_mask)
|
|
{
|
|
ipv6_addr_cpu_to_be32(spec->ip6src, rule->tuples.src_ip);
|
|
ipv6_addr_cpu_to_be32(spec->ip6dst, rule->tuples.dst_ip);
|
|
if (rule->unused_tuple & BIT(INNER_SRC_IP))
|
|
memset(spec_mask->ip6src, 0, sizeof(spec_mask->ip6src));
|
|
else
|
|
ipv6_addr_cpu_to_be32(spec_mask->ip6src,
|
|
rule->tuples_mask.src_ip);
|
|
|
|
if (rule->unused_tuple & BIT(INNER_DST_IP))
|
|
memset(spec_mask->ip6dst, 0, sizeof(spec_mask->ip6dst));
|
|
else
|
|
ipv6_addr_cpu_to_be32(spec_mask->ip6dst,
|
|
rule->tuples_mask.dst_ip);
|
|
|
|
spec->tclass = rule->tuples.ip_tos;
|
|
spec_mask->tclass = rule->unused_tuple & BIT(INNER_IP_TOS) ?
|
|
0 : rule->tuples_mask.ip_tos;
|
|
|
|
spec->psrc = cpu_to_be16(rule->tuples.src_port);
|
|
spec_mask->psrc = rule->unused_tuple & BIT(INNER_SRC_PORT) ?
|
|
0 : cpu_to_be16(rule->tuples_mask.src_port);
|
|
|
|
spec->pdst = cpu_to_be16(rule->tuples.dst_port);
|
|
spec_mask->pdst = rule->unused_tuple & BIT(INNER_DST_PORT) ?
|
|
0 : cpu_to_be16(rule->tuples_mask.dst_port);
|
|
}
|
|
|
|
static void hclge_fd_get_ip6_info(struct hclge_fd_rule *rule,
|
|
struct ethtool_usrip6_spec *spec,
|
|
struct ethtool_usrip6_spec *spec_mask)
|
|
{
|
|
ipv6_addr_cpu_to_be32(spec->ip6src, rule->tuples.src_ip);
|
|
ipv6_addr_cpu_to_be32(spec->ip6dst, rule->tuples.dst_ip);
|
|
if (rule->unused_tuple & BIT(INNER_SRC_IP))
|
|
memset(spec_mask->ip6src, 0, sizeof(spec_mask->ip6src));
|
|
else
|
|
ipv6_addr_cpu_to_be32(spec_mask->ip6src,
|
|
rule->tuples_mask.src_ip);
|
|
|
|
if (rule->unused_tuple & BIT(INNER_DST_IP))
|
|
memset(spec_mask->ip6dst, 0, sizeof(spec_mask->ip6dst));
|
|
else
|
|
ipv6_addr_cpu_to_be32(spec_mask->ip6dst,
|
|
rule->tuples_mask.dst_ip);
|
|
|
|
spec->tclass = rule->tuples.ip_tos;
|
|
spec_mask->tclass = rule->unused_tuple & BIT(INNER_IP_TOS) ?
|
|
0 : rule->tuples_mask.ip_tos;
|
|
|
|
spec->l4_proto = rule->tuples.ip_proto;
|
|
spec_mask->l4_proto = rule->unused_tuple & BIT(INNER_IP_PROTO) ?
|
|
0 : rule->tuples_mask.ip_proto;
|
|
}
|
|
|
|
static void hclge_fd_get_ether_info(struct hclge_fd_rule *rule,
|
|
struct ethhdr *spec,
|
|
struct ethhdr *spec_mask)
|
|
{
|
|
ether_addr_copy(spec->h_source, rule->tuples.src_mac);
|
|
ether_addr_copy(spec->h_dest, rule->tuples.dst_mac);
|
|
|
|
if (rule->unused_tuple & BIT(INNER_SRC_MAC))
|
|
eth_zero_addr(spec_mask->h_source);
|
|
else
|
|
ether_addr_copy(spec_mask->h_source, rule->tuples_mask.src_mac);
|
|
|
|
if (rule->unused_tuple & BIT(INNER_DST_MAC))
|
|
eth_zero_addr(spec_mask->h_dest);
|
|
else
|
|
ether_addr_copy(spec_mask->h_dest, rule->tuples_mask.dst_mac);
|
|
|
|
spec->h_proto = cpu_to_be16(rule->tuples.ether_proto);
|
|
spec_mask->h_proto = rule->unused_tuple & BIT(INNER_ETH_TYPE) ?
|
|
0 : cpu_to_be16(rule->tuples_mask.ether_proto);
|
|
}
|
|
|
|
static void hclge_fd_get_user_def_info(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
if ((rule->unused_tuple & HCLGE_FD_TUPLE_USER_DEF_TUPLES) ==
|
|
HCLGE_FD_TUPLE_USER_DEF_TUPLES) {
|
|
fs->h_ext.data[0] = 0;
|
|
fs->h_ext.data[1] = 0;
|
|
fs->m_ext.data[0] = 0;
|
|
fs->m_ext.data[1] = 0;
|
|
} else {
|
|
fs->h_ext.data[0] = cpu_to_be32(rule->ep.user_def.offset);
|
|
fs->h_ext.data[1] = cpu_to_be32(rule->ep.user_def.data);
|
|
fs->m_ext.data[0] =
|
|
cpu_to_be32(HCLGE_FD_USER_DEF_OFFSET_UNMASK);
|
|
fs->m_ext.data[1] = cpu_to_be32(rule->ep.user_def.data_mask);
|
|
}
|
|
}
|
|
|
|
static void hclge_fd_get_ext_info(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
if (fs->flow_type & FLOW_EXT) {
|
|
fs->h_ext.vlan_tci = cpu_to_be16(rule->tuples.vlan_tag1);
|
|
fs->m_ext.vlan_tci =
|
|
rule->unused_tuple & BIT(INNER_VLAN_TAG_FST) ?
|
|
0 : cpu_to_be16(rule->tuples_mask.vlan_tag1);
|
|
|
|
hclge_fd_get_user_def_info(fs, rule);
|
|
}
|
|
|
|
if (fs->flow_type & FLOW_MAC_EXT) {
|
|
ether_addr_copy(fs->h_ext.h_dest, rule->tuples.dst_mac);
|
|
if (rule->unused_tuple & BIT(INNER_DST_MAC))
|
|
eth_zero_addr(fs->m_u.ether_spec.h_dest);
|
|
else
|
|
ether_addr_copy(fs->m_u.ether_spec.h_dest,
|
|
rule->tuples_mask.dst_mac);
|
|
}
|
|
}
|
|
|
|
static struct hclge_fd_rule *hclge_get_fd_rule(struct hclge_dev *hdev,
|
|
u16 location)
|
|
{
|
|
struct hclge_fd_rule *rule = NULL;
|
|
struct hlist_node *node2;
|
|
|
|
hlist_for_each_entry_safe(rule, node2, &hdev->fd_rule_list, rule_node) {
|
|
if (rule->location == location)
|
|
return rule;
|
|
else if (rule->location > location)
|
|
return NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void hclge_fd_get_ring_cookie(struct ethtool_rx_flow_spec *fs,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
if (rule->action == HCLGE_FD_ACTION_DROP_PACKET) {
|
|
fs->ring_cookie = RX_CLS_FLOW_DISC;
|
|
} else {
|
|
u64 vf_id;
|
|
|
|
fs->ring_cookie = rule->queue_id;
|
|
vf_id = rule->vf_id;
|
|
vf_id <<= ETHTOOL_RX_FLOW_SPEC_RING_VF_OFF;
|
|
fs->ring_cookie |= vf_id;
|
|
}
|
|
}
|
|
|
|
int hclge_get_fd_rule_info(struct hnae3_handle *handle,
|
|
struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_fd_rule *rule = NULL;
|
|
struct hclge_dev *hdev = vport->back;
|
|
struct ethtool_rx_flow_spec *fs;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return -EOPNOTSUPP;
|
|
|
|
fs = (struct ethtool_rx_flow_spec *)&cmd->fs;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
|
|
rule = hclge_get_fd_rule(hdev, fs->location);
|
|
if (!rule) {
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return -ENOENT;
|
|
}
|
|
|
|
fs->flow_type = rule->flow_type;
|
|
switch (fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT)) {
|
|
case SCTP_V4_FLOW:
|
|
case TCP_V4_FLOW:
|
|
case UDP_V4_FLOW:
|
|
hclge_fd_get_tcpip4_info(rule, &fs->h_u.tcp_ip4_spec,
|
|
&fs->m_u.tcp_ip4_spec);
|
|
break;
|
|
case IP_USER_FLOW:
|
|
hclge_fd_get_ip4_info(rule, &fs->h_u.usr_ip4_spec,
|
|
&fs->m_u.usr_ip4_spec);
|
|
break;
|
|
case SCTP_V6_FLOW:
|
|
case TCP_V6_FLOW:
|
|
case UDP_V6_FLOW:
|
|
hclge_fd_get_tcpip6_info(rule, &fs->h_u.tcp_ip6_spec,
|
|
&fs->m_u.tcp_ip6_spec);
|
|
break;
|
|
case IPV6_USER_FLOW:
|
|
hclge_fd_get_ip6_info(rule, &fs->h_u.usr_ip6_spec,
|
|
&fs->m_u.usr_ip6_spec);
|
|
break;
|
|
/* The flow type of fd rule has been checked before adding in to rule
|
|
* list. As other flow types have been handled, it must be ETHER_FLOW
|
|
* for the default case
|
|
*/
|
|
default:
|
|
hclge_fd_get_ether_info(rule, &fs->h_u.ether_spec,
|
|
&fs->m_u.ether_spec);
|
|
break;
|
|
}
|
|
|
|
hclge_fd_get_ext_info(fs, rule);
|
|
|
|
hclge_fd_get_ring_cookie(fs, rule);
|
|
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hclge_get_all_rules(struct hnae3_handle *handle,
|
|
struct ethtool_rxnfc *cmd, u32 *rule_locs)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
struct hclge_fd_rule *rule;
|
|
struct hlist_node *node2;
|
|
u32 cnt = 0;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return -EOPNOTSUPP;
|
|
|
|
cmd->data = hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1];
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
hlist_for_each_entry_safe(rule, node2,
|
|
&hdev->fd_rule_list, rule_node) {
|
|
if (cnt == cmd->rule_cnt) {
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
if (rule->state == HCLGE_FD_TO_DEL)
|
|
continue;
|
|
|
|
rule_locs[cnt] = rule->location;
|
|
cnt++;
|
|
}
|
|
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
|
|
cmd->rule_cnt = cnt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hclge_fd_get_flow_tuples(const struct flow_keys *fkeys,
|
|
struct hclge_fd_rule_tuples *tuples)
|
|
{
|
|
#define flow_ip6_src fkeys->addrs.v6addrs.src.in6_u.u6_addr32
|
|
#define flow_ip6_dst fkeys->addrs.v6addrs.dst.in6_u.u6_addr32
|
|
|
|
tuples->ether_proto = be16_to_cpu(fkeys->basic.n_proto);
|
|
tuples->ip_proto = fkeys->basic.ip_proto;
|
|
tuples->dst_port = be16_to_cpu(fkeys->ports.dst);
|
|
|
|
if (fkeys->basic.n_proto == htons(ETH_P_IP)) {
|
|
tuples->src_ip[3] = be32_to_cpu(fkeys->addrs.v4addrs.src);
|
|
tuples->dst_ip[3] = be32_to_cpu(fkeys->addrs.v4addrs.dst);
|
|
} else {
|
|
int i;
|
|
|
|
for (i = 0; i < IPV6_ADDR_WORDS; i++) {
|
|
tuples->src_ip[i] = be32_to_cpu(flow_ip6_src[i]);
|
|
tuples->dst_ip[i] = be32_to_cpu(flow_ip6_dst[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* traverse all rules, check whether an existed rule has the same tuples */
|
|
static struct hclge_fd_rule *
|
|
hclge_fd_search_flow_keys(struct hclge_dev *hdev,
|
|
const struct hclge_fd_rule_tuples *tuples)
|
|
{
|
|
struct hclge_fd_rule *rule = NULL;
|
|
struct hlist_node *node;
|
|
|
|
hlist_for_each_entry_safe(rule, node, &hdev->fd_rule_list, rule_node) {
|
|
if (!memcmp(tuples, &rule->tuples, sizeof(*tuples)))
|
|
return rule;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void hclge_fd_build_arfs_rule(const struct hclge_fd_rule_tuples *tuples,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
rule->unused_tuple = BIT(INNER_SRC_MAC) | BIT(INNER_DST_MAC) |
|
|
BIT(INNER_VLAN_TAG_FST) | BIT(INNER_IP_TOS) |
|
|
BIT(INNER_SRC_PORT);
|
|
rule->action = 0;
|
|
rule->vf_id = 0;
|
|
rule->rule_type = HCLGE_FD_ARFS_ACTIVE;
|
|
rule->state = HCLGE_FD_TO_ADD;
|
|
if (tuples->ether_proto == ETH_P_IP) {
|
|
if (tuples->ip_proto == IPPROTO_TCP)
|
|
rule->flow_type = TCP_V4_FLOW;
|
|
else
|
|
rule->flow_type = UDP_V4_FLOW;
|
|
} else {
|
|
if (tuples->ip_proto == IPPROTO_TCP)
|
|
rule->flow_type = TCP_V6_FLOW;
|
|
else
|
|
rule->flow_type = UDP_V6_FLOW;
|
|
}
|
|
memcpy(&rule->tuples, tuples, sizeof(rule->tuples));
|
|
memset(&rule->tuples_mask, 0xFF, sizeof(rule->tuples_mask));
|
|
}
|
|
|
|
int hclge_add_fd_entry_by_arfs(struct hnae3_handle *handle, u16 queue_id,
|
|
u16 flow_id, struct flow_keys *fkeys)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_fd_rule_tuples new_tuples = {};
|
|
struct hclge_dev *hdev = vport->back;
|
|
struct hclge_fd_rule *rule;
|
|
u16 bit_id;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return -EOPNOTSUPP;
|
|
|
|
/* when there is already fd rule existed add by user,
|
|
* arfs should not work
|
|
*/
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
if (hdev->fd_active_type != HCLGE_FD_ARFS_ACTIVE &&
|
|
hdev->fd_active_type != HCLGE_FD_RULE_NONE) {
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
hclge_fd_get_flow_tuples(fkeys, &new_tuples);
|
|
|
|
/* check is there flow director filter existed for this flow,
|
|
* if not, create a new filter for it;
|
|
* if filter exist with different queue id, modify the filter;
|
|
* if filter exist with same queue id, do nothing
|
|
*/
|
|
rule = hclge_fd_search_flow_keys(hdev, &new_tuples);
|
|
if (!rule) {
|
|
bit_id = find_first_zero_bit(hdev->fd_bmap, MAX_FD_FILTER_NUM);
|
|
if (bit_id >= hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1]) {
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
rule = kzalloc_obj(*rule, GFP_ATOMIC);
|
|
if (!rule) {
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
rule->location = bit_id;
|
|
rule->arfs.flow_id = flow_id;
|
|
rule->queue_id = queue_id;
|
|
hclge_fd_build_arfs_rule(&new_tuples, rule);
|
|
hclge_update_fd_list(hdev, rule->state, rule->location, rule);
|
|
hdev->fd_active_type = HCLGE_FD_ARFS_ACTIVE;
|
|
} else if (rule->queue_id != queue_id) {
|
|
rule->queue_id = queue_id;
|
|
rule->state = HCLGE_FD_TO_ADD;
|
|
set_bit(HCLGE_STATE_FD_TBL_CHANGED, &hdev->state);
|
|
hclge_task_schedule(hdev, 0);
|
|
}
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return rule->location;
|
|
}
|
|
|
|
void hclge_rfs_filter_expire(struct hclge_dev *hdev)
|
|
{
|
|
#ifdef CONFIG_RFS_ACCEL
|
|
struct hnae3_handle *handle = &hdev->vport[0].nic;
|
|
struct hclge_fd_rule *rule;
|
|
struct hlist_node *node;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
if (hdev->fd_active_type != HCLGE_FD_ARFS_ACTIVE) {
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return;
|
|
}
|
|
hlist_for_each_entry_safe(rule, node, &hdev->fd_rule_list, rule_node) {
|
|
if (rule->state != HCLGE_FD_ACTIVE)
|
|
continue;
|
|
if (rps_may_expire_flow(handle->netdev, rule->queue_id,
|
|
rule->arfs.flow_id, rule->location)) {
|
|
rule->state = HCLGE_FD_TO_DEL;
|
|
set_bit(HCLGE_STATE_FD_TBL_CHANGED, &hdev->state);
|
|
}
|
|
}
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
#endif
|
|
}
|
|
|
|
/* make sure being called after lock up with fd_rule_lock */
|
|
int hclge_clear_arfs_rules(struct hclge_dev *hdev)
|
|
{
|
|
#ifdef CONFIG_RFS_ACCEL
|
|
struct hclge_fd_rule *rule;
|
|
struct hlist_node *node;
|
|
int ret;
|
|
|
|
if (hdev->fd_active_type != HCLGE_FD_ARFS_ACTIVE)
|
|
return 0;
|
|
|
|
hlist_for_each_entry_safe(rule, node, &hdev->fd_rule_list, rule_node) {
|
|
switch (rule->state) {
|
|
case HCLGE_FD_TO_DEL:
|
|
case HCLGE_FD_ACTIVE:
|
|
ret = hclge_fd_tcam_config(hdev, HCLGE_FD_STAGE_1, true,
|
|
rule->location, NULL, false);
|
|
if (ret)
|
|
return ret;
|
|
fallthrough;
|
|
case HCLGE_FD_TO_ADD:
|
|
hclge_fd_dec_rule_cnt(hdev, rule->location);
|
|
hlist_del(&rule->rule_node);
|
|
kfree(rule);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
hclge_sync_fd_state(hdev);
|
|
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void hclge_get_cls_key_basic(const struct flow_rule *flow,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
if (flow_rule_match_key(flow, FLOW_DISSECTOR_KEY_BASIC)) {
|
|
struct flow_match_basic match;
|
|
u16 ethtype_key, ethtype_mask;
|
|
|
|
flow_rule_match_basic(flow, &match);
|
|
ethtype_key = ntohs(match.key->n_proto);
|
|
ethtype_mask = ntohs(match.mask->n_proto);
|
|
|
|
if (ethtype_key == ETH_P_ALL) {
|
|
ethtype_key = 0;
|
|
ethtype_mask = 0;
|
|
}
|
|
rule->tuples.ether_proto = ethtype_key;
|
|
rule->tuples_mask.ether_proto = ethtype_mask;
|
|
rule->tuples.ip_proto = match.key->ip_proto;
|
|
rule->tuples_mask.ip_proto = match.mask->ip_proto;
|
|
} else {
|
|
rule->unused_tuple |= BIT(INNER_IP_PROTO);
|
|
rule->unused_tuple |= BIT(INNER_ETH_TYPE);
|
|
}
|
|
}
|
|
|
|
static void hclge_get_cls_key_mac(const struct flow_rule *flow,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
if (flow_rule_match_key(flow, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
|
|
struct flow_match_eth_addrs match;
|
|
|
|
flow_rule_match_eth_addrs(flow, &match);
|
|
ether_addr_copy(rule->tuples.dst_mac, match.key->dst);
|
|
ether_addr_copy(rule->tuples_mask.dst_mac, match.mask->dst);
|
|
ether_addr_copy(rule->tuples.src_mac, match.key->src);
|
|
ether_addr_copy(rule->tuples_mask.src_mac, match.mask->src);
|
|
if (is_zero_ether_addr(match.mask->dst))
|
|
rule->unused_tuple |= BIT(INNER_DST_MAC);
|
|
if (is_zero_ether_addr(match.mask->src))
|
|
rule->unused_tuple |= BIT(INNER_SRC_MAC);
|
|
} else {
|
|
rule->unused_tuple |= BIT(INNER_DST_MAC);
|
|
rule->unused_tuple |= BIT(INNER_SRC_MAC);
|
|
}
|
|
}
|
|
|
|
static void hclge_get_cls_key_vlan(const struct flow_rule *flow,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
if (flow_rule_match_key(flow, FLOW_DISSECTOR_KEY_VLAN)) {
|
|
struct flow_match_vlan match;
|
|
|
|
flow_rule_match_vlan(flow, &match);
|
|
rule->tuples.vlan_tag1 = match.key->vlan_id |
|
|
(match.key->vlan_priority << VLAN_PRIO_SHIFT);
|
|
rule->tuples_mask.vlan_tag1 = match.mask->vlan_id |
|
|
(match.mask->vlan_priority << VLAN_PRIO_SHIFT);
|
|
} else {
|
|
rule->unused_tuple |= BIT(INNER_VLAN_TAG_FST);
|
|
}
|
|
}
|
|
|
|
static int hclge_get_cls_key_ip(const struct flow_rule *flow,
|
|
struct hclge_fd_rule *rule,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
u16 addr_type = 0;
|
|
|
|
if (flow_rule_match_key(flow, FLOW_DISSECTOR_KEY_CONTROL)) {
|
|
struct flow_match_control match;
|
|
|
|
flow_rule_match_control(flow, &match);
|
|
addr_type = match.key->addr_type;
|
|
|
|
if (flow_rule_has_control_flags(match.mask->flags, extack))
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
|
|
struct flow_match_ipv4_addrs match;
|
|
|
|
flow_rule_match_ipv4_addrs(flow, &match);
|
|
rule->tuples.src_ip[IPV4_INDEX] = be32_to_cpu(match.key->src);
|
|
rule->tuples_mask.src_ip[IPV4_INDEX] =
|
|
be32_to_cpu(match.mask->src);
|
|
rule->tuples.dst_ip[IPV4_INDEX] = be32_to_cpu(match.key->dst);
|
|
rule->tuples_mask.dst_ip[IPV4_INDEX] =
|
|
be32_to_cpu(match.mask->dst);
|
|
if (!match.mask->src)
|
|
rule->unused_tuple |= BIT(INNER_SRC_IP);
|
|
if (!match.mask->dst)
|
|
rule->unused_tuple |= BIT(INNER_DST_IP);
|
|
} else if (addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
|
|
struct flow_match_ipv6_addrs match;
|
|
|
|
flow_rule_match_ipv6_addrs(flow, &match);
|
|
ipv6_addr_be32_to_cpu(rule->tuples.src_ip,
|
|
match.key->src.s6_addr32);
|
|
ipv6_addr_be32_to_cpu(rule->tuples_mask.src_ip,
|
|
match.mask->src.s6_addr32);
|
|
ipv6_addr_be32_to_cpu(rule->tuples.dst_ip,
|
|
match.key->dst.s6_addr32);
|
|
ipv6_addr_be32_to_cpu(rule->tuples_mask.dst_ip,
|
|
match.mask->dst.s6_addr32);
|
|
if (ipv6_addr_any(&match.mask->src))
|
|
rule->unused_tuple |= BIT(INNER_SRC_IP);
|
|
if (ipv6_addr_any(&match.mask->dst))
|
|
rule->unused_tuple |= BIT(INNER_DST_IP);
|
|
} else {
|
|
rule->unused_tuple |= BIT(INNER_SRC_IP);
|
|
rule->unused_tuple |= BIT(INNER_DST_IP);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hclge_get_cls_key_port(const struct flow_rule *flow,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
if (flow_rule_match_key(flow, FLOW_DISSECTOR_KEY_PORTS)) {
|
|
struct flow_match_ports match;
|
|
|
|
flow_rule_match_ports(flow, &match);
|
|
|
|
rule->tuples.src_port = be16_to_cpu(match.key->src);
|
|
rule->tuples_mask.src_port = be16_to_cpu(match.mask->src);
|
|
rule->tuples.dst_port = be16_to_cpu(match.key->dst);
|
|
rule->tuples_mask.dst_port = be16_to_cpu(match.mask->dst);
|
|
} else {
|
|
rule->unused_tuple |= BIT(INNER_SRC_PORT);
|
|
rule->unused_tuple |= BIT(INNER_DST_PORT);
|
|
}
|
|
}
|
|
|
|
static int hclge_get_cls_enc_keyid(struct hclge_dev *hdev,
|
|
const struct flow_rule *flow,
|
|
struct hclge_fd_rule *rule,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
if (flow_rule_match_key(flow, FLOW_DISSECTOR_KEY_ENC_KEYID)) {
|
|
struct flow_match_enc_keyid match;
|
|
|
|
flow_rule_match_enc_keyid(flow, &match);
|
|
|
|
/* vni is only 24 bits and must be greater than 0,
|
|
* and it can not be masked.
|
|
*/
|
|
if (be32_to_cpu(match.mask->keyid) !=
|
|
HCLGE_FD_VXLAN_VNI_UNMASK ||
|
|
be32_to_cpu(match.key->keyid) >= VXLAN_N_VID ||
|
|
!match.key->keyid) {
|
|
NL_SET_ERR_MSG_MOD(extack, "invalid enc_keyid");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rule->tuples.outer_tun_vni = be32_to_cpu(match.key->keyid);
|
|
rule->tuples_mask.outer_tun_vni =
|
|
be32_to_cpu(match.mask->keyid);
|
|
} else {
|
|
rule->unused_tuple |= BIT(OUTER_TUN_VNI);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_get_cls_key_ip_tos(const struct flow_rule *flow,
|
|
struct hclge_fd_rule *rule,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
if (flow_rule_match_key(flow, FLOW_DISSECTOR_KEY_IP)) {
|
|
struct flow_match_ip match;
|
|
|
|
flow_rule_match_ip(flow, &match);
|
|
|
|
if (match.mask->ttl) {
|
|
NL_SET_ERR_MSG_MOD(extack, "unsupported TTL");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
rule->tuples.ip_tos = match.key->tos;
|
|
rule->tuples_mask.ip_tos = match.mask->tos;
|
|
if (!rule->tuples_mask.ip_tos)
|
|
rule->unused_tuple |= BIT(INNER_IP_TOS);
|
|
} else {
|
|
rule->unused_tuple |= BIT(INNER_IP_TOS);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hclge_get_tc_flower_action(struct hclge_dev *hdev,
|
|
struct flow_cls_offload *cls_flower,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
struct flow_rule *flow = flow_cls_offload_flow_rule(cls_flower);
|
|
struct netlink_ext_ack *extack = cls_flower->common.extack;
|
|
struct hnae3_handle *handle = &hdev->vport[0].nic;
|
|
struct flow_action *action = &flow->action;
|
|
struct flow_action_entry *act;
|
|
int tc;
|
|
|
|
if (!flow_action_has_entries(&flow->action)) {
|
|
tc = tc_classid_to_hwtc(handle->netdev, cls_flower->classid);
|
|
if (tc < 0 || tc > hdev->tc_max) {
|
|
NL_SET_ERR_MSG_FMT_MOD(extack,
|
|
"invalid traffic class: %d", tc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rule->action = HCLGE_FD_ACTION_SELECT_TC;
|
|
rule->cls_flower.tc = tc;
|
|
return 0;
|
|
}
|
|
|
|
act = &action->entries[0];
|
|
switch (act->id) {
|
|
case FLOW_ACTION_RX_QUEUE_MAPPING:
|
|
if (act->rx_queue >= handle->kinfo.num_tqps) {
|
|
NL_SET_ERR_MSG_FMT_MOD(extack,
|
|
"queue id (%u) should be less than %u",
|
|
act->rx_queue,
|
|
handle->kinfo.num_tqps);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rule->queue_id = act->rx_queue;
|
|
rule->action = HCLGE_FD_ACTION_SELECT_QUEUE;
|
|
return 0;
|
|
case FLOW_ACTION_DROP:
|
|
rule->action = HCLGE_FD_ACTION_DROP_PACKET;
|
|
return 0;
|
|
default:
|
|
NL_SET_ERR_MSG_FMT_MOD(extack,
|
|
"unsupported action(%d)", act->id);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static int hclge_parse_cls_flower(struct hclge_dev *hdev,
|
|
struct flow_cls_offload *cls_flower,
|
|
struct hclge_fd_rule *rule)
|
|
{
|
|
struct flow_rule *flow = flow_cls_offload_flow_rule(cls_flower);
|
|
struct netlink_ext_ack *extack = cls_flower->common.extack;
|
|
int ret;
|
|
|
|
/* not support any user def tuples */
|
|
rule->unused_tuple |= HCLGE_FD_TUPLE_USER_DEF_TUPLES;
|
|
|
|
hclge_get_cls_key_basic(flow, rule);
|
|
hclge_get_cls_key_mac(flow, rule);
|
|
hclge_get_cls_key_vlan(flow, rule);
|
|
|
|
ret = hclge_get_cls_key_ip(flow, rule, extack);
|
|
if (ret)
|
|
return ret;
|
|
|
|
hclge_get_cls_key_port(flow, rule);
|
|
ret = hclge_get_cls_key_ip_tos(flow, rule, extack);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return hclge_get_cls_enc_keyid(hdev, flow, rule, extack);
|
|
}
|
|
|
|
static int hclge_check_cls_flower(struct hclge_dev *hdev,
|
|
struct flow_cls_offload *cls_flower)
|
|
{
|
|
struct flow_rule *flow = flow_cls_offload_flow_rule(cls_flower);
|
|
struct netlink_ext_ack *extack = cls_flower->common.extack;
|
|
struct flow_dissector *dissector = flow->match.dissector;
|
|
u32 prio = cls_flower->common.prio;
|
|
u64 support_keys;
|
|
|
|
if (prio == 0 ||
|
|
prio > hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1]) {
|
|
NL_SET_ERR_MSG_FMT_MOD(extack,
|
|
"prio %u should be in range[1, %u]",
|
|
prio,
|
|
hdev->fd_cfg.rule_num[HCLGE_FD_STAGE_1]);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (test_bit(prio - 1, hdev->fd_bmap)) {
|
|
NL_SET_ERR_MSG_FMT_MOD(extack,
|
|
"prio %u is already used", prio);
|
|
return -EINVAL;
|
|
}
|
|
|
|
support_keys = BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_VLAN) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_IP);
|
|
|
|
if (hdev->fd_cfg.fd_mode == HCLGE_FD_MODE_DEPTH_2K_WIDTH_400B_STAGE_1)
|
|
support_keys |= BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_ENC_KEYID);
|
|
|
|
if (dissector->used_keys & ~support_keys) {
|
|
NL_SET_ERR_MSG_FMT_MOD(extack, "unsupported key set: %#llx",
|
|
dissector->used_keys);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* driver will parses classid into an action */
|
|
if (cls_flower->classid && flow_action_has_entries(&flow->action)) {
|
|
NL_SET_ERR_MSG_MOD(extack,
|
|
"cannot specify both classid and action");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!flow_action_has_entries(&flow->action) && !cls_flower->classid) {
|
|
NL_SET_ERR_MSG_MOD(extack,
|
|
"must specify either classid or action");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (flow_action_has_entries(&flow->action) &&
|
|
!flow_offload_has_one_action(&flow->action)) {
|
|
NL_SET_ERR_MSG_MOD(extack, "unsupported multiple actions");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hclge_add_cls_flower(struct hnae3_handle *handle,
|
|
struct flow_cls_offload *cls_flower)
|
|
{
|
|
struct netlink_ext_ack *extack = cls_flower->common.extack;
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
struct hclge_fd_rule *rule;
|
|
int ret;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev)) {
|
|
NL_SET_ERR_MSG_MOD(extack, "cls flower is not supported");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
ret = hclge_check_cls_flower(hdev, cls_flower);
|
|
if (ret)
|
|
return ret;
|
|
|
|
rule = kzalloc_obj(*rule);
|
|
if (!rule)
|
|
return -ENOMEM;
|
|
|
|
ret = hclge_parse_cls_flower(hdev, cls_flower, rule);
|
|
if (ret) {
|
|
kfree(rule);
|
|
return ret;
|
|
}
|
|
|
|
ret = hclge_get_tc_flower_action(hdev, cls_flower, rule);
|
|
if (ret) {
|
|
kfree(rule);
|
|
return ret;
|
|
}
|
|
|
|
rule->location = cls_flower->common.prio - 1;
|
|
rule->vf_id = 0;
|
|
rule->cls_flower.cookie = cls_flower->cookie;
|
|
rule->rule_type = HCLGE_FD_TC_FLOWER_ACTIVE;
|
|
|
|
ret = hclge_add_fd_entry_common(hdev, rule);
|
|
if (ret)
|
|
kfree(rule);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct hclge_fd_rule *hclge_find_cls_flower(struct hclge_dev *hdev,
|
|
unsigned long cookie)
|
|
{
|
|
struct hclge_fd_rule *rule;
|
|
struct hlist_node *node;
|
|
|
|
hlist_for_each_entry_safe(rule, node, &hdev->fd_rule_list, rule_node) {
|
|
if (rule->cls_flower.cookie == cookie)
|
|
return rule;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int hclge_del_cls_flower(struct hnae3_handle *handle,
|
|
struct flow_cls_offload *cls_flower)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
struct hclge_fd_rule *rule;
|
|
int ret;
|
|
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return -EOPNOTSUPP;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
|
|
rule = hclge_find_cls_flower(hdev, cls_flower->cookie);
|
|
if (!rule) {
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = hclge_fd_tcam_config(hdev, HCLGE_FD_STAGE_1, true, rule->location,
|
|
NULL, false);
|
|
if (ret) {
|
|
/* if tcam config fail, set rule state to TO_DEL,
|
|
* so the rule will be deleted when periodic
|
|
* task being scheduled.
|
|
*/
|
|
hclge_update_fd_list(hdev, HCLGE_FD_TO_DEL,
|
|
rule->location, NULL);
|
|
set_bit(HCLGE_STATE_FD_TBL_CHANGED, &hdev->state);
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
return ret;
|
|
}
|
|
|
|
hclge_update_fd_list(hdev, HCLGE_FD_DELETED, rule->location, NULL);
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hclge_sync_fd_list(struct hclge_dev *hdev, struct hlist_head *hlist)
|
|
{
|
|
struct hclge_fd_rule *rule;
|
|
struct hlist_node *node;
|
|
int ret = 0;
|
|
|
|
if (!test_and_clear_bit(HCLGE_STATE_FD_TBL_CHANGED, &hdev->state))
|
|
return;
|
|
|
|
spin_lock_bh(&hdev->fd_rule_lock);
|
|
|
|
hlist_for_each_entry_safe(rule, node, hlist, rule_node) {
|
|
switch (rule->state) {
|
|
case HCLGE_FD_TO_ADD:
|
|
ret = hclge_fd_config_rule(hdev, rule);
|
|
if (ret)
|
|
goto out;
|
|
rule->state = HCLGE_FD_ACTIVE;
|
|
break;
|
|
case HCLGE_FD_TO_DEL:
|
|
ret = hclge_fd_tcam_config(hdev, HCLGE_FD_STAGE_1, true,
|
|
rule->location, NULL, false);
|
|
if (ret)
|
|
goto out;
|
|
hclge_fd_dec_rule_cnt(hdev, rule->location);
|
|
hclge_fd_free_node(hdev, rule);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (ret)
|
|
set_bit(HCLGE_STATE_FD_TBL_CHANGED, &hdev->state);
|
|
|
|
spin_unlock_bh(&hdev->fd_rule_lock);
|
|
}
|
|
|
|
void hclge_sync_fd_table(struct hclge_dev *hdev)
|
|
{
|
|
if (!hnae3_ae_dev_fd_supported(hdev->ae_dev))
|
|
return;
|
|
|
|
if (test_and_clear_bit(HCLGE_STATE_FD_CLEAR_ALL, &hdev->state)) {
|
|
bool clear_list = hdev->fd_active_type == HCLGE_FD_ARFS_ACTIVE;
|
|
|
|
hclge_clear_fd_rules_in_list(hdev, clear_list);
|
|
}
|
|
|
|
hclge_sync_fd_user_def_cfg(hdev, false);
|
|
|
|
hclge_sync_fd_list(hdev, &hdev->fd_rule_list);
|
|
}
|
|
|
|
void hclge_enable_fd(struct hnae3_handle *handle, bool enable)
|
|
{
|
|
struct hclge_vport *vport = hclge_get_vport(handle);
|
|
struct hclge_dev *hdev = vport->back;
|
|
|
|
hdev->fd_en = enable;
|
|
|
|
if (!enable)
|
|
set_bit(HCLGE_STATE_FD_CLEAR_ALL, &hdev->state);
|
|
else
|
|
hclge_restore_fd_entries(handle);
|
|
|
|
hclge_task_schedule(hdev, 0);
|
|
}
|