/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */

#include <api_oam_sdk/common/error_codes/inc/mr_error_codes.h>
#include <api_oam_sdk/common/registers/inc/mr_registers.h>
#include <api_oam_sdk/common/types/inc/mr_api_types.h>
#include <api_oam_sdk/continuity_check/inc/cc.h>
#include <api_oam_sdk/metroam_hal/inc/mr_hal.h>
#include <api_oam_sdk/rmep_db/src/rmep_types.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <api_oam_sdk/table_access/inc/table_access_constants.h>
#include <api_oam_sdk/table_mem_alloc/inc/table_mem_alloc.h>
#include <stdio.h>
#include <string.h>

#include "egl_assert.h"
#include "egl_log.h"

// Global Variables
const uint32_t	g_rxSMTimerValues[8] = {0,0xD,0x29,0x1A7,0x1091,0xA5B7,0x3E250,0x26D72D};

static uint16_t		log_handle				= 0;						// handle used to log messages for this module

// -----------------------------------------------------------------------------------------------------
//
// Initialise the g_lmRxTblIdx table to null values.
// The g_lmRxTblIdx table is used to record the rxLMepNum Table entry index used for each LM. It is
// indexed by lmNum
// =====================================================================================================
int tableInit(void)
{
	return(NO_ERR);
}

/*
 * This function should be called at device initialisation. Its primary
 * function is to initialise logging for this module
 */
void tableConfigInit(void)
{
	log_init();

	// Get a log handle if we don't already have one
	if (log_handle == 0)
		log_handle = log_register("TBL_CONFIG");

	tableInit();
}

/*
 * This function is used to change the lowest level severity to log for
 * this module. Values of eLOG_LEVEL_DEBUG (lowest) to eLOG_LEVEL_ERROR
 * can be selected. Levels with greater severity than eLOG_LEVEL_ERROR
 * will default to eLOG_LEVEL_ERROR since severity of eLOG_LEVEL_ERROR
 * and higher are non maskable.
 */
void setTableConfigLogLevel(log_level_t logLevel)
{
	setModuleLogLevel(log_handle,logLevel);
}

int disableCcmTx(uint16_t lMepNum)
{
	int 				retVal;
	txLMepNumRamEntry_t	txLMepNumRamEntry;

	// TODO: Need to lock access to this table before accessing it
	// Disable the TX side...
	//-----------------------
	// Set the CCM Entry to Invalid
	if ((retVal = readTxLMepNumRam(lMepNum,&txLMepNumRamEntry)))
		return(retVal);
	txLMepNumRamEntry.ccmEn = disabled;
	if ((retVal = writeTxLMepNumRam(lMepNum,&txLMepNumRamEntry)))
		return(retVal);
	return(NO_ERR);
}

int deleteCcm(uint16_t lMepNum)
{
	int 				retVal;
	txLMepNumRamEntry_t	txLMepNumRamEntry;

	// TODO: Need to lock access to this table before accessing it
	// Disable the TX side...
	//-----------------------
	// Set the CCM Entry to Invalid
	if ((retVal = readTxLMepNumRam(lMepNum,&txLMepNumRamEntry)))
		return(retVal);
	txLMepNumRamEntry.ccmEn = disabled;
	txLMepNumRamEntry.ccmInt = intervalInvalid;
	txLMepNumRamEntry.iFace = wanPort;			// set to a default
	// if AIS or LCK are not enabled then set the entry as invalid
	if (!(txLMepNumRamEntry.aisEn || txLMepNumRamEntry.lckEn))
		txLMepNumRamEntry.ccmValid = invalid;
	if ((retVal = writeTxLMepNumRam(lMepNum,&txLMepNumRamEntry)))
		return(retVal);
	return(NO_ERR);
}

bool isCcmTxEnabled(uint16_t lMepNum)
{
	txLMepNumRamEntry_t	txLMepNumRamEntry;

	readTxLMepNumRam(lMepNum,&txLMepNumRamEntry);

	if (txLMepNumRamEntry.ccmEn == enable)
	{
		return(true);
	}
	return (false);
}

int enableCcmTx(uint16_t lMepNum)
{
	txLMepNumRamEntry_t	txLMepNumRamEntry;

	// TODO: Need to lock access to this table before accessing it?
	// Disable the TX side...
	//-----------------------
	readTxLMepNumRam(lMepNum,&txLMepNumRamEntry);

	// only enable it if not already enabled
	if (txLMepNumRamEntry.ccmEn != enable)
	{
		txLMepNumRamEntry.ccmEn = enable;
		// update the type field and pmNum...
#ifdef DEBUG_PRINT_L2
		ts_printf("enableCcmTx txLMepNumRamEntry.ccmEn != enable lMepNum = %d\n\r",lMepNum);
#endif
		writeTxLMepNumRam(lMepNum,&txLMepNumRamEntry);
	}
	return(NO_ERR);
}

