/* =========================================================================
 *  This code (or certain portions of it) is released for use under license
 *  from Imagination Technologies Limited.
 *  Copyright (c) 2019 Imagination Technologies Limited
 *  Copyright 2020-2022 NXP
 *
 *  NXP Confidential. This software is owned or controlled by NXP and may only
 *  be used strictly in accordance with the applicable license terms. By
 *  expressly accepting such terms or by downloading, installing, activating
 *  and/or otherwise using the software, you are agreeing that you have read,
 *  and that you agree to comply with and are bound by, such license terms. If
 *  you do not agree to be bound by the applicable license terms, then you may
 *  not retain, install, activate or otherwise use the software.
 *  <<< PFE Restricted Software >>>
 *
 * ========================================================================= */

/*==================================================================================================
*                              SOURCE FILE VERSION INFORMATION
==================================================================================================*/
#define PFE_SRC_VERSION_CHECK 43100440

#include "pfe_cfg.h"
#include "oal.h"
#include "linked_list.h"
#include "pfe_hif_ptp.h"

/*	Number of entries in the DB producing warning message */
#define PFE_HIF_PTP_DB_WARNING_THRESHOLD	50U

/*	Maximum allowed number of entries */
#define PFE_HIF_PTP_DB_MAX_CAPACITY			(PFE_HIF_PTP_DB_WARNING_THRESHOLD + 10U)

/*	Entry timeout in number of ticks */
#define PFE_HIF_PTP_DB_TIMEOUT				1

#define PFE_PTP_WORKER_SHUTDOWN		(1)
#define PFE_PTP_WORKER_TICK			(2)

typedef struct
{
	uint32_t ticks;		/* Timeout counter (in number of ticks). Zero means entry is aged. */
	uint16_t refnum;	/* Reference to identify ETS report */
	uint8_t type;		/* PTP Message type */
	bool_t rx;			/* If TRUE then entry refers to ingress message */
	uint16_t port;		/* PTP Port */
	uint16_t seq_id;	/* PTP Sequence ID */
	uint32_t ts_sec;
	uint32_t ts_nsec;
	bool_t ts_valid;
	/*	List member for chaining */
	LLIST_t lm;
} pfe_hif_ptp_ts_db_entry_t;

#ifdef PFE_CFG_TARGET_OS_AUTOSAR
#define ETH_43_PFE_START_SEC_CODE
#include "Eth_43_PFE_MemMap.h"
#endif /* PFE_CFG_TARGET_OS_AUTOSAR */

/**
 * @brief       Periodically called db maintenance function
 * @details     In AUTOSAR MCAL driver this function is called from hif drv,
 *              in other drivers from an internal thread.
 */
void pfe_hif_ptp_ts_db_tick_iteration(void *arg)
{
	pfe_hif_ptp_ts_db_t *db = (pfe_hif_ptp_ts_db_t *)arg;
	LLIST_t *item, *aux;
	pfe_hif_ptp_ts_db_entry_t *entry;

	if (EOK != oal_mutex_lock(&db->lock))
	{
		NXP_LOG_DEBUG("Mutex lock failed\n");
	}

	/* Release aged entries */
	LLIST_ForEachRemovable(item, aux, &db->entries)
	{
		entry = LLIST_Data(item, pfe_hif_ptp_ts_db_entry_t, lm);
		if (NULL != entry)
		{
            if (entry->ticks <= 0U)
            {
                NXP_LOG_INFO("Removing aged TS DB entry (Type: 0x%x, Port: 0x%x, SeqID: 0x%x)\n",
                        entry->type, entry->port, entry->seq_id);
                LLIST_Remove(item);
                oal_mm_free(entry);
                db->count--;
            }
            else
            {
                entry->ticks--;
            }
        }
	}

	if (EOK != oal_mutex_unlock(&db->lock))
	{
		NXP_LOG_DEBUG("Mutex unlock failed\n");
	}
}

#ifndef PFE_CFG_TARGET_OS_AUTOSAR
/**
 * @brief    Worker function running within internal thread
 */
static void *pfe_hif_ptp_ts_db_tick(void *arg)
{
	pfe_hif_ptp_ts_db_t *db = (pfe_hif_ptp_ts_db_t *)arg;
	oal_mbox_msg_t msg;

	while (1)
	{
		if (EOK == oal_mbox_receive(db->mbox, &msg))
		{
			if (PFE_PTP_WORKER_SHUTDOWN == msg.payload.code)
			{
				break;
			}
			else
			{
				pfe_hif_ptp_ts_db_tick_iteration(arg);
			}
		}
		else
		{
			NXP_LOG_WARNING("mbox_receive() failed\n");
		}
	}

	NXP_LOG_INFO("TS DB worker shutting down\n");

	return NULL;
}
#endif /* PFE_CFG_TARGET_OS_AUTOSAR */

