/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */
#include <api_oam_sdk/loopback/src/lb_support.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/system/inc/system.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <string.h>

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

static bool lbInitialised = false;
static lbMgnt_t		lbMgntArray[MAX_LB_CHANNELS];
static lbEntry_t	lbInfoArray[MAX_NUM_MEPS];
static osMutex_t	lbListLock				= NULL;
static uint16_t		log_handle				= 0;		// handle used to log messages for this module

extern lbSvc_t	lbSvcList[MAX_LB_CHANNELS_FOR_Y1564];

#define LOCK_LB_LIST(timeout)	lockMDBranch(0,timeout)	//osRecursiveMutexTake(lbListLock,timeout)
#define UNLOCK_LB_LIST()		unlockMDBranch(0)		//osRecursiveMutexGive(lbListLock)

#define IPG_BYTES	20

/*
 * 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 setLBLogLevel(log_level_t logLevel)
{
	setModuleLogLevel(log_handle,logLevel);
}

/*
 * @brief	This sets the lbEntry values to their default
 *
 * In most cases this should only be done with mepLock enabled
 * to ensure thread safe operation
 */
static void setLbDefaults(lbEntry_t *lbEntry)
{
	memset(lbEntry,0,sizeof(lbEntry_t));
	lbEntry->mepLbInfo.txLbmResultOk		= true;
	lbEntry->mepLbMefInfo._testTlvPattern	= tp_null;
	lbEntry->mepLbMefInfo.timeout			= 5000;		// 5 seconds
	lbEntry->mepLbMefInfo.frameSize			= 64;
	lbEntry->mepLbMefInfo.interval			= 1000;		// 1 second
	lbEntry->mepLbMefInfo._dataPattern		= zeroPattern;
}

/*
 * @brief Clears the Lb list
 *
 * This should be called to initialise the Lb list.
 * Any LB info on the list will be lost.
 */
static void clearLbList(void)
{
	lMepNum_t	k;

	// Initialise all entries in the MA array to defaults
	for (k = 0; k < MAX_NUM_MEPS; k++)
	{
		setLbDefaults(&lbInfoArray[k]);
	}

	for (k = 0; k < MAX_LB_CHANNELS; k++)
	{
		memset(&lbMgntArray[k],0,sizeof(lbMgnt_t));
	}
}

/*
 *
 */
void lbInit(void)
{
	uint8_t			k;
	lbmStoreEntry_t	lbmStoreEntry;

	log_init();	// Initialises the logging system if it's not already initialised

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

	// Create a mutex to protect the loopback list if we don't already have one
	if (lbListLock == NULL)
	{
		lbListLock = osRecursiveMutexCreate();
		ASSERT_NOT_NULL(lbListLock);
	}

	// If not already initialised clear the loopback list for first use.
	if (!lbInitialised)
	{
		// Set the default logging level to NOTICE
		setModuleLogLevel(log_handle, eLOG_LEVEL_NOTICE);

		// Initialise the LBM store counters to 0 - Used to count LBM received/LBRs transmitted
		lbmStoreEntry.counter = 0;
		lbmStoreEntry.lMepNum = INVALID_MEP_NUM;
		for (k=0;k<MAX_LB_LBM_STORES;k++)
		{
			writeLBRLMepNum(k,INVALID_MEP_NUM,0);
			writeLbmStore(k,&lbmStoreEntry,wanPort);
			writeLbmStore(k,&lbmStoreEntry,lanPort);
		}

		DO_LOG_DEBUG(log_handle,"Preparing the LB List for first use");
		clearLbList();
		lbInitialised = true;
	}
}

/*
 *
 */
static void lbSetMode(uint8_t lbChnl, lbMode_t lbMode, iFace_t iFace)
{
	uint32_t	data;

	// TODO : Update code to support more than 1 LB channel
	// Beware the channel will not be stopped if it is running when this function is called
	mrHalRegRead(LB_CONFIG_REG_ADDR,&data);
	// preserve all bits in reg excepts the mode bits
	data &= ~(LB_CONFIG_REG_BURST_MODE_EN_MASK | LB_CONFIG_REG_PORT_SEL_MASK);
	// mask in the new burst size value
	data |=  ((((uint32_t) lbMode) << LB_CONFIG_REG_BURST_MODE_EN_OFFSET) & LB_CONFIG_REG_BURST_MODE_EN_MASK) | (((uint32_t)iFace) << LB_CONFIG_REG_PORT_SEL_OFFSET);
	mrHalRegWrite(LB_CONFIG_REG_ADDR, data);
}

void _lbSetMode(uint8_t lbChnl, lbMode_t lbMode, iFace_t iFace)
{
	lbSetMode(lbChnl,lbMode,iFace);
}

/*
 * Max value of numBursts = MAX_LB_NUM_BURSTS (number of bursts to send)
 * Max value of pktsPerBurst = MAX_LB_PKTS_PER_BURSTS
 */
void _lbConfigureBurst(uint8_t lbChnl, uint16_t pktsPerBurst,uint16_t numBursts,uint32_t interBurstTime)
{
	uint32_t	data;

	// TODO : Update code to support more than 1 LB channel
	// Beware the channel will not be stopped if it is running when this function is called
	mrHalRegWrite(LB_BURST_SIZE_REG_ADDR, ((((uint32_t) pktsPerBurst) << LB_BURST_SIZE_OFFSET) & LB_BURST_SIZE_MASK));

	// generate the new register value with the burst count and inter-burst time
	data = ((((uint32_t) numBursts) << LB_BURST_REG_BURST_CNT_OFFSET) & LB_BURST_REG_BURST_CNT_MASK) |
			((interBurstTime << LB_BURST_REG_BURST_IBG_OFFSET) & LB_BURST_REG_BURST_IBG_MASK);
	mrHalRegWrite(LB_BURST_REG_ADDR, data);

}

static void lbClearTxStats(uint8_t lbChnl)
{
	uint32_t	data;

	// Clear the Tx Stats
	mrHalRegRead(LB_RESET_REG_ADDR,&data);

	// The Bits in RESET_LB_REG do not auto clear so pulse stats clear bit high...
	// Set bit
	mrHalRegWrite(LB_RESET_REG_ADDR,data | (LB_RESET_REG_STATS_CLR_MASK << (lbChnl)));	   // Reset Tx Stats on that channel
	// Clear bit
	mrHalRegWrite(LB_RESET_REG_ADDR,data & ~(LB_RESET_REG_STATS_CLR_MASK << (lbChnl)));	  // Reset Tx Stats on that channel

}


