/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright (c) 2022 NVIDIA Corporation & Affiliates
 */

#include "mlx5dr_internal.h"

static bool mlx5dr_matcher_requires_col_tbl(uint8_t log_num_of_rules)
{
	/* Collision table concatenation is done only for large rule tables */
	return log_num_of_rules > MLX5DR_MATCHER_ASSURED_RULES_TH;
}

static uint8_t mlx5dr_matcher_rules_to_tbl_depth(uint8_t log_num_of_rules)
{
	if (mlx5dr_matcher_requires_col_tbl(log_num_of_rules))
		return MLX5DR_MATCHER_ASSURED_MAIN_TBL_DEPTH;

	/* For small rule tables we use a single deep table to assure insertion */
	return RTE_MIN(log_num_of_rules, MLX5DR_MATCHER_ASSURED_COL_TBL_DEPTH);
}

static int mlx5dr_matcher_create_end_ft(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_table *tbl = matcher->tbl;

	matcher->end_ft = mlx5dr_table_create_default_ft(tbl);
	if (!matcher->end_ft) {
		DR_LOG(ERR, "Failed to create matcher end flow table");
		return rte_errno;
	}
	return 0;
}

static void mlx5dr_matcher_destroy_end_ft(struct mlx5dr_matcher *matcher)
{
	mlx5dr_table_destroy_default_ft(matcher->tbl, matcher->end_ft);
}

static int mlx5dr_matcher_free_rtc_pointing(uint32_t fw_ft_type,
					    enum mlx5dr_table_type type,
					    struct mlx5dr_devx_obj *devx_obj)
{
	struct mlx5dr_cmd_ft_modify_attr ft_attr = {0};
	int ret;

	if (type != MLX5DR_TABLE_TYPE_FDB)
		return 0;

	ft_attr.modify_fs = MLX5_IFC_MODIFY_FLOW_TABLE_RTC_ID;
	ft_attr.type = fw_ft_type;
	ft_attr.rtc_id_0 = 0;
	ft_attr.rtc_id_1 = 0;

	ret = mlx5dr_cmd_flow_table_modify(devx_obj, &ft_attr);
	if (ret) {
		DR_LOG(ERR, "Failed to disconnect previous RTC");
		return ret;
	}

	return 0;
}

static int mlx5dr_matcher_connect(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_cmd_ft_modify_attr ft_attr = {0};
	struct mlx5dr_table *tbl = matcher->tbl;
	struct mlx5dr_matcher *prev = NULL;
	struct mlx5dr_matcher *next = NULL;
	struct mlx5dr_matcher *tmp_matcher;
	struct mlx5dr_devx_obj *ft;
	int ret;

	/* Find location in matcher list */
	if (LIST_EMPTY(&tbl->head)) {
		LIST_INSERT_HEAD(&tbl->head, matcher, next);
		goto connect;
	}

	LIST_FOREACH(tmp_matcher, &tbl->head, next) {
		if (tmp_matcher->attr.priority > matcher->attr.priority) {
			next = tmp_matcher;
			break;
		}
		prev = tmp_matcher;
	}

	if (next)
		LIST_INSERT_BEFORE(next, matcher, next);
	else
		LIST_INSERT_AFTER(prev, matcher, next);

connect:
	ft_attr.modify_fs = MLX5_IFC_MODIFY_FLOW_TABLE_RTC_ID;
	ft_attr.type = tbl->fw_ft_type;

	/* Connect to next */
	if (next) {
		if (next->match_ste.rtc_0)
			ft_attr.rtc_id_0 = next->match_ste.rtc_0->id;
		if (next->match_ste.rtc_1)
			ft_attr.rtc_id_1 = next->match_ste.rtc_1->id;

		ret = mlx5dr_cmd_flow_table_modify(matcher->end_ft, &ft_attr);
		if (ret) {
			DR_LOG(ERR, "Failed to connect new matcher to next RTC");
			goto remove_from_list;
		}
	}

	/* Connect to previous */
	ft = prev ? prev->end_ft : tbl->ft;

	if (matcher->match_ste.rtc_0)
		ft_attr.rtc_id_0 = matcher->match_ste.rtc_0->id;
	if (matcher->match_ste.rtc_1)
		ft_attr.rtc_id_1 = matcher->match_ste.rtc_1->id;

	ret = mlx5dr_cmd_flow_table_modify(ft, &ft_attr);
	if (ret) {
		DR_LOG(ERR, "Failed to connect new matcher to previous FT");
		goto remove_from_list;
	}

	return 0;

remove_from_list:
	LIST_REMOVE(matcher, next);
	return ret;
}

