// 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_ctx *ste_ctx, 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(ste_ctx, 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_CTRL, 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_ctx *ste_ctx = dmn->ste_ctx; 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(ste_ctx, 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; ste->htbl->pointing_ste = orig_ste->htbl->pointing_ste; /* 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); /* Copy data to ste, only reduced size or control, the last 16B (mask) * is already written to the hw. */ if (ste_info->size == DR_STE_SIZE_CTRL) memcpy(ste_info->ste->hw_ste, ste_info->data, DR_STE_SIZE_CTRL); else memcpy(ste_info->ste->hw_ste, ste_info->data, DR_STE_SIZE_REDUCED); ret = mlx5dr_send_postsend_ste(dmn, ste_info->ste, ste_info->data, ste_info->size, ste_info->offset); if (ret) goto out; 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_domain *dmn = matcher->tbl->dmn; struct mlx5dr_ste *new_ste; int ret; new_ste = dr_rule_create_collision_htbl(matcher, nic_matcher, hw_ste); if (!new_ste) return NULL; /* Update collision pointing STE */ new_ste->htbl->pointing_ste = col_ste->htbl->pointing_ste; /* 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(dmn->ste_ctx, new_ste, mlx5dr_ste_get_miss_list(col_ste), update_list); if (ret) { mlx5dr_dbg(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 (new_ste->next_htbl) new_ste->next_htbl->pointing_ste = new_ste; /* We need to copy the refcount since this ste * may have been traversed several times */ new_ste->refcount = cur_ste->refcount; /* Link old STEs rule to the new ste */ mlx5dr_rule_set_last_member(cur_ste->rule_rx_tx, new_ste, false); } 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_domain *dmn = matcher->tbl->dmn; 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(dmn->ste_ctx, 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_is_not_used(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(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_is_not_used(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->ste_ctx, dmn->info.caps.gvmi, nic_dmn->type, 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(dmn->ste_ctx, 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(dmn->ste_ctx, 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_CTRL, 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_domain *dmn = matcher->tbl->dmn; struct mlx5dr_ste_ctx *ste_ctx = dmn->ste_ctx; 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(ste_ctx, new_ste, miss_list, send_list)) { mlx5dr_dbg(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; } void mlx5dr_rule_set_last_member(struct mlx5dr_rule_rx_tx *nic_rule, struct mlx5dr_ste *ste, bool force) { /* Update rule member is usually done for the last STE or during rule * creation to recover from mid-creation failure (for this peruse the * force flag is used) */ if (ste->next_htbl && !force) return; /* Update is required since each rule keeps track of its last STE */ ste->rule_rx_tx = nic_rule; nic_rule->last_rule_ste = ste; } static struct mlx5dr_ste *dr_rule_get_pointed_ste(struct mlx5dr_ste *curr_ste) { struct mlx5dr_ste *first_ste; first_ste = list_first_entry(mlx5dr_ste_get_miss_list(curr_ste), struct mlx5dr_ste, miss_list_node); return first_ste->htbl->pointing_ste; } int mlx5dr_rule_get_reverse_rule_members(struct mlx5dr_ste **ste_arr, struct mlx5dr_ste *curr_ste, int *num_of_stes) { bool first = false; *num_of_stes = 0; if (!curr_ste) return -ENOENT; /* Iterate from last to first */ while (!first) { first = curr_ste->ste_chain_location == 1; ste_arr[*num_of_stes] = curr_ste; *num_of_stes += 1; curr_ste = dr_rule_get_pointed_ste(curr_ste); } return 0; } static void dr_rule_clean_rule_members(struct mlx5dr_rule *rule, struct mlx5dr_rule_rx_tx *nic_rule) { struct mlx5dr_ste *ste_arr[DR_RULE_MAX_STES + DR_ACTION_MAX_STES]; struct mlx5dr_ste *curr_ste = nic_rule->last_rule_ste; int i; if (mlx5dr_rule_get_reverse_rule_members(ste_arr, curr_ste, &i)) return; while (i--) mlx5dr_ste_put(ste_arr[i], rule->matcher, nic_rule->nic_matcher); } static u16 dr_get_bits_per_mask(u16 byte_mask) { u16 bits = 0; while (byte_mask) { byte_mask = byte_mask & (byte_mask - 1); bits++; } return bits; } 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; int threshold; if (dmn->info.max_log_sw_icm_sz <= htbl->chunk_size) return false; if (!mlx5dr_ste_htbl_may_grow(htbl)) return false; if (dr_get_bits_per_mask(htbl->byte_mask) * BITS_PER_BYTE <= htbl->chunk_size) return false; threshold = mlx5dr_ste_htbl_increase_threshold(htbl); if (ctrl->num_of_collisions >= threshold && (ctrl->num_of_valid_entries - ctrl->num_of_collisions) >= threshold) return true; return false; } 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; struct mlx5dr_domain *dmn = matcher->tbl->dmn; u8 *curr_hw_ste, *prev_hw_ste; struct mlx5dr_ste *action_ste; int i, k; /* 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. */ 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); action_ste->htbl->pointing_ste = last_ste; last_ste->next_htbl = action_ste->htbl; last_ste = 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(dmn->ste_ctx, prev_hw_ste, action_ste->htbl); mlx5dr_rule_set_last_member(nic_rule, action_ste, true); 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); } last_ste->next_htbl = NULL; return 0; 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_domain *dmn = matcher->tbl->dmn; 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(dmn->ste_ctx, 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(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_is_not_used(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_err(dmn, "Failed creating rehash table, htbl-log_size: %d\n", cur_htbl->chunk_size); mlx5dr_htbl_put(cur_htbl); } 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 > DR_SZ_MATCH_PARAM || (value_size % sizeof(u32)))) { mlx5dr_err(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_err(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_err(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_err(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_err(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_err(matcher->tbl->dmn, "Rule misc3 parameters contains a value not specified by mask\n"); return false; } } if (match_criteria & DR_MATCHER_CRITERIA_MISC4) { s_idx = offsetof(struct mlx5dr_match_param, misc4); e_idx = min(s_idx + sizeof(param->misc4), value_size); if (!dr_rule_cmp_value_to_mask(mask_p, param_p, s_idx, e_idx)) { mlx5dr_err(matcher->tbl->dmn, "Rule misc4 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) { mlx5dr_domain_nic_lock(nic_rule->nic_matcher->nic_tbl->nic_dmn); dr_rule_clean_rule_members(rule, nic_rule); mlx5dr_domain_nic_unlock(nic_rule->nic_matcher->nic_tbl->nic_dmn); 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 enum mlx5dr_ipv dr_rule_get_ipv(struct mlx5dr_match_spec *spec) { if (spec->ip_version == 6 || spec->ethertype == ETH_P_IPV6) return DR_RULE_IPV6; return DR_RULE_IPV4; } static bool dr_rule_skip(enum mlx5dr_domain_type domain, enum mlx5dr_domain_nic_type nic_type, struct mlx5dr_match_param *mask, struct mlx5dr_match_param *value, u32 flow_source) { bool rx = nic_type == DR_DOMAIN_NIC_TYPE_RX; if (domain != MLX5DR_DOMAIN_TYPE_FDB) return false; if (mask->misc.source_port) { if (rx && value->misc.source_port != MLX5_VPORT_UPLINK) return true; if (!rx && value->misc.source_port == MLX5_VPORT_UPLINK) return true; } if (rx && flow_source == MLX5_FLOW_CONTEXT_FLOW_SOURCE_LOCAL_VPORT) return true; if (!rx && flow_source == MLX5_FLOW_CONTEXT_FLOW_SOURCE_UPLINK) 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; if (dr_rule_skip(dmn->type, nic_dmn->type, &matcher->mask, param, rule->flow_source)) return 0; hw_ste_arr = kzalloc(DR_RULE_MAX_STE_CHAIN * DR_STE_SIZE, GFP_KERNEL); if (!hw_ste_arr) return -ENOMEM; mlx5dr_domain_nic_lock(nic_dmn); ret = mlx5dr_matcher_select_builders(matcher, nic_matcher, dr_rule_get_ipv(¶m->outer), dr_rule_get_ipv(¶m->inner)); if (ret) goto free_hw_ste; /* 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; mlx5dr_ste_get(ste); mlx5dr_rule_set_last_member(nic_rule, ste, true); } /* 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); mlx5dr_domain_nic_unlock(nic_dmn); kfree(hw_ste_arr); return 0; 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: mlx5dr_domain_nic_unlock(nic_dmn); kfree(hw_ste_arr); 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(©_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, ©_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[], u32 flow_source) { struct mlx5dr_domain *dmn = matcher->tbl->dmn; struct mlx5dr_match_param param = {}; struct mlx5dr_rule *rule; int ret; if (!dr_rule_verify(matcher, value, ¶m)) return NULL; rule = kzalloc(sizeof(*rule), GFP_KERNEL); if (!rule) return NULL; rule->matcher = matcher; rule->flow_source = flow_source; 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, ¶m, 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, ¶m, 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, ¶m, 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_err(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[], u32 flow_source) { struct mlx5dr_rule *rule; refcount_inc(&matcher->refcount); rule = dr_rule_create_rule(matcher, value, num_actions, actions, flow_source); if (!rule) refcount_dec(&matcher->refcount); return rule; } int mlx5dr_rule_destroy(struct mlx5dr_rule *rule) { struct mlx5dr_matcher *matcher = rule->matcher; int ret; ret = dr_rule_destroy_rule(rule); if (!ret) refcount_dec(&matcher->refcount); return ret; }