/**
 * @brief	Initialize TS database
 */
errno_t pfe_hif_ptp_ts_db_init(pfe_hif_ptp_ts_db_t *db)
{
	errno_t ret;
	(void)memset(db, 0, sizeof(pfe_hif_ptp_ts_db_t));

	db->lock = PFE_HIF_PTP_TS_DB_MUTEX;
	ret = oal_mutex_init(&db->lock);
	if (EOK != ret)
	{
		NXP_LOG_ERROR("Mutex init failed\n");
		return ret;
	}

	LLIST_Init(&db->entries);

#ifndef PFE_CFG_TARGET_OS_AUTOSAR
	db->mbox = oal_mbox_create();
	if (NULL == db->mbox)
	{
		NXP_LOG_ERROR("Can't create mbox\n");
		oal_mm_free(db->lock);
		return ENOEXEC;
	}

	if (EOK != oal_mbox_attach_timer(db->mbox, 1000U, PFE_PTP_WORKER_TICK))
	{
		NXP_LOG_ERROR("Could not attach timer\n");
		oal_mbox_destroy(db->mbox);
		oal_mm_free(db->lock);
		return ENOEXEC;
	}

	db->worker = oal_thread_create(&pfe_hif_ptp_ts_db_tick, db, "TS DB worker", 0);
	if (NULL == db->worker)
	{
		oal_mbox_detach_timer(db->mbox);
		oal_mbox_destroy(db->mbox);
		oal_mm_free(db->lock);
		return ENOMEM;
	}
#endif /* PFE_CFG_TARGET_OS_AUTOSAR */

	return EOK;
}

/**
 * @brief	Finalize the TS database
 */
void pfe_hif_ptp_ts_db_fini(pfe_hif_ptp_ts_db_t *db)
{
	LLIST_t *item, *aux;
	pfe_hif_ptp_ts_db_entry_t *entry;

#ifndef PFE_CFG_TARGET_OS_AUTOSAR
	if (NULL != db->worker)
	{
		NXP_LOG_INFO("Stopping TS DB worker\n");

		if (EOK != oal_mbox_send_signal(db->mbox, PFE_PTP_WORKER_SHUTDOWN))
		{
			NXP_LOG_ERROR("Could not send shutdown signal\n");
		}

		if (EOK != oal_thread_join(db->worker, NULL))
		{
			NXP_LOG_ERROR("Can't join TS DB worker thread\n");
		}
		else
		{
			NXP_LOG_INFO("TS DB worker stopped\n");
			db->worker = NULL;
		}
	}
#endif /* PFE_CFG_TARGET_OS_AUTOSAR */

	if (EOK != oal_mutex_lock(&db->lock))
	{
		NXP_LOG_DEBUG("Mutex lock failed\n");
	}

	/*	Release all entries */
	LLIST_ForEachRemovable(item, aux, &db->entries)
	{
		entry = LLIST_Data(item, pfe_hif_ptp_ts_db_entry_t, lm);
		if (NULL != entry)
		{
			LLIST_Remove(item);
			oal_mm_free(entry);
		}
	}

	if (EOK != oal_mutex_unlock(&db->lock))
	{
		NXP_LOG_DEBUG("Mutex unlock failed\n");
	}

#ifndef PFE_CFG_TARGET_OS_AUTOSAR
	oal_mbox_destroy(db->mbox);
#endif /* PFE_CFG_TARGET_OS_AUTOSAR */
	(void)oal_mutex_destroy(&db->lock);

}

/**
 * @brief	Add PTP message to the DB. TS will be added later.
 */