static int mlx5dr_matcher_disconnect(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_cmd_ft_modify_attr ft_attr = {0};
	struct mlx5dr_table *tbl = matcher->tbl;
	struct mlx5dr_matcher *tmp_matcher;
	struct mlx5dr_devx_obj *prev_ft;
	struct mlx5dr_matcher *next;
	int ret;

	prev_ft = matcher->tbl->ft;
	LIST_FOREACH(tmp_matcher, &tbl->head, next) {
		if (tmp_matcher == matcher)
			break;

		prev_ft = tmp_matcher->end_ft;
	}

	next = matcher->next.le_next;

	ft_attr.modify_fs = MLX5_IFC_MODIFY_FLOW_TABLE_RTC_ID;
	ft_attr.type = matcher->tbl->fw_ft_type;

	if (next) {
		/* Connect previous end FT to next RTC if exists */
		if (next->match_ste.rtc_0)
			ft_attr.rtc_id_0 = next->match_ste.rtc_0->id;
		if (next->match_ste.rtc_1)
			ft_attr.rtc_id_1 = next->match_ste.rtc_1->id;
	} else {
		/* Matcher is last, point prev end FT to default miss */
		mlx5dr_cmd_set_attr_connect_miss_tbl(tbl->ctx,
						     tbl->fw_ft_type,
						     tbl->type,
						     &ft_attr);
	}

	ret = mlx5dr_cmd_flow_table_modify(prev_ft, &ft_attr);
	if (ret) {
		DR_LOG(ERR, "Failed to disconnect matcher");
		return ret;
	}

	LIST_REMOVE(matcher, next);

	if (!next) {
		/* ft no longer points to any RTC, drop refcount */
		ret = mlx5dr_matcher_free_rtc_pointing(tbl->fw_ft_type,
						       tbl->type,
						       prev_ft);
		if (ret) {
			DR_LOG(ERR, "Failed to reset last RTC refcount");
			return ret;
		}
	}

	return 0;
}