static void lbClearStats(uint8_t lbChnl, uint32_t seqNum)
{
	lbClearTxStats(lbChnl);
	clrLBRxStats(lbChnl, seqNum);
}

void _lbClearStats(uint8_t lbChnl, uint32_t seqNum, svcDir_t selDir)
{
	if (selDir == tx_only || selDir == both_tx_rx)
	{
		lbClearTxStats(lbChnl);
		writeSeqNumInTxTempl(lbChnl,seqNum,LB_TEMPL);
	}
	if (selDir == rx_only || selDir == both_tx_rx)
	{
		clrLBRxStats(lbChnl, seqNum);
	}
}

static void lbStart(uint8_t lbChnl)
{
	uint32_t	data;

	mrHalRegRead(LB_RESET_REG_ADDR,&data);

	// To Enable a LB Channel the disable bit for the channel must be toggled from Hi to Low...
	// Set the disable bit for the channel (leave clear stats alone)
	mrHalRegWrite(LB_RESET_REG_ADDR,data | (LB_RESET_REG_CHNL_DISABLE_MASK << (lbChnl)));

	// Clear the disable bit for the channel and ensure clear stats is not enabled (i.e. = 0)
	mrHalRegWrite(LB_RESET_REG_ADDR,data & ~((LB_RESET_REG_CHNL_DISABLE_MASK + LB_RESET_REG_STATS_CLR_MASK) << (lbChnl)));
}

static void lbStop(uint8_t lbChnl)
{
	uint32_t	data;

	mrHalRegRead(LB_RESET_REG_ADDR,&data);

	// Set the disable bit for the channel (leave clear stats alone)
	mrHalRegWrite(LB_RESET_REG_ADDR,data | (LB_RESET_REG_CHNL_DISABLE_MASK << (lbChnl)));
}

/*
 * @brief initialises a txTemplateEntry_t structure for use as a LBM transmit template
 */
void generateLBMTemplate(
		lMepNum_t			lMepNum,
		rMepNum_t			rMepNum,
		macAddress_t		destMacAddress,
		iFace_t				iFace,
		uint16_t			dataTlvLength,
		uint8_t				mdLevel,
		tagEntry_t			*innerTag,
		tagEntry_t			*outerTag,
		bool				multicast,
		txTemplateEntry_t	*txLBMTempl)
{
	uint16_t pktLen;

	// Calculate the packet length. Basic length is 23 bytes then there is the optional inner and outer tags
	// and the 'optional' data TLV. The HW actually always inserts a data TLV and sizes it to pad the packet
	// up to the required packet size. Here we must do the calculation in reverse, work out the packet size
	// based on the data TLV size...The pktLen value includes the TLV (i.e. pktLen=80 => 76 + 4crc)
	pktLen = 23 + (dataTlvLength == 0 ? 0 : (3 + dataTlvLength)) + (innerTag->present ? 4 : 0) + (outerTag->present ? 4 : 0);
	if (pktLen > MAX_LBM_PKT_SIZE)
	{
		pktLen = MAX_LBM_PKT_SIZE;
	}

	if (multicast)
	{
		// If the muticast bit is set them the DA must be set to the
		// CCM group multicast MAC and the appropriate level set
		txLBMTempl->DA[0] = 0x01;
		txLBMTempl->DA[1] = 0x80;
		txLBMTempl->DA[2] = 0xc2;
		txLBMTempl->DA[3] = 0x00;
		txLBMTempl->DA[4] = 0x00;
		txLBMTempl->DA[5] = 0x30 + mdLevel;
	}
	else
	{
		if (rMepNum != -1)
		{
			// If the rMep Handle is not Null then use the MAC from that rMep
			readSAInRMepDB(rMepNum,txLBMTempl->DA);
		}
		else
		{
			// If the rMep is NULL use the MAC supplied in destMacAddress
			memcpy(txLBMTempl->DA,destMacAddress,sizeof(macAddress_t));
		}
	}
	generateBasicCfmTemplate(lMepNum,iFace,pktLen,innerTag,outerTag,txLBMTempl);

	txLBMTempl->comCfmHdr[0]	= mdLevel << 5;
	txLBMTempl->comCfmHdr[1]	= MR_LBM_OPCODE;
	txLBMTempl->comCfmHdr[2]	= 0;
	txLBMTempl->comCfmHdr[3]	= MR_LB_1ST_OFF;
	txLBMTempl->seqNum			= lbInfoArray[lMepNum].mepLbInfo.txLbmSeqNumber;
}

/*
 *
 * NOTE: the receive stats are not cleared in this function
 */
static void configLBSvcOnLMep(uint16_t lMepNum,uint8_t multiCast,txTemplateEntry_t *txLbTempl,templateType_t templType,uint8_t dirCfgMask, uint8_t lbChnl)
{
	lbrRamEntry_t	lbrRamEntry;
	uint8_t			prioDe;
	uint32_t		data;

	// If dirCfgMask = 0 it means LBR packets are to be used (not TST) so MR_BOTH_TX_RX are affected
	if ((dirCfgMask & MR_TX_ONLY) || (dirCfgMask == 0))
	{
		// before proceeding disable the LB channel
		if (dirCfgMask == 0)
			disableLBChnl(lbChnl,MR_BOTH_TX_RX);
		else
			disableLBChnl(lbChnl,MR_TX_ONLY);

		// update the txTemplate with the appropriate common CFM values
		if (templType == TST_TEMPL)
			// if a TST template is requested then change the opcode appropriately
			txLbTempl->comCfmHdr[1] = MR_TST_OPCODE;
		else
			txLbTempl->comCfmHdr[1] = MR_LBM_OPCODE;
		// even if a TST template is request...apart from the opcode change above the
		// template to use is still the basic loopback template.
		templType = LB_TEMPL;

		writeTxTempl(lbChnl,txLbTempl,templType,0,NULL);

		mrHalRegRead(LB_CONFIG_REG_ADDR,&data);
		// preserve all bits in reg except the packet size bits
		data &= ~(LB_CONFIG_REG_PKT_SIZE_MASK);
		// mask in the new packet size value
		//!!Important!! The packet size in bytes (including CRC = 4 and IPG = 20)
		// must be multiplied by 1000 to satisfy HW requirements.
		data |=  ((((((uint32_t) txLbTempl->pktLen) + 4 + IPG_BYTES) * 1000) << LB_CONFIG_REG_PKT_SIZE_OFFSET) & LB_CONFIG_REG_PKT_SIZE_MASK);
		mrHalRegWrite(LB_CONFIG_REG_ADDR, data);
	}

	// Determine the PCP field associated with the VLAN... This is needed in order
	// to configure the Rx Side to differentiate different PRIOs on a Vlan (LMEP)
	if (txLbTempl->sTagPresent)
		prioDe = txLbTempl->sTagVID[0] >> 4;
	else if (txLbTempl->cTagPresent)
		prioDe = txLbTempl->cTagVID[0] >> 4;
	else
		prioDe = txLbTempl->sTagVID[0] >> 4;		// No Tag on packet, use the port PCP - not easy to obtain here so...
												// !!! this is encoded into the STag fields by calling function!!!
	if ((dirCfgMask & MR_RX_ONLY)  || (dirCfgMask == 0))
	{
		// NOTE: the receive stats are not cleared in this function
		readLBRRam(lbChnl,&lbrRamEntry);
		// Update just the multicast bit....
		lbrRamEntry.multicast = multiCast;
		writeLBRRam(lbChnl,&lbrRamEntry);

		// associate the lMepNum with the lbChnl in the LBR RAM for use by RX logic
		writeLBRLMepNum(lbChnl,lMepNum, prioDe);

		lbSvcList[lbChnl].lMepNum = lMepNum;
		lbSvcList[lbChnl].pcp = prioDe;
		lbSvcList[lbChnl].multicast = multiCast;
	}
}