int disableXConnSM(uint16_t lMepNum)
{
	xConnSMEntry_t 		xConnSMEntry;

	// Turn off the xConn State machine
	readXConnSMRam(lMepNum, &xConnSMEntry);

	xConnSMEntry.enable = disable;
	xConnSMEntry.state	= IDLE;
	xConnSMEntry.timer = 0;

	writeXConnSMRam(lMepNum, &xConnSMEntry);
	return(NO_ERR);
}

int enableXConnSM(uint16_t lMepNum, ccmInterval_t ccmInterval)
{
	xConnSMEntry_t 		xConnSMEntry;

	readXConnSMRam(lMepNum, &xConnSMEntry);

	// only enable it if not already enabled
	if (xConnSMEntry.enable != enable)
	{
		// Turn on the xConn State machine
		xConnSMEntry.enable = enable;
		xConnSMEntry.state = IDLE;
		xConnSMEntry.timer = g_rxSMTimerValues[ccmInterval];

		writeXConnSMRam(lMepNum, &xConnSMEntry);
	}
	return(NO_ERR);
}

int disableRMepErrSM(uint16_t lMepNum)
{
	rMepErrSMEntry_t	rMepErrSMEntry;

	// Turn off the RMepErr State machine
	readRMepErrSMRam(lMepNum, &rMepErrSMEntry);

	rMepErrSMEntry.enable = disable;
	rMepErrSMEntry.state = IDLE;
	rMepErrSMEntry.timer = 0;

	writeRMepErrSMRam(lMepNum,&rMepErrSMEntry);
	return(NO_ERR);
}

int enableRMepErrSM(uint16_t lMepNum, ccmInterval_t ccmInterval)
{
	rMepErrSMEntry_t	rMepErrSMEntry;

	readRMepErrSMRam(lMepNum,&rMepErrSMEntry);

	// only enable it if not already enabled
	if (rMepErrSMEntry.enable != enable)
	{
		// Turn on the RMepErr State machine
		rMepErrSMEntry.enable = enable;
		rMepErrSMEntry.state = IDLE;
		rMepErrSMEntry.timer = g_rxSMTimerValues[ccmInterval];
		writeRMepErrSMRam(lMepNum,&rMepErrSMEntry);
	}
	return(NO_ERR);
}

int disableRMepSM(uint16_t rMepNum)
{
	rMepSMEntry_t 		rMepSMEntry;

	// Disable the RX side...
	//-----------------------
	// Turn off the rMep State machine
	readRMepSMRam(rMepNum, &rMepSMEntry);

	rMepSMEntry.enable 	= disable;
	rMepSMEntry.state	= SM_IDLE;

	writeRMepSMRam(rMepNum, &rMepSMEntry);
	return(NO_ERR);
}

int enableRMepSM(uint16_t rMepNum, ccmInterval_t ccmInterval)
{
	rMepSMEntry_t 		rMepSMEntry;

	readRMepSMRam(rMepNum, &rMepSMEntry);

	// only enable it if not already enabled
	if (rMepSMEntry.enable != enable)
	{
		// Turn on the rMep State machine
		// Initialise the remote MEP SM
		rMepSMEntry.state 		= SM_IDLE;
		rMepSMEntry.goodCount	= 0;
		rMepSMEntry.enable 		= enable;
		rMepSMEntry.rdi			= CLR;
		rMepSMEntry.timer		= g_rxSMTimerValues[ccmInterval];

		writeRMepSMRam(rMepNum, &rMepSMEntry);
	}
	return(NO_ERR);
}

uint16_t getSTagId(void)
{
	uint32_t sAndCTag;

	mrHalRegRead(TPID1_REG_ADDR,&sAndCTag);
	return((uint16_t)((sAndCTag & TPID1_REG_STAG_MASK) >> TPID1_REG_STAG_OFFSET));
}

uint16_t getCTagId(void)
{
	uint32_t sAndCTag;

	mrHalRegRead(TPID1_REG_ADDR,&sAndCTag);
	return((uint16_t)((sAndCTag & TPID1_REG_CTAG_MASK) >> TPID1_REG_CTAG_OFFSET));
}

