linux-brain/drivers/net/ethernet/mellanox/mlx5/core/steering/dr_rule.c
Alex Vesker 0041412694 net/mlx5: DR, Allow insertion of duplicate rules
Duplicate rules were not allowed to be configured with SW steering.
This restriction caused failures with the replace rule logic done by
upper layers.

This fix allows for multiple rules with the same match values, in
such case the first inserted rules will match.

Fixes: 41d0707415 ("net/mlx5: DR, Expose steering rule functionality")
Signed-off-by: Alex Vesker <valex@mellanox.com>
Signed-off-by: Tariq Toukan <tariqt@mellanox.com>
Signed-off-by: Jakub Kicinski <jakub.kicinski@netronome.com>
2019-10-08 16:14:50 -07:00

1242 lines
33 KiB
C

// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2019 Mellanox Technologies. */
#include "dr_types.h"
#define DR_RULE_MAX_STE_CHAIN (DR_RULE_MAX_STES + DR_ACTION_MAX_STES)
struct mlx5dr_rule_action_member {
struct mlx5dr_action *action;
struct list_head list;
};
static int dr_rule_append_to_miss_list(struct mlx5dr_ste *new_last_ste,
struct list_head *miss_list,
struct list_head *send_list)
{
struct mlx5dr_ste_send_info *ste_info_last;
struct mlx5dr_ste *last_ste;
/* The new entry will be inserted after the last */
last_ste = list_last_entry(miss_list, struct mlx5dr_ste, miss_list_node);
WARN_ON(!last_ste);
ste_info_last = kzalloc(sizeof(*ste_info_last), GFP_KERNEL);
if (!ste_info_last)
return -ENOMEM;
mlx5dr_ste_set_miss_addr(last_ste->hw_ste,
mlx5dr_ste_get_icm_addr(new_last_ste));
list_add_tail(&new_last_ste->miss_list_node, miss_list);
mlx5dr_send_fill_and_append_ste_send_info(last_ste, DR_STE_SIZE_REDUCED,
0, last_ste->hw_ste,
ste_info_last, send_list, true);
return 0;
}
static struct mlx5dr_ste *
dr_rule_create_collision_htbl(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
u8 *hw_ste)
{
struct mlx5dr_domain *dmn = matcher->tbl->dmn;
struct mlx5dr_ste_htbl *new_htbl;
struct mlx5dr_ste *ste;
/* Create new table for miss entry */
new_htbl = mlx5dr_ste_htbl_alloc(dmn->ste_icm_pool,
DR_CHUNK_SIZE_1,
MLX5DR_STE_LU_TYPE_DONT_CARE,
0);
if (!new_htbl) {
mlx5dr_dbg(dmn, "Failed allocating collision table\n");
return NULL;
}
/* One and only entry, never grows */
ste = new_htbl->ste_arr;
mlx5dr_ste_set_miss_addr(hw_ste, nic_matcher->e_anchor->chunk->icm_addr);
mlx5dr_htbl_get(new_htbl);
return ste;
}
static struct mlx5dr_ste *
dr_rule_create_collision_entry(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
u8 *hw_ste,
struct mlx5dr_ste *orig_ste)
{
struct mlx5dr_ste *ste;
ste = dr_rule_create_collision_htbl(matcher, nic_matcher, hw_ste);
if (!ste) {
mlx5dr_dbg(matcher->tbl->dmn, "Failed creating collision entry\n");
return NULL;
}
ste->ste_chain_location = orig_ste->ste_chain_location;
/* In collision entry, all members share the same miss_list_head */
ste->htbl->miss_list = mlx5dr_ste_get_miss_list(orig_ste);
/* Next table */
if (mlx5dr_ste_create_next_htbl(matcher, nic_matcher, ste, hw_ste,
DR_CHUNK_SIZE_1)) {
mlx5dr_dbg(matcher->tbl->dmn, "Failed allocating table\n");
goto free_tbl;
}
return ste;
free_tbl:
mlx5dr_ste_free(ste, matcher, nic_matcher);
return NULL;
}
static int
dr_rule_handle_one_ste_in_update_list(struct mlx5dr_ste_send_info *ste_info,
struct mlx5dr_domain *dmn)
{
int ret;
list_del(&ste_info->send_list);
ret = mlx5dr_send_postsend_ste(dmn, ste_info->ste, ste_info->data,
ste_info->size, ste_info->offset);
if (ret)
goto out;
/* Copy data to ste, only reduced size, the last 16B (mask)
* is already written to the hw.
*/
memcpy(ste_info->ste->hw_ste, ste_info->data, DR_STE_SIZE_REDUCED);
out:
kfree(ste_info);
return ret;
}
static int dr_rule_send_update_list(struct list_head *send_ste_list,
struct mlx5dr_domain *dmn,
bool is_reverse)
{
struct mlx5dr_ste_send_info *ste_info, *tmp_ste_info;
int ret;
if (is_reverse) {
list_for_each_entry_safe_reverse(ste_info, tmp_ste_info,
send_ste_list, send_list) {
ret = dr_rule_handle_one_ste_in_update_list(ste_info,
dmn);
if (ret)
return ret;
}
} else {
list_for_each_entry_safe(ste_info, tmp_ste_info,
send_ste_list, send_list) {
ret = dr_rule_handle_one_ste_in_update_list(ste_info,
dmn);
if (ret)
return ret;
}
}
return 0;
}
static struct mlx5dr_ste *
dr_rule_find_ste_in_miss_list(struct list_head *miss_list, u8 *hw_ste)
{
struct mlx5dr_ste *ste;
if (list_empty(miss_list))
return NULL;
/* Check if hw_ste is present in the list */
list_for_each_entry(ste, miss_list, miss_list_node) {
if (mlx5dr_ste_equal_tag(ste->hw_ste, hw_ste))
return ste;
}
return NULL;
}
static struct mlx5dr_ste *
dr_rule_rehash_handle_collision(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
struct list_head *update_list,
struct mlx5dr_ste *col_ste,
u8 *hw_ste)
{
struct mlx5dr_ste *new_ste;
int ret;
new_ste = dr_rule_create_collision_htbl(matcher, nic_matcher, hw_ste);
if (!new_ste)
return NULL;
/* In collision entry, all members share the same miss_list_head */
new_ste->htbl->miss_list = mlx5dr_ste_get_miss_list(col_ste);
/* Update the previous from the list */
ret = dr_rule_append_to_miss_list(new_ste,
mlx5dr_ste_get_miss_list(col_ste),
update_list);
if (ret) {
mlx5dr_dbg(matcher->tbl->dmn, "Failed update dup entry\n");
goto err_exit;
}
return new_ste;
err_exit:
mlx5dr_ste_free(new_ste, matcher, nic_matcher);
return NULL;
}
static void dr_rule_rehash_copy_ste_ctrl(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
struct mlx5dr_ste *cur_ste,
struct mlx5dr_ste *new_ste)
{
new_ste->next_htbl = cur_ste->next_htbl;
new_ste->ste_chain_location = cur_ste->ste_chain_location;
if (!mlx5dr_ste_is_last_in_rule(nic_matcher, new_ste->ste_chain_location))
new_ste->next_htbl->pointing_ste = new_ste;
/* We need to copy the refcount since this ste
* may have been traversed several times
*/
refcount_set(&new_ste->refcount, refcount_read(&cur_ste->refcount));
/* Link old STEs rule_mem list to the new ste */
mlx5dr_rule_update_rule_member(cur_ste, new_ste);
INIT_LIST_HEAD(&new_ste->rule_list);
list_splice_tail_init(&cur_ste->rule_list, &new_ste->rule_list);
}
static struct mlx5dr_ste *
dr_rule_rehash_copy_ste(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
struct mlx5dr_ste *cur_ste,
struct mlx5dr_ste_htbl *new_htbl,
struct list_head *update_list)
{
struct mlx5dr_ste_send_info *ste_info;
bool use_update_list = false;
u8 hw_ste[DR_STE_SIZE] = {};
struct mlx5dr_ste *new_ste;
int new_idx;
u8 sb_idx;
/* Copy STE mask from the matcher */
sb_idx = cur_ste->ste_chain_location - 1;
mlx5dr_ste_set_bit_mask(hw_ste, nic_matcher->ste_builder[sb_idx].bit_mask);
/* Copy STE control and tag */
memcpy(hw_ste, cur_ste->hw_ste, DR_STE_SIZE_REDUCED);
mlx5dr_ste_set_miss_addr(hw_ste, nic_matcher->e_anchor->chunk->icm_addr);
new_idx = mlx5dr_ste_calc_hash_index(hw_ste, new_htbl);
new_ste = &new_htbl->ste_arr[new_idx];
if (mlx5dr_ste_not_used_ste(new_ste)) {
mlx5dr_htbl_get(new_htbl);
list_add_tail(&new_ste->miss_list_node,
mlx5dr_ste_get_miss_list(new_ste));
} else {
new_ste = dr_rule_rehash_handle_collision(matcher,
nic_matcher,
update_list,
new_ste,
hw_ste);
if (!new_ste) {
mlx5dr_dbg(matcher->tbl->dmn, "Failed adding collision entry, index: %d\n",
new_idx);
return NULL;
}
new_htbl->ctrl.num_of_collisions++;
use_update_list = true;
}
memcpy(new_ste->hw_ste, hw_ste, DR_STE_SIZE_REDUCED);
new_htbl->ctrl.num_of_valid_entries++;
if (use_update_list) {
ste_info = kzalloc(sizeof(*ste_info), GFP_KERNEL);
if (!ste_info)
goto err_exit;
mlx5dr_send_fill_and_append_ste_send_info(new_ste, DR_STE_SIZE, 0,
hw_ste, ste_info,
update_list, true);
}
dr_rule_rehash_copy_ste_ctrl(matcher, nic_matcher, cur_ste, new_ste);
return new_ste;
err_exit:
mlx5dr_ste_free(new_ste, matcher, nic_matcher);
return NULL;
}
static int dr_rule_rehash_copy_miss_list(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
struct list_head *cur_miss_list,
struct mlx5dr_ste_htbl *new_htbl,
struct list_head *update_list)
{
struct mlx5dr_ste *tmp_ste, *cur_ste, *new_ste;
if (list_empty(cur_miss_list))
return 0;
list_for_each_entry_safe(cur_ste, tmp_ste, cur_miss_list, miss_list_node) {
new_ste = dr_rule_rehash_copy_ste(matcher,
nic_matcher,
cur_ste,
new_htbl,
update_list);
if (!new_ste)
goto err_insert;
list_del(&cur_ste->miss_list_node);
mlx5dr_htbl_put(cur_ste->htbl);
}
return 0;
err_insert:
mlx5dr_err(matcher->tbl->dmn, "Fatal error during resize\n");
WARN_ON(true);
return -EINVAL;
}
static int dr_rule_rehash_copy_htbl(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
struct mlx5dr_ste_htbl *cur_htbl,
struct mlx5dr_ste_htbl *new_htbl,
struct list_head *update_list)
{
struct mlx5dr_ste *cur_ste;
int cur_entries;
int err = 0;
int i;
cur_entries = mlx5dr_icm_pool_chunk_size_to_entries(cur_htbl->chunk_size);
if (cur_entries < 1) {
mlx5dr_dbg(matcher->tbl->dmn, "Invalid number of entries\n");
return -EINVAL;
}
for (i = 0; i < cur_entries; i++) {
cur_ste = &cur_htbl->ste_arr[i];
if (mlx5dr_ste_not_used_ste(cur_ste)) /* Empty, nothing to copy */
continue;
err = dr_rule_rehash_copy_miss_list(matcher,
nic_matcher,
mlx5dr_ste_get_miss_list(cur_ste),
new_htbl,
update_list);
if (err)
goto clean_copy;
}
clean_copy:
return err;
}
static struct mlx5dr_ste_htbl *
dr_rule_rehash_htbl(struct mlx5dr_rule *rule,
struct mlx5dr_rule_rx_tx *nic_rule,
struct mlx5dr_ste_htbl *cur_htbl,
u8 ste_location,
struct list_head *update_list,
enum mlx5dr_icm_chunk_size new_size)
{
struct mlx5dr_ste_send_info *del_ste_info, *tmp_ste_info;
struct mlx5dr_matcher *matcher = rule->matcher;
struct mlx5dr_domain *dmn = matcher->tbl->dmn;
struct mlx5dr_matcher_rx_tx *nic_matcher;
struct mlx5dr_ste_send_info *ste_info;
struct mlx5dr_htbl_connect_info info;
struct mlx5dr_domain_rx_tx *nic_dmn;
u8 formatted_ste[DR_STE_SIZE] = {};
LIST_HEAD(rehash_table_send_list);
struct mlx5dr_ste *ste_to_update;
struct mlx5dr_ste_htbl *new_htbl;
int err;
nic_matcher = nic_rule->nic_matcher;
nic_dmn = nic_matcher->nic_tbl->nic_dmn;
ste_info = kzalloc(sizeof(*ste_info), GFP_KERNEL);
if (!ste_info)
return NULL;
new_htbl = mlx5dr_ste_htbl_alloc(dmn->ste_icm_pool,
new_size,
cur_htbl->lu_type,
cur_htbl->byte_mask);
if (!new_htbl) {
mlx5dr_err(dmn, "Failed to allocate new hash table\n");
goto free_ste_info;
}
/* Write new table to HW */
info.type = CONNECT_MISS;
info.miss_icm_addr = nic_matcher->e_anchor->chunk->icm_addr;
mlx5dr_ste_set_formatted_ste(dmn->info.caps.gvmi,
nic_dmn,
new_htbl,
formatted_ste,
&info);
new_htbl->pointing_ste = cur_htbl->pointing_ste;
new_htbl->pointing_ste->next_htbl = new_htbl;
err = dr_rule_rehash_copy_htbl(matcher,
nic_matcher,
cur_htbl,
new_htbl,
&rehash_table_send_list);
if (err)
goto free_new_htbl;
if (mlx5dr_send_postsend_htbl(dmn, new_htbl, formatted_ste,
nic_matcher->ste_builder[ste_location - 1].bit_mask)) {
mlx5dr_err(dmn, "Failed writing table to HW\n");
goto free_new_htbl;
}
/* Writing to the hw is done in regular order of rehash_table_send_list,
* in order to have the origin data written before the miss address of
* collision entries, if exists.
*/
if (dr_rule_send_update_list(&rehash_table_send_list, dmn, false)) {
mlx5dr_err(dmn, "Failed updating table to HW\n");
goto free_ste_list;
}
/* Connect previous hash table to current */
if (ste_location == 1) {
/* The previous table is an anchor, anchors size is always one STE */
struct mlx5dr_ste_htbl *prev_htbl = cur_htbl->pointing_ste->htbl;
/* On matcher s_anchor we keep an extra refcount */
mlx5dr_htbl_get(new_htbl);
mlx5dr_htbl_put(cur_htbl);
nic_matcher->s_htbl = new_htbl;
/* It is safe to operate dr_ste_set_hit_addr on the hw_ste here
* (48B len) which works only on first 32B
*/
mlx5dr_ste_set_hit_addr(prev_htbl->ste_arr[0].hw_ste,
new_htbl->chunk->icm_addr,
new_htbl->chunk->num_of_entries);
ste_to_update = &prev_htbl->ste_arr[0];
} else {
mlx5dr_ste_set_hit_addr_by_next_htbl(cur_htbl->pointing_ste->hw_ste,
new_htbl);
ste_to_update = cur_htbl->pointing_ste;
}
mlx5dr_send_fill_and_append_ste_send_info(ste_to_update, DR_STE_SIZE_REDUCED,
0, ste_to_update->hw_ste, ste_info,
update_list, false);
return new_htbl;
free_ste_list:
/* Clean all ste_info's from the new table */
list_for_each_entry_safe(del_ste_info, tmp_ste_info,
&rehash_table_send_list, send_list) {
list_del(&del_ste_info->send_list);
kfree(del_ste_info);
}
free_new_htbl:
mlx5dr_ste_htbl_free(new_htbl);
free_ste_info:
kfree(ste_info);
mlx5dr_info(dmn, "Failed creating rehash table\n");
return NULL;
}
static struct mlx5dr_ste_htbl *dr_rule_rehash(struct mlx5dr_rule *rule,
struct mlx5dr_rule_rx_tx *nic_rule,
struct mlx5dr_ste_htbl *cur_htbl,
u8 ste_location,
struct list_head *update_list)
{
struct mlx5dr_domain *dmn = rule->matcher->tbl->dmn;
enum mlx5dr_icm_chunk_size new_size;
new_size = mlx5dr_icm_next_higher_chunk(cur_htbl->chunk_size);
new_size = min_t(u32, new_size, dmn->info.max_log_sw_icm_sz);
if (new_size == cur_htbl->chunk_size)
return NULL; /* Skip rehash, we already at the max size */
return dr_rule_rehash_htbl(rule, nic_rule, cur_htbl, ste_location,
update_list, new_size);
}
static struct mlx5dr_ste *
dr_rule_handle_collision(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
struct mlx5dr_ste *ste,
u8 *hw_ste,
struct list_head *miss_list,
struct list_head *send_list)
{
struct mlx5dr_ste_send_info *ste_info;
struct mlx5dr_ste *new_ste;
ste_info = kzalloc(sizeof(*ste_info), GFP_KERNEL);
if (!ste_info)
return NULL;
new_ste = dr_rule_create_collision_entry(matcher, nic_matcher, hw_ste, ste);
if (!new_ste)
goto free_send_info;
if (dr_rule_append_to_miss_list(new_ste, miss_list, send_list)) {
mlx5dr_dbg(matcher->tbl->dmn, "Failed to update prev miss_list\n");
goto err_exit;
}
mlx5dr_send_fill_and_append_ste_send_info(new_ste, DR_STE_SIZE, 0, hw_ste,
ste_info, send_list, false);
ste->htbl->ctrl.num_of_collisions++;
ste->htbl->ctrl.num_of_valid_entries++;
return new_ste;
err_exit:
mlx5dr_ste_free(new_ste, matcher, nic_matcher);
free_send_info:
kfree(ste_info);
return NULL;
}
static void dr_rule_remove_action_members(struct mlx5dr_rule *rule)
{
struct mlx5dr_rule_action_member *action_mem;
struct mlx5dr_rule_action_member *tmp;
list_for_each_entry_safe(action_mem, tmp, &rule->rule_actions_list, list) {
list_del(&action_mem->list);
refcount_dec(&action_mem->action->refcount);
kvfree(action_mem);
}
}
static int dr_rule_add_action_members(struct mlx5dr_rule *rule,
size_t num_actions,
struct mlx5dr_action *actions[])
{
struct mlx5dr_rule_action_member *action_mem;
int i;
for (i = 0; i < num_actions; i++) {
action_mem = kvzalloc(sizeof(*action_mem), GFP_KERNEL);
if (!action_mem)
goto free_action_members;
action_mem->action = actions[i];
INIT_LIST_HEAD(&action_mem->list);
list_add_tail(&action_mem->list, &rule->rule_actions_list);
refcount_inc(&action_mem->action->refcount);
}
return 0;
free_action_members:
dr_rule_remove_action_members(rule);
return -ENOMEM;
}
/* While the pointer of ste is no longer valid, like while moving ste to be
* the first in the miss_list, and to be in the origin table,
* all rule-members that are attached to this ste should update their ste member
* to the new pointer
*/
void mlx5dr_rule_update_rule_member(struct mlx5dr_ste *ste,
struct mlx5dr_ste *new_ste)
{
struct mlx5dr_rule_member *rule_mem;
if (!list_empty(&ste->rule_list))
list_for_each_entry(rule_mem, &ste->rule_list, use_ste_list)
rule_mem->ste = new_ste;
}
static void dr_rule_clean_rule_members(struct mlx5dr_rule *rule,
struct mlx5dr_rule_rx_tx *nic_rule)
{
struct mlx5dr_rule_member *rule_mem;
struct mlx5dr_rule_member *tmp_mem;
if (list_empty(&nic_rule->rule_members_list))
return;
list_for_each_entry_safe(rule_mem, tmp_mem, &nic_rule->rule_members_list, list) {
list_del(&rule_mem->list);
list_del(&rule_mem->use_ste_list);
mlx5dr_ste_put(rule_mem->ste, rule->matcher, nic_rule->nic_matcher);
kvfree(rule_mem);
}
}
static bool dr_rule_need_enlarge_hash(struct mlx5dr_ste_htbl *htbl,
struct mlx5dr_domain *dmn,
struct mlx5dr_domain_rx_tx *nic_dmn)
{
struct mlx5dr_ste_htbl_ctrl *ctrl = &htbl->ctrl;
if (dmn->info.max_log_sw_icm_sz <= htbl->chunk_size)
return false;
if (!ctrl->may_grow)
return false;
if (ctrl->num_of_collisions >= ctrl->increase_threshold &&
(ctrl->num_of_valid_entries - ctrl->num_of_collisions) >= ctrl->increase_threshold)
return true;
return false;
}
static int dr_rule_add_member(struct mlx5dr_rule_rx_tx *nic_rule,
struct mlx5dr_ste *ste)
{
struct mlx5dr_rule_member *rule_mem;
rule_mem = kvzalloc(sizeof(*rule_mem), GFP_KERNEL);
if (!rule_mem)
return -ENOMEM;
rule_mem->ste = ste;
list_add_tail(&rule_mem->list, &nic_rule->rule_members_list);
list_add_tail(&rule_mem->use_ste_list, &ste->rule_list);
return 0;
}
static int dr_rule_handle_action_stes(struct mlx5dr_rule *rule,
struct mlx5dr_rule_rx_tx *nic_rule,
struct list_head *send_ste_list,
struct mlx5dr_ste *last_ste,
u8 *hw_ste_arr,
u32 new_hw_ste_arr_sz)
{
struct mlx5dr_matcher_rx_tx *nic_matcher = nic_rule->nic_matcher;
struct mlx5dr_ste_send_info *ste_info_arr[DR_ACTION_MAX_STES];
u8 num_of_builders = nic_matcher->num_of_builders;
struct mlx5dr_matcher *matcher = rule->matcher;
u8 *curr_hw_ste, *prev_hw_ste;
struct mlx5dr_ste *action_ste;
int i, k, ret;
/* Two cases:
* 1. num_of_builders is equal to new_hw_ste_arr_sz, the action in the ste
* 2. num_of_builders is less then new_hw_ste_arr_sz, new ste was added
* to support the action.
*/
if (num_of_builders == new_hw_ste_arr_sz)
return 0;
for (i = num_of_builders, k = 0; i < new_hw_ste_arr_sz; i++, k++) {
curr_hw_ste = hw_ste_arr + i * DR_STE_SIZE;
prev_hw_ste = (i == 0) ? curr_hw_ste : hw_ste_arr + ((i - 1) * DR_STE_SIZE);
action_ste = dr_rule_create_collision_htbl(matcher,
nic_matcher,
curr_hw_ste);
if (!action_ste)
return -ENOMEM;
mlx5dr_ste_get(action_ste);
/* While free ste we go over the miss list, so add this ste to the list */
list_add_tail(&action_ste->miss_list_node,
mlx5dr_ste_get_miss_list(action_ste));
ste_info_arr[k] = kzalloc(sizeof(*ste_info_arr[k]),
GFP_KERNEL);
if (!ste_info_arr[k])
goto err_exit;
/* Point current ste to the new action */
mlx5dr_ste_set_hit_addr_by_next_htbl(prev_hw_ste, action_ste->htbl);
ret = dr_rule_add_member(nic_rule, action_ste);
if (ret) {
mlx5dr_dbg(matcher->tbl->dmn, "Failed adding rule member\n");
goto free_ste_info;
}
mlx5dr_send_fill_and_append_ste_send_info(action_ste, DR_STE_SIZE, 0,
curr_hw_ste,
ste_info_arr[k],
send_ste_list, false);
}
return 0;
free_ste_info:
kfree(ste_info_arr[k]);
err_exit:
mlx5dr_ste_put(action_ste, matcher, nic_matcher);
return -ENOMEM;
}
static int dr_rule_handle_empty_entry(struct mlx5dr_matcher *matcher,
struct mlx5dr_matcher_rx_tx *nic_matcher,
struct mlx5dr_ste_htbl *cur_htbl,
struct mlx5dr_ste *ste,
u8 ste_location,
u8 *hw_ste,
struct list_head *miss_list,
struct list_head *send_list)
{
struct mlx5dr_ste_send_info *ste_info;
/* Take ref on table, only on first time this ste is used */
mlx5dr_htbl_get(cur_htbl);
/* new entry -> new branch */
list_add_tail(&ste->miss_list_node, miss_list);
mlx5dr_ste_set_miss_addr(hw_ste, nic_matcher->e_anchor->chunk->icm_addr);
ste->ste_chain_location = ste_location;
ste_info = kzalloc(sizeof(*ste_info), GFP_KERNEL);
if (!ste_info)
goto clean_ste_setting;
if (mlx5dr_ste_create_next_htbl(matcher,
nic_matcher,
ste,
hw_ste,
DR_CHUNK_SIZE_1)) {
mlx5dr_dbg(matcher->tbl->dmn, "Failed allocating table\n");
goto clean_ste_info;
}
cur_htbl->ctrl.num_of_valid_entries++;
mlx5dr_send_fill_and_append_ste_send_info(ste, DR_STE_SIZE, 0, hw_ste,
ste_info, send_list, false);
return 0;
clean_ste_info:
kfree(ste_info);
clean_ste_setting:
list_del_init(&ste->miss_list_node);
mlx5dr_htbl_put(cur_htbl);
return -ENOMEM;
}
static struct mlx5dr_ste *
dr_rule_handle_ste_branch(struct mlx5dr_rule *rule,
struct mlx5dr_rule_rx_tx *nic_rule,
struct list_head *send_ste_list,
struct mlx5dr_ste_htbl *cur_htbl,
u8 *hw_ste,
u8 ste_location,
struct mlx5dr_ste_htbl **put_htbl)
{
struct mlx5dr_matcher *matcher = rule->matcher;
struct mlx5dr_domain *dmn = matcher->tbl->dmn;
struct mlx5dr_matcher_rx_tx *nic_matcher;
struct mlx5dr_domain_rx_tx *nic_dmn;
struct mlx5dr_ste_htbl *new_htbl;
struct mlx5dr_ste *matched_ste;
struct list_head *miss_list;
bool skip_rehash = false;
struct mlx5dr_ste *ste;
int index;
nic_matcher = nic_rule->nic_matcher;
nic_dmn = nic_matcher->nic_tbl->nic_dmn;
again:
index = mlx5dr_ste_calc_hash_index(hw_ste, cur_htbl);
miss_list = &cur_htbl->chunk->miss_list[index];
ste = &cur_htbl->ste_arr[index];
if (mlx5dr_ste_not_used_ste(ste)) {
if (dr_rule_handle_empty_entry(matcher, nic_matcher, cur_htbl,
ste, ste_location,
hw_ste, miss_list,
send_ste_list))
return NULL;
} else {
/* Hash table index in use, check if this ste is in the miss list */
matched_ste = dr_rule_find_ste_in_miss_list(miss_list, hw_ste);
if (matched_ste) {
/* If it is last STE in the chain, and has the same tag
* it means that all the previous stes are the same,
* if so, this rule is duplicated.
*/
if (!mlx5dr_ste_is_last_in_rule(nic_matcher, ste_location))
return matched_ste;
mlx5dr_dbg(dmn, "Duplicate rule inserted\n");
}
if (!skip_rehash && dr_rule_need_enlarge_hash(cur_htbl, dmn, nic_dmn)) {
/* Hash table index in use, try to resize of the hash */
skip_rehash = true;
/* Hold the table till we update.
* Release in dr_rule_create_rule()
*/
*put_htbl = cur_htbl;
mlx5dr_htbl_get(cur_htbl);
new_htbl = dr_rule_rehash(rule, nic_rule, cur_htbl,
ste_location, send_ste_list);
if (!new_htbl) {
mlx5dr_htbl_put(cur_htbl);
mlx5dr_info(dmn, "failed creating rehash table, htbl-log_size: %d\n",
cur_htbl->chunk_size);
} else {
cur_htbl = new_htbl;
}
goto again;
} else {
/* Hash table index in use, add another collision (miss) */
ste = dr_rule_handle_collision(matcher,
nic_matcher,
ste,
hw_ste,
miss_list,
send_ste_list);
if (!ste) {
mlx5dr_dbg(dmn, "failed adding collision entry, index: %d\n",
index);
return NULL;
}
}
}
return ste;
}
static bool dr_rule_cmp_value_to_mask(u8 *mask, u8 *value,
u32 s_idx, u32 e_idx)
{
u32 i;
for (i = s_idx; i < e_idx; i++) {
if (value[i] & ~mask[i]) {
pr_info("Rule parameters contains a value not specified by mask\n");
return false;
}
}
return true;
}
static bool dr_rule_verify(struct mlx5dr_matcher *matcher,
struct mlx5dr_match_parameters *value,
struct mlx5dr_match_param *param)
{
u8 match_criteria = matcher->match_criteria;
size_t value_size = value->match_sz;
u8 *mask_p = (u8 *)&matcher->mask;
u8 *param_p = (u8 *)param;
u32 s_idx, e_idx;
if (!value_size ||
(value_size > sizeof(struct mlx5dr_match_param) ||
(value_size % sizeof(u32)))) {
mlx5dr_dbg(matcher->tbl->dmn, "Rule parameters length is incorrect\n");
return false;
}
mlx5dr_ste_copy_param(matcher->match_criteria, param, value);
if (match_criteria & DR_MATCHER_CRITERIA_OUTER) {
s_idx = offsetof(struct mlx5dr_match_param, outer);
e_idx = min(s_idx + sizeof(param->outer), value_size);
if (!dr_rule_cmp_value_to_mask(mask_p, param_p, s_idx, e_idx)) {
mlx5dr_dbg(matcher->tbl->dmn, "Rule outer parameters contains a value not specified by mask\n");
return false;
}
}
if (match_criteria & DR_MATCHER_CRITERIA_MISC) {
s_idx = offsetof(struct mlx5dr_match_param, misc);
e_idx = min(s_idx + sizeof(param->misc), value_size);
if (!dr_rule_cmp_value_to_mask(mask_p, param_p, s_idx, e_idx)) {
mlx5dr_dbg(matcher->tbl->dmn, "Rule misc parameters contains a value not specified by mask\n");
return false;
}
}
if (match_criteria & DR_MATCHER_CRITERIA_INNER) {
s_idx = offsetof(struct mlx5dr_match_param, inner);
e_idx = min(s_idx + sizeof(param->inner), value_size);
if (!dr_rule_cmp_value_to_mask(mask_p, param_p, s_idx, e_idx)) {
mlx5dr_dbg(matcher->tbl->dmn, "Rule inner parameters contains a value not specified by mask\n");
return false;
}
}
if (match_criteria & DR_MATCHER_CRITERIA_MISC2) {
s_idx = offsetof(struct mlx5dr_match_param, misc2);
e_idx = min(s_idx + sizeof(param->misc2), value_size);
if (!dr_rule_cmp_value_to_mask(mask_p, param_p, s_idx, e_idx)) {
mlx5dr_dbg(matcher->tbl->dmn, "Rule misc2 parameters contains a value not specified by mask\n");
return false;
}
}
if (match_criteria & DR_MATCHER_CRITERIA_MISC3) {
s_idx = offsetof(struct mlx5dr_match_param, misc3);
e_idx = min(s_idx + sizeof(param->misc3), value_size);
if (!dr_rule_cmp_value_to_mask(mask_p, param_p, s_idx, e_idx)) {
mlx5dr_dbg(matcher->tbl->dmn, "Rule misc3 parameters contains a value not specified by mask\n");
return false;
}
}
return true;
}
static int dr_rule_destroy_rule_nic(struct mlx5dr_rule *rule,
struct mlx5dr_rule_rx_tx *nic_rule)
{
dr_rule_clean_rule_members(rule, nic_rule);
return 0;
}
static int dr_rule_destroy_rule_fdb(struct mlx5dr_rule *rule)
{
dr_rule_destroy_rule_nic(rule, &rule->rx);
dr_rule_destroy_rule_nic(rule, &rule->tx);
return 0;
}
static int dr_rule_destroy_rule(struct mlx5dr_rule *rule)
{
struct mlx5dr_domain *dmn = rule->matcher->tbl->dmn;
switch (dmn->type) {
case MLX5DR_DOMAIN_TYPE_NIC_RX:
dr_rule_destroy_rule_nic(rule, &rule->rx);
break;
case MLX5DR_DOMAIN_TYPE_NIC_TX:
dr_rule_destroy_rule_nic(rule, &rule->tx);
break;
case MLX5DR_DOMAIN_TYPE_FDB:
dr_rule_destroy_rule_fdb(rule);
break;
default:
return -EINVAL;
}
dr_rule_remove_action_members(rule);
kfree(rule);
return 0;
}
static bool dr_rule_is_ipv6(struct mlx5dr_match_param *param)
{
return (param->outer.ip_version == 6 ||
param->inner.ip_version == 6 ||
param->outer.ethertype == ETH_P_IPV6 ||
param->inner.ethertype == ETH_P_IPV6);
}
static bool dr_rule_skip(enum mlx5dr_domain_type domain,
enum mlx5dr_ste_entry_type ste_type,
struct mlx5dr_match_param *mask,
struct mlx5dr_match_param *value)
{
if (domain != MLX5DR_DOMAIN_TYPE_FDB)
return false;
if (mask->misc.source_port) {
if (ste_type == MLX5DR_STE_TYPE_RX)
if (value->misc.source_port != WIRE_PORT)
return true;
if (ste_type == MLX5DR_STE_TYPE_TX)
if (value->misc.source_port == WIRE_PORT)
return true;
}
/* Metadata C can be used to describe the source vport */
if (mask->misc2.metadata_reg_c_0) {
if (ste_type == MLX5DR_STE_TYPE_RX)
if ((value->misc2.metadata_reg_c_0 & WIRE_PORT) != WIRE_PORT)
return true;
if (ste_type == MLX5DR_STE_TYPE_TX)
if ((value->misc2.metadata_reg_c_0 & WIRE_PORT) == WIRE_PORT)
return true;
}
return false;
}
static int
dr_rule_create_rule_nic(struct mlx5dr_rule *rule,
struct mlx5dr_rule_rx_tx *nic_rule,
struct mlx5dr_match_param *param,
size_t num_actions,
struct mlx5dr_action *actions[])
{
struct mlx5dr_ste_send_info *ste_info, *tmp_ste_info;
struct mlx5dr_matcher *matcher = rule->matcher;
struct mlx5dr_domain *dmn = matcher->tbl->dmn;
struct mlx5dr_matcher_rx_tx *nic_matcher;
struct mlx5dr_domain_rx_tx *nic_dmn;
struct mlx5dr_ste_htbl *htbl = NULL;
struct mlx5dr_ste_htbl *cur_htbl;
struct mlx5dr_ste *ste = NULL;
LIST_HEAD(send_ste_list);
u8 *hw_ste_arr = NULL;
u32 new_hw_ste_arr_sz;
int ret, i;
nic_matcher = nic_rule->nic_matcher;
nic_dmn = nic_matcher->nic_tbl->nic_dmn;
INIT_LIST_HEAD(&nic_rule->rule_members_list);
if (dr_rule_skip(dmn->type, nic_dmn->ste_type, &matcher->mask, param))
return 0;
ret = mlx5dr_matcher_select_builders(matcher,
nic_matcher,
dr_rule_is_ipv6(param));
if (ret)
goto out_err;
hw_ste_arr = kzalloc(DR_RULE_MAX_STE_CHAIN * DR_STE_SIZE, GFP_KERNEL);
if (!hw_ste_arr) {
ret = -ENOMEM;
goto out_err;
}
/* Set the tag values inside the ste array */
ret = mlx5dr_ste_build_ste_arr(matcher, nic_matcher, param, hw_ste_arr);
if (ret)
goto free_hw_ste;
/* Set the actions values/addresses inside the ste array */
ret = mlx5dr_actions_build_ste_arr(matcher, nic_matcher, actions,
num_actions, hw_ste_arr,
&new_hw_ste_arr_sz);
if (ret)
goto free_hw_ste;
cur_htbl = nic_matcher->s_htbl;
/* Go over the array of STEs, and build dr_ste accordingly.
* The loop is over only the builders which are equal or less to the
* number of stes, in case we have actions that lives in other stes.
*/
for (i = 0; i < nic_matcher->num_of_builders; i++) {
/* Calculate CRC and keep new ste entry */
u8 *cur_hw_ste_ent = hw_ste_arr + (i * DR_STE_SIZE);
ste = dr_rule_handle_ste_branch(rule,
nic_rule,
&send_ste_list,
cur_htbl,
cur_hw_ste_ent,
i + 1,
&htbl);
if (!ste) {
mlx5dr_err(dmn, "Failed creating next branch\n");
ret = -ENOENT;
goto free_rule;
}
cur_htbl = ste->next_htbl;
/* Keep all STEs in the rule struct */
ret = dr_rule_add_member(nic_rule, ste);
if (ret) {
mlx5dr_dbg(dmn, "Failed adding rule member index %d\n", i);
goto free_ste;
}
mlx5dr_ste_get(ste);
}
/* Connect actions */
ret = dr_rule_handle_action_stes(rule, nic_rule, &send_ste_list,
ste, hw_ste_arr, new_hw_ste_arr_sz);
if (ret) {
mlx5dr_dbg(dmn, "Failed apply actions\n");
goto free_rule;
}
ret = dr_rule_send_update_list(&send_ste_list, dmn, true);
if (ret) {
mlx5dr_err(dmn, "Failed sending ste!\n");
goto free_rule;
}
if (htbl)
mlx5dr_htbl_put(htbl);
return 0;
free_ste:
mlx5dr_ste_put(ste, matcher, nic_matcher);
free_rule:
dr_rule_clean_rule_members(rule, nic_rule);
/* Clean all ste_info's */
list_for_each_entry_safe(ste_info, tmp_ste_info, &send_ste_list, send_list) {
list_del(&ste_info->send_list);
kfree(ste_info);
}
free_hw_ste:
kfree(hw_ste_arr);
out_err:
return ret;
}
static int
dr_rule_create_rule_fdb(struct mlx5dr_rule *rule,
struct mlx5dr_match_param *param,
size_t num_actions,
struct mlx5dr_action *actions[])
{
struct mlx5dr_match_param copy_param = {};
int ret;
/* Copy match_param since they will be consumed during the first
* nic_rule insertion.
*/
memcpy(&copy_param, param, sizeof(struct mlx5dr_match_param));
ret = dr_rule_create_rule_nic(rule, &rule->rx, param,
num_actions, actions);
if (ret)
return ret;
ret = dr_rule_create_rule_nic(rule, &rule->tx, &copy_param,
num_actions, actions);
if (ret)
goto destroy_rule_nic_rx;
return 0;
destroy_rule_nic_rx:
dr_rule_destroy_rule_nic(rule, &rule->rx);
return ret;
}
static struct mlx5dr_rule *
dr_rule_create_rule(struct mlx5dr_matcher *matcher,
struct mlx5dr_match_parameters *value,
size_t num_actions,
struct mlx5dr_action *actions[])
{
struct mlx5dr_domain *dmn = matcher->tbl->dmn;
struct mlx5dr_match_param param = {};
struct mlx5dr_rule *rule;
int ret;
if (!dr_rule_verify(matcher, value, &param))
return NULL;
rule = kzalloc(sizeof(*rule), GFP_KERNEL);
if (!rule)
return NULL;
rule->matcher = matcher;
INIT_LIST_HEAD(&rule->rule_actions_list);
ret = dr_rule_add_action_members(rule, num_actions, actions);
if (ret)
goto free_rule;
switch (dmn->type) {
case MLX5DR_DOMAIN_TYPE_NIC_RX:
rule->rx.nic_matcher = &matcher->rx;
ret = dr_rule_create_rule_nic(rule, &rule->rx, &param,
num_actions, actions);
break;
case MLX5DR_DOMAIN_TYPE_NIC_TX:
rule->tx.nic_matcher = &matcher->tx;
ret = dr_rule_create_rule_nic(rule, &rule->tx, &param,
num_actions, actions);
break;
case MLX5DR_DOMAIN_TYPE_FDB:
rule->rx.nic_matcher = &matcher->rx;
rule->tx.nic_matcher = &matcher->tx;
ret = dr_rule_create_rule_fdb(rule, &param,
num_actions, actions);
break;
default:
ret = -EINVAL;
break;
}
if (ret)
goto remove_action_members;
return rule;
remove_action_members:
dr_rule_remove_action_members(rule);
free_rule:
kfree(rule);
mlx5dr_info(dmn, "Failed creating rule\n");
return NULL;
}
struct mlx5dr_rule *mlx5dr_rule_create(struct mlx5dr_matcher *matcher,
struct mlx5dr_match_parameters *value,
size_t num_actions,
struct mlx5dr_action *actions[])
{
struct mlx5dr_rule *rule;
mutex_lock(&matcher->tbl->dmn->mutex);
refcount_inc(&matcher->refcount);
rule = dr_rule_create_rule(matcher, value, num_actions, actions);
if (!rule)
refcount_dec(&matcher->refcount);
mutex_unlock(&matcher->tbl->dmn->mutex);
return rule;
}
int mlx5dr_rule_destroy(struct mlx5dr_rule *rule)
{
struct mlx5dr_matcher *matcher = rule->matcher;
struct mlx5dr_table *tbl = rule->matcher->tbl;
int ret;
mutex_lock(&tbl->dmn->mutex);
ret = dr_rule_destroy_rule(rule);
mutex_unlock(&tbl->dmn->mutex);
if (!ret)
refcount_dec(&matcher->refcount);
return ret;
}