void _configLBSvcOnLMep(uint16_t lMepNum,uint8_t multiCast,txTemplateEntry_t *txLbTempl,templateType_t templType,svcDir_t svcDir, uint8_t lbChnl)
{
	configLBSvcOnLMep(lMepNum, multiCast, txLbTempl,templType,svcDir,lbChnl);
}

static void lbSetRate(uint8_t lbChnl, uint16_t lbRate)
{
	uint32_t	data;

	switch (lbChnl)
	{
		case 0:
		{
			mrHalRegRead(LB_RATE_012_REG_ADDR, &data);
			data = (data & (~LB_RATE_X_REG_C0C3C6_RATE_MASK)) | ((lbRate << LB_RATE_X_REG_C0C3C6_RATE_OFFSET) & LB_RATE_X_REG_C0C3C6_RATE_MASK);
			mrHalRegWrite(LB_RATE_012_REG_ADDR, data);
			break;
		}
		case 1:
		{
			mrHalRegRead(LB_RATE_012_REG_ADDR, &data);
			data = (data & ~(LB_RATE_X_REG_C1C4C7_RATE_MASK)) | ((((uint32_t) lbRate) << LB_RATE_X_REG_C1C4C7_RATE_OFFSET) & LB_RATE_X_REG_C1C4C7_RATE_MASK);
			mrHalRegWrite(LB_RATE_012_REG_ADDR, data);
			break;
		}
		case 2:
		{
			mrHalRegRead(LB_RATE_012_REG_ADDR, &data);
			data = (data & ~(LB_RATE_X_REG_C2C5_RATE_MASK)) | ((((uint32_t) lbRate) << LB_RATE_X_REG_C2C5_RATE_OFFSET) & LB_RATE_X_REG_C2C5_RATE_MASK);
			mrHalRegWrite(LB_RATE_012_REG_ADDR, data);
			break;
		}
		case 3:
		{
			mrHalRegRead(LB_RATE_345_REG_ADDR, &data);
			data = (data & (~LB_RATE_X_REG_C0C3C6_RATE_MASK)) | ((lbRate << LB_RATE_X_REG_C0C3C6_RATE_OFFSET) & LB_RATE_X_REG_C0C3C6_RATE_MASK);
			mrHalRegWrite(LB_RATE_345_REG_ADDR, data);
			break;
		}
		case 4:
		{
			mrHalRegRead(LB_RATE_345_REG_ADDR, &data);
			data = (data & ~(LB_RATE_X_REG_C1C4C7_RATE_MASK)) | ((((uint32_t) lbRate) << LB_RATE_X_REG_C1C4C7_RATE_OFFSET) & LB_RATE_X_REG_C1C4C7_RATE_MASK);
			mrHalRegWrite(LB_RATE_345_REG_ADDR, data);
			break;
		}
		case 5:
		{
			mrHalRegRead(LB_RATE_345_REG_ADDR, &data);
			data = (data & ~(LB_RATE_X_REG_C2C5_RATE_MASK)) | ((((uint32_t) lbRate) << LB_RATE_X_REG_C2C5_RATE_OFFSET) & LB_RATE_X_REG_C2C5_RATE_MASK);
			mrHalRegWrite(LB_RATE_345_REG_ADDR, data);
			break;
		}
		case 6:
		{
			mrHalRegRead(LB_RATE_67_REG_ADDR, &data);
			data = (data & (~LB_RATE_X_REG_C0C3C6_RATE_MASK)) | ((lbRate << LB_RATE_X_REG_C0C3C6_RATE_OFFSET) & LB_RATE_X_REG_C0C3C6_RATE_MASK);
			mrHalRegWrite(LB_RATE_67_REG_ADDR, data);
			break;
		}
		case 7:
		{
			mrHalRegRead(LB_RATE_67_REG_ADDR, &data);
			data = (data & ~(LB_RATE_X_REG_C1C4C7_RATE_MASK)) | ((((uint32_t) lbRate) << LB_RATE_X_REG_C1C4C7_RATE_OFFSET) & LB_RATE_X_REG_C1C4C7_RATE_MASK);
			mrHalRegWrite(LB_RATE_67_REG_ADDR, data);
			break;
		}
	}
}

void _lbSetRate(uint8_t lbChnl, uint16_t lbRate)
{
	lbSetRate(lbChnl,lbRate);
}

/*
 *
 */
