// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2021 MediaTek Inc.
 */
#include <linux/vmalloc.h>
#include <linux/mutex.h>

#include "alp_parser_if.h"
#include "alp_parser.h"
#include "alp_lmt.h"
#include "net_buf.h"
#include "atsc30_utils.h"
#include "atsc30_log.h"

//-------------------------------------------------------------------------------------------------
//  Debug Constants and Macros
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Constants
//-------------------------------------------------------------------------------------------------
#define MAX_ALP_PARSER_CONTEXT_SUPPORTED   (2)

//-------------------------------------------------------------------------------------------------
//  Macros
//-------------------------------------------------------------------------------------------------
#define ALP_PARSER_IF_GET_LOCK(framer_idx) \
	({ \
		struct mutex *_lock = &alp_praser_if_lock_1; \
		if ((framer_idx) == 0) \
			_lock = &alp_praser_if_lock_0; \
		_lock; \
	})
#define ALP_PARSER_LOCK(framer_idx) \
	mutex_lock(ALP_PARSER_IF_GET_LOCK(framer_idx))
#define ALP_PARSER_UNLOCK(framer_idx) \
	mutex_unlock(ALP_PARSER_IF_GET_LOCK(framer_idx))

//-------------------------------------------------------------------------------------------------
//  Structs and Enums
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Functions Prototype
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Variables
//-------------------------------------------------------------------------------------------------
static alp_context_t *g_alp_ctx[MAX_ALP_PARSER_CONTEXT_SUPPORTED] = { NULL };
static DEFINE_MUTEX(alp_praser_if_lock_0);
static DEFINE_MUTEX(alp_praser_if_lock_1);

//-------------------------------------------------------------------------------------------------
//  Internal Functions
//-------------------------------------------------------------------------------------------------
static inline alp_context_t *_alp_parser_get_alp_ctx(
		const u8 framer_idx)
{
	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return NULL;

	return g_alp_ctx[framer_idx];
}

static bool _alp_parser_set_alp_ctx(
		const u8 framer_idx,
		alp_context_t *alp_ctx)
{
	bool ret = true;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;
	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return false;

	if (g_alp_ctx[framer_idx] == NULL)
		g_alp_ctx[framer_idx] = alp_ctx;
	else
		ret = false;

	return ret;
}

static bool _alp_parser_reset_alp_ctx(
		const u8 framer_idx)
{
	bool ret = true;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;

	if (g_alp_ctx[framer_idx] != NULL) {
		vfree(g_alp_ctx[framer_idx]);
		g_alp_ctx[framer_idx] = NULL;
	} else
		ret = false;

	return ret;
}


static const char *_alp_parser_input_type_2_str(
		alp_parser_input_packet_type_t input_type)
{
	static char unknown_enum[ATSC30_LOG_UNKNOWN_ENUM_SIZE] = { 0 };
	static const char names[][ATSC30_LOG_UNKNOWN_ENUM_SIZE] = {
		"PARSER_INPUT_TYPE_ALP",
		"PARSER_INPUT_TYPE_IP",
		"PARSER_INPUT_TYPE_ROHC",
		"PARSER_INPUT_TYPE_PUD",
		"PARSER_INPUT_TYPE_UNKONWN",
	};

	return atsc30_log_enum_2_str_in_range(
			names,
			PARSER_INPUT_TYPE_ALP,
			PARSER_INPUT_TYPE_UNKONWN,
			unknown_enum,
			sizeof(unknown_enum),
			input_type);
}

static ALP_RET_CODE_T _alp_parser_if_run(
		net_buf_t packet,
		const alp_parser_input_info_t *input_info)
{
	alp_context_t *alp_ctx = NULL;

	if (ALP_PARSER_VERIFY(input_info == NULL))
		return ALP_NULL_PTR;
	alp_ctx = _alp_parser_get_alp_ctx(input_info->framer_idx);
	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return ALP_NULL_PTR;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return ALP_WAIT_FOR_CONTEXT_TO_ESTABLISH;

	return alp_parser_run(alp_ctx, packet, input_info);
}