errno_t pfe_hif_ptp_ts_db_push_msg(pfe_hif_ptp_ts_db_t *db, bool_t rx,
		uint16_t refnum, uint8_t type, uint16_t port, uint16_t seq_id)
{
	pfe_hif_ptp_ts_db_entry_t *entry;

	/*	We should somehow limit number of entries.. */
	if (db->count >= PFE_HIF_PTP_DB_MAX_CAPACITY)
	{
		return ENOSPC;
	}

	entry = oal_mm_malloc(sizeof(pfe_hif_ptp_ts_db_entry_t));
	if (NULL == entry)
	{
		return ENOMEM;
	}
	else
	{
		/*	Fill entry */
		entry->refnum = refnum;
		entry->type = type;
		entry->port = port;
		entry->seq_id = seq_id;
		entry->ts_valid = FALSE;
		entry->ticks = PFE_HIF_PTP_DB_TIMEOUT;
		entry->rx = rx;

		/*	Link-in */
		if (EOK != oal_mutex_lock(&db->lock))
		{
			NXP_LOG_DEBUG("Mutex lock failed\n");
		}

		LLIST_AddAtEnd(&entry->lm, &db->entries);
		db->count++;

		if ((db->count > PFE_HIF_PTP_DB_WARNING_THRESHOLD) && !db->reported)
		{
			NXP_LOG_WARNING("More than %d entries in PTP DB...\n", PFE_HIF_PTP_DB_WARNING_THRESHOLD);
			db->reported = TRUE;
		}

		if (EOK != oal_mutex_unlock(&db->lock))
		{
			NXP_LOG_DEBUG("Mutex unlock failed\n");
		}
	}

	return EOK;
}

/**
 * @brief	Bind TS with existing entry
 */
errno_t pfe_hif_ptp_ts_db_push_ts(pfe_hif_ptp_ts_db_t *db,
		uint16_t refnum, uint32_t ts_sec, uint32_t ts_nsec)
{
	LLIST_t *item;
	pfe_hif_ptp_ts_db_entry_t *entry;
	bool_t found = FALSE;

	/*	Find matching entry and add the timestamp */
	if (EOK != oal_mutex_lock(&db->lock))
	{
		NXP_LOG_DEBUG("Mutex lock failed\n");
	}

	LLIST_ForEach(item, &db->entries)
	{
		entry = LLIST_Data(item, pfe_hif_ptp_ts_db_entry_t, lm);
		if (NULL != entry)
		{
			if (entry->refnum == refnum)
			{
				found = TRUE;
				entry->ts_sec = ts_sec;
				entry->ts_nsec = ts_nsec;
				entry->ts_valid = TRUE;
				break;
			}
		}
	}

	if (EOK != oal_mutex_unlock(&db->lock))
	{
		NXP_LOG_DEBUG("Mutex unlock failed\n");
	}

	return (found) ? EOK : ENOENT;
}

/**
 * @brief	Get TS associated with give PTP message
 */
errno_t pfe_hif_ptp_ts_db_pop(pfe_hif_ptp_ts_db_t *db,
		uint8_t type, uint16_t port, uint16_t seq_id,
			uint32_t *ts_sec, uint32_t *ts_nsec, bool_t rx)
{
	errno_t ret_val = ENOENT;
	LLIST_t *item;
	pfe_hif_ptp_ts_db_entry_t *entry;

	/*	Find matching entry and get the timestamp */
	if (EOK != oal_mutex_lock(&db->lock))
	{
		NXP_LOG_DEBUG("Mutex lock failed\n");
	}

	LLIST_ForEach(item, &db->entries)
	{
		entry = LLIST_Data(item, pfe_hif_ptp_ts_db_entry_t, lm);
		if (NULL != entry)
		{
			if ((entry->rx == rx)
					&& (entry->type == type)
					&& (entry->port == port)
					&& (entry->seq_id == seq_id))
			{
				if (entry->ts_valid == TRUE)
				{
					ret_val = EOK;
					*ts_sec = entry->ts_sec;
					*ts_nsec = entry->ts_nsec;
				}
				else
				{
					ret_val = EAGAIN;
				}
				break;
			}
		}
	}

	if (EOK == ret_val)
	{
		/*	Remove from DB */
		LLIST_Remove(item);
		oal_mm_free(entry);
		db->count--;
		if ((db->count <= (PFE_HIF_PTP_DB_WARNING_THRESHOLD/4U)) && db->reported)
		{
			db->reported = FALSE;
		}
	}

	if (EOK != oal_mutex_unlock(&db->lock))
	{
		NXP_LOG_DEBUG("Mutex unlock failed\n");
	}

	return ret_val;
}


#ifdef PFE_CFG_TARGET_OS_AUTOSAR
#define ETH_43_PFE_STOP_SEC_CODE
#include "Eth_43_PFE_MemMap.h"
#endif /* PFE_CFG_TARGET_OS_AUTOSAR */