static void lbConfigureChnl(uint8_t lbChnl, mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepLbTxInfo_t *mepLbTxInfo, mepLbMefInfo_t *mepLbMefTxInfo, bool testEn)
{
	txTemplateEntry_t 	txLBMTempl;
	lMepNum_t			lMepNum;
	rMepNum_t			rMepNum;
	bool 				multicast;
	uint16_t			dataTlvLength;

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);

	if (mepLbTxInfo->txLbmDestIsMepId)
	{
		rMepNum = _rMepIdToRMepNum(mdIndex, maIndex, lMepId, mepLbTxInfo->txLbmDestMepId);
	}
	else
	{
		rMepNum = -1;
	}

	mepInfo_t	mepInfo;
	lMepRead(mdIndex, maIndex,lMepId,&mepInfo,NULL,NULL,NULL,NULL,NULL,NULL);

	tagEntry_t	innerTag;
	tagEntry_t	outerTag;
	generatePacketTags(
			&innerTag,
			&outerTag,
			getPrimaryVidForMep(mdIndex, maIndex, lMepId),
			mepLbTxInfo->txLbmVlanPriority,
			mepLbTxInfo->txLbmVlanDropEnable,
			_maGetTagType(mdIndex, maIndex),
			mepInfo.iFace);

	// Read the MD info as we will need it - MDMibId has already been tested to be valid
	mdMibInfo_t	mdMibInfo;
	mdRead(mdIndex, &mdMibInfo,NULL);

	if (mepLbMefTxInfo == NULL)
	{
		// This is not a MEF compatible LB service
		multicast = false;
		dataTlvLength = mepLbTxInfo->_txLbmDataTlvLength;
	}
	else
	{
		multicast = mepLbMefTxInfo->multicastEnable;
		if (mepLbTxInfo->_txLbmDataTlvLength == 0)
		{
			// The Frame length is made up of basic frame (23), dataTLV (3 + dataTlvLength), crc (4) and tags. This means dataTlvlength is:
			dataTlvLength = mepLbMefTxInfo->frameSize - 23 - 3 - 4 - (innerTag.present ? 4 : 0) - (outerTag.present ? 4 : 0);
		}
		else
		{
			// if _txLbmDataTlvLength is non zero it must be used to determine the frame length and mepLbMefTxInfo->frameSize is ignored
			dataTlvLength = mepLbTxInfo->_txLbmDataTlvLength;
		}

		//TODO: We do not yet support the _dataPattern, _testTlvIncluded or _testTlvPattern options. They need to be added here if/when we do
	}

	if (multicast)
	{
		uint32_t		k;
		macAddress_t	macAddr;

		// Clear the LB Multi-cast table before sending a multi-cast LBM
		memset(macAddr,0,sizeof(macAddress_t));
		for (k = 0; k < MAX_LB_MULTICAST_RX_LIST_SIZE; k++)
		{
			writeMcLbrStore(k, macAddr);
		}
		// Clear the HW list head pointer
		mrHalRegWrite(MC_LIST_HD_REG_ADDR,0);
	}

	generateLBMTemplate(
			lMepNum,
			rMepNum,
			mepLbTxInfo->txLbmDestMacAddr,
			mepInfo.iFace,
			dataTlvLength,
			mdMibInfo.level,
			&innerTag,
			&outerTag,
			multicast,
			&txLBMTempl);

	configLBSvcOnLMep(lMepNum, multicast, &txLBMTempl, LB_TEMPL, MR_BOTH_TX_RX, lbChnl);

}

/*
 * @brief Find the HW Loopback channel that is being used for the specified MEP
 * Returns the Loopback channel number if found otherwise returns -1
 */
static int8_t lbGetHwChannel(lMepNum_t lMepNum)
{
	int8_t chnlNum;

	for (chnlNum = 0; chnlNum < MAX_LB_CHANNELS; chnlNum++)
	{
		if (lbMgntArray[chnlNum].inUse && lbMgntArray[chnlNum].callingLMepNum == lMepNum)
		{
			// Use the first channel that is not in use.
			return(chnlNum);
		}
	}
	return (-1);
}

static uint32_t	lbReadLbrTxCount(lMepNum_t lMepNum)
{
	uint8_t			index;
	lbmStoreEntry_t lbmStoreEntry;

	// TODO: Verify that this is the correct means to read the number of LBR sent by this MEP
	for (index = 0; index < MAX_LB_LBM_STORES; index++)
	{
		readLbmStore(index, &lbmStoreEntry,_getMepInterface(lMepNum));
		if (lbmStoreEntry.lMepNum == lMepNum)
		{
			return lbmStoreEntry.counter;	// (ad)Total number of Loopback Replies transmitted
		}
	}
	return 0;
}

static void lbReadStatsFromHw(lMepNum_t lMepNum)
{
	int8_t 			chnlNum;

	// First find the loopback channel that is being used for this MEP
	chnlNum = lbGetHwChannel(lMepNum);
	if (chnlNum == -1)
	{
		return;
	}

	// Read the stats for this loopback channel
	readLBStats(chnlNum, &lbInfoArray[lMepNum].mepLbAdvStats);

	// Update the MIB variables with the appropriate values...
	lbInfoArray[lMepNum].mepLbInfo.nextLbmTransId	= lbInfoArray[lMepNum].mepLbAdvStats.numTxPkts;				// (x)Next sequence number/transaction identifier to be sent in a Loopback message
	lbInfoArray[lMepNum].mepLbInfo._lbrIn			= lbInfoArray[lMepNum].mepLbAdvStats.numRxPkts;				// (y)Total number of valid, in-order Loopback Replies received
	lbInfoArray[lMepNum].mepLbInfo._lbrInOutOfOrder	= lbInfoArray[lMepNum].mepLbAdvStats.sequenceErr ? 1 : 0;	// (z)The total number of valid, out-of-order Loopback Replies received
	lbInfoArray[lMepNum].mepLbInfo.__lbrInBadMsdu	= 0;														// (aa)The total number of LBRs received whose mac_service_data_unit did not match (except for the OpCode)that of the corresponding LBM (20.2.3).

	lbInfoArray[lMepNum].mepLbInfo._lbrOut = lbReadLbrTxCount(lMepNum);

	// Adjust the numTxPkts value (The tx count is based on reading back the seq number, so subtract the starting seq number)...
	lbInfoArray[lMepNum].mepLbAdvStats.numTxPkts -= lbInfoArray[lMepNum].mepLbInfo.txLbmSeqNumber;

	lbInfoArray[lMepNum].mepLbInfo.txLbmStatus		= isLbActive(chnlNum);								// Set True to indicate another LBM is being transmitted
	//lbInfoArray[lMepNum].mepLbInfo.txLbmResultOk	= lbInfoArray[lMepNum].mepLbInfo.txLbmResultOk;		// Indicates the result of the operation: -true: The Loopback Message(s) will be (or has been) sent. -false: The Loopback Message(s) will not be sent.
	//lbInfoArray[lMepNum].mepLbInfo.txLbmSeqNumber;													// The Transaction Id (nextLbmTransId) of the first LBM (to be) sent. The value returned is undefined if txLbmResultOK is false
}

static void lbSaveStats(uint8_t chnlNum, lMepNum_t lMepNum)
{
	lbReadStatsFromHw(lMepNum);
	lbInfoArray[lMepNum].readFromHwRequired = false;
}