uint16_t getCfmTypeId(void)
{
	uint32_t cfmType;

	mrHalRegRead(CFM_DEFN1_REG_ADDR,&cfmType);
	return((uint16_t)((cfmType & CFM_DEFN1_REG_ETYPE_MASK) >> CFM_DEFN1_REG_ETYPE_OFFSET));
}

// -----------------------------------------------------------------------------------------------------
// This function is called when a new remote MEP is being added to the system. It carries
// out the task of adding a new remote MEP (identified by it MEPID) to the rMep Table.
// The table is populated such that a cluster of MAX_RMEPS_PER_LM remote MEPs can be
// associated with each local MEP. An empty entry is identified by a rMepID value = 0
// =====================================================================================================
static int addMep2RMepTbl(lMepNum_t lMepNum, uint16_t rMepID, rMepNum_t rMepNum,rMepTblIdx_t *rMepTblIdx)
{
	uint8_t		k;
	uint16_t 	rdRMepID;
	rMepNum_t	rdRMepNum;
	int			retVal;

	// Search the cluster associated with this localMep for assigned remote MEPs
	for (k=0;k<MAX_RMEPS_PER_LM;k++)
	{
		if ((retVal = readRMepTbl((MAX_RMEPS_PER_LM * lMepNum) + k,&rdRMepID,&rdRMepNum)))
			return(retVal);

		if (rdRMepID == 0)
			break;
	}

	if (k == MAX_RMEPS_PER_LM)
	{
		// no free entry was found!
		DO_LOG_INFO(log_handle,"rMep Table Full (lMepNum=%d)",lMepNum);
		return(ERR_HW_RAM_FULL);
	}

	*rMepTblIdx = (MAX_RMEPS_PER_LM * lMepNum) + k;
	if ((retVal = writeRMepTbl(*rMepTblIdx,rMepID,rMepNum)))
		return(retVal);

	DO_LOG_DEBUG(log_handle,"addMep2RMepTbl: RMEP (ID=%d) added @index %d",rMepID,*rMepTblIdx);
	return(NO_ERR);
}

// -----------------------------------------------------------------------------------------------------
// This function is called to add a new local MEP entry into the local Mep (lMep) Table.
// This is a simple linear table so a free space is requested from the scavenger and
// if one is available it will be populated with the new entry and the index of the
// entry returned. If no more space is available the function returns with an error.
// =====================================================================================================
int addLMepOld(lMepNum_t lMepNum, uint16_t mepID)
{
	int			retVal;
	uint32_t	data;

#if 0
	*newLMepIdx = MR_NULL_LM_NUM;
	// request space for a new local MEP in the Local MEP Table...
	if ((retVal = requestNextFreeLMepTblIdx(maNum,newLMepIdx)))
		return(retVal);					// This can happen if no more space is available
#endif

	if ((retVal = readRawLMepTbl((lMepNum * LMEP_ENTRY_SIZE), &data)))
		return(retVal);

	// preserve the MD &MA nums in the upper 16 bits and the MA interval
	data &= LMEP_RAM_MD_NUM_MASK | LMEP_RAM_MA_NUM_MASK | LMEP_RAM_INTERVAL_MASK;
	// Or in the mepID 
	data |= ((((uint32_t) mepID) << LMEP_RAM_MEPID_OFFSET) & LMEP_RAM_MEPID_MASK);

	if ((retVal = writeRawLMepTbl((lMepNum * LMEP_ENTRY_SIZE), data)))
		return(retVal);					// This should never happen!
#if 0
	// After successfully writing to the new table entry
	// inform the scavenger that the location was accepted.
	acceptNextFreeLMepTblIdx(*newLMepIdx);
#endif
	DO_LOG_DEBUG(log_handle,"addLMep - Added a NEW LMep to lMep Table @index %d",lMepNum);
	return(NO_ERR);
}

// -----------------------------------------------------------------------------------------------------
// This function is used to add a transmit(tx) CCM svc to a local MEP.
// If there is insufficient memory the function will return a non-zero error number.
// =====================================================================================================
int addLMep2TxNumRam(uint16_t txLMepNum, interval_t interval, iFace_t iFace)
{
	int					retVal;
	txLMepNumRamEntry_t	txLMepNumRamEntry;

	txLMepNumRamEntry.aisEn		= disabled;
	txLMepNumRamEntry.lckEn		= disabled;
	txLMepNumRamEntry.ccmValid	= valid;
	txLMepNumRamEntry.ccmEn		= disabled;
	txLMepNumRamEntry.ccmInt	= interval;
	txLMepNumRamEntry.iFace		= iFace;
	if ((retVal = writeTxLMepNumRam(txLMepNum,&txLMepNumRamEntry)))
	{
	    ts_printf("addLMep2TxNumRam: error %d",retVal);
		return(retVal);
	}
#ifdef DEBUG_PRINT_L2
	//DO_LOG_DEBUG(log_handle,"addLMep2TxNumRam: Tx CCM added @index %d",txLMepNum);
	ts_printf("addLMep2TxNumRam: Tx CCM added @index %d",txLMepNum);
#endif
	return(NO_ERR);
}

