// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB /* Copyright (c) 2024 NVIDIA Corporation & Affiliates */ #include "internal.h" enum mlx5hws_matcher_rtc_type { HWS_MATCHER_RTC_TYPE_MATCH, HWS_MATCHER_RTC_TYPE_STE_ARRAY, HWS_MATCHER_RTC_TYPE_MAX, }; static const char * const mlx5hws_matcher_rtc_type_str[] = { [HWS_MATCHER_RTC_TYPE_MATCH] = "MATCH", [HWS_MATCHER_RTC_TYPE_STE_ARRAY] = "STE_ARRAY", [HWS_MATCHER_RTC_TYPE_MAX] = "UNKNOWN", }; static const char *hws_matcher_rtc_type_to_str(enum mlx5hws_matcher_rtc_type rtc_type) { if (rtc_type > HWS_MATCHER_RTC_TYPE_MAX) rtc_type = HWS_MATCHER_RTC_TYPE_MAX; return mlx5hws_matcher_rtc_type_str[rtc_type]; } static bool hws_matcher_requires_col_tbl(u8 log_num_of_rules) { /* Collision table concatenation is done only for large rule tables */ return log_num_of_rules > MLX5HWS_MATCHER_ASSURED_RULES_TH; } static u8 hws_matcher_rules_to_tbl_depth(u8 log_num_of_rules) { if (hws_matcher_requires_col_tbl(log_num_of_rules)) return MLX5HWS_MATCHER_ASSURED_MAIN_TBL_DEPTH; /* For small rule tables we use a single deep table to assure insertion */ return min(log_num_of_rules, MLX5HWS_MATCHER_ASSURED_COL_TBL_DEPTH); } static void hws_matcher_destroy_end_ft(struct mlx5hws_matcher *matcher) { mlx5hws_table_destroy_default_ft(matcher->tbl, matcher->end_ft_id); } static int hws_matcher_create_end_ft(struct mlx5hws_matcher *matcher) { struct mlx5hws_table *tbl = matcher->tbl; int ret; ret = mlx5hws_table_create_default_ft(tbl->ctx->mdev, tbl, &matcher->end_ft_id); if (ret) { mlx5hws_err(tbl->ctx, "Failed to create matcher end flow table\n"); return ret; } return 0; } static int hws_matcher_connect(struct mlx5hws_matcher *matcher) { struct mlx5hws_table *tbl = matcher->tbl; struct mlx5hws_context *ctx = tbl->ctx; struct mlx5hws_matcher *prev = NULL; struct mlx5hws_matcher *next = NULL; struct mlx5hws_matcher *tmp_matcher; int ret; /* Find location in matcher list */ if (list_empty(&tbl->matchers_list)) { list_add(&matcher->list_node, &tbl->matchers_list); goto connect; } list_for_each_entry(tmp_matcher, &tbl->matchers_list, list_node) { if (tmp_matcher->attr.priority > matcher->attr.priority) { next = tmp_matcher; break; } prev = tmp_matcher; } if (next) /* insert before next */ list_add_tail(&matcher->list_node, &next->list_node); else /* insert after prev */ list_add(&matcher->list_node, &prev->list_node); connect: if (next) { /* Connect to next RTC */ ret = mlx5hws_table_ft_set_next_rtc(ctx, matcher->end_ft_id, tbl->fw_ft_type, next->match_ste.rtc_0_id, next->match_ste.rtc_1_id); if (ret) { mlx5hws_err(ctx, "Failed to connect new matcher to next RTC\n"); goto remove_from_list; } } else { /* Connect last matcher to next miss_tbl if exists */ ret = mlx5hws_table_connect_to_miss_table(tbl, tbl->default_miss.miss_tbl); if (ret) { mlx5hws_err(ctx, "Failed connect new matcher to miss_tbl\n"); goto remove_from_list; } } /* Connect to previous FT */ ret = mlx5hws_table_ft_set_next_rtc(ctx, prev ? prev->end_ft_id : tbl->ft_id, tbl->fw_ft_type, matcher->match_ste.rtc_0_id, matcher->match_ste.rtc_1_id); if (ret) { mlx5hws_err(ctx, "Failed to connect new matcher to previous FT\n"); goto remove_from_list; } /* Reset prev matcher FT default miss (drop refcount) */ ret = mlx5hws_table_ft_set_default_next_ft(tbl, prev ? prev->end_ft_id : tbl->ft_id); if (ret) { mlx5hws_err(ctx, "Failed to reset matcher ft default miss\n"); goto remove_from_list; } if (!prev) { /* Update tables missing to current matcher in the table */ ret = mlx5hws_table_update_connected_miss_tables(tbl); if (ret) { mlx5hws_err(ctx, "Fatal error, failed to update connected miss table\n"); goto remove_from_list; } } return 0; remove_from_list: list_del_init(&matcher->list_node); return ret; } static int hws_matcher_disconnect(struct mlx5hws_matcher *matcher) { struct mlx5hws_matcher *next = NULL, *prev = NULL; struct mlx5hws_table *tbl = matcher->tbl; u32 prev_ft_id = tbl->ft_id; int ret; if (!list_is_first(&matcher->list_node, &tbl->matchers_list)) { prev = list_prev_entry(matcher, list_node); prev_ft_id = prev->end_ft_id; } if (!list_is_last(&matcher->list_node, &tbl->matchers_list)) next = list_next_entry(matcher, list_node); list_del_init(&matcher->list_node); if (next) { /* Connect previous end FT to next RTC */ ret = mlx5hws_table_ft_set_next_rtc(tbl->ctx, prev_ft_id, tbl->fw_ft_type, next->match_ste.rtc_0_id, next->match_ste.rtc_1_id); if (ret) { mlx5hws_err(tbl->ctx, "Failed to disconnect matcher\n"); goto matcher_reconnect; } } else { ret = mlx5hws_table_connect_to_miss_table(tbl, tbl->default_miss.miss_tbl); if (ret) { mlx5hws_err(tbl->ctx, "Failed to disconnect last matcher\n"); goto matcher_reconnect; } } /* Removing first matcher, update connected miss tables if exists */ if (prev_ft_id == tbl->ft_id) { ret = mlx5hws_table_update_connected_miss_tables(tbl); if (ret) { mlx5hws_err(tbl->ctx, "Fatal error, failed to update connected miss table\n"); goto matcher_reconnect; } } ret = mlx5hws_table_ft_set_default_next_ft(tbl, prev_ft_id); if (ret) { mlx5hws_err(tbl->ctx, "Fatal error, failed to restore matcher ft default miss\n"); goto matcher_reconnect; } return 0; matcher_reconnect: if (list_empty(&tbl->matchers_list) || !prev) list_add(&matcher->list_node, &tbl->matchers_list); else /* insert after prev matcher */ list_add(&matcher->list_node, &prev->list_node); return ret; } static void hws_matcher_set_rtc_attr_sz(struct mlx5hws_matcher *matcher, struct mlx5hws_cmd_rtc_create_attr *rtc_attr, enum mlx5hws_matcher_rtc_type rtc_type, bool is_mirror) { struct mlx5hws_pool_chunk *ste = &matcher->action_ste[MLX5HWS_ACTION_STE_IDX_ANY].ste; enum mlx5hws_matcher_flow_src flow_src = matcher->attr.optimize_flow_src; bool is_match_rtc = rtc_type == HWS_MATCHER_RTC_TYPE_MATCH; if ((flow_src == MLX5HWS_MATCHER_FLOW_SRC_VPORT && !is_mirror) || (flow_src == MLX5HWS_MATCHER_FLOW_SRC_WIRE && is_mirror)) { /* Optimize FDB RTC */ rtc_attr->log_size = 0; rtc_attr->log_depth = 0; } else { /* Keep original values */ rtc_attr->log_size = is_match_rtc ? matcher->attr.table.sz_row_log : ste->order; rtc_attr->log_depth = is_match_rtc ? matcher->attr.table.sz_col_log : 0; } } static int hws_matcher_create_rtc(struct mlx5hws_matcher *matcher, enum mlx5hws_matcher_rtc_type rtc_type, u8 action_ste_selector) { struct mlx5hws_matcher_attr *attr = &matcher->attr; struct mlx5hws_cmd_rtc_create_attr rtc_attr = {0}; struct mlx5hws_match_template *mt = matcher->mt; struct mlx5hws_context *ctx = matcher->tbl->ctx; struct mlx5hws_action_default_stc *default_stc; struct mlx5hws_matcher_action_ste *action_ste; struct mlx5hws_table *tbl = matcher->tbl; struct mlx5hws_pool *ste_pool, *stc_pool; struct mlx5hws_pool_chunk *ste; u32 *rtc_0_id, *rtc_1_id; u32 obj_id; int ret; switch (rtc_type) { case HWS_MATCHER_RTC_TYPE_MATCH: rtc_0_id = &matcher->match_ste.rtc_0_id; rtc_1_id = &matcher->match_ste.rtc_1_id; ste_pool = matcher->match_ste.pool; ste = &matcher->match_ste.ste; ste->order = attr->table.sz_col_log + attr->table.sz_row_log; rtc_attr.log_size = attr->table.sz_row_log; rtc_attr.log_depth = attr->table.sz_col_log; rtc_attr.is_frst_jumbo = mlx5hws_matcher_mt_is_jumbo(mt); rtc_attr.is_scnd_range = 0; rtc_attr.miss_ft_id = matcher->end_ft_id; if (attr->insert_mode == MLX5HWS_MATCHER_INSERT_BY_HASH) { /* The usual Hash Table */ rtc_attr.update_index_mode = MLX5_IFC_RTC_STE_UPDATE_MODE_BY_HASH; /* The first mt is used since all share the same definer */ rtc_attr.match_definer_0 = mlx5hws_definer_get_id(mt->definer); } else if (attr->insert_mode == MLX5HWS_MATCHER_INSERT_BY_INDEX) { rtc_attr.update_index_mode = MLX5_IFC_RTC_STE_UPDATE_MODE_BY_OFFSET; rtc_attr.num_hash_definer = 1; if (attr->distribute_mode == MLX5HWS_MATCHER_DISTRIBUTE_BY_HASH) { /* Hash Split Table */ rtc_attr.access_index_mode = MLX5_IFC_RTC_STE_ACCESS_MODE_BY_HASH; rtc_attr.match_definer_0 = mlx5hws_definer_get_id(mt->definer); } else if (attr->distribute_mode == MLX5HWS_MATCHER_DISTRIBUTE_BY_LINEAR) { /* Linear Lookup Table */ rtc_attr.access_index_mode = MLX5_IFC_RTC_STE_ACCESS_MODE_LINEAR; rtc_attr.match_definer_0 = ctx->caps->linear_match_definer; } } /* Match pool requires implicit allocation */ ret = mlx5hws_pool_chunk_alloc(ste_pool, ste); if (ret) { mlx5hws_err(ctx, "Failed to allocate STE for %s RTC", hws_matcher_rtc_type_to_str(rtc_type)); return ret; } break; case HWS_MATCHER_RTC_TYPE_STE_ARRAY: action_ste = &matcher->action_ste[action_ste_selector]; rtc_0_id = &action_ste->rtc_0_id; rtc_1_id = &action_ste->rtc_1_id; ste_pool = action_ste->pool; ste = &action_ste->ste; ste->order = ilog2(roundup_pow_of_two(action_ste->max_stes)) + attr->table.sz_row_log; rtc_attr.log_size = ste->order; rtc_attr.log_depth = 0; rtc_attr.update_index_mode = MLX5_IFC_RTC_STE_UPDATE_MODE_BY_OFFSET; /* The action STEs use the default always hit definer */ rtc_attr.match_definer_0 = ctx->caps->trivial_match_definer; rtc_attr.is_frst_jumbo = false; rtc_attr.miss_ft_id = 0; break; default: mlx5hws_err(ctx, "HWS Invalid RTC type\n"); return -EINVAL; } obj_id = mlx5hws_pool_chunk_get_base_id(ste_pool, ste); rtc_attr.pd = ctx->pd_num; rtc_attr.ste_base = obj_id; rtc_attr.ste_offset = ste->offset; rtc_attr.reparse_mode = mlx5hws_context_get_reparse_mode(ctx); rtc_attr.table_type = mlx5hws_table_get_res_fw_ft_type(tbl->type, false); hws_matcher_set_rtc_attr_sz(matcher, &rtc_attr, rtc_type, false); /* STC is a single resource (obj_id), use any STC for the ID */ stc_pool = ctx->stc_pool[tbl->type]; default_stc = ctx->common_res[tbl->type].default_stc; obj_id = mlx5hws_pool_chunk_get_base_id(stc_pool, &default_stc->default_hit); rtc_attr.stc_base = obj_id; ret = mlx5hws_cmd_rtc_create(ctx->mdev, &rtc_attr, rtc_0_id); if (ret) { mlx5hws_err(ctx, "Failed to create matcher RTC of type %s", hws_matcher_rtc_type_to_str(rtc_type)); goto free_ste; } if (tbl->type == MLX5HWS_TABLE_TYPE_FDB) { obj_id = mlx5hws_pool_chunk_get_base_mirror_id(ste_pool, ste); rtc_attr.ste_base = obj_id; rtc_attr.table_type = mlx5hws_table_get_res_fw_ft_type(tbl->type, true); obj_id = mlx5hws_pool_chunk_get_base_mirror_id(stc_pool, &default_stc->default_hit); rtc_attr.stc_base = obj_id; hws_matcher_set_rtc_attr_sz(matcher, &rtc_attr, rtc_type, true); ret = mlx5hws_cmd_rtc_create(ctx->mdev, &rtc_attr, rtc_1_id); if (ret) { mlx5hws_err(ctx, "Failed to create peer matcher RTC of type %s", hws_matcher_rtc_type_to_str(rtc_type)); goto destroy_rtc_0; } } return 0; destroy_rtc_0: mlx5hws_cmd_rtc_destroy(ctx->mdev, *rtc_0_id); free_ste: if (rtc_type == HWS_MATCHER_RTC_TYPE_MATCH) mlx5hws_pool_chunk_free(ste_pool, ste); return ret; } static void hws_matcher_destroy_rtc(struct mlx5hws_matcher *matcher, enum mlx5hws_matcher_rtc_type rtc_type, u8 action_ste_selector) { struct mlx5hws_matcher_action_ste *action_ste; struct mlx5hws_table *tbl = matcher->tbl; struct mlx5hws_pool_chunk *ste; struct mlx5hws_pool *ste_pool; u32 rtc_0_id, rtc_1_id; switch (rtc_type) { case HWS_MATCHER_RTC_TYPE_MATCH: rtc_0_id = matcher->match_ste.rtc_0_id; rtc_1_id = matcher->match_ste.rtc_1_id; ste_pool = matcher->match_ste.pool; ste = &matcher->match_ste.ste; break; case HWS_MATCHER_RTC_TYPE_STE_ARRAY: action_ste = &matcher->action_ste[action_ste_selector]; rtc_0_id = action_ste->rtc_0_id; rtc_1_id = action_ste->rtc_1_id; ste_pool = action_ste->pool; ste = &action_ste->ste; break; default: return; } if (tbl->type == MLX5HWS_TABLE_TYPE_FDB) mlx5hws_cmd_rtc_destroy(matcher->tbl->ctx->mdev, rtc_1_id); mlx5hws_cmd_rtc_destroy(matcher->tbl->ctx->mdev, rtc_0_id); if (rtc_type == HWS_MATCHER_RTC_TYPE_MATCH) mlx5hws_pool_chunk_free(ste_pool, ste); } static int hws_matcher_check_attr_sz(struct mlx5hws_cmd_query_caps *caps, struct mlx5hws_matcher *matcher) { struct mlx5hws_matcher_attr *attr = &matcher->attr; if (attr->table.sz_col_log > caps->rtc_log_depth_max) { mlx5hws_err(matcher->tbl->ctx, "Matcher depth exceeds limit %d\n", caps->rtc_log_depth_max); return -EOPNOTSUPP; } if (attr->table.sz_col_log + attr->table.sz_row_log > caps->ste_alloc_log_max) { mlx5hws_err(matcher->tbl->ctx, "Total matcher size exceeds limit %d\n", caps->ste_alloc_log_max); return -EOPNOTSUPP; } if (attr->table.sz_col_log + attr->table.sz_row_log < caps->ste_alloc_log_gran) { mlx5hws_err(matcher->tbl->ctx, "Total matcher size below limit %d\n", caps->ste_alloc_log_gran); return -EOPNOTSUPP; } return 0; } static void hws_matcher_set_pool_attr(struct mlx5hws_pool_attr *attr, struct mlx5hws_matcher *matcher) { switch (matcher->attr.optimize_flow_src) { case MLX5HWS_MATCHER_FLOW_SRC_VPORT: attr->opt_type = MLX5HWS_POOL_OPTIMIZE_ORIG; break; case MLX5HWS_MATCHER_FLOW_SRC_WIRE: attr->opt_type = MLX5HWS_POOL_OPTIMIZE_MIRROR; break; default: break; } } static int hws_matcher_check_and_process_at(struct mlx5hws_matcher *matcher, struct mlx5hws_action_template *at) { struct mlx5hws_context *ctx = matcher->tbl->ctx; bool valid; int ret; valid = mlx5hws_action_check_combo(ctx, at->action_type_arr, matcher->tbl->type); if (!valid) { mlx5hws_err(ctx, "Invalid combination in action template\n"); return -EINVAL; } /* Process action template to setters */ ret = mlx5hws_action_template_process(at); if (ret) { mlx5hws_err(ctx, "Failed to process action template\n"); return ret; } return 0; } static int hws_matcher_resize_init(struct mlx5hws_matcher *src_matcher) { struct mlx5hws_matcher_resize_data *resize_data; resize_data = kzalloc(sizeof(*resize_data), GFP_KERNEL); if (!resize_data) return -ENOMEM; resize_data->max_stes = src_matcher->action_ste[MLX5HWS_ACTION_STE_IDX_ANY].max_stes; resize_data->action_ste[0].stc = src_matcher->action_ste[0].stc; resize_data->action_ste[0].rtc_0_id = src_matcher->action_ste[0].rtc_0_id; resize_data->action_ste[0].rtc_1_id = src_matcher->action_ste[0].rtc_1_id; resize_data->action_ste[0].pool = src_matcher->action_ste[0].max_stes ? src_matcher->action_ste[0].pool : NULL; resize_data->action_ste[1].stc = src_matcher->action_ste[1].stc; resize_data->action_ste[1].rtc_0_id = src_matcher->action_ste[1].rtc_0_id; resize_data->action_ste[1].rtc_1_id = src_matcher->action_ste[1].rtc_1_id; resize_data->action_ste[1].pool = src_matcher->action_ste[1].max_stes ? src_matcher->action_ste[1].pool : NULL; /* Place the new resized matcher on the dst matcher's list */ list_add(&resize_data->list_node, &src_matcher->resize_dst->resize_data); /* Move all the previous resized matchers to the dst matcher's list */ while (!list_empty(&src_matcher->resize_data)) { resize_data = list_first_entry(&src_matcher->resize_data, struct mlx5hws_matcher_resize_data, list_node); list_del_init(&resize_data->list_node); list_add(&resize_data->list_node, &src_matcher->resize_dst->resize_data); } return 0; } static void hws_matcher_resize_uninit(struct mlx5hws_matcher *matcher) { struct mlx5hws_matcher_resize_data *resize_data; if (!mlx5hws_matcher_is_resizable(matcher)) return; while (!list_empty(&matcher->resize_data)) { resize_data = list_first_entry(&matcher->resize_data, struct mlx5hws_matcher_resize_data, list_node); list_del_init(&resize_data->list_node); if (resize_data->max_stes) { mlx5hws_action_free_single_stc(matcher->tbl->ctx, matcher->tbl->type, &resize_data->action_ste[1].stc); mlx5hws_action_free_single_stc(matcher->tbl->ctx, matcher->tbl->type, &resize_data->action_ste[0].stc); if (matcher->tbl->type == MLX5HWS_TABLE_TYPE_FDB) { mlx5hws_cmd_rtc_destroy(matcher->tbl->ctx->mdev, resize_data->action_ste[1].rtc_1_id); mlx5hws_cmd_rtc_destroy(matcher->tbl->ctx->mdev, resize_data->action_ste[0].rtc_1_id); } mlx5hws_cmd_rtc_destroy(matcher->tbl->ctx->mdev, resize_data->action_ste[1].rtc_0_id); mlx5hws_cmd_rtc_destroy(matcher->tbl->ctx->mdev, resize_data->action_ste[0].rtc_0_id); if (resize_data->action_ste[MLX5HWS_ACTION_STE_IDX_ANY].pool) { mlx5hws_pool_destroy(resize_data->action_ste[1].pool); mlx5hws_pool_destroy(resize_data->action_ste[0].pool); } } kfree(resize_data); } } static int hws_matcher_bind_at_idx(struct mlx5hws_matcher *matcher, u8 action_ste_selector) { struct mlx5hws_cmd_stc_modify_attr stc_attr = {0}; struct mlx5hws_matcher_action_ste *action_ste; struct mlx5hws_table *tbl = matcher->tbl; struct mlx5hws_pool_attr pool_attr = {0}; struct mlx5hws_context *ctx = tbl->ctx; int ret; action_ste = &matcher->action_ste[action_ste_selector]; /* Allocate action STE mempool */ pool_attr.table_type = tbl->type; pool_attr.pool_type = MLX5HWS_POOL_TYPE_STE; pool_attr.flags = MLX5HWS_POOL_FLAGS_FOR_STE_ACTION_POOL; pool_attr.alloc_log_sz = ilog2(roundup_pow_of_two(action_ste->max_stes)) + matcher->attr.table.sz_row_log; hws_matcher_set_pool_attr(&pool_attr, matcher); action_ste->pool = mlx5hws_pool_create(ctx, &pool_attr); if (!action_ste->pool) { mlx5hws_err(ctx, "Failed to create action ste pool\n"); return -EINVAL; } /* Allocate action RTC */ ret = hws_matcher_create_rtc(matcher, HWS_MATCHER_RTC_TYPE_STE_ARRAY, action_ste_selector); if (ret) { mlx5hws_err(ctx, "Failed to create action RTC\n"); goto free_ste_pool; } /* Allocate STC for jumps to STE */ stc_attr.action_offset = MLX5HWS_ACTION_OFFSET_HIT; stc_attr.action_type = MLX5_IFC_STC_ACTION_TYPE_JUMP_TO_STE_TABLE; stc_attr.reparse_mode = MLX5_IFC_STC_REPARSE_IGNORE; stc_attr.ste_table.ste = action_ste->ste; stc_attr.ste_table.ste_pool = action_ste->pool; stc_attr.ste_table.match_definer_id = ctx->caps->trivial_match_definer; ret = mlx5hws_action_alloc_single_stc(ctx, &stc_attr, tbl->type, &action_ste->stc); if (ret) { mlx5hws_err(ctx, "Failed to create action jump to table STC\n"); goto free_rtc; } return 0; free_rtc: hws_matcher_destroy_rtc(matcher, HWS_MATCHER_RTC_TYPE_STE_ARRAY, action_ste_selector); free_ste_pool: mlx5hws_pool_destroy(action_ste->pool); return ret; } static void hws_matcher_unbind_at_idx(struct mlx5hws_matcher *matcher, u8 action_ste_selector) { struct mlx5hws_matcher_action_ste *action_ste; struct mlx5hws_table *tbl = matcher->tbl; action_ste = &matcher->action_ste[action_ste_selector]; if (!action_ste->max_stes || matcher->flags & MLX5HWS_MATCHER_FLAGS_COLLISION || mlx5hws_matcher_is_in_resize(matcher)) return; mlx5hws_action_free_single_stc(tbl->ctx, tbl->type, &action_ste->stc); hws_matcher_destroy_rtc(matcher, HWS_MATCHER_RTC_TYPE_STE_ARRAY, action_ste_selector); mlx5hws_pool_destroy(action_ste->pool); } static int hws_matcher_bind_at(struct mlx5hws_matcher *matcher) { bool is_jumbo = mlx5hws_matcher_mt_is_jumbo(matcher->mt); struct mlx5hws_table *tbl = matcher->tbl; struct mlx5hws_context *ctx = tbl->ctx; u32 required_stes; u8 max_stes = 0; int i, ret; if (matcher->flags & MLX5HWS_MATCHER_FLAGS_COLLISION) return 0; for (i = 0; i < matcher->num_of_at; i++) { struct mlx5hws_action_template *at = &matcher->at[i]; ret = hws_matcher_check_and_process_at(matcher, at); if (ret) { mlx5hws_err(ctx, "Invalid at %d", i); return ret; } required_stes = at->num_of_action_stes - (!is_jumbo || at->only_term); max_stes = max(max_stes, required_stes); /* Future: Optimize reparse */ } /* There are no additional STEs required for matcher */ if (!max_stes) return 0; matcher->action_ste[0].max_stes = max_stes; matcher->action_ste[1].max_stes = max_stes; ret = hws_matcher_bind_at_idx(matcher, 0); if (ret) return ret; ret = hws_matcher_bind_at_idx(matcher, 1); if (ret) goto free_at_0; return 0; free_at_0: hws_matcher_unbind_at_idx(matcher, 0); return ret; } static void hws_matcher_unbind_at(struct mlx5hws_matcher *matcher) { hws_matcher_unbind_at_idx(matcher, 1); hws_matcher_unbind_at_idx(matcher, 0); } static int hws_matcher_bind_mt(struct mlx5hws_matcher *matcher) { struct mlx5hws_context *ctx = matcher->tbl->ctx; struct mlx5hws_pool_attr pool_attr = {0}; int ret; /* Calculate match, range and hash definers */ if (!(matcher->flags & MLX5HWS_MATCHER_FLAGS_COLLISION)) { ret = mlx5hws_definer_mt_init(ctx, matcher->mt); if (ret) { if (ret == -E2BIG) mlx5hws_err(ctx, "Failed to set matcher templates with match definers\n"); return ret; } } /* Create an STE pool per matcher*/ pool_attr.table_type = matcher->tbl->type; pool_attr.pool_type = MLX5HWS_POOL_TYPE_STE; pool_attr.flags = MLX5HWS_POOL_FLAGS_FOR_MATCHER_STE_POOL; pool_attr.alloc_log_sz = matcher->attr.table.sz_col_log + matcher->attr.table.sz_row_log; hws_matcher_set_pool_attr(&pool_attr, matcher); matcher->match_ste.pool = mlx5hws_pool_create(ctx, &pool_attr); if (!matcher->match_ste.pool) { mlx5hws_err(ctx, "Failed to allocate matcher STE pool\n"); ret = -EOPNOTSUPP; goto uninit_match_definer; } return 0; uninit_match_definer: if (!(matcher->flags & MLX5HWS_MATCHER_FLAGS_COLLISION)) mlx5hws_definer_mt_uninit(ctx, matcher->mt); return ret; } static void hws_matcher_unbind_mt(struct mlx5hws_matcher *matcher) { mlx5hws_pool_destroy(matcher->match_ste.pool); if (!(matcher->flags & MLX5HWS_MATCHER_FLAGS_COLLISION)) mlx5hws_definer_mt_uninit(matcher->tbl->ctx, matcher->mt); } static int hws_matcher_validate_insert_mode(struct mlx5hws_cmd_query_caps *caps, struct mlx5hws_matcher *matcher) { struct mlx5hws_matcher_attr *attr = &matcher->attr; struct mlx5hws_context *ctx = matcher->tbl->ctx; switch (attr->insert_mode) { case MLX5HWS_MATCHER_INSERT_BY_HASH: if (matcher->attr.distribute_mode != MLX5HWS_MATCHER_DISTRIBUTE_BY_HASH) { mlx5hws_err(ctx, "Invalid matcher distribute mode\n"); return -EOPNOTSUPP; } break; case MLX5HWS_MATCHER_INSERT_BY_INDEX: if (attr->table.sz_col_log) { mlx5hws_err(ctx, "Matcher with INSERT_BY_INDEX supports only Nx1 table size\n"); return -EOPNOTSUPP; } if (attr->distribute_mode == MLX5HWS_MATCHER_DISTRIBUTE_BY_HASH) { /* Hash Split Table */ if (!caps->rtc_hash_split_table) { mlx5hws_err(ctx, "FW doesn't support insert by index and hash distribute\n"); return -EOPNOTSUPP; } } else if (attr->distribute_mode == MLX5HWS_MATCHER_DISTRIBUTE_BY_LINEAR) { /* Linear Lookup Table */ if (!caps->rtc_linear_lookup_table || !IS_BIT_SET(caps->access_index_mode, MLX5_IFC_RTC_STE_ACCESS_MODE_LINEAR)) { mlx5hws_err(ctx, "FW doesn't support insert by index and linear distribute\n"); return -EOPNOTSUPP; } if (attr->table.sz_row_log > MLX5_IFC_RTC_LINEAR_LOOKUP_TBL_LOG_MAX) { mlx5hws_err(ctx, "Matcher with linear distribute: rows exceed limit %d", MLX5_IFC_RTC_LINEAR_LOOKUP_TBL_LOG_MAX); return -EOPNOTSUPP; } } else { mlx5hws_err(ctx, "Matcher has unsupported distribute mode\n"); return -EOPNOTSUPP; } break; default: mlx5hws_err(ctx, "Matcher has unsupported insert mode\n"); return -EOPNOTSUPP; } return 0; } static int hws_matcher_process_attr(struct mlx5hws_cmd_query_caps *caps, struct mlx5hws_matcher *matcher) { struct mlx5hws_matcher_attr *attr = &matcher->attr; if (hws_matcher_validate_insert_mode(caps, matcher)) return -EOPNOTSUPP; if (matcher->tbl->type != MLX5HWS_TABLE_TYPE_FDB && attr->optimize_flow_src) { mlx5hws_err(matcher->tbl->ctx, "NIC domain doesn't support flow_src\n"); return -EOPNOTSUPP; } /* Convert number of rules to the required depth */ if (attr->mode == MLX5HWS_MATCHER_RESOURCE_MODE_RULE && attr->insert_mode == MLX5HWS_MATCHER_INSERT_BY_HASH) attr->table.sz_col_log = hws_matcher_rules_to_tbl_depth(attr->rule.num_log); matcher->flags |= attr->resizable ? MLX5HWS_MATCHER_FLAGS_RESIZABLE : 0; return hws_matcher_check_attr_sz(caps, matcher); } static int hws_matcher_create_and_connect(struct mlx5hws_matcher *matcher) { int ret; /* Select and create the definers for current matcher */ ret = hws_matcher_bind_mt(matcher); if (ret) return ret; /* Calculate and verify action combination */ ret = hws_matcher_bind_at(matcher); if (ret) goto unbind_mt; /* Create matcher end flow table anchor */ ret = hws_matcher_create_end_ft(matcher); if (ret) goto unbind_at; /* Allocate the RTC for the new matcher */ ret = hws_matcher_create_rtc(matcher, HWS_MATCHER_RTC_TYPE_MATCH, 0); if (ret) goto destroy_end_ft; /* Connect the matcher to the matcher list */ ret = hws_matcher_connect(matcher); if (ret) goto destroy_rtc; return 0; destroy_rtc: hws_matcher_destroy_rtc(matcher, HWS_MATCHER_RTC_TYPE_MATCH, 0); destroy_end_ft: hws_matcher_destroy_end_ft(matcher); unbind_at: hws_matcher_unbind_at(matcher); unbind_mt: hws_matcher_unbind_mt(matcher); return ret; } static void hws_matcher_destroy_and_disconnect(struct mlx5hws_matcher *matcher) { hws_matcher_resize_uninit(matcher); hws_matcher_disconnect(matcher); hws_matcher_destroy_rtc(matcher, HWS_MATCHER_RTC_TYPE_MATCH, 0); hws_matcher_destroy_end_ft(matcher); hws_matcher_unbind_at(matcher); hws_matcher_unbind_mt(matcher); } static int hws_matcher_create_col_matcher(struct mlx5hws_matcher *matcher) { struct mlx5hws_context *ctx = matcher->tbl->ctx; struct mlx5hws_matcher *col_matcher; int ret; if (matcher->attr.mode != MLX5HWS_MATCHER_RESOURCE_MODE_RULE || matcher->attr.insert_mode == MLX5HWS_MATCHER_INSERT_BY_INDEX) return 0; if (!hws_matcher_requires_col_tbl(matcher->attr.rule.num_log)) return 0; col_matcher = kzalloc(sizeof(*matcher), GFP_KERNEL); if (!col_matcher) return -ENOMEM; INIT_LIST_HEAD(&col_matcher->resize_data); col_matcher->tbl = matcher->tbl; col_matcher->mt = matcher->mt; col_matcher->at = matcher->at; col_matcher->num_of_at = matcher->num_of_at; col_matcher->num_of_mt = matcher->num_of_mt; col_matcher->attr.priority = matcher->attr.priority; col_matcher->flags = matcher->flags; col_matcher->flags |= MLX5HWS_MATCHER_FLAGS_COLLISION; col_matcher->attr.mode = MLX5HWS_MATCHER_RESOURCE_MODE_HTABLE; col_matcher->attr.optimize_flow_src = matcher->attr.optimize_flow_src; col_matcher->attr.table.sz_row_log = matcher->attr.rule.num_log; col_matcher->attr.table.sz_col_log = MLX5HWS_MATCHER_ASSURED_COL_TBL_DEPTH; if (col_matcher->attr.table.sz_row_log > MLX5HWS_MATCHER_ASSURED_ROW_RATIO) col_matcher->attr.table.sz_row_log -= MLX5HWS_MATCHER_ASSURED_ROW_RATIO; col_matcher->attr.max_num_of_at_attach = matcher->attr.max_num_of_at_attach; ret = hws_matcher_process_attr(ctx->caps, col_matcher); if (ret) goto free_col_matcher; ret = hws_matcher_create_and_connect(col_matcher); if (ret) goto free_col_matcher; matcher->col_matcher = col_matcher; return 0; free_col_matcher: kfree(col_matcher); mlx5hws_err(ctx, "Failed to create assured collision matcher\n"); return ret; } static void hws_matcher_destroy_col_matcher(struct mlx5hws_matcher *matcher) { if (matcher->attr.mode != MLX5HWS_MATCHER_RESOURCE_MODE_RULE || matcher->attr.insert_mode == MLX5HWS_MATCHER_INSERT_BY_INDEX) return; if (matcher->col_matcher) { hws_matcher_destroy_and_disconnect(matcher->col_matcher); kfree(matcher->col_matcher); } } static int hws_matcher_init(struct mlx5hws_matcher *matcher) { struct mlx5hws_context *ctx = matcher->tbl->ctx; int ret; INIT_LIST_HEAD(&matcher->resize_data); mutex_lock(&ctx->ctrl_lock); /* Allocate matcher resource and connect to the packet pipe */ ret = hws_matcher_create_and_connect(matcher); if (ret) goto unlock_err; /* Create additional matcher for collision handling */ ret = hws_matcher_create_col_matcher(matcher); if (ret) goto destory_and_disconnect; mutex_unlock(&ctx->ctrl_lock); return 0; destory_and_disconnect: hws_matcher_destroy_and_disconnect(matcher); unlock_err: mutex_unlock(&ctx->ctrl_lock); return ret; } static int hws_matcher_uninit(struct mlx5hws_matcher *matcher) { struct mlx5hws_context *ctx = matcher->tbl->ctx; mutex_lock(&ctx->ctrl_lock); hws_matcher_destroy_col_matcher(matcher); hws_matcher_destroy_and_disconnect(matcher); mutex_unlock(&ctx->ctrl_lock); return 0; } int mlx5hws_matcher_attach_at(struct mlx5hws_matcher *matcher, struct mlx5hws_action_template *at) { bool is_jumbo = mlx5hws_matcher_mt_is_jumbo(matcher->mt); struct mlx5hws_context *ctx = matcher->tbl->ctx; u32 required_stes; int ret; if (!matcher->attr.max_num_of_at_attach) { mlx5hws_dbg(ctx, "Num of current at (%d) exceed allowed value\n", matcher->num_of_at); return -EOPNOTSUPP; } ret = hws_matcher_check_and_process_at(matcher, at); if (ret) return ret; required_stes = at->num_of_action_stes - (!is_jumbo || at->only_term); if (matcher->action_ste[MLX5HWS_ACTION_STE_IDX_ANY].max_stes < required_stes) { mlx5hws_dbg(ctx, "Required STEs [%d] exceeds initial action template STE [%d]\n", required_stes, matcher->action_ste[MLX5HWS_ACTION_STE_IDX_ANY].max_stes); return -ENOMEM; } matcher->at[matcher->num_of_at] = *at; matcher->num_of_at += 1; matcher->attr.max_num_of_at_attach -= 1; if (matcher->col_matcher) matcher->col_matcher->num_of_at = matcher->num_of_at; return 0; } static int hws_matcher_set_templates(struct mlx5hws_matcher *matcher, struct mlx5hws_match_template *mt[], u8 num_of_mt, struct mlx5hws_action_template *at[], u8 num_of_at) { struct mlx5hws_context *ctx = matcher->tbl->ctx; int ret = 0; int i; if (!num_of_mt || !num_of_at) { mlx5hws_err(ctx, "Number of action/match template cannot be zero\n"); return -EOPNOTSUPP; } matcher->mt = kcalloc(num_of_mt, sizeof(*matcher->mt), GFP_KERNEL); if (!matcher->mt) return -ENOMEM; matcher->at = kcalloc(num_of_at + matcher->attr.max_num_of_at_attach, sizeof(*matcher->at), GFP_KERNEL); if (!matcher->at) { mlx5hws_err(ctx, "Failed to allocate action template array\n"); ret = -ENOMEM; goto free_mt; } for (i = 0; i < num_of_mt; i++) matcher->mt[i] = *mt[i]; for (i = 0; i < num_of_at; i++) matcher->at[i] = *at[i]; matcher->num_of_mt = num_of_mt; matcher->num_of_at = num_of_at; return 0; free_mt: kfree(matcher->mt); return ret; } static void hws_matcher_unset_templates(struct mlx5hws_matcher *matcher) { kfree(matcher->at); kfree(matcher->mt); } struct mlx5hws_matcher * mlx5hws_matcher_create(struct mlx5hws_table *tbl, struct mlx5hws_match_template *mt[], u8 num_of_mt, struct mlx5hws_action_template *at[], u8 num_of_at, struct mlx5hws_matcher_attr *attr) { struct mlx5hws_context *ctx = tbl->ctx; struct mlx5hws_matcher *matcher; int ret; matcher = kzalloc(sizeof(*matcher), GFP_KERNEL); if (!matcher) return NULL; matcher->tbl = tbl; matcher->attr = *attr; ret = hws_matcher_process_attr(tbl->ctx->caps, matcher); if (ret) goto free_matcher; ret = hws_matcher_set_templates(matcher, mt, num_of_mt, at, num_of_at); if (ret) goto free_matcher; ret = hws_matcher_init(matcher); if (ret) { mlx5hws_err(ctx, "Failed to initialise matcher: %d\n", ret); goto unset_templates; } return matcher; unset_templates: hws_matcher_unset_templates(matcher); free_matcher: kfree(matcher); return NULL; } int mlx5hws_matcher_destroy(struct mlx5hws_matcher *matcher) { hws_matcher_uninit(matcher); hws_matcher_unset_templates(matcher); kfree(matcher); return 0; } struct mlx5hws_match_template * mlx5hws_match_template_create(struct mlx5hws_context *ctx, u32 *match_param, u32 match_param_sz, u8 match_criteria_enable) { struct mlx5hws_match_template *mt; mt = kzalloc(sizeof(*mt), GFP_KERNEL); if (!mt) return NULL; mt->match_param = kzalloc(MLX5_ST_SZ_BYTES(fte_match_param), GFP_KERNEL); if (!mt->match_param) goto free_template; memcpy(mt->match_param, match_param, match_param_sz); mt->match_criteria_enable = match_criteria_enable; return mt; free_template: kfree(mt); return NULL; } int mlx5hws_match_template_destroy(struct mlx5hws_match_template *mt) { kfree(mt->match_param); kfree(mt); return 0; } static int hws_matcher_resize_precheck(struct mlx5hws_matcher *src_matcher, struct mlx5hws_matcher *dst_matcher) { struct mlx5hws_context *ctx = src_matcher->tbl->ctx; int i; if (src_matcher->tbl->type != dst_matcher->tbl->type) { mlx5hws_err(ctx, "Table type mismatch for src/dst matchers\n"); return -EINVAL; } if (!mlx5hws_matcher_is_resizable(src_matcher) || !mlx5hws_matcher_is_resizable(dst_matcher)) { mlx5hws_err(ctx, "Src/dst matcher is not resizable\n"); return -EINVAL; } if (mlx5hws_matcher_is_insert_by_idx(src_matcher) != mlx5hws_matcher_is_insert_by_idx(dst_matcher)) { mlx5hws_err(ctx, "Src/dst matchers insert mode mismatch\n"); return -EINVAL; } if (mlx5hws_matcher_is_in_resize(src_matcher) || mlx5hws_matcher_is_in_resize(dst_matcher)) { mlx5hws_err(ctx, "Src/dst matcher is already in resize\n"); return -EINVAL; } /* Compare match templates - make sure the definers are equivalent */ if (src_matcher->num_of_mt != dst_matcher->num_of_mt) { mlx5hws_err(ctx, "Src/dst matcher match templates mismatch\n"); return -EINVAL; } if (src_matcher->action_ste[MLX5HWS_ACTION_STE_IDX_ANY].max_stes > dst_matcher->action_ste[0].max_stes) { mlx5hws_err(ctx, "Src/dst matcher max STEs mismatch\n"); return -EINVAL; } for (i = 0; i < src_matcher->num_of_mt; i++) { if (mlx5hws_definer_compare(src_matcher->mt[i].definer, dst_matcher->mt[i].definer)) { mlx5hws_err(ctx, "Src/dst matcher definers mismatch\n"); return -EINVAL; } } return 0; } int mlx5hws_matcher_resize_set_target(struct mlx5hws_matcher *src_matcher, struct mlx5hws_matcher *dst_matcher) { int ret = 0; mutex_lock(&src_matcher->tbl->ctx->ctrl_lock); ret = hws_matcher_resize_precheck(src_matcher, dst_matcher); if (ret) goto out; src_matcher->resize_dst = dst_matcher; ret = hws_matcher_resize_init(src_matcher); if (ret) src_matcher->resize_dst = NULL; out: mutex_unlock(&src_matcher->tbl->ctx->ctrl_lock); return ret; } int mlx5hws_matcher_resize_rule_move(struct mlx5hws_matcher *src_matcher, struct mlx5hws_rule *rule, struct mlx5hws_rule_attr *attr) { struct mlx5hws_context *ctx = src_matcher->tbl->ctx; if (unlikely(!mlx5hws_matcher_is_in_resize(src_matcher))) { mlx5hws_err(ctx, "Matcher is not resizable or not in resize\n"); return -EINVAL; } if (unlikely(src_matcher != rule->matcher)) { mlx5hws_err(ctx, "Rule doesn't belong to src matcher\n"); return -EINVAL; } return mlx5hws_rule_move_hws_add(rule, attr); }