static void lbFreeChannel(lMepNum_t lMepNum)
{
	int8_t chnlNum;

	// Find the channel associated with the given lMepNum
	for (chnlNum = 0; chnlNum < MAX_LB_CHANNELS; chnlNum++)
	{
		if(lbMgntArray[chnlNum].callingLMepNum == lMepNum)
		{
			lbStop(chnlNum);
			if (lMepNum < MAX_NUM_MEPS)	// Y1564/RFC2544 can request channels but will use a mepNum > MAX_NUM_MEPS
			{
				// save the stats to context
				lbSaveStats(chnlNum, lbMgntArray[chnlNum].callingLMepNum);
			}
			// release the channel
			lbMgntArray[chnlNum].inUse = false;
		}
	}
	// It is possible that no channel was found associated with the lMepNum
	// because the another loopback service may have taken the channel
	// when it went inactive.
}

void _lbFreeChannel(lMepNum_t lMepNum)
{
	LOCK_LB_LIST(0);
	lbFreeChannel(lMepNum);
	UNLOCK_LB_LIST();
}

static int8_t lbRequestChannel(lMepNum_t lMepNum)
{
	int8_t chnlNum;

	for (chnlNum = 0; chnlNum < MAX_LB_CHANNELS; chnlNum++)
	{
		if ((!lbMgntArray[chnlNum].locked) && (!lbMgntArray[chnlNum].inUse))
		{
			// Use the first channel that is not in use.
			// Channel is not active, but stop it to be safe
			lbStop(chnlNum);
			// No need to save the stats
			lbMgntArray[chnlNum].callingLMepNum = lMepNum;
			lbMgntArray[chnlNum].inUse = true;
			return(chnlNum);
		}
	}

	for (chnlNum = 0; chnlNum < MAX_LB_CHANNELS; chnlNum++)
	{
		if (!lbMgntArray[chnlNum].locked)
		{
			// Try to find a channel that is no longer active...
			if (!isLbActive(chnlNum))
			{
				// Channel is not active, but stop it to be safe
				lbStop(chnlNum);
				// save the stats to context
				lbSaveStats(chnlNum, lbMgntArray[chnlNum].callingLMepNum);

				// If the channel is being re-assigned then update the LB status for the MEP that previously had the channel
				if (lbMgntArray[chnlNum].callingLMepNum != lMepNum)
				{
					lbInfoArray[lbMgntArray[chnlNum].callingLMepNum].mepLbInfo.txLbmStatus = false;		// Indicate that LBMs are not being transmitted
					// Check if the number of LBMs send doesn't match the number requested...
					if (lbInfoArray[lbMgntArray[chnlNum].callingLMepNum].mepLbAdvStats.numTxPkts != lbInfoArray[lbMgntArray[chnlNum].callingLMepNum].mepLbInfo.txLbmNumMessages)
					{
						lbInfoArray[lbMgntArray[chnlNum].callingLMepNum].mepLbInfo.txLbmResultOk = false;	// Indicate that any remaining LBMs will not be sent
					}
					else
					{
						lbInfoArray[lbMgntArray[chnlNum].callingLMepNum].mepLbInfo.txLbmResultOk = true;	// Indicate that all requested LBMs were sent
					}
				}

				lbMgntArray[chnlNum].callingLMepNum = lMepNum;
				lbMgntArray[chnlNum].inUse = true;
				return (chnlNum);
			}
		}
	}
	return (-1);		// All channels are assigned and busy
}

int8_t _lbRequestChannel(lMepNum_t lMepNum)
{
	int8_t retVal;

	LOCK_LB_LIST(0);
	retVal = lbRequestChannel(lMepNum);
	UNLOCK_LB_LIST();
	return(retVal);
}

static int8_t lbLockChannel(uint8_t chnlNum, lMepNum_t lMepNum)
{
	// Lock a LB channel so it cannot be re-assigned (needed for Y1564 and RFC2544)
	// First validate that the channel number is valid
	if (chnlNum < MAX_LB_CHANNELS)
	{
		// Confirm that the channel is assigned to the specified local MEP
		if (lbMgntArray[chnlNum].inUse && (lbMgntArray[chnlNum].callingLMepNum == lMepNum))
		{
			// Lock the channel to this Local MEP
			lbMgntArray[chnlNum].locked = true;
			return (0);
		}
	}
	return (-1);
}

int8_t _lbLockChannel(uint8_t chnlNum, lMepNum_t lMepNum)
{
	int8_t retVal;
	int8_t availChnlNum;

	LOCK_LB_LIST(0);
	availChnlNum = lbRequestChannel(lMepNum);
	if (availChnlNum == -1)
	{
		return(availChnlNum);
	}

	// Now lock this channel so no one else can take it
	retVal = lbLockChannel((uint8_t)availChnlNum, lMepNum);
	UNLOCK_LB_LIST();
	return(retVal);
}

static int8_t lbUnLockChannel(uint8_t chnlNum, lMepNum_t lMepNum)
{
	// unLock a LB channel so it can be re-assigned.
	// First validate that the channel number is valid
	if (chnlNum < MAX_LB_CHANNELS)
	{
		// Confirm that the channel is assigned to the specified local MEP
		if (lbMgntArray[chnlNum].inUse && (lbMgntArray[chnlNum].callingLMepNum == lMepNum))
		{
			// unlock the channel from this Local MEP
			lbMgntArray[chnlNum].locked = false;
			return (0);
		}
	}
	return (-1);
}

int8_t _lbUnLockChannel(uint8_t chnlNum, lMepNum_t lMepNum)
{
	int8_t retVal;

	LOCK_LB_LIST(0);
	retVal = lbUnLockChannel(chnlNum, lMepNum);
	UNLOCK_LB_LIST();
	return(retVal);
}

/*
 * @brief Configure loopback on a local MEP to transmit one or more LBM packets
 */
