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

#include "mlx5dr_internal.h"

enum mlx5dr_arg_chunk_size
mlx5dr_arg_data_size_to_arg_log_size(uint16_t data_size)
{
	/* Return the roundup of log2(data_size) */
	if (data_size <= MLX5DR_ARG_DATA_SIZE)
		return MLX5DR_ARG_CHUNK_SIZE_1;
	if (data_size <= MLX5DR_ARG_DATA_SIZE * 2)
		return MLX5DR_ARG_CHUNK_SIZE_2;
	if (data_size <= MLX5DR_ARG_DATA_SIZE * 4)
		return MLX5DR_ARG_CHUNK_SIZE_3;
	if (data_size <= MLX5DR_ARG_DATA_SIZE * 8)
		return MLX5DR_ARG_CHUNK_SIZE_4;

	return MLX5DR_ARG_CHUNK_SIZE_MAX;
}

uint32_t mlx5dr_arg_data_size_to_arg_size(uint16_t data_size)
{
	return BIT(mlx5dr_arg_data_size_to_arg_log_size(data_size));
}

enum mlx5dr_arg_chunk_size
mlx5dr_arg_get_arg_log_size(uint16_t num_of_actions)
{
	return mlx5dr_arg_data_size_to_arg_log_size(num_of_actions *
						    MLX5DR_MODIFY_ACTION_SIZE);
}

uint32_t mlx5dr_arg_get_arg_size(uint16_t num_of_actions)
{
	return BIT(mlx5dr_arg_get_arg_log_size(num_of_actions));
}

/* Cache and cache element handling */
int mlx5dr_pat_init_pattern_cache(struct mlx5dr_pattern_cache **cache)
{
	struct mlx5dr_pattern_cache *new_cache;

	new_cache = simple_calloc(1, sizeof(*new_cache));
	if (!new_cache) {
		rte_errno = ENOMEM;
		return rte_errno;
	}
	LIST_INIT(&new_cache->head);
	pthread_spin_init(&new_cache->lock, PTHREAD_PROCESS_PRIVATE);

	*cache = new_cache;

	return 0;
}

void mlx5dr_pat_uninit_pattern_cache(struct mlx5dr_pattern_cache *cache)
{
	simple_free(cache);
}

static bool mlx5dr_pat_compare_pattern(enum mlx5dr_action_type cur_type,
				       int cur_num_of_actions,
				       __be64 cur_actions[],
				       enum mlx5dr_action_type type,
				       int num_of_actions,
				       __be64 actions[])
{
	int i;

	if (cur_num_of_actions != num_of_actions || cur_type != type)
		return false;

	 /* All decap-l3 look the same, only change is the num of actions */
	if (type == MLX5DR_ACTION_TYP_TNL_L3_TO_L2)
		return true;

	for (i = 0; i < num_of_actions; i++) {
		u8 action_id =
			MLX5_GET(set_action_in, &actions[i], action_type);

		if (action_id == MLX5_MODIFICATION_TYPE_COPY) {
			if (actions[i] != cur_actions[i])
				return false;
		} else {
			/* Compare just the control, not the values */
			if ((__be32)actions[i] !=
			    (__be32)cur_actions[i])
				return false;
		}
	}

	return true;
}

static struct mlx5dr_pat_cached_pattern *
mlx5dr_pat_find_cached_pattern(struct mlx5dr_pattern_cache *cache,
			       struct mlx5dr_action *action,
			       uint16_t num_of_actions,
			       __be64 *actions)
{
	struct mlx5dr_pat_cached_pattern *cached_pat;

	LIST_FOREACH(cached_pat, &cache->head, next) {
		if (mlx5dr_pat_compare_pattern(cached_pat->type,
					       cached_pat->mh_data.num_of_actions,
					       (__be64 *)cached_pat->mh_data.data,
					       action->type,
					       num_of_actions,
					       actions))
			return cached_pat;
	}

	return NULL;
}

static struct mlx5dr_pat_cached_pattern *
mlx5dr_pat_get_existing_cached_pattern(struct mlx5dr_pattern_cache *cache,
				       struct mlx5dr_action *action,
				       uint16_t num_of_actions,
				       __be64 *actions)
{
	struct mlx5dr_pat_cached_pattern *cached_pattern;

	cached_pattern = mlx5dr_pat_find_cached_pattern(cache, action, num_of_actions, actions);
	if (cached_pattern) {
		/* LRU: move it to be first in the list */
		LIST_REMOVE(cached_pattern, next);
		LIST_INSERT_HEAD(&cache->head, cached_pattern, next);
		cached_pattern->refcount++;
	}

	return cached_pattern;
}

