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

#include "mlx5dr_internal.h"

static int mlx5dr_context_pools_init(struct mlx5dr_context *ctx)
{
	struct mlx5dr_pool_attr pool_attr = {0};
	uint8_t max_log_sz;
	int i;

	if (mlx5dr_pat_init_pattern_cache(&ctx->pattern_cache))
		return rte_errno;

	/* Create an STC pool per FT type */
	pool_attr.pool_type = MLX5DR_POOL_TYPE_STC;
	pool_attr.flags = MLX5DR_POOL_FLAGS_FOR_STC_POOL;
	max_log_sz = RTE_MIN(MLX5DR_POOL_STC_LOG_SZ, ctx->caps->stc_alloc_log_max);
	pool_attr.alloc_log_sz = RTE_MAX(max_log_sz, ctx->caps->stc_alloc_log_gran);

	for (i = 0; i < MLX5DR_TABLE_TYPE_MAX; i++) {
		pool_attr.table_type = i;
		ctx->stc_pool[i] = mlx5dr_pool_create(ctx, &pool_attr);
		if (!ctx->stc_pool[i]) {
			DR_LOG(ERR, "Failed to allocate STC pool [%d]", i);
			goto free_stc_pools;
		}
	}

	return 0;

free_stc_pools:
	for (i = 0; i < MLX5DR_TABLE_TYPE_MAX; i++)
		if (ctx->stc_pool[i])
			mlx5dr_pool_destroy(ctx->stc_pool[i]);

	mlx5dr_pat_uninit_pattern_cache(ctx->pattern_cache);

	return rte_errno;
}

static void mlx5dr_context_pools_uninit(struct mlx5dr_context *ctx)
{
	int i;

	mlx5dr_pat_uninit_pattern_cache(ctx->pattern_cache);

	for (i = 0; i < MLX5DR_TABLE_TYPE_MAX; i++) {
		if (ctx->stc_pool[i])
			mlx5dr_pool_destroy(ctx->stc_pool[i]);
	}
}

static int mlx5dr_context_init_pd(struct mlx5dr_context *ctx,
				  struct ibv_pd *pd)
{
	struct mlx5dv_pd mlx5_pd = {0};
	struct mlx5dv_obj obj;
	int ret;

	if (pd) {
		ctx->pd = pd;
	} else {
		ctx->pd = mlx5_glue->alloc_pd(ctx->ibv_ctx);
		if (!ctx->pd) {
			DR_LOG(ERR, "Failed to allocate PD");
			rte_errno = errno;
			return rte_errno;
		}
		ctx->flags |= MLX5DR_CONTEXT_FLAG_PRIVATE_PD;
	}

	obj.pd.in = ctx->pd;
	obj.pd.out = &mlx5_pd;

	ret = mlx5_glue->dv_init_obj(&obj, MLX5DV_OBJ_PD);
	if (ret)
		goto free_private_pd;

	ctx->pd_num = mlx5_pd.pdn;

	return 0;

free_private_pd:
	if (ctx->flags & MLX5DR_CONTEXT_FLAG_PRIVATE_PD)
		mlx5_glue->dealloc_pd(ctx->pd);

	return ret;
}

static int mlx5dr_context_uninit_pd(struct mlx5dr_context *ctx)
{
	if (ctx->flags & MLX5DR_CONTEXT_FLAG_PRIVATE_PD)
		return mlx5_glue->dealloc_pd(ctx->pd);

	return 0;
}

static void mlx5dr_context_check_hws_supp(struct mlx5dr_context *ctx)
{
	struct mlx5dr_cmd_query_caps *caps = ctx->caps;

	/* HWS not supported on device / FW */
	if (!caps->wqe_based_update) {
		DR_LOG(INFO, "Required HWS WQE based insertion cap not supported");
		return;
	}

	/* Current solution requires all rules to set reparse bit */
	if ((!caps->nic_ft.reparse ||
	     (!caps->fdb_ft.reparse && caps->eswitch_manager)) ||
	    !IS_BIT_SET(caps->rtc_reparse_mode, MLX5_IFC_RTC_REPARSE_ALWAYS)) {
		DR_LOG(INFO, "Required HWS reparse cap not supported");
		return;
	}

	/* FW/HW must support 8DW STE */
	if (!IS_BIT_SET(caps->ste_format, MLX5_IFC_RTC_STE_FORMAT_8DW)) {
		DR_LOG(INFO, "Required HWS STE format not supported");
		return;
	}

	/* Adding rules by hash and by offset are requirements */
	if (!IS_BIT_SET(caps->rtc_index_mode, MLX5_IFC_RTC_STE_UPDATE_MODE_BY_HASH) ||
	    !IS_BIT_SET(caps->rtc_index_mode, MLX5_IFC_RTC_STE_UPDATE_MODE_BY_OFFSET)) {
		DR_LOG(INFO, "Required HWS RTC update mode not supported");
		return;
	}

	/* Support for SELECT definer ID is required */
	if (!IS_BIT_SET(caps->definer_format_sup, MLX5_IFC_DEFINER_FORMAT_ID_SELECT)) {
		DR_LOG(INFO, "Required HWS Dynamic definer not supported");
		return;
	}

	ctx->flags |= MLX5DR_CONTEXT_FLAG_HWS_SUPPORT;
}

static int mlx5dr_context_init_hws(struct mlx5dr_context *ctx,
				   struct mlx5dr_context_attr *attr)
{
	int ret;

	mlx5dr_context_check_hws_supp(ctx);

	if (!(ctx->flags & MLX5DR_CONTEXT_FLAG_HWS_SUPPORT))
		return 0;

	ret = mlx5dr_context_init_pd(ctx, attr->pd);
	if (ret)
		return ret;

	ret = mlx5dr_context_pools_init(ctx);
	if (ret)
		goto uninit_pd;

	ret = mlx5dr_send_queues_open(ctx, attr->queues, attr->queue_size);
	if (ret)
		goto pools_uninit;

	return 0;

pools_uninit:
	mlx5dr_context_pools_uninit(ctx);
uninit_pd:
	mlx5dr_context_uninit_pd(ctx);
	return ret;
}

static void mlx5dr_context_uninit_hws(struct mlx5dr_context *ctx)
{
	if (!(ctx->flags & MLX5DR_CONTEXT_FLAG_HWS_SUPPORT))
		return;

	mlx5dr_send_queues_close(ctx);
	mlx5dr_context_pools_uninit(ctx);
	mlx5dr_context_uninit_pd(ctx);
}

struct mlx5dr_context *mlx5dr_context_open(struct ibv_context *ibv_ctx,
					   struct mlx5dr_context_attr *attr)
{
	struct mlx5dr_context *ctx;
	int ret;

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

	ctx->ibv_ctx = ibv_ctx;
	pthread_spin_init(&ctx->ctrl_lock, PTHREAD_PROCESS_PRIVATE);

	ctx->caps = simple_calloc(1, sizeof(*ctx->caps));
	if (!ctx->caps)
		goto free_ctx;

	ret = mlx5dr_cmd_query_caps(ibv_ctx, ctx->caps);
	if (ret)
		goto free_caps;

	ret = mlx5dr_context_init_hws(ctx, attr);
	if (ret)
		goto free_caps;

	return ctx;

free_caps:
	simple_free(ctx->caps);
free_ctx:
	simple_free(ctx);
	return NULL;
}

int mlx5dr_context_close(struct mlx5dr_context *ctx)
{
	mlx5dr_context_uninit_hws(ctx);
	simple_free(ctx->caps);
	pthread_spin_destroy(&ctx->ctrl_lock);
	simple_free(ctx);
	return 0;
}