int lbTransmit(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepLbTxInfo_t *mepLbTxInfo, mepLbMefTxInfo_t *mepLbMefTxInfo)
{
	int8_t			lbChnl;
	lMepNum_t		lMepNum;
	bool 			macValid;
	macAddress_t	macAddress;
	uint32_t		ipg = 1000000;		// default inter packet time is 1 second

	if (!mepIsLMepIdValid(mdIndex, maIndex, lMepId))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to nonexistent MEPID (%lu.%lu.%d)",mdIndex,maIndex,lMepId);
		return (1);
	}

	if (mepLbTxInfo == NULL)
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid mepLbTxInfo. It cannot be NULL.");
		return (-2);
	}

	if ((mepLbTxInfo->txLbmNumMessages == 0) || (mepLbTxInfo->txLbmNumMessages > MAX_LB_NUM_BURSTS))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid number of LBMs to be transmitted (min=1 max=%d)",MAX_LB_NUM_BURSTS);
		return (2);
	}

	if ((mepLbTxInfo->_txLbmDataTlvLength > MAX_LB_DATA_TLV))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to too much data to be transmitted (max=%d)",MAX_LB_DATA_TLV);
		return (3);
	}

	if ((mepLbTxInfo->txLbmVlanPriority < prio0) || (mepLbTxInfo->txLbmVlanPriority > 7))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid priority (%d)",mepLbTxInfo->txLbmVlanPriority);
		return (4);
	}

	if (mepLbTxInfo->txLbmDestIsMepId)
	{
		// We can't send loopback to a remote MEP that is not actively receiving CCMs - MAC may be unknown!
		// See if the MAC is known...if it is we are good to go
		rMepGetMAC(mdIndex, maIndex, lMepId,mepLbTxInfo->txLbmDestMepId,&macValid,macAddress);
		//if (!rMepIsActive(mdIndex, maIndex, lMepId,mepLbTxInfo->txLbmDestMepId))
		if(!macValid)
		{
			DO_LOG_INFO(log_handle,"Operation rejected due to invalid MEP state.");
			return (-3);
		}
	}

	if (mepLbMefTxInfo != NULL)
	{
		// The MEF Tx Information is only relevant if _txLbmDataTlvLength is 0.
		if ((mepLbTxInfo->_txLbmDataTlvLength == 0) && ((mepLbMefTxInfo->frameSize > MAX_LBM_PKT_SIZE) || (mepLbMefTxInfo->frameSize < 64)))
		{
			DO_LOG_INFO(log_handle,"Operation rejected due to too much data to be transmitted (max=%d)",MAX_LBM_PKT_SIZE);
			return (3);
		}

		if (mepLbMefTxInfo->interval >= ((LB_BURST_REG_BURST_IBG_MASK >> LB_BURST_REG_BURST_IBG_OFFSET)/1000))
		{
			DO_LOG_INFO(log_handle,"Operation rejected due to packet interval time too large MAX=2096ms");
			return (-1);
		}

		ipg = mepLbMefTxInfo->interval * 1000; // convert interval to uSeconds

	}


	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	if (lMepNum == -1)
	{
		DO_LOG_CRITICAL(log_handle,"lbTransmit() failed. Failed to find the local mep %lu.%lu.%d",mdIndex, maIndex, lMepId);
		return (1);
	}
	//ASSERT(lMepNum >= 0, 1);	// Ensure we always get a valid lMepNum

	LOCK_LB_LIST(0);
	lbChnl = lbRequestChannel(lMepNum);
	if (lbChnl == -1)
	{
		UNLOCK_LB_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to insufficient LB resources");
		return (7);
	}

	// Copy the tx configuration info in to the LB context info so it is preserved for readback
	memcpy(lbInfoArray[lMepNum].mepLbInfo._txLbmDataTlv,mepLbTxInfo->_txLbmDataTlv,32);
	lbInfoArray[lMepNum].mepLbInfo._txLbmDataTlvLength	= mepLbTxInfo->_txLbmDataTlvLength;
	lbInfoArray[lMepNum].mepLbInfo.txLbmDestIsMepId		= mepLbTxInfo->txLbmDestIsMepId;
	memcpy(lbInfoArray[lMepNum].mepLbInfo.txLbmDestMacAddr,mepLbTxInfo->txLbmDestMacAddr,6);
	lbInfoArray[lMepNum].mepLbInfo.txLbmDestMepId		= mepLbTxInfo->txLbmDestMepId;
	lbInfoArray[lMepNum].mepLbInfo.txLbmNumMessages		= mepLbTxInfo->txLbmNumMessages;
	lbInfoArray[lMepNum].mepLbInfo.txLbmStatus			= mepLbTxInfo->txLbmStatus;
	lbInfoArray[lMepNum].mepLbInfo.txLbmVlanDropEnable	= mepLbTxInfo->txLbmVlanDropEnable;
	lbInfoArray[lMepNum].mepLbInfo.txLbmVlanPriority	= mepLbTxInfo->txLbmVlanPriority;

	if (mepLbMefTxInfo != NULL)
	{
		lbInfoArray[lMepNum].mepLbMefInfo._dataPattern		= mepLbMefTxInfo->_dataPattern;
		lbInfoArray[lMepNum].mepLbMefInfo._testTlvIncluded	= mepLbMefTxInfo->_testTlvIncluded;
		lbInfoArray[lMepNum].mepLbMefInfo._testTlvPattern	= mepLbMefTxInfo->_testTlvPattern;
		lbInfoArray[lMepNum].mepLbMefInfo.frameSize			= mepLbMefTxInfo->frameSize;
		lbInfoArray[lMepNum].mepLbMefInfo.interval			= mepLbMefTxInfo->interval;
		lbInfoArray[lMepNum].mepLbMefInfo.multicastEnable	= mepLbMefTxInfo->multicastEnable;
		lbInfoArray[lMepNum].mepLbMefInfo.timeout			= mepLbMefTxInfo->timeout;
	}

	// get this LB service to continue with it's previous LB sequence number
	lbInfoArray[lMepNum].mepLbInfo.txLbmSeqNumber = lbInfoArray[lMepNum].mepLbInfo.nextLbmTransId;

	mepInfo_t	mepInfo;
	lMepRead(mdIndex, maIndex,lMepId,&mepInfo,NULL,NULL,NULL,NULL,NULL,NULL);

	if (lbInfoArray[lMepNum].mepLbAdvInfo.enableAdvMode)
	{
		// Advanced LB mode...
		lbMgntArray[lbChnl].inAdvancedMode = true;
		lbSetMode(lbChnl,lbInfoArray[lMepNum].mepLbAdvInfo.lbMode, mepInfo.iFace);

		// use the IPG supplied and bursts of 1 packet
		_lbConfigureBurst(lbChnl,lbInfoArray[lMepNum].mepLbAdvInfo.pktsPerBurst,
				lbInfoArray[lMepNum].mepLbAdvInfo.numberOfBursts,
				lbInfoArray[lMepNum].mepLbAdvInfo.interBurstTime);

		// Set the default rate to 100% (1000Mb/s)
		lbSetRate(lbChnl,lbInfoArray[lMepNum].mepLbAdvInfo.transmitRate);

		// TODO: mepLbTxInfo->txLbmStatus: should this MIB param be used to start/stop LB?

		// Configure the Tx and Rx hardware (txTemplate and Rx SM)
		lbConfigureChnl(lbChnl, mdIndex, maIndex, lMepId, mepLbTxInfo, &lbInfoArray[lMepNum].mepLbMefInfo, false);

		// Clear both Tx and Rx stats
		lbClearStats(lbChnl, lbInfoArray[lMepNum].mepLbInfo.txLbmSeqNumber);

		// Start transmitting
		if (lbInfoArray[lMepNum].mepLbAdvInfo.txActive)
		{
			lbStart(lbChnl);
		}
	}
	else
	{
		// Normal CFM LB mode...
		lbMgntArray[lbChnl].inAdvancedMode = false;
		lbSetMode(lbChnl,lb_burstMode, mepInfo.iFace);

		// use the IPG supplied and bursts of 1 packet
		_lbConfigureBurst(lbChnl,1,mepLbTxInfo->txLbmNumMessages, ipg);

		// Set the default rate to 100% (1000Mb/s)
		lbSetRate(lbChnl,1000);

		// TODO: mepLbTxInfo->txLbmStatus: should this MIB param be used to start/stop LB?

		// Configure the Tx and Rx hardware (txTemplate and Rx SM)
		lbConfigureChnl(lbChnl, mdIndex, maIndex, lMepId, mepLbTxInfo, &lbInfoArray[lMepNum].mepLbMefInfo, false);

		// Clear both Tx and Rx stats
		lbClearStats(lbChnl, lbInfoArray[lMepNum].mepLbInfo.txLbmSeqNumber);

		// Start transmitting
		lbStart(lbChnl);
	}

	// A new LB transmit sequence is started so mark the context info as out of date
	lbInfoArray[lMepNum].readFromHwRequired = true;

	UNLOCK_LB_LIST();
	DO_LOG_NOTICE(log_handle,"LBM Transmit requested on MEP %lu.%lu.%d",mdIndex, maIndex,lMepId);
	return (6);	// The LBM(s) will be (or have been) sent
}