static struct mlx5dr_pat_cached_pattern *
mlx5dr_pat_get_cached_pattern_by_action(struct mlx5dr_pattern_cache *cache,
					struct mlx5dr_action *action)
{
	struct mlx5dr_pat_cached_pattern *cached_pattern;

	LIST_FOREACH(cached_pattern, &cache->head, next) {
		if (cached_pattern->mh_data.pattern_obj->id == action->modify_header.pattern_obj->id)
			return cached_pattern;
	}

	return NULL;
}

static struct mlx5dr_pat_cached_pattern *
mlx5dr_pat_add_pattern_to_cache(struct mlx5dr_pattern_cache *cache,
				struct mlx5dr_devx_obj *pattern_obj,
				enum mlx5dr_action_type type,
				uint16_t num_of_actions,
				__be64 *actions)
{
	struct mlx5dr_pat_cached_pattern *cached_pattern;

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

	cached_pattern->type = type;
	cached_pattern->mh_data.num_of_actions = num_of_actions;
	cached_pattern->mh_data.pattern_obj = pattern_obj;
	cached_pattern->mh_data.data =
		simple_malloc(num_of_actions * MLX5DR_MODIFY_ACTION_SIZE);
	if (!cached_pattern->mh_data.data) {
		DR_LOG(ERR, "Failed to allocate mh_data.data");
		rte_errno = ENOMEM;
		goto free_cached_obj;
	}

	memcpy(cached_pattern->mh_data.data, actions,
	       num_of_actions * MLX5DR_MODIFY_ACTION_SIZE);

	LIST_INSERT_HEAD(&cache->head, cached_pattern, next);
	cached_pattern->refcount = 1;

	return cached_pattern;

free_cached_obj:
	simple_free(cached_pattern);
	return NULL;
}

static void
mlx5dr_pat_remove_pattern(struct mlx5dr_pat_cached_pattern *cached_pattern)
{
	LIST_REMOVE(cached_pattern, next);
	simple_free(cached_pattern->mh_data.data);
	simple_free(cached_pattern);
}

static void
mlx5dr_pat_put_pattern(struct mlx5dr_pattern_cache *cache,
		       struct mlx5dr_action *action)
{
	struct mlx5dr_pat_cached_pattern *cached_pattern;

	pthread_spin_lock(&cache->lock);
	cached_pattern = mlx5dr_pat_get_cached_pattern_by_action(cache, action);
	if (!cached_pattern) {
		DR_LOG(ERR, "Failed to find pattern according to action with pt");
		assert(false);
		goto out;
	}

	if (--cached_pattern->refcount)
		goto out;

	mlx5dr_pat_remove_pattern(cached_pattern);

out:
	pthread_spin_unlock(&cache->lock);
}

static int mlx5dr_pat_get_pattern(struct mlx5dr_context *ctx,
				  struct mlx5dr_action *action,
				  uint16_t num_of_actions,
				  size_t pattern_sz,
				  __be64 *pattern)
{
	struct mlx5dr_pat_cached_pattern *cached_pattern;
	int ret = 0;

	pthread_spin_lock(&ctx->pattern_cache->lock);

	cached_pattern = mlx5dr_pat_get_existing_cached_pattern(ctx->pattern_cache,
								action,
								num_of_actions,
								pattern);
	if (cached_pattern) {
		action->modify_header.pattern_obj = cached_pattern->mh_data.pattern_obj;
		goto out_unlock;
	}

	action->modify_header.pattern_obj =
		mlx5dr_cmd_header_modify_pattern_create(ctx->ibv_ctx,
							pattern_sz,
							(uint8_t *)pattern);
	if (!action->modify_header.pattern_obj) {
		DR_LOG(ERR, "Failed to create pattern FW object");

		ret = rte_errno;
		goto out_unlock;
	}

	cached_pattern =
		mlx5dr_pat_add_pattern_to_cache(ctx->pattern_cache,
						action->modify_header.pattern_obj,
						action->type,
						num_of_actions,
						pattern);
	if (!cached_pattern) {
		DR_LOG(ERR, "Failed to add pattern to cache");
		ret = rte_errno;
		goto clean_pattern;
	}

out_unlock:
	pthread_spin_unlock(&ctx->pattern_cache->lock);
	return ret;

clean_pattern:
	mlx5dr_cmd_destroy_obj(action->modify_header.pattern_obj);
	pthread_spin_unlock(&ctx->pattern_cache->lock);
	return ret;
}