static void mlx5dr_matcher_set_rtc_attr_sz(struct mlx5dr_matcher *matcher,
					   struct mlx5dr_cmd_rtc_create_attr *rtc_attr,
					   bool is_match_rtc,
					   bool is_mirror)
{
	enum mlx5dr_matcher_flow_src flow_src = matcher->attr.optimize_flow_src;
	struct mlx5dr_pool_chunk *ste = &matcher->action_ste.ste;

	if ((flow_src == MLX5DR_MATCHER_FLOW_SRC_VPORT && !is_mirror) ||
	    (flow_src == MLX5DR_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 mlx5dr_matcher_create_rtc(struct mlx5dr_matcher *matcher,
				     bool is_match_rtc)
{
	const char *rtc_type_str = is_match_rtc ? "match" : "action";
	struct mlx5dr_cmd_rtc_create_attr rtc_attr = {0};
	struct mlx5dr_context *ctx = matcher->tbl->ctx;
	struct mlx5dr_action_default_stc *default_stc;
	struct mlx5dr_table *tbl = matcher->tbl;
	struct mlx5dr_devx_obj **rtc_0, **rtc_1;
	struct mlx5dr_pool *ste_pool, *stc_pool;
	struct mlx5dr_devx_obj *devx_obj;
	struct mlx5dr_pool_chunk *ste;
	int ret;

	if (is_match_rtc) {
		rtc_0 = &matcher->match_ste.rtc_0;
		rtc_1 = &matcher->match_ste.rtc_1;
		ste_pool = matcher->match_ste.pool;
		ste = &matcher->match_ste.ste;
		ste->order = matcher->attr.table.sz_col_log +
			     matcher->attr.table.sz_row_log;
		rtc_attr.log_size = matcher->attr.table.sz_row_log;
		rtc_attr.log_depth = matcher->attr.table.sz_col_log;
		rtc_attr.update_index_mode = MLX5_IFC_RTC_STE_UPDATE_MODE_BY_HASH;
		/* The first match template is used since all share the same definer */
		rtc_attr.definer_id = mlx5dr_definer_get_id(matcher->mt[0]->definer);
		rtc_attr.is_jumbo = mlx5dr_definer_is_jumbo(matcher->mt[0]->definer);
		rtc_attr.miss_ft_id = matcher->end_ft->id;
		/* Match pool requires implicit allocation */
		ret = mlx5dr_pool_chunk_alloc(ste_pool, ste);
		if (ret) {
			DR_LOG(ERR, "Failed to allocate STE for %s RTC", rtc_type_str);
			return ret;
		}
	} else {
		rtc_0 = &matcher->action_ste.rtc_0;
		rtc_1 = &matcher->action_ste.rtc_1;
		ste_pool = matcher->action_ste.pool;
		ste = &matcher->action_ste.ste;
		ste->order = rte_log2_u32(matcher->action_ste.max_stes) +
			     matcher->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.definer_id = ctx->caps->trivial_match_definer;
		rtc_attr.is_jumbo = false;
		rtc_attr.miss_ft_id = 0;
	}

	devx_obj = mlx5dr_pool_chunk_get_base_devx_obj(ste_pool, ste);

	rtc_attr.pd = ctx->pd_num;
	rtc_attr.ste_base = devx_obj->id;
	rtc_attr.ste_offset = ste->offset;
	rtc_attr.table_type = mlx5dr_table_get_res_fw_ft_type(tbl->type, false);
	mlx5dr_matcher_set_rtc_attr_sz(matcher, &rtc_attr, is_match_rtc, false);

	/* STC is a single resource (devx_obj), use any STC for the ID */
	stc_pool = ctx->stc_pool[tbl->type];
	default_stc = ctx->common_res[tbl->type].default_stc;
	devx_obj = mlx5dr_pool_chunk_get_base_devx_obj(stc_pool, &default_stc->default_hit);
	rtc_attr.stc_base = devx_obj->id;

	*rtc_0 = mlx5dr_cmd_rtc_create(ctx->ibv_ctx, &rtc_attr);
	if (!*rtc_0) {
		DR_LOG(ERR, "Failed to create matcher %s RTC", rtc_type_str);
		goto free_ste;
	}

	if (tbl->type == MLX5DR_TABLE_TYPE_FDB) {
		devx_obj = mlx5dr_pool_chunk_get_base_devx_obj_mirror(ste_pool, ste);
		rtc_attr.ste_base = devx_obj->id;
		rtc_attr.table_type = mlx5dr_table_get_res_fw_ft_type(tbl->type, true);

		devx_obj = mlx5dr_pool_chunk_get_base_devx_obj_mirror(stc_pool, &default_stc->default_hit);
		rtc_attr.stc_base = devx_obj->id;
		mlx5dr_matcher_set_rtc_attr_sz(matcher, &rtc_attr, is_match_rtc, true);

		*rtc_1 = mlx5dr_cmd_rtc_create(ctx->ibv_ctx, &rtc_attr);
		if (!*rtc_1) {
			DR_LOG(ERR, "Failed to create peer matcher %s RTC0", rtc_type_str);
			goto destroy_rtc_0;
		}
	}

	return 0;

destroy_rtc_0:
	mlx5dr_cmd_destroy_obj(*rtc_0);
free_ste:
	if (is_match_rtc)
		mlx5dr_pool_chunk_free(ste_pool, ste);
	return rte_errno;
}

static void mlx5dr_matcher_destroy_rtc(struct mlx5dr_matcher *matcher,
				       bool is_match_rtc)
{
	struct mlx5dr_table *tbl = matcher->tbl;
	struct mlx5dr_devx_obj *rtc_0, *rtc_1;
	struct mlx5dr_pool_chunk *ste;
	struct mlx5dr_pool *ste_pool;

	if (is_match_rtc) {
		rtc_0 = matcher->match_ste.rtc_0;
		rtc_1 = matcher->match_ste.rtc_1;
		ste_pool = matcher->match_ste.pool;
		ste = &matcher->match_ste.ste;
	} else {
		rtc_0 = matcher->action_ste.rtc_0;
		rtc_1 = matcher->action_ste.rtc_1;
		ste_pool = matcher->action_ste.pool;
		ste = &matcher->action_ste.ste;
	}

	if (tbl->type == MLX5DR_TABLE_TYPE_FDB)
		mlx5dr_cmd_destroy_obj(rtc_1);

	mlx5dr_cmd_destroy_obj(rtc_0);
	if (is_match_rtc)
		mlx5dr_pool_chunk_free(ste_pool, ste);
}

static void mlx5dr_matcher_set_pool_attr(struct mlx5dr_pool_attr *attr,
					 struct mlx5dr_matcher *matcher)
{
	switch (matcher->attr.optimize_flow_src) {
	case MLX5DR_MATCHER_FLOW_SRC_VPORT:
		attr->opt_type = MLX5DR_POOL_OPTIMIZE_ORIG;
		break;
	case MLX5DR_MATCHER_FLOW_SRC_WIRE:
		attr->opt_type = MLX5DR_POOL_OPTIMIZE_MIRROR;
		break;
	default:
		break;
	}
}

static int mlx5dr_matcher_bind_at(struct mlx5dr_matcher *matcher)
{
	bool is_jumbo = mlx5dr_definer_is_jumbo(matcher->mt[0]->definer);
	struct mlx5dr_cmd_stc_modify_attr stc_attr = {0};
	struct mlx5dr_table *tbl = matcher->tbl;
	struct mlx5dr_pool_attr pool_attr = {0};
	struct mlx5dr_context *ctx = tbl->ctx;
	uint32_t required_stes;
	int i, ret;
	bool valid;

	for (i = 0; i < matcher->num_of_at; i++) {
		struct mlx5dr_action_template *at = matcher->at[i];

		/* Check if action combinabtion is valid */
		valid = mlx5dr_action_check_combo(at->action_type_arr, matcher->tbl->type);
		if (!valid) {
			DR_LOG(ERR, "Invalid combination in action template %d", i);
			return rte_errno;
		}

		/* Process action template to setters */
		ret = mlx5dr_action_template_process(at);
		if (ret) {
			DR_LOG(ERR, "Failed to process action template %d", i);
			return rte_errno;
		}

		required_stes = at->num_of_action_stes - (!is_jumbo || at->only_term);
		matcher->action_ste.max_stes = RTE_MAX(matcher->action_ste.max_stes, required_stes);

		/* Future: Optimize reparse */
	}

	/* There are no additioanl STEs required for matcher */
	if (!matcher->action_ste.max_stes)
		return 0;

	/* Allocate action STE mempool */
	pool_attr.table_type = tbl->type;
	pool_attr.pool_type = MLX5DR_POOL_TYPE_STE;
	pool_attr.flags = MLX5DR_POOL_FLAGS_FOR_STE_ACTION_POOL;
	pool_attr.alloc_log_sz = rte_log2_u32(matcher->action_ste.max_stes) +
				 matcher->attr.table.sz_row_log;
	mlx5dr_matcher_set_pool_attr(&pool_attr, matcher);
	matcher->action_ste.pool = mlx5dr_pool_create(ctx, &pool_attr);
	if (!matcher->action_ste.pool) {
		DR_LOG(ERR, "Failed to create action ste pool");
		return rte_errno;
	}

	/* Allocate action RTC */
	ret = mlx5dr_matcher_create_rtc(matcher, false);
	if (ret) {
		DR_LOG(ERR, "Failed to create action RTC");
		goto free_ste_pool;
	}

	/* Allocate STC for jumps to STE */
	stc_attr.action_offset = MLX5DR_ACTION_OFFSET_HIT;
	stc_attr.action_type = MLX5_IFC_STC_ACTION_TYPE_JUMP_TO_STE_TABLE;
	stc_attr.ste_table.ste = matcher->action_ste.ste;
	stc_attr.ste_table.ste_pool = matcher->action_ste.pool;
	stc_attr.ste_table.match_definer_id = ctx->caps->trivial_match_definer;

	ret = mlx5dr_action_alloc_single_stc(ctx, &stc_attr, tbl->type,
					     &matcher->action_ste.stc);
	if (ret) {
		DR_LOG(ERR, "Failed to create action jump to table STC");
		goto free_rtc;
	}

	return 0;

free_rtc:
	mlx5dr_matcher_destroy_rtc(matcher, false);
free_ste_pool:
	mlx5dr_pool_destroy(matcher->action_ste.pool);
	return rte_errno;
}

static void mlx5dr_matcher_unbind_at(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_table *tbl = matcher->tbl;

	if (!matcher->action_ste.max_stes)
		return;

	mlx5dr_action_free_single_stc(tbl->ctx, tbl->type, &matcher->action_ste.stc);
	mlx5dr_matcher_destroy_rtc(matcher, false);
	mlx5dr_pool_destroy(matcher->action_ste.pool);
}

static int mlx5dr_matcher_bind_mt(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_context *ctx = matcher->tbl->ctx;
	struct mlx5dr_pool_attr pool_attr = {0};
	int i, created = 0;
	int ret = -1;

	for (i = 0; i < matcher->num_of_mt; i++) {
		/* Get a definer for each match template */
		ret = mlx5dr_definer_get(ctx, matcher->mt[i]);
		if (ret)
			goto definer_put;

		created++;

		/* Verify all templates produce the same definer */
		if (i == 0)
			continue;

		ret = mlx5dr_definer_compare(matcher->mt[i]->definer,
					     matcher->mt[i - 1]->definer);
		if (ret) {
			DR_LOG(ERR, "Match templates cannot be used on the same matcher");
			rte_errno = ENOTSUP;
			goto definer_put;
		}
	}

	/* Create an STE pool per matcher*/
	pool_attr.pool_type = MLX5DR_POOL_TYPE_STE;
	pool_attr.flags = MLX5DR_POOL_FLAGS_FOR_MATCHER_STE_POOL;
	pool_attr.table_type = matcher->tbl->type;
	pool_attr.alloc_log_sz = matcher->attr.table.sz_col_log +
				 matcher->attr.table.sz_row_log;
	mlx5dr_matcher_set_pool_attr(&pool_attr, matcher);

	matcher->match_ste.pool = mlx5dr_pool_create(ctx, &pool_attr);
	if (!matcher->match_ste.pool) {
		DR_LOG(ERR, "Failed to allocate matcher STE pool");
		goto definer_put;
	}

	return 0;

definer_put:
	while (created--)
		mlx5dr_definer_put(matcher->mt[created]);

	return ret;
}

static void mlx5dr_matcher_unbind_mt(struct mlx5dr_matcher *matcher)
{
	int i;

	for (i = 0; i < matcher->num_of_mt; i++)
		mlx5dr_definer_put(matcher->mt[i]);

	mlx5dr_pool_destroy(matcher->match_ste.pool);
}

static int
mlx5dr_matcher_process_attr(struct mlx5dr_cmd_query_caps *caps,
			    struct mlx5dr_matcher *matcher,
			    bool is_root)
{
	struct mlx5dr_matcher_attr *attr = &matcher->attr;

	if (matcher->tbl->type != MLX5DR_TABLE_TYPE_FDB  && attr->optimize_flow_src) {
		DR_LOG(ERR, "NIC domain doesn't support flow_src");
		goto not_supported;
	}

	if (is_root) {
		if (attr->mode != MLX5DR_MATCHER_RESOURCE_MODE_RULE) {
			DR_LOG(ERR, "Root matcher supports only rule resource mode");
			goto not_supported;
		}
		if (attr->optimize_flow_src) {
			DR_LOG(ERR, "Root matcher can't specify FDB direction");
			goto not_supported;
		}
		return 0;
	}

	/* Convert number of rules to the required depth */
	if (attr->mode == MLX5DR_MATCHER_RESOURCE_MODE_RULE)
		attr->table.sz_col_log = mlx5dr_matcher_rules_to_tbl_depth(attr->rule.num_log);

	if (attr->table.sz_col_log > caps->rtc_log_depth_max) {
		DR_LOG(ERR, "Matcher depth exceeds limit %d", caps->rtc_log_depth_max);
		goto not_supported;
	}

	if (attr->table.sz_col_log + attr->table.sz_row_log > caps->ste_alloc_log_max) {
		DR_LOG(ERR, "Total matcher size exceeds limit %d", caps->ste_alloc_log_max);
		goto not_supported;
	}

	if (attr->table.sz_col_log + attr->table.sz_row_log < caps->ste_alloc_log_gran) {
		DR_LOG(ERR, "Total matcher size below limit %d", caps->ste_alloc_log_gran);
		goto not_supported;
	}

	return 0;

not_supported:
	rte_errno = EOPNOTSUPP;
	return rte_errno;
}

static int mlx5dr_matcher_create_and_connect(struct mlx5dr_matcher *matcher)
{
	int ret;

	/* Select and create the definers for current matcher */
	ret = mlx5dr_matcher_bind_mt(matcher);
	if (ret)
		return ret;

	/* Calculate and verify action combination */
	ret = mlx5dr_matcher_bind_at(matcher);
	if (ret)
		goto unbind_mt;

	/* Create matcher end flow table anchor */
	ret = mlx5dr_matcher_create_end_ft(matcher);
	if (ret)
		goto unbind_at;

	/* Allocate the RTC for the new matcher */
	ret = mlx5dr_matcher_create_rtc(matcher, true);
	if (ret)
		goto destroy_end_ft;

	/* Connect the matcher to the matcher list */
	ret = mlx5dr_matcher_connect(matcher);
	if (ret)
		goto destroy_rtc;

	return 0;

destroy_rtc:
	mlx5dr_matcher_destroy_rtc(matcher, true);
destroy_end_ft:
	mlx5dr_matcher_destroy_end_ft(matcher);
unbind_at:
	mlx5dr_matcher_unbind_at(matcher);
unbind_mt:
	mlx5dr_matcher_unbind_mt(matcher);
	return ret;
}

static void mlx5dr_matcher_destroy_and_disconnect(struct mlx5dr_matcher *matcher)
{
	mlx5dr_matcher_disconnect(matcher);
	mlx5dr_matcher_destroy_rtc(matcher, true);
	mlx5dr_matcher_destroy_end_ft(matcher);
	mlx5dr_matcher_unbind_at(matcher);
	mlx5dr_matcher_unbind_mt(matcher);
}

static int
mlx5dr_matcher_create_col_matcher(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_context *ctx = matcher->tbl->ctx;
	struct mlx5dr_matcher *col_matcher;
	int ret;

	if (matcher->attr.mode != MLX5DR_MATCHER_RESOURCE_MODE_RULE)
		return 0;

	if (!mlx5dr_matcher_requires_col_tbl(matcher->attr.rule.num_log))
		return 0;

	col_matcher = simple_calloc(1, sizeof(*matcher));
	if (!col_matcher) {
		rte_errno = ENOMEM;
		return rte_errno;
	}

	col_matcher->tbl = matcher->tbl;
	col_matcher->num_of_mt = matcher->num_of_mt;
	memcpy(col_matcher->mt, matcher->mt, matcher->num_of_mt * sizeof(*matcher->mt));
	col_matcher->num_of_at = matcher->num_of_at;
	memcpy(col_matcher->at, matcher->at, matcher->num_of_at * sizeof(*matcher->at));

	col_matcher->attr.priority = matcher->attr.priority;
	col_matcher->attr.mode = MLX5DR_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 = MLX5DR_MATCHER_ASSURED_COL_TBL_DEPTH;
	if (col_matcher->attr.table.sz_row_log > MLX5DR_MATCHER_ASSURED_ROW_RATIO)
		col_matcher->attr.table.sz_row_log -= MLX5DR_MATCHER_ASSURED_ROW_RATIO;

	ret = mlx5dr_matcher_process_attr(ctx->caps, col_matcher, false);
	if (ret)
		goto free_col_matcher;

	ret = mlx5dr_matcher_create_and_connect(col_matcher);
	if (ret)
		goto free_col_matcher;

	matcher->col_matcher = col_matcher;

	return 0;

free_col_matcher:
	simple_free(col_matcher);
	DR_LOG(ERR, "Failed to create assured collision matcher");
	return ret;
}

static void
mlx5dr_matcher_destroy_col_matcher(struct mlx5dr_matcher *matcher)
{
	if (matcher->attr.mode != MLX5DR_MATCHER_RESOURCE_MODE_RULE)
		return;

	if (matcher->col_matcher) {
		mlx5dr_matcher_destroy_and_disconnect(matcher->col_matcher);
		simple_free(matcher->col_matcher);
	}
}

static int mlx5dr_matcher_init(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_context *ctx = matcher->tbl->ctx;
	int ret;

	pthread_spin_lock(&ctx->ctrl_lock);

	/* Allocate matcher resource and connect to the packet pipe */
	ret = mlx5dr_matcher_create_and_connect(matcher);
	if (ret)
		goto unlock_err;

	/* Create additional matcher for collision handling */
	ret = mlx5dr_matcher_create_col_matcher(matcher);
	if (ret)
		goto destory_and_disconnect;

	pthread_spin_unlock(&ctx->ctrl_lock);

	return 0;

destory_and_disconnect:
	mlx5dr_matcher_destroy_and_disconnect(matcher);
unlock_err:
	pthread_spin_unlock(&ctx->ctrl_lock);
	return ret;
}

static int mlx5dr_matcher_uninit(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_context *ctx = matcher->tbl->ctx;

	pthread_spin_lock(&ctx->ctrl_lock);
	mlx5dr_matcher_destroy_col_matcher(matcher);
	mlx5dr_matcher_destroy_and_disconnect(matcher);
	pthread_spin_unlock(&ctx->ctrl_lock);

	return 0;
}

static int mlx5dr_matcher_init_root(struct mlx5dr_matcher *matcher)
{
	enum mlx5dr_table_type type = matcher->tbl->type;
	struct mlx5dr_context *ctx = matcher->tbl->ctx;
	struct mlx5dv_flow_matcher_attr attr = {0};
	struct mlx5dv_flow_match_parameters *mask;
	struct mlx5_flow_attr flow_attr = {0};
	struct rte_flow_error rte_error;
	uint8_t match_criteria;
	int ret;

#ifdef HAVE_MLX5DV_FLOW_MATCHER_FT_TYPE
	attr.comp_mask = MLX5DV_FLOW_MATCHER_MASK_FT_TYPE;

	switch (type) {
	case MLX5DR_TABLE_TYPE_NIC_RX:
		attr.ft_type = MLX5DV_FLOW_TABLE_TYPE_NIC_RX;
		break;
	case MLX5DR_TABLE_TYPE_NIC_TX:
		attr.ft_type = MLX5DV_FLOW_TABLE_TYPE_NIC_TX;
		break;
	case MLX5DR_TABLE_TYPE_FDB:
		attr.ft_type = MLX5DV_FLOW_TABLE_TYPE_FDB;
		break;
	default:
		assert(0);
		break;
	}
#endif

	if (matcher->attr.priority > UINT16_MAX) {
		DR_LOG(ERR, "Root matcher priority exceeds allowed limit");
		rte_errno = EINVAL;
		return rte_errno;
	}

	mask = simple_calloc(1, MLX5_ST_SZ_BYTES(fte_match_param) +
			     offsetof(struct mlx5dv_flow_match_parameters, match_buf));
	if (!mask) {
		rte_errno = ENOMEM;
		return rte_errno;
	}

	flow_attr.tbl_type = type;

	/* On root table matcher, only a single match template is supported */
	ret = flow_dv_translate_items_hws(matcher->mt[0]->items,
					  &flow_attr, mask->match_buf,
					  MLX5_SET_MATCHER_HS_M, NULL,
					  &match_criteria,
					  &rte_error);
	if (ret) {
		DR_LOG(ERR, "Failed to convert items to PRM [%s]", rte_error.message);
		goto free_mask;
	}

	mask->match_sz = MLX5_ST_SZ_BYTES(fte_match_param);
	attr.match_mask = mask;
	attr.match_criteria_enable = match_criteria;
	attr.type = IBV_FLOW_ATTR_NORMAL;
	attr.priority = matcher->attr.priority;

	matcher->dv_matcher =
		mlx5_glue->dv_create_flow_matcher_root(ctx->ibv_ctx, &attr);
	if (!matcher->dv_matcher) {
		DR_LOG(ERR, "Failed to create DV flow matcher");
		rte_errno = errno;
		goto free_mask;
	}

	simple_free(mask);

	pthread_spin_lock(&ctx->ctrl_lock);
	LIST_INSERT_HEAD(&matcher->tbl->head, matcher, next);
	pthread_spin_unlock(&ctx->ctrl_lock);

	return 0;

free_mask:
	simple_free(mask);
	return rte_errno;
}

static int mlx5dr_matcher_uninit_root(struct mlx5dr_matcher *matcher)
{
	struct mlx5dr_context *ctx = matcher->tbl->ctx;
	int ret;

	pthread_spin_lock(&ctx->ctrl_lock);
	LIST_REMOVE(matcher, next);
	pthread_spin_unlock(&ctx->ctrl_lock);

	ret = mlx5_glue->dv_destroy_flow_matcher_root(matcher->dv_matcher);
	if (ret) {
		DR_LOG(ERR, "Failed to Destroy DV flow matcher");
		rte_errno = errno;
	}

	return ret;
}

static int
mlx5dr_matcher_check_template(uint8_t num_of_mt, uint8_t num_of_at, bool is_root)
{
	uint8_t max_num_of_mt;

	max_num_of_mt = is_root ?
		MLX5DR_MATCHER_MAX_MT_ROOT :
		MLX5DR_MATCHER_MAX_MT;

	if (!num_of_mt || !num_of_at) {
		DR_LOG(ERR, "Number of action/match template cannot be zero");
		goto out_not_sup;
	}

	if (num_of_at > MLX5DR_MATCHER_MAX_AT) {
		DR_LOG(ERR, "Number of action templates exceeds limit");
		goto out_not_sup;
	}

	if (num_of_mt > max_num_of_mt) {
		DR_LOG(ERR, "Number of match templates exceeds limit");
		goto out_not_sup;
	}

	return 0;

out_not_sup:
	rte_errno = ENOTSUP;
	return rte_errno;
}

struct mlx5dr_matcher *
mlx5dr_matcher_create(struct mlx5dr_table *tbl,
		      struct mlx5dr_match_template *mt[],
		      uint8_t num_of_mt,
		      struct mlx5dr_action_template *at[],
		      uint8_t num_of_at,
		      struct mlx5dr_matcher_attr *attr)
{
	bool is_root = mlx5dr_table_is_root(tbl);
	struct mlx5dr_matcher *matcher;
	int ret;

	ret = mlx5dr_matcher_check_template(num_of_mt, num_of_at, is_root);
	if (ret)
		return NULL;

	matcher = simple_calloc(1, sizeof(*matcher));
	if (!matcher) {
		rte_errno = ENOMEM;
		return NULL;
	}

	matcher->tbl = tbl;
	matcher->attr = *attr;
	matcher->num_of_mt = num_of_mt;
	memcpy(matcher->mt, mt, num_of_mt * sizeof(*mt));
	matcher->num_of_at = num_of_at;
	memcpy(matcher->at, at, num_of_at * sizeof(*at));

	ret = mlx5dr_matcher_process_attr(tbl->ctx->caps, matcher, is_root);
	if (ret)
		goto free_matcher;

	if (is_root)
		ret = mlx5dr_matcher_init_root(matcher);
	else
		ret = mlx5dr_matcher_init(matcher);

	if (ret) {
		DR_LOG(ERR, "Failed to initialise matcher: %d", ret);
		goto free_matcher;
	}

	return matcher;

free_matcher:
	simple_free(matcher);
	return NULL;
}

int mlx5dr_matcher_destroy(struct mlx5dr_matcher *matcher)
{
	if (mlx5dr_table_is_root(matcher->tbl))
		mlx5dr_matcher_uninit_root(matcher);
	else
		mlx5dr_matcher_uninit(matcher);

	simple_free(matcher);
	return 0;
}

struct mlx5dr_match_template *
mlx5dr_match_template_create(const struct rte_flow_item items[],
			     enum mlx5dr_match_template_flags flags)
{
	struct mlx5dr_match_template *mt;
	struct rte_flow_error error;
	int ret, len;

	if (flags > MLX5DR_MATCH_TEMPLATE_FLAG_RELAXED_MATCH) {
		DR_LOG(ERR, "Unsupported match template flag provided");
		rte_errno = EINVAL;
		return NULL;
	}

	mt = simple_calloc(1, sizeof(*mt));
	if (!mt) {
		DR_LOG(ERR, "Failed to allocate match template");
		rte_errno = ENOMEM;
		return NULL;
	}

	mt->flags = flags;

	/* Duplicate the user given items */
	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN, NULL, 0, items, &error);
	if (ret <= 0) {
		DR_LOG(ERR, "Unable to process items (%s): %s",
		       error.message ? error.message : "unspecified",
		       strerror(rte_errno));
		goto free_template;
	}

	len = RTE_ALIGN(ret, 16);
	mt->items = simple_calloc(1, len);
	if (!mt->items) {
		DR_LOG(ERR, "Failed to allocate item copy");
		rte_errno = ENOMEM;
		goto free_template;
	}

	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN, mt->items, ret, items, &error);
	if (ret <= 0)
		goto free_dst;

	return mt;

free_dst:
	simple_free(mt->items);
free_template:
	simple_free(mt);
	return NULL;
}

int mlx5dr_match_template_destroy(struct mlx5dr_match_template *mt)
{
	assert(!mt->refcount);
	simple_free(mt->items);
	simple_free(mt);
	return 0;
}