/*
 *
 */
int lbWrite(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepLbMefInfo_t *mepLbMefInfo)
{
	lMepNum_t	lMepNum;

	if (!mepIsLMepIdValid(mdIndex, maIndex, lMepId))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to nonexistent MEPID (%lu.%lu.%d)",mdIndex,maIndex,lMepId);
		return (1);
	}

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	if (lMepNum == -1)
	{
		DO_LOG_CRITICAL(log_handle,"lbWrite() failed. Failed to find the local mep %lu.%lu.%d",mdIndex, maIndex, lMepId);
		return (1);
	}
	//ASSERT(lMepNum >= 0, 1);	// Ensure we always get a valid lMepNum

	LOCK_LB_LIST(0);

	if (mepLbMefInfo != NULL)
	{
		memcpy(&lbInfoArray[lMepNum].mepLbMefInfo,mepLbMefInfo,sizeof(mepLbMefInfo_t));
	}

	UNLOCK_LB_LIST();
	DO_LOG_DEBUG(log_handle,"LBM MEF Info updated on MEP %lu.%lu.%d",mdIndex, maIndex,lMepId);
	return (6);
}

/*
 * @brief Read all Loopback related parameters for the selected local MEP
 */
void lbRead(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepLbInfo_t *mepLbInfo, mepLbMefInfo_t *mepLbMefInfo)
{
	lMepNum_t	lMepNum;

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	if (lMepNum == -1)
	{
		DO_LOG_CRITICAL(log_handle,"lbRead() failed. Failed to find the local mep %lu.%lu.%d",mdIndex, maIndex, lMepId);
		return;
	}
	//ASSERT(lMepNum >= 0, 1);	// Ensure we always get a valid lMepNum

	LOCK_LB_LIST(0);
	// Check has the data been copied from HW to Context, if not copy it
	if (lbInfoArray[lMepNum].readFromHwRequired)
	{
		lbReadStatsFromHw(lMepNum);
	}

	if (mepLbInfo != NULL)	// Only read the LB info if a non NULL pointer is provided
	{
		memcpy(mepLbInfo,&lbInfoArray[lMepNum].mepLbInfo, sizeof(mepLbInfo_t));
		mepLbInfo->_lbrOut = lbReadLbrTxCount(lMepNum);
	}

	if (mepLbMefInfo != NULL)	// Only read the LB MEF info if a non NULL pointer is provided
	{
		memcpy(mepLbMefInfo,&lbInfoArray[lMepNum].mepLbMefInfo, sizeof(mepLbMefInfo_t));
	}

	UNLOCK_LB_LIST();
	DO_LOG_DEBUG(log_handle,"Read LBM information on MEPID %lu.%lu.%d",mdIndex, maIndex, lMepId);
}

/*
 * @Brief Reads the SA MACs received and stored by HW in the LBR Multicast store RAM
 */
int lbReadNextMcMAC(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, uint32_t lbmSeqNum, uint8_t receiveOrder, macAddress_t macAddress)
{
	lMepNum_t	lMepNum;
	uint32_t	maxIndex;
	int8_t 		chnlNum;

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	if (lMepNum == -1)	// Ensure we always get a valid lMepNum
	{
		return -1;		// Indicates an error with indices
	}

	// First find the loopback channel that is being used for this MEP
	chnlNum = lbGetHwChannel(lMepNum);
	if (chnlNum == -1)			// Something is wrong if the lMepNum isn't found
	{
		return(-1);		// Indicates that last index is invalid
	}

	mrHalRegRead(MC_LIST_HD_REG_ADDR,&maxIndex);
	// If maxIndex is 5 then 5 LMRs have been received. receiveOrder = 1 is used to read the first LMR received
	if ((receiveOrder > 0) && (receiveOrder < (maxIndex + 1)))
	{
		// Read the MAC at the index provided
		readMcLbrStore(receiveOrder-1, macAddress);

		// NOTE: We do not make use of lbmSeqNum because it is not stored/used by HW.
		// It is a function parameter to be consistent with the MIB
		return(1);	// indicates that the value in macAddress is valid and there are more MACs
	}
	return(0);	// indicates that the value in macAddress is invalid and there are no more MACs

}

/*
 *
 */