static void
mlx5d_arg_init_send_attr(struct mlx5dr_send_engine_post_attr *send_attr,
			 void *comp_data,
			 uint32_t arg_idx)
{
	send_attr->opcode = MLX5DR_WQE_OPCODE_TBL_ACCESS;
	send_attr->opmod = MLX5DR_WQE_GTA_OPMOD_MOD_ARG;
	send_attr->len = MLX5DR_WQE_SZ_GTA_CTRL + MLX5DR_WQE_SZ_GTA_DATA;
	send_attr->id = arg_idx;
	send_attr->user_data = comp_data;
}

void mlx5dr_arg_decapl3_write(struct mlx5dr_send_engine *queue,
			      uint32_t arg_idx,
			      uint8_t *arg_data,
			      uint16_t num_of_actions)
{
	struct mlx5dr_send_engine_post_attr send_attr = {0};
	struct mlx5dr_wqe_gta_data_seg_arg *wqe_arg;
	struct mlx5dr_send_engine_post_ctrl ctrl;
	struct mlx5dr_wqe_gta_ctrl_seg *wqe_ctrl;
	size_t wqe_len;

	mlx5d_arg_init_send_attr(&send_attr, NULL, arg_idx);

	ctrl = mlx5dr_send_engine_post_start(queue);
	mlx5dr_send_engine_post_req_wqe(&ctrl, (void *)&wqe_ctrl, &wqe_len);
	memset(wqe_ctrl, 0, wqe_len);
	mlx5dr_send_engine_post_req_wqe(&ctrl, (void *)&wqe_arg, &wqe_len);
	mlx5dr_action_prepare_decap_l3_data(arg_data, (uint8_t *)wqe_arg,
					    num_of_actions);
	mlx5dr_send_engine_post_end(&ctrl, &send_attr);
}

static int
mlx5dr_arg_poll_for_comp(struct mlx5dr_context *ctx, uint16_t queue_id)
{
	struct rte_flow_op_result comp[1];
	int ret;

	while (true) {
		ret = mlx5dr_send_queue_poll(ctx, queue_id, comp, 1);
		if (ret) {
			if (ret < 0) {
				DR_LOG(ERR, "Failed mlx5dr_send_queue_poll");
			} else if (comp[0].status == RTE_FLOW_OP_ERROR) {
				DR_LOG(ERR, "Got comp with error");
				rte_errno = ENOENT;
			}
			break;
		}
	}
	return (ret == 1 ? 0 : ret);
}

void mlx5dr_arg_write(struct mlx5dr_send_engine *queue,
		      void *comp_data,
		      uint32_t arg_idx,
		      uint8_t *arg_data,
		      size_t data_size)
{
	struct mlx5dr_send_engine_post_attr send_attr = {0};
	struct mlx5dr_wqe_gta_data_seg_arg *wqe_arg;
	struct mlx5dr_send_engine_post_ctrl ctrl;
	struct mlx5dr_wqe_gta_ctrl_seg *wqe_ctrl;
	int i, full_iter, leftover;
	size_t wqe_len;

	mlx5d_arg_init_send_attr(&send_attr, comp_data, arg_idx);

	/* Each WQE can hold 64B of data, it might require multiple iteration */
	full_iter = data_size / MLX5DR_ARG_DATA_SIZE;
	leftover = data_size & (MLX5DR_ARG_DATA_SIZE - 1);

	for (i = 0; i < full_iter; i++) {
		ctrl = mlx5dr_send_engine_post_start(queue);
		mlx5dr_send_engine_post_req_wqe(&ctrl, (void *)&wqe_ctrl, &wqe_len);
		memset(wqe_ctrl, 0, wqe_len);
		mlx5dr_send_engine_post_req_wqe(&ctrl, (void *)&wqe_arg, &wqe_len);
		memcpy(wqe_arg, arg_data, wqe_len);
		send_attr.id = arg_idx++;
		mlx5dr_send_engine_post_end(&ctrl, &send_attr);

		/* Move to next argument data */
		arg_data += MLX5DR_ARG_DATA_SIZE;
	}

	if (leftover) {
		ctrl = mlx5dr_send_engine_post_start(queue);
		mlx5dr_send_engine_post_req_wqe(&ctrl, (void *)&wqe_ctrl, &wqe_len);
		memset(wqe_ctrl, 0, wqe_len);
		mlx5dr_send_engine_post_req_wqe(&ctrl, (void *)&wqe_arg, &wqe_len);
		memcpy(wqe_arg, arg_data, leftover);
		send_attr.id = arg_idx;
		mlx5dr_send_engine_post_end(&ctrl, &send_attr);
	}
}