static bool _alp_parser_if_init(
		const u8 framer_idx,
		const struct alp_parser_operations *alp_parser_ops,
		const struct alp_priv_packet_operations *alp_priv_packet_ops,
		const alp_sw_parser_config_t sw_cfg)
{
	alp_context_t *alp_ctx = NULL;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;
	if (ALP_PARSER_VERIFY(
		ALP_PARSER_OPERATIONS_IS_INVALID(alp_parser_ops)))
		return false;
	if (ALP_PARSER_VERIFY(
		ALP_PRIV_PACKET_OPERATIONS_IS_INVALID(alp_priv_packet_ops)))
		return false;

	alp_ctx = _alp_parser_get_alp_ctx(framer_idx);
	if (alp_ctx == NULL) {
		alp_ctx = vzalloc(sizeof(alp_context_t));
		if (alp_ctx == NULL) {
			ALP_PARSER_LOG_ENHANCED_ERROR(
				"Memory allocation for 0x%zx is failed\n",
				sizeof(alp_context_t));

			return false;
		}

		if (!_alp_parser_set_alp_ctx(framer_idx, alp_ctx)) {
			ALP_PARSER_LOG_ENHANCED_ERROR(
				"_alp_parser_set_alp_ctx_nolock(%d, ...) is failed\n",
				framer_idx);
			vfree(alp_ctx);

			return false;
		}
	}

	if (ALP_PARSER_VERIFY(alp_ctx->initialized))
		return true;

	return alp_parser_init(
			alp_ctx,
			framer_idx,
			alp_parser_ops,
			alp_priv_packet_ops,
			sw_cfg);
}

static bool _alp_parser_if_deinit(const u8 framer_idx)
{
	bool r;
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return false;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return true;

	r = alp_parser_deinit(alp_ctx);
	if (r)
		r = _alp_parser_reset_alp_ctx(framer_idx);
	return r;
}

static const alp_stream_t *_alp_parser_if_get_stream(
		const u8 framer_idx,
		alp_stream_ID strm_id)
{
	const alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return NULL;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return NULL;

	return alp_parser_get_stream(alp_ctx, strm_id);
}

static alp_stream_ID _alp_parser_if_set_stream(
		const u8 framer_idx,
		const alp_stream_t *const new_strm)
{
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return INVALID_ALP_STREAM_ID;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return INVALID_ALP_STREAM_ID;

	return alp_parser_set_stream(alp_ctx, new_strm);
}

static bool _alp_parser_if_update_stream_state(
		const u8 framer_idx,
		alp_stream_ID strm_id,
		ALP_STREAM_STATE_T state)
{
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return false;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return false;

	return alp_parser_update_stream_state(alp_ctx, strm_id, state);
}

static bool _alp_parser_if_set_plp_id_filter(
		u8 framer_idx,
		const u8 *const plp_id_arr,
		u8 plp_cnt)
{
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return false;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return false;

	return alp_parser_set_plp_id_filter(alp_ctx, plp_id_arr, plp_cnt);
}

static bool _alp_parser_if_clear_plp_id_filter(
		u8 framer_idx,
		const u8 *const plp_id_arr,
		u8 plp_cnt)
{
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return false;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return false;

	return alp_parser_clear_plp_id_filter(alp_ctx, plp_id_arr, plp_cnt);
}

static void _alp_parser_if_clear_all_plp_id_filters(u8 framer_idx)
{
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return;

	alp_parser_clear_all_plp_id_filters(alp_ctx);
}

static u8 _alp_parser_if_get_plp_num(u8 framer_idx)
{
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return (MAX_PLP_NUMBER_SUPPORTED + 1);
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return (MAX_PLP_NUMBER_SUPPORTED + 1);

	return alp_ctx->plp_ctx.plp_num;
}

static bool _alp_parser_if_get_plp_id(
		u8 framer_idx,
		u8 *plp_id_arr,
		u8 plp_cnt_max)
{
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return false;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return false;

	return alp_parser_get_plp_id(
			alp_ctx,
			plp_id_arr,
			plp_cnt_max);
}

static void _alp_parser_if_collect_statistics(const u8 framer_idx)
{
	alp_context_t *alp_ctx = _alp_parser_get_alp_ctx(framer_idx);

	if (ALP_PARSER_VERIFY(alp_ctx == NULL))
		return;
	if (ALP_PARSER_VERIFY(!alp_ctx->initialized))
		return;

	alp_parser_collect_statistics(alp_ctx);
}