// -----------------------------------------------------------------------------------------------------
// This function carries out all the tasks necessary to add a new local MEP the HW tables without
// interfering with any existing configuration.
// If the function is successful it will return a value of 0 and a new lMepNum is assigned.
// If the function fails for any reason it returns a meaningful non-zero error code and the lMepNum
// is invalid.
// =====================================================================================================
int addLocalMep(lMepNum_t lMepNum, uint16_t lMepId, uint16_t vLan, uint8_t mdLvl, uint8_t interval, iFace_t iFace, simpleMaid_t maid, uint16_t *rxLmepTableAddress)
{
	uint32_t	rdData;
	uint32_t	newLvlMask;
	uint8_t		k;
	int			retVal;

	rxLMepTblIdx_t		newRxLMepTblIdx;
	vlanTblEntry_t		vlanTblEntry;
	rxLMepNumRamEntry_t	rxLMepRamEntry;

	// To add a new Local MEP one must populate all entries necessary to access that LMep for both
	// Tx and RX. For Rx this includes the rxLMepNumRam and the lMep Table.
	// The Tx side is only populated when a CCM service is added!
#ifdef DEBUG_PRINT_L1_1
	DO_LOG_DEBUG(log_handle,"addLocalMep:Adding New LMep MEPID=%d, vLan=%d, mdLevel=%d",lMepId,vLan,mdLvl);
	ts_printf("addLocalMep:Adding New LMep MEPID=%d, vLan=%d, mdLevel=%d",lMepId,vLan,mdLvl);
#endif
	// First check if the vLan has an entry in the vLanPort
	readVlanTbl(vLan, &vlanTblEntry,iFace);

	if (!vlanTblEntry.cfmValid)
	{
		// This Vlan was not in use, so a new rxLMepNum table entry will be needed

		// request a new rxLmepNum table entry....this also tests that there is sufficient space left
		if ((retVal = requestNextFreeRxLMepRAMIdx(&newRxLMepTblIdx,iFace)))
		{
		   // ts_printf("addLocalMep:requestNextFreeRxLMepRAMIdx \n\r");
		    return(retVal);
		}


		// set the cfmPntr to this new rxLMepRam index for use later.
		vlanTblEntry.cfmIdx = newRxLMepTblIdx;		// !HW multiplies this index by 8 and adds mdLvl
	}
	else
	{
		// There is at least 1 lMep here already
		// Read the rxLMepNumRam at the level provided to see if it is free
		readRawRxLMepNumRamLower((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + mdLvl, &rdData, iFace);

		if (((rdData & RX_LMEP_MP_TYPE_MASK) == mp_isAMep) || ((rdData & RX_LMEP_MP_TYPE_MASK) == mp_isAMip))
		{
			// Report an attempt to add a duplicate LMep!!
			DO_LOG_INFO(log_handle,"addLocalMep:MEP or MIP already exists MEPID=%d, vLan=%d, mdLevel=%d",lMepId,vLan,mdLvl);
			ts_printf("addLocalMep:MEP or MIP already exists MEPID=%d, vLan=%d, mdLevel=%d",lMepId,vLan,mdLvl);

			return(ERR_DUPLICATE_ENTRY);
		}
	}

	// create the new Local MepTable Entry
	lMepTblEntry_t lMepTblEntry;

	lMepTblEntry.interval = interval;
	lMepTblEntry.lMepID = lMepId;
	memcpy(lMepTblEntry.maid,maid,sizeof(simpleMaid_t));
	lMepTblEntry.maNum	= 0;		// this entry is now redundant
	lMepTblEntry.mdNum	= 0;		// this entry is now redundant
	writeLMepTbl(lMepNum, &lMepTblEntry);

	// We have confirmed that a New Mep will fit and isn't duplicated
	if (!vlanTblEntry.cfmValid)
	{
		// There was previously no MEPs on this Vlan so update the entry
		vlanTblEntry.cfmValid = valid;
		// Leave all other values unchanged and write them back
		writeVlanTbl(vLan, &vlanTblEntry, iFace);
	}

	// Back in the rxLMep table...
	// Read the rxLMepNumRam lower entry so the level mask field can be updated
	readRawRxLMepNumRamLower((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + mdLvl, &rdData, iFace);

	// The Level Mask will need to be updated on all MEPs on this vlan
	newLvlMask = 0x00001 << mdLvl;		// This mask is used again later for the other levels

	// update the rxLMepNum entry
	rxLMepRamEntry.rxLMepNum	= lMepNum;
	rxLMepRamEntry.idmPmNum		= RX_LMEP_NUM_RAM_IDM_PM_NUM_MASK >> RX_LMEP_NUM_RAM_IDM_PM_NUM_OFFSET; // All 1's means invalid - ignored by HW
	rxLMepRamEntry.idmPrio		= 0;
	rxLMepRamEntry.mpType		= mp_isAMep;
	rxLMepRamEntry.levelMask	= ((rdData & RX_LMEP_NUM_LVL_MASK_MASK) >> RX_LMEP_NUM_LVL_MASK_OFFSET) | newLvlMask;
	writeRxLMepNumRam((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + mdLvl, &rxLMepRamEntry, iFace);
	// Pass back this level so it cane be stored in the MEP context for reverse lookup.
	*rxLmepTableAddress 		= (vlanTblEntry.cfmIdx * MR_LVL_ROWS) + mdLvl;

	// Update all the other level masks on this Vlan - needed by HW for XConn reporting!
	// First adjust the level mask to allow raw read and writes to the RAM
	newLvlMask = newLvlMask << RX_LMEP_NUM_LVL_MASK_OFFSET;
	for (k=0;k<8;k++)
	{
		if (k != mdLvl)
		{
			// Read the rxLMepNumRam at the level provided to see if it is free
			readRawRxLMepNumRamLower((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + k, &rdData, iFace);

			// Append the new level bit to the current mask
			rdData |= newLvlMask;

			// Write the updated level Mask to the rxLMepNumRam at the level provided
			writeRawRxLMepNumRamLower((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + k, rdData, iFace);
		}
	}

	// If there were no errors updating the rxLmepTable then accept the table
	// There is no harm in re-accepting it even if it was not newly allocated!
	acceptNextFreeRxLMepRAMIdx(vlanTblEntry.cfmIdx, iFace);

	// Note: code execution will only get to this point if a valid new LMep is being added and the
	// Receive side tables have been configured without error.
	// Transmit side tables are only configured when a CCM service is added.

	disableRMepErrSM(lMepNum);
	disableXConnSM(lMepNum);

	// Initialise the sequence error count for this lMep to 0
	writeSeqErr(lMepNum,0);

	cfmTblEntry_t cfmTblEntry;
	// Read the EVC table on this MEPs port and extract the highest Rx Level
	readCfmTbl(vlanTblEntry.cfmIdx, &cfmTblEntry, iFace);
	// If the Rx highest level valid bit is not set OR the current MEP level is higher than the rx highest level
	// the highest level for rx on this port and Tx on the opposite port must be updated
	if (!(cfmTblEntry.rxHighestMepLevelVal) || (mdLvl > cfmTblEntry.rxHighestMepLevel))
	{
		cfmTblEntry.rxHighestMepLevel		= mdLvl;
		cfmTblEntry.rxHighestMepLevelVal	= true;
		writeCfmTbl(vlanTblEntry.cfmIdx, &cfmTblEntry, iFace);
		// read and updated the tx highest level in the opposite port to this MEP
		readCfmTbl(vlanTblEntry.cfmIdx, &cfmTblEntry, (iFace == wanPort?lanPort:wanPort));
		cfmTblEntry.txHighestMepLevel		= mdLvl;
		cfmTblEntry.txHighestMepLevelVal	= true;
		writeCfmTbl(vlanTblEntry.cfmIdx, &cfmTblEntry, (iFace == wanPort?lanPort:wanPort));
	}
#ifdef DEBUG_PRINT_L1_1
	//DO_LOG_DEBUG(log_handle,"addLocalMep - Successfully added LMep MEPID=%d, vLan=%d, mdLevel=%d @%d",lMepId,vLan,mdLvl,lMepNum);
	ts_printf("addLocalMep - Successfully added LMep MEPID=%d, vLan=%d, mdLevel=%d @%d",lMepId,vLan,mdLvl,lMepNum);
#endif
#ifdef DEBUG_PRINT_L1
    //DO_LOG_DEBUG(log_handle,"addLocalMep - Successfully added LMep MEPID=%d, vLan=%d, mdLevel=%d @%d",lMepId,vLan,mdLvl,lMepNum);
    ts_printf("addLocalMep - Successfully added Local MEP");
#endif
    return(NO_ERR);
}

// -----------------------------------------------------------------------------------------------------
// This function is called to add a remote MEP to an existing Local MEP.
// It will configure all the necessary HW tables to add the remote MEP.
// If the function is successful it will return a zero value and newRMepNum is updated with the
// new remote MEP number.
// If the function fails it will return a meaningful non-zero value and newRMepNum is invalid.
// =====================================================================================================
int addRemoteMep(lMepNum_t lMepNum, uint16_t lMepId, uint16_t rMepID, uint8_t interval, uint8_t SA[6], rMepNum_t rMepNum)
{
	uint32_t	rdData;
	int			retVal;

	rMepTblIdx_t		rMepTblIdx;
	rMepDbTblEntry_t 	rMepDbTblEntry;
	rMepSMEntry_t 		rMepSMEntry;

	DO_LOG_DEBUG(log_handle,"addRemoteMep:Adding New rMep MEPID=%d, lMepId=%d, interval=%d",rMepID,lMepId,interval);

	// To add a new Remote MEP (RMep) a LMep must already be present. So before proceeding
	// confirm that the LMep is present using the lMepNum provided.

	// Just read the 1st dword, no need to read MAID!
	if ((retVal = readRawLMepTbl(lMepNum * LMEP_ENTRY_SIZE, &rdData)))
		return(retVal);				// this should never happen

	if (((rdData & LMEP_RAM_MEPID_MASK) >> LMEP_RAM_MEPID_OFFSET) != lMepId)
	{
		DO_LOG_ERROR(log_handle,"addRemoteMep:lMepId (%d) in lMepTable (@%d) doesn't match expected lMepId (%d)",(rdData & LMEP_RAM_MEPID_MASK) >> LMEP_RAM_MEPID_OFFSET,lMepNum,lMepId);
		return(-1);
		//ASSERT(0,ERR_TABLE_MISCONFIG);
	}

	// Initialise the Database entry
	rMepDbTblEntry.remoteMepID = rMepID;
	rMepDbTblEntry.seqNum = 0;

	// Write the initial TLV bytes to 0's
	memset(rMepDbTblEntry.tlv,0,sizeof(rMepDbTblEntry.tlv));

	memset(rMepDbTblEntry.SA, 0, 6);
	rMepDbTblEntry.rMepIdx = lMepNum * MAX_RMEPS_PER_LM; // initial value only...must be updated later
#if 0 // REMOVED FOR V2
	for (i=0;i<8;i++)
	{
		rMepDbTblEntry.lc[i].lcNum = MR_NULL_LC_NUM;
		rMepDbTblEntry.lc[i].lcValid = MR_INVALID;
	}
#endif
	//rMepDbTblEntry.soTLV[51];
	if ((retVal = writeRMepDB(rMepNum,&rMepDbTblEntry)))
		return(retVal);				// This should never happen

	// Add a new rMep entry in the rMepTbl list for this local MEP
	if ((retVal = addMep2RMepTbl(lMepNum,rMepID,rMepNum,&rMepTblIdx)))
		return(retVal);

	// Update the rMepTblIdx value in the DB. This is needed for services and MEP removal
	if ((retVal = writeRMepIdxInRMepDB(rMepNum,rMepTblIdx)))
		return(retVal);
	
	rMepSMEntry.state 		= SM_IDLE;
	rMepSMEntry.timer 		= g_rxSMTimerValues[interval];
	rMepSMEntry.goodCount 	= 0;
	rMepSMEntry.enable 		= disable;
	rMepSMEntry.rdi 		= CLR;
	if ((retVal = writeRMepSMRam(rMepNum,&rMepSMEntry)))
		return(retVal);				// This should never happen

	DO_LOG_DEBUG(log_handle,"addRemoteMep:Successfully added rMep MEPID=%d, lMepId=%d, interval=%d @%d",rMepID,lMepId,interval,rMepNum);
	return(NO_ERR);
}

// -----------------------------------------------------------------------------------------------------
// This function is used to remove a remote MEP identified by its MEP ID from the HW tables.
// If the function is successful it returns a value of zero. If it fails it returns a
// meaningful non-zero value.
// =====================================================================================================
int deleteRemoteMep(rMepNum_t rMepNum)
{
	rMepSMEntry_t	rMepSMEntry;
	int				retVal;
	uint16_t		rMepTblIdx;

	DO_LOG_DEBUG(log_handle,"deleteRemoteMep: deleting rMepNum = %d ",rMepNum);

	// Read the rMepTblIdx entry in the rMepDB
	if ((retVal = readRMepIdxInRMepDB(rMepNum, &rMepTblIdx)))
		return(retVal);					// This should never happen

	// Clear the rMep entry from the rMepTable (Table entry is 'clear' if MEPID=0)
	if ((retVal = writeRMepTbl(rMepTblIdx, NULL_MEPID,0)))
		return(retVal);					// This should never happen

	rMepSMEntry.state 		= SM_IDLE;
	rMepSMEntry.timer 		= 0;
	rMepSMEntry.goodCount 	= 0;
	rMepSMEntry.enable 		= disable;
	rMepSMEntry.rdi 		= 0;
	if ((retVal = writeRMepSMRam(rMepNum , &rMepSMEntry)))
		return(retVal);					// This should never happen

	// Release the entry from the database RAM
	releaseRMepDBIdx(rMepNum);

	DO_LOG_DEBUG(log_handle,"deleteRemoteMep: Successfully deleted rMepNum = %d ",rMepNum);
	return(NO_ERR);
}

// -----------------------------------------------------------------------------------------------------
// This function is called to delete a local MEP. It will remove all HW table entries in such a way
// as to not interfere with the HW.
// Deleting a local MEP involves:
// remove the lMepNum from the txLMepNum table and deallocate the entry
// deallocating all the rMepDB entries associated with the supplied lMepNum and disabling the rMep SM for each
// deallocating the lMep entry associated with the supplied lMepNum
// Disabling the xConnErr and rMepErr SMs associated with the supplied lMepNum
// remove the lMep Number from the rxLMepNum list and if necessary create a new list
// clear the dir/level mask bit and if necessary deallocate the rxLMepNum list or update the entry to point to a new list
// if necessary clear the port Mask bit and if necessary deallocate the dir/level list or update the entry to point to a new list
// If it is successful it will return a value of zero. If there is a problem it will return a non-zero
// error code.
// =====================================================================================================
int deleteLocalMep(
	uint16_t 		vLan,
	uint8_t 		mdLvl,
	uint16_t 		lMepNum,
	uint16_t		lMepId,
	iFace_t			iFace)
{

	uint32_t			rdData;
	vlanTblEntry_t 		vlanTblEntry;
	rxLMepNumRamEntry_t	rxLMepNumRamEntry;
	uint32_t			clrLvlMask;
	uint8_t				k;

	DO_LOG_DEBUG(log_handle,"deleteLocalMep: Deleting vLan = 0x%X, mdLevel = %d, lMepNum = %d, lMepId = %X",vLan,mdLvl,lMepNum,lMepId);

	readVlanTbl(vLan, &vlanTblEntry,iFace);

	if (!vlanTblEntry.cfmValid)
	{
		DO_LOG_ERROR(log_handle,"deleteLocalMep: Vlan (0x%X) entry has no MEPs!\n",vLan);
	}

	// Read the entry from the rxLMepNumRam list
	readRxLMepNumRam((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + mdLvl, &rxLMepNumRamEntry, iFace);

	// confirm that the lMepNum in the list entry matches the one provided
	if (rxLMepNumRamEntry.rxLMepNum != lMepNum)
	{
		// The lMepNums do not match so there is a problem!
		DO_LOG_ERROR(log_handle,"deleteLocalMep: lMepNum(%d) found @%d in rxLMepNumTable + level=%d did not match expected value (%d)",rxLMepNumRamEntry.rxLMepNum,vlanTblEntry.cfmIdx,mdLvl,lMepNum);
		return(ERR_TABLE_MISCONFIG);
	}

	// Basic checks have been done so continue deleting the Rx side Local MEP entries...

	// disable the xConn and rMepErr State machines
	disableXConnSM(lMepNum);
	disableRMepErrSM(lMepNum);

	// Clear the current level bit from the mask and apply the change to all entries in the table
	clrLvlMask = ~(0x00001 << (mdLvl + RX_LMEP_NUM_LVL_MASK_OFFSET));
	for (k=0;k<8;k++)
	{
		if (k != mdLvl)
		{
			// Read the rxLMepNumRam at the level provided to see if it is free
			readRawRxLMepNumRamLower((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + k, &rdData, iFace);

			// Clear the appropriate level bit in the level mask
			rdData &= clrLvlMask;

			// Write the updated level Mask to the rxLMepNumRam at the level provided
			writeRawRxLMepNumRamLower((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + k, rdData, iFace);
		}
	}

	rdData &= RX_LMEP_NUM_LVL_MASK_MASK;  // clear everything but the level mask
	// Write the updated level Mask to the rxLMepNumRam at the level provided
	writeRawRxLMepNumRamLower((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + mdLvl, rdData, iFace);


	// reset the contents of the upper portion of the rxLMepNum entry to defaults
	writeRawRxLMepNumRamUpper((vlanTblEntry.cfmIdx * MR_LVL_ROWS) + mdLvl, RX_LMEP_NUM_RAM_IDM_PM_NUM_MASK | RX_LMEP_NUM_RAM_LMEP_NUM_MASK, iFace);

	// *** NEW for V2 features....
	// Generate the basic level mask
	mdLevel_t	nextHighestLevel;
	rdData = (rdData & RX_LMEP_NUM_LVL_MASK_MASK) >> RX_LMEP_NUM_LVL_MASK_OFFSET;
	nextHighestLevel = level0;
	for (k=0;k<MR_LVL_ROWS;k++)
	{
		if (rdData & (0x1 << k))
		{
			nextHighestLevel = k;
		}
	}

	cfmTblEntry_t cfmTblEntry;
	// Read the EVC table on this MEPs port and extract the highest Rx Level
	readCfmTbl(vlanTblEntry.cfmIdx, &cfmTblEntry, iFace);
	// If the Rx highest level valid bit is not set OR the current MEP level is higher than the rx highest level
	// the highest level for rx on this port and Tx on the opposite port must be updated
	if ((cfmTblEntry.rxHighestMepLevelVal) && (mdLvl == cfmTblEntry.rxHighestMepLevel))
	{
		if (rdData == 0)
		{
			// If the level mask is 0 then there are no MEP left!
			cfmTblEntry.rxHighestMepLevel		= level0;
			cfmTblEntry.rxHighestMepLevelVal	= false;
		}
		else
		{
			cfmTblEntry.rxHighestMepLevel		= nextHighestLevel;
			cfmTblEntry.rxHighestMepLevelVal	= true;
		}
		writeCfmTbl(vlanTblEntry.cfmIdx, &cfmTblEntry, iFace);

		// read and updated the tx highest level in the opposite port to this MEP
		readCfmTbl(vlanTblEntry.cfmIdx, &cfmTblEntry, (iFace == wanPort?lanPort:wanPort));
		if (rdData == 0)
		{
			cfmTblEntry.txHighestMepLevel		= level0;
			cfmTblEntry.txHighestMepLevelVal	= false;
		}
		else
		{
			cfmTblEntry.txHighestMepLevel		= nextHighestLevel;
			cfmTblEntry.txHighestMepLevelVal	= true;
		}
		writeCfmTbl(vlanTblEntry.cfmIdx, &cfmTblEntry, (iFace == wanPort?lanPort:wanPort));

	}
	// *** END of new code for V2.

	// rdData holds only the new level Mask... test is it = 0
	if (rdData == 0)
	{
		// There are no other MEPs on this Vlan so the cluster can be deallocated
		releaseRxLMepRAMIdx(vlanTblEntry.cfmIdx, iFace);

		// The Vlan table entry can be set invalid
		vlanTblEntry.cfmValid = invalid;
		vlanTblEntry.vlanFilt = false;
		writeVlanTbl(vLan, &vlanTblEntry, iFace);
	}

	DO_LOG_DEBUG(log_handle, "deleteLocalMep: Successfully deleted lMep with lMepNum = %d", lMepNum);
	return(NO_ERR);
}

int changeMepCcmInterval(lMepNum_t lMepNum, ccmInterval_t newCcmInterval)
{
	uint16_t lMepID;
	ccmInterval_t oldInterval;
	mdNum_t mdNum;
	maNum_t maNum;
	txLMepNumRamEntry_t txLMepNumRamEntry;

	// if the interval of a MEP must be updated then the tx template,
	// the Local MEP table, the TxLMepNum table and all S/M timers must be updated...

	// Update the Local MEP table entry...
	read1stDwdInLMepTbl(lMepNum, &lMepID, &oldInterval, &mdNum,&maNum);
	write1stDwdInLMepTbl(lMepNum, lMepID, newCcmInterval, mdNum, maNum);

	// Update the CCM Tx Table...
	readTxLMepNumRam(lMepNum, &txLMepNumRamEntry);
	txLMepNumRamEntry.ccmInt = newCcmInterval;
	writeTxLMepNumRam(lMepNum, &txLMepNumRamEntry);

	// Update the CCM Template...
	writeCcmIntervalInTxTempl(lMepNum, newCcmInterval);

	// Update the State Machine timers...
	// These get updated by disabling, then re-enabling the Admin State of the MEP, which
	// should be done by a higher level calling function!
	return (NO_ERR);
}