int mlx5dr_arg_write_inline_arg_data(struct mlx5dr_context *ctx,
				     uint32_t arg_idx,
				     uint8_t *arg_data,
				     size_t data_size)
{
	struct mlx5dr_send_engine *queue;
	int ret;

	pthread_spin_lock(&ctx->ctrl_lock);

	/* Get the control queue */
	queue = &ctx->send_queue[ctx->queues - 1];

	mlx5dr_arg_write(queue, arg_data, arg_idx, arg_data, data_size);

	mlx5dr_send_engine_flush_queue(queue);

	/* Poll for completion */
	ret = mlx5dr_arg_poll_for_comp(ctx, ctx->queues - 1);
	if (ret)
		DR_LOG(ERR, "Failed to get completions for shared action");

	pthread_spin_unlock(&ctx->ctrl_lock);

	return ret;
}

bool mlx5dr_arg_is_valid_arg_request_size(struct mlx5dr_context *ctx,
					  uint32_t arg_size)
{
	if (arg_size < ctx->caps->log_header_modify_argument_granularity ||
	    arg_size > ctx->caps->log_header_modify_argument_max_alloc) {
		return false;
	}
	return true;
}

static int
mlx5dr_arg_create_modify_header_arg(struct mlx5dr_context *ctx,
				    struct mlx5dr_action *action,
				    uint16_t num_of_actions,
				    __be64 *pattern,
				    uint32_t bulk_size)
{
	uint32_t flags = action->flags;
	uint16_t args_log_size;
	int ret = 0;

	/* Alloc bulk of args */
	args_log_size = mlx5dr_arg_get_arg_log_size(num_of_actions);
	if (args_log_size >= MLX5DR_ARG_CHUNK_SIZE_MAX) {
		DR_LOG(ERR, "Exceed number of allowed actions %u",
			num_of_actions);
		rte_errno = EINVAL;
		return rte_errno;
	}

	if (!mlx5dr_arg_is_valid_arg_request_size(ctx, args_log_size + bulk_size)) {
		DR_LOG(ERR, "Arg size %d does not fit FW capability",
		       args_log_size + bulk_size);
		rte_errno = EINVAL;
		return rte_errno;
	}

	action->modify_header.arg_obj =
		mlx5dr_cmd_arg_create(ctx->ibv_ctx, args_log_size + bulk_size,
				      ctx->pd_num);
	if (!action->modify_header.arg_obj) {
		DR_LOG(ERR, "Failed allocating arg in order: %d",
			args_log_size + bulk_size);
		return rte_errno;
	}

	/* When INLINE need to write the arg data */
	if (flags & MLX5DR_ACTION_FLAG_SHARED)
		ret = mlx5dr_arg_write_inline_arg_data(ctx,
						       action->modify_header.arg_obj->id,
						       (uint8_t *)pattern,
						       num_of_actions *
						       MLX5DR_MODIFY_ACTION_SIZE);
	if (ret) {
		DR_LOG(ERR, "Failed writing INLINE arg in order: %d",
			args_log_size + bulk_size);
		mlx5dr_cmd_destroy_obj(action->modify_header.arg_obj);
		return rte_errno;
	}

	return 0;
}

int mlx5dr_pat_arg_create_modify_header(struct mlx5dr_context *ctx,
					struct mlx5dr_action *action,
					size_t pattern_sz,
					__be64 pattern[],
					uint32_t bulk_size)
{
	uint16_t num_of_actions;
	int ret;

	num_of_actions = pattern_sz / MLX5DR_MODIFY_ACTION_SIZE;
	if (num_of_actions == 0) {
		DR_LOG(ERR, "Invalid number of actions %u\n", num_of_actions);
		rte_errno = EINVAL;
		return rte_errno;
	}

	action->modify_header.num_of_actions = num_of_actions;

	ret = mlx5dr_arg_create_modify_header_arg(ctx, action,
						  num_of_actions,
						  pattern,
						  bulk_size);
	if (ret) {
		DR_LOG(ERR, "Failed to allocate arg");
		return ret;
	}

	ret = mlx5dr_pat_get_pattern(ctx, action, num_of_actions, pattern_sz,
				     pattern);
	if (ret) {
		DR_LOG(ERR, "Failed to allocate pattern");
		goto free_arg;
	}

	return 0;

free_arg:
	mlx5dr_cmd_destroy_obj(action->modify_header.arg_obj);
	return rte_errno;
}

void mlx5dr_pat_arg_destroy_modify_header(struct mlx5dr_context *ctx,
					  struct mlx5dr_action *action)
{
	mlx5dr_cmd_destroy_obj(action->modify_header.arg_obj);
	mlx5dr_pat_put_pattern(ctx->pattern_cache, action);
}