//-------------------------------------------------------------------------------------------------
//  Public Functions
//-------------------------------------------------------------------------------------------------
ALP_RET_CODE_T alp_parser_if_run(
		net_buf_t packet,
		const alp_parser_input_info_t *input_info)
{
	ALP_RET_CODE_T ret = ALP_NULL_PTR;

	if (ALP_PARSER_VERIFY(input_info == NULL))
		return ALP_NULL_PTR;
	if (ALP_PARSER_VERIFY(
		input_info->framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return ALP_NULL_PTR;

	ALP_PARSER_LOCK(input_info->framer_idx);
	ret = _alp_parser_if_run(
			packet,
			input_info);
	ALP_PARSER_UNLOCK(input_info->framer_idx);

	return ret;
}

bool alp_parser_if_init(
		const u8 framer_idx,
		const struct alp_parser_operations *alp_parser_ops,
		const struct alp_priv_packet_operations *alp_priv_packet_ops,
		const alp_sw_parser_config_t sw_cfg)
{
	bool ret = false;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_init(
		framer_idx,
		alp_parser_ops,
		alp_priv_packet_ops,
		sw_cfg);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

bool alp_parser_if_deinit(const u8 framer_idx)
{
	bool ret = false;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_deinit(framer_idx);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

const alp_stream_t *alp_parser_if_get_stream(
		const u8 framer_idx,
		alp_stream_ID strm_id)
{
	const alp_stream_t *ret = NULL;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return NULL;

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_get_stream(
		framer_idx,
		strm_id);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

alp_stream_ID alp_parser_if_set_stream(
		const u8 framer_idx,
		const alp_stream_t *const new_strm)
{
	alp_stream_ID ret = INVALID_ALP_STREAM_ID;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return INVALID_ALP_STREAM_ID;

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_set_stream(
		framer_idx,
		new_strm);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

bool alp_parser_if_update_stream_state(
		const u8 framer_idx,
		alp_stream_ID strm_id,
		ALP_STREAM_STATE_T state)
{
	bool ret = false;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_update_stream_state(
		framer_idx,
		strm_id,
		state);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

bool alp_parser_if_set_plp_id_filter(
		u8 framer_idx,
		const u8 *const plp_id_arr,
		u8 plp_cnt)
{
	bool ret = false;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_set_plp_id_filter(
		framer_idx,
		plp_id_arr,
		plp_cnt);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

bool alp_parser_if_clear_plp_id_filter(
		u8 framer_idx,
		const u8 *const plp_id_arr,
		u8 plp_cnt)
{
	bool ret = false;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_clear_plp_id_filter(
		framer_idx,
		plp_id_arr,
		plp_cnt);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

void alp_parser_if_clear_all_plp_id_filters(u8 framer_idx)
{
	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return;

	ALP_PARSER_LOCK(framer_idx);
	_alp_parser_if_clear_all_plp_id_filters(framer_idx);
	ALP_PARSER_UNLOCK(framer_idx);
}

u8 alp_parser_if_get_plp_num(u8 framer_idx)
{
	u8 ret = (MAX_PLP_NUMBER_SUPPORTED + 1);

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return (MAX_PLP_NUMBER_SUPPORTED + 1);

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_get_plp_num(framer_idx);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

bool alp_parser_if_get_plp_id(
		u8 framer_idx,
		u8 *plp_id_arr,
		u8 plp_cnt_max)
{
	bool ret = true;

	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return false;

	ALP_PARSER_LOCK(framer_idx);
	ret = _alp_parser_if_get_plp_id(
		framer_idx,
		plp_id_arr,
		plp_cnt_max);
	ALP_PARSER_UNLOCK(framer_idx);

	return ret;
}

void alp_parser_if_collect_statistics(const u8 framer_idx)
{
	if (ALP_PARSER_VERIFY(
		framer_idx >= MAX_ALP_PARSER_CONTEXT_SUPPORTED))
		return;

	ALP_PARSER_LOCK(framer_idx);
	_alp_parser_if_collect_statistics(framer_idx);
	ALP_PARSER_UNLOCK(framer_idx);
}

void alp_parser_if_dump_input_info(
		int log_level,
		const alp_parser_input_info_t *prs_info)
{
	if (ATSC30_NULL_PTR_CHECK(prs_info))
		return;

	ALP_PARSER_LOG(log_level,
		"alp_parser_input_info_t\n");

	ALP_PARSER_LOG(log_level,
		"framer_idx = %d\n",
		prs_info->framer_idx);
	ALP_PARSER_LOG(log_level,
		"sid = 0x%x\n",
		prs_info->sid);
	ALP_PARSER_LOG(log_level,
		"sid_flag = %s\n",
		ATSC30_BOOL_2_STR(prs_info->sid_flag));
	ALP_PARSER_LOG(log_level,
		"plp_id = 0x%x\n",
		prs_info->plp_id);
	ALP_PARSER_LOG(log_level,
		"max_cid = 0x%x\n",
		prs_info->max_cid);
	ALP_PARSER_LOG(log_level,
		"strm_idx = 0x%x\n",
		prs_info->strm_idx);
	ALP_PARSER_LOG(log_level,
		"input_type = %s\n",
		_alp_parser_input_type_2_str(prs_info->input_type));
	ALP_PARSER_LOG(log_level,
		"timestamp = 0x%x\n",
		prs_info->timestamp);
}