int lbTransmitAdvanced(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepLbTxAdvanceCfg_t *mepLbTxAdvanceCfg)
{
	int8_t		lbChnl;
	lMepNum_t	lMepNum;

	if (!mepIsLMepIdValid(mdIndex, maIndex, lMepId))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to nonexistent MEPID (%lu.%lu.%d)",mdIndex,maIndex,lMepId);
		return (1);
	}

	if (mepLbTxAdvanceCfg->enableAdvMode)
	{

		if (mepLbTxAdvanceCfg->lbMode == lb_burstMode)
		{
			if ((mepLbTxAdvanceCfg->pktsPerBurst == 0) || (mepLbTxAdvanceCfg->pktsPerBurst > MAX_LB_PKTS_PER_BURSTS))
			{
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid number of packets per burst: %d (min=1 max=%d)",mepLbTxAdvanceCfg->pktsPerBurst, MAX_LB_PKTS_PER_BURSTS);
				return (2);
			}
			if ((mepLbTxAdvanceCfg->numberOfBursts == 0) || (mepLbTxAdvanceCfg->numberOfBursts > MAX_LB_NUM_BURSTS))
			{
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid number of Bursts: %d (min=1 max=%d)",mepLbTxAdvanceCfg->numberOfBursts,MAX_LB_NUM_BURSTS);
				return (2);
			}
			if ((mepLbTxAdvanceCfg->interBurstTime > MAX_LB_IBG_USEC))
			{
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid Inter Burst Gap %luus (interBurstTime: min=0 max=%luus)",mepLbTxAdvanceCfg->interBurstTime, MAX_LB_IBG_USEC);
				return (2);
			}
		}

		if ((mepLbTxAdvanceCfg->transmitRate < 1) || (mepLbTxAdvanceCfg->transmitRate > 1000))
		{
			DO_LOG_INFO(log_handle,"Operation rejected due to invalid Transmit Rate %d (min=1 max=1000)",mepLbTxAdvanceCfg->transmitRate);
			return (3);
		}
	}

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	if (lMepNum == -1)
	{
		DO_LOG_CRITICAL(log_handle,"lbTransmitAdvanced() failed. Failed to find the local mep %lu.%lu.%d",mdIndex, maIndex, lMepId);
		return (1);
	}
	//ASSERT(lMepNum >= 0, 1);	// Ensure we always get a valid lMepNum

	LOCK_LB_LIST(0);
	if (!mepLbTxAdvanceCfg->enableAdvMode)
	{
		//If disabling Advanced LB mode then...
		// test if the LB channel was already in advance mode
		if (lbInfoArray[lMepNum].mepLbAdvInfo.enableAdvMode)
		{
			// if it was, then when disabling advance mode we must
			// also ensure that if in continuous mode we stop the channel
			if (lbInfoArray[lMepNum].mepLbAdvInfo.lbMode == lb_continuousMode)
			{
				lbChnl = lbGetHwChannel(lMepNum);
				if (lbChnl != -1)
				{
					// If we get here then a LB channel is assigned to this local MEP and
					// LB may be running in Advanced mode, so stop it.
					lbStop(lbChnl);
				}
			}
		}

		// Mark the Channel as operating in normal LB mode
		lbMgntArray[lbChnl].inAdvancedMode = false;
	}

	// If already in advanced mode and txActive is false we may need to disable the loopback channel
	if(lbInfoArray[lMepNum].mepLbAdvInfo.enableAdvMode && !mepLbTxAdvanceCfg->txActive)
	{
		lbChnl = lbGetHwChannel(lMepNum);
		if (lbChnl != -1)
		{
			if (mepLbTxAdvanceCfg->txActive)
			{
				// If we get here then a LB channel is assigned to this local MEP and
				// LB is in Advanced mode, we should enabled it.
				lbStart(lbChnl);
			}
			else
			{
				// If we get here then a LB channel is assigned to this local MEP and
				// LB may be running in Advanced mode, so stop it.
				lbStop(lbChnl);
			}
		}
	}

	// Copy the Advanced tx configuration info into the LB context info
	memcpy(&lbInfoArray[lMepNum].mepLbAdvInfo,mepLbTxAdvanceCfg,sizeof(mepLbTxAdvanceCfg_t));


	UNLOCK_LB_LIST();
	DO_LOG_NOTICE(log_handle,"Advanced LBM Transmit requested on MEP %lu.%lu.%d",mdIndex, maIndex,lMepId);
	return (4);
}

/*
 * @brief Read all Loopback related parameters for the selected local MEP
 */
int lbReadAdvancedStats(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepLbAdvanceStats_t *mepLbAdvanceStats)
{
	lMepNum_t	lMepNum;

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	if (lMepNum == -1)
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to nonexistent MEPID (%lu.%lu.%d)",mdIndex,maIndex,lMepId);
		return (1);
	}

	LOCK_LB_LIST(0);
	// Check has the data been copied from HW to Context, if not copy it
	if (lbInfoArray[lMepNum].readFromHwRequired)
	{
		lbReadStatsFromHw(lMepNum);
	}

	if (mepLbAdvanceStats != NULL)	// Only read the LB Advanced stats info if a non NULL pointer is provided
	{
		memcpy(mepLbAdvanceStats,&lbInfoArray[lMepNum].mepLbAdvStats, sizeof(mepLbAdvanceStats_t));
	}

	UNLOCK_LB_LIST();
	DO_LOG_DEBUG(log_handle,"Read LBM Advanced Stats information on MEPID %lu.%lu.%d",mdIndex, maIndex, lMepId);
	return (2);
}

int lbAbort(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId)
{
	lMepNum_t	lMepNum;

	if (!mepIsLMepIdValid(mdIndex, maIndex, lMepId))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to nonexistent MEPID (%lu.%lu.%d)",mdIndex,maIndex,lMepId);
		return (1);
	}

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	if (lMepNum == -1)
	{
		DO_LOG_CRITICAL(log_handle,"lbAbort() failed. Failed to find the local mep %lu.%lu.%d",mdIndex, maIndex, lMepId);
		return (1);
	}
	//ASSERT(lMepNum >= 0, 1);	// Ensure we always get a valid lMepNum

	// Free the associated Loopback channel: This will stop the LB, update the
	// stats and release the loopback channel (if not already free!)
	lbFreeChannel(lMepNum);
	if (lbInfoArray[lMepNum].mepLbAdvStats.numTxPkts != lbInfoArray[lMepNum].mepLbInfo.txLbmNumMessages)
	{
		lbInfoArray[lMepNum].mepLbInfo.txLbmStatus = false;		// Indicate that LBMs are not being transmitted
		lbInfoArray[lMepNum].mepLbInfo.txLbmResultOk = false;	// Indicate that any remaining LBMs will not be sent
	}

	DO_LOG_DEBUG(log_handle,"Aborting the LB session on MEPID %lu.%lu.%d",mdIndex, maIndex, lMepId);
	return 2;

}
