/*******************************************************************************
 * (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/loopback/src/lb_support.h>
#include <api_oam_sdk/loopback/src/lb_types.h>
#include <api_oam_sdk/metroam_hal/inc/mr_hal.h>
#include <api_oam_sdk/notifications/inc/egl_notifications.h>
#include <api_oam_sdk/rmep_db/src/rmep_types.h>
#include <api_oam_sdk/service_activation/src/service_activation_types.h>
#include <api_oam_sdk/system/inc/system.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <math.h>
#include <string.h>

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

// Defines the data resolution the hardware can achieve
#define RATE_RESOLUTION_IN_KBPS				(1000)

// The macro converts values in kbps to the nearest Mbps
#define CONVERT_KBPS_TO_MBPS(x) 			(((x)/1000) + (((x) % 1000) >= 500 ? 1 : 0))
#define CONVERT_MBPS_TO_KBPS(x) 			(((uint32_t)x) * 1000)
#define CONVERT_MBPS_TO_BPS(x) 				(((uint32_t)x) * 1000000)
#define CONVERT_KBPS_TO_BPS(x) 				(((uint32_t)x) * 1000)

// This macro converts from utilised Line rate to information rate
#define LINE_RATE_TO_INFO_RATE(lr,pktSize) 	((((uint32_t)lr) * (pktSize)) / ((pktSize) + 20))

// This macro will adjust the frame size so that when multiplied by frameCount per sec one gets utilised line rate
#define ADJUST_FOR_UTILISED_LINE_RATE(x)	((x) + 20)

#define GANGED_OPERATION					(0x8000)	// used to indicate if Service Activation channels operate in ganged mode
#define RFC2544_TASK_STACKSIZE				(1536)
#define Y1564_TASK_STACKSIZE				(1024)
#define FRAME_LOSS_MILLI_PERCENT_TO_FLR(x)	(((double)x) * 0.00001)		// Used to convert FL in milli percent to a FLR value.
#define FLR_TO_FRAME_LOSS_MILLI_PERCENT(x)	((x) * 100000)		// Used to convert FLR to FL in milli percent.

#define EN_CHNL_0							(0x01)
#define EN_CHNL_0_1							(0x03)
#define EN_ALL_CHNL							(0xFF)

static bool 		saInitialised 		= false;
static osMutex_t	saListLock			= NULL;
static osMutex_t	saReadLock			= NULL;
static uint16_t		log_handle			= 0;		// handle used to log messages for this module
static bool			saActiveFlag		= false;	// A flag used to indicate if any SA test is running
static bool			saStopFlag			= false;	// A flag used to indicate a user request to stop the test early

rfc2544Cfg_t	saRfc2544Cfg;
y1564Cfg_t		saY1564Cfg;

rfc2544Results_t	saRfc2544Results;
y1564Results_t		saY1564Results;

extern testStatus_t 	y1564Status;
extern testStatus_t 	rfc2544Status;

#define LOCK_SA_LIST(timeout)	osRecursiveMutexTake(saListLock,timeout)
#define UNLOCK_SA_LIST()		osRecursiveMutexGive(saListLock)

void LOCK_SA_READ(uint32_t timeout)
{
	osRecursiveMutexTake(saReadLock,timeout);
}

void UNLOCK_SA_READ(void)
{
	osRecursiveMutexGive(saReadLock);
}

// Global Parameters for LB Test (Y1564 & RFC2544)
uint8_t		tstTailPtr[MAX_LB_CHANNELS_FOR_Y1564];
uint64_t	tstAcuTxCnt[MAX_LB_CHANNELS_FOR_Y1564];			// counts the total Tx packets for the test
uint16_t	tstRxPktSize[MAX_LB_CHANNELS_FOR_Y1564];		// stores the packet size for IR calculations
uint64_t	tstAcuRxCnt[MAX_LB_CHANNELS_FOR_Y1564];			// counts the total Rx packets for the test
uint32_t	tstLastSampTxCnt[MAX_LB_CHANNELS_FOR_Y1564];	// holds the last Tx packet count read from 1sec FIFO
uint32_t	tstLastSampRxCnt[MAX_LB_CHANNELS_FOR_Y1564];	// holds the last Rx packet count read from 1sec FIFO
uint32_t	tstLastTxCnt[MAX_LB_CHANNELS_FOR_Y1564];		// holds the last Tx packet count read from Tx Seq No.
uint32_t	tstLastRxCnt[MAX_LB_CHANNELS_FOR_Y1564];		// holds the last Rx packet count read from LBR ram.
uint64_t	tstAcuTotalTxCnt[MAX_LB_CHANNELS_FOR_Y1564];	//
uint64_t	tstAcuTotalRxCnt[MAX_LB_CHANNELS_FOR_Y1564];	//
uint32_t	tstValidCnt[MAX_LB_CHANNELS_FOR_Y1564];			// count the number of samples read from 1Sec FIFO
uint32_t	tstDuration[MAX_LB_CHANNELS_FOR_Y1564];			// Holds the test duration value in Seconds
uint64_t	tstAcuDeltaTxCnt[MAX_LB_CHANNELS_FOR_Y1564];	// The sum of Delta Tx'd packets for duration of test
uint64_t	tstAcuDeltaRxCnt[MAX_LB_CHANNELS_FOR_Y1564];	// The sum of Delta Rx'd packets for duration of test
uint8_t		consecAvailSec[MAX_LB_CHANNELS_FOR_Y1564];		// Counts No. of consecutive available seconds
uint8_t		consecUnAvailSec[MAX_LB_CHANNELS_FOR_Y1564];	// Counts No. of consecutive unavailable seconds
uint32_t	availSecCnt[MAX_LB_CHANNELS_FOR_Y1564];			// Counts No. of available seconds for the test
double		tstFlrThres[MAX_LB_CHANNELS_FOR_Y1564];			//
uint32_t	tstIrMin[MAX_LB_CHANNELS_FOR_Y1564];			//
uint32_t	tstIrMax[MAX_LB_CHANNELS_FOR_Y1564];			//
uint8_t		tstTLag[MAX_LB_CHANNELS_FOR_Y1564];				// holds the no of sec to wait for last pkt after tx stopped
uint8_t		tstStartCnt[MAX_LB_CHANNELS_FOR_Y1564];			// counts zeroed samples at start of test
bool		availSt[MAX_LB_CHANNELS_FOR_Y1564];
bool		tstFirstSamp[MAX_LB_CHANNELS_FOR_Y1564];		// used to manage the start of test measurement
uint8_t		gangMaskList[MAX_LB_CHANNELS_FOR_Y1564+1];		// Stores channel select masks for grouped operation



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


static void setSaRfc2544CfgDefaults(rfc2544Cfg_t	*rfc2544Cfg)
{
	memset(rfc2544Cfg,0,sizeof(rfc2544Cfg_t));
	rfc2544Cfg->frameType						= y1731_lbm_frames;
	rfc2544Cfg->targetIsMep 					= true;
	rfc2544Cfg->throughPutCfg.frameSizeList[0]	= Y1564_DEFAULT_FRAME_SIZE;
	rfc2544Cfg->throughPutCfg.trialDuration		= RFC2544_TRIAL_DURATION;
	rfc2544Cfg->throughPutCfg.minRate			= RFC2544_MIN_RATE;
	rfc2544Cfg->throughPutCfg.maxRate			= RFC2544_MAX_RATE;
	rfc2544Cfg->throughPutCfg.stepSize			= RFC2544_STEP_SIZE;
	rfc2544Cfg->delayCfg.frameSizeList[0]		= Y1564_DEFAULT_FRAME_SIZE;
	rfc2544Cfg->delayCfg.trialDuration			= RFC2544_TRIAL_DURATION_LATENCY;
	rfc2544Cfg->delayCfg.minRate				= RFC2544_MIN_RATE;
	rfc2544Cfg->delayCfg.maxRate				= RFC2544_MAX_RATE;
	rfc2544Cfg->delayCfg.stepSize				= RFC2544_STEP_SIZE;
	rfc2544Cfg->frameLossCfg.frameSizeList[0]	= Y1564_DEFAULT_FRAME_SIZE;
	rfc2544Cfg->frameLossCfg.trialDuration		= RFC2544_TRIAL_DURATION;
	rfc2544Cfg->frameLossCfg.minRate			= RFC2544_MIN_RATE;
	rfc2544Cfg->frameLossCfg.maxRate			= RFC2544_MAX_RATE;
	rfc2544Cfg->frameLossCfg.stepSize			= RFC2544_STEP_SIZE;
	rfc2544Cfg->backToBackCfg.frameSizeList[0]	= Y1564_DEFAULT_FRAME_SIZE;
	rfc2544Cfg->backToBackCfg.repeat			= RFC2544_B2B_REPEATS;
	rfc2544Cfg->backToBackCfg.trialDuration		= RFC2544_TRIAL_DURATION_B2B;
	rfc2544Cfg->backToBackCfg.stepSize			= MAX_LB_PKTS_PER_BURSTS;
	rfc2544Cfg->binaryDuration					= RFC2544_BINARY_DURATION;
}

static void setSaY1564CfgDefaults(y1564Cfg_t *y1564Cfg)
{
	uint8_t	s;
	memset(y1564Cfg,0,sizeof(y1564Cfg_t));
	y1564Cfg->configStepDuration	= 60;
	y1564Cfg->performanceDuration	= 15;
	y1564Cfg->frameType				= y1731_lbm_frames;

	for (s = 0; s < MAX_NUM_SA_SERVICES; s++)
	{
		y1564Cfg->serviceCfg[s].frameSize	= 512;
		y1564Cfg->serviceCfg[s].emix[0]		= 0x02; //Together with next octet make 0x0200 (512 decimal = used for performance-frame-size)
		y1564Cfg->serviceCfg[s].emix[1]		= 0x00;
		y1564Cfg->serviceCfg[s].emix[2]		= 'd';
		y1564Cfg->serviceCfg[s].emix[3]		= 'd';
		y1564Cfg->serviceCfg[s].emix[4]		= 'd';
		y1564Cfg->serviceCfg[s].emix[5]		= 0;
		y1564Cfg->serviceCfg[s].availabiltyThresh	= 100000; // Default is 100%, units are milli percent
	}
}

static void setThruputResultDefaults(throughPutData_t *throughPutData)
{
	memset(throughPutData,0,sizeof(throughPutData_t));
}

static void setDelayResultDefaults(delayData_t *delayData)
{
	memset(delayData,0,sizeof(delayData_t));
}

static void setFrameLossResultDefaults(frameLossData_t *frameLossData)
{
	memset(frameLossData,0,sizeof(frameLossData_t));
}

static void setBackToBackResultDefaults(backToBackData_t *backToBackData)
{
	memset(backToBackData,0,sizeof(backToBackData_t));
}

static void setSaRfc2544ResultDefaults(rfc2544Results_t	*rfc2544Results)
{
	uint8_t	k;

	for (k=0; k<MAX_NUM_FRAME_SIZES; k++)
	{
		setThruputResultDefaults(&(rfc2544Results->throughPutResults.data[k]));
		setDelayResultDefaults(&(rfc2544Results->delayResults.data[k]));
		setFrameLossResultDefaults(&(rfc2544Results->frameLossResults.data[k]));
		setBackToBackResultDefaults(&(rfc2544Results->backToBackResults.data[k]));
	}
}

static void setSaY1564ResultDefaults(y1564Results_t *y1564Results)
{
	memset(y1564Results,0,sizeof(y1564Results_t));
}


/*
 * @brief Sets the SA Context values to defaults
 *
 */
static void clearSaContext(void)
{
	// Initialise all to defaults
	setSaRfc2544CfgDefaults(&saRfc2544Cfg);
	setSaY1564CfgDefaults(&saY1564Cfg);
	setSaRfc2544ResultDefaults(&saRfc2544Results);
	setSaY1564ResultDefaults(&saY1564Results);
}

/*
 *
 */
void saInit(void)
{
	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("SA");

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

	// Create a mutex to protect the reading of results if we don't already have one
	if (saReadLock == NULL)
	{
		saReadLock = osRecursiveMutexCreate();
		ASSERT_NOT_NULL(saReadLock);
	}

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

		DO_LOG_DEBUG(log_handle,"Preparing the Service Activation for first use");
		clearSaContext();
		saInitialised = true;
	}
}

/*
 * @brief This will return the current date and time in a format that can be used by PM sessions
 */
static dateAndTime_t getCurrentRTC(void)
{
	static dateAndTime_t timeNow;
	struct tm 	*currtime;
	rtcRaw_t 	rtcTime;

	// Get the current time
	rtcGetTime(&rtcTime);

	// Convert it to local time
	currtime		= localtime((time_t *)&rtcTime.seconds);
	timeNow.hour	= (uint8_t)currtime->tm_hour;
	timeNow.min		= (uint8_t)currtime->tm_min;
	timeNow.seconds = (uint8_t)currtime->tm_sec;
	timeNow.year	= (uint16_t)(currtime->tm_year + 1900);
	timeNow.month	= (uint8_t)(currtime->tm_mon + 1);
	timeNow.day		= (uint8_t)currtime->tm_mday;
	timeNow.deciSeconds = (uint8_t)(rtcTime.nanoSec/100000000);
	timeNow.utcDir = '+';
	timeNow.utcHours = 0;
	timeNow.utcMinutes = 0;
	return(timeNow);
}

int rfc2544UpdateConfig(rfc2544Cfg_t *rfc2544Cfg)
{
	LOCK_SA_LIST(0);
	if (saActiveFlag)
	{
		UNLOCK_SA_LIST();
		return(3);		// Operation failed because a test is already active
	}
	else
	{
		memcpy(&saRfc2544Cfg,rfc2544Cfg,sizeof(rfc2544Cfg_t));
	}
	UNLOCK_SA_LIST();
	DO_LOG_DEBUG(log_handle,"RFC2544 Configuration changed");
	return (4);
}

int y1564UpdateConfig(y1564Cfg_t *y1564Cfg)
{
	LOCK_SA_LIST(0);
	if (saActiveFlag)
	{
		UNLOCK_SA_LIST();
		return(3);		// Operation successful
	}
	else
	{
		memcpy(&saY1564Cfg,y1564Cfg,sizeof(y1564Cfg_t));
	}
	UNLOCK_SA_LIST();
	DO_LOG_DEBUG(log_handle,"Y1564 Configuration changed");
	return (4);
}

// -----------------------------------------------------------------------------------------------------
// processStats
// -----------------------------------------------------------------------------------------------------
// This function is called to process the raw 1-second sampled Loopback Rx data and generate
// per-second measurement information which is store it the y1564OneSecEntry_t data structure.
// This function acts on a single Loopback channel at a time, identified by the chnl parameter.
// It updates a count of valid entries that have been processed.
// The function has an embedded algorithm for 'auto-starting' measurement. It will detect
// when non-zero rx counts are received and start producing valid measurements on the next
// sample. If 0 rx counts are detected for a MR_TST_MAX_START samples then it be begin generating
// measurements despite no received packets.
// In 2-way operation (en1Way == 0) this function will stop Tx on all channels in the Gang Mask
// (if gang mode is enabled) and the channel itself, when the testDuration value has been
// reached.
// Packet will continue to be processed for tstTLag seconds after the testDuration has been
// reached in order to allow time for in-flight packets to be received.
// =====================================================================================================
void processStats(uint8_t *count, uint8_t chnl,uint32_t txCnt,uint32_t rxCnt,y1564OneSecEntry_t* OneSecEntry, uint16_t pktSize, bool en1Way)
{
	uint32_t	deltaRx;
	uint32_t	deltaTx;
	uint8_t	selGang;
	uint8_t	k;
	bool	prevAvailSt;

	// capture the current rx and tx counts.
	OneSecEntry->fifoRxCntVal = rxCnt;
	OneSecEntry->fifoTxSeqNum = txCnt;

	// Check for non-zero received and auto-start returning valid measurements
	// Force a start after MR_TST_MAX_START samples if rxCnt remains 0
	if ((rxCnt == 0) && (tstValidCnt[chnl] == 0) && (tstStartCnt[chnl] < MR_TST_MAX_START))
	{
		// count the number of samples for which no packets are received
		// at the start of the test (e.g. broken link) and limit this
		// to MR_TST_MAX_START seconds before returning valid values.
		tstStartCnt[chnl]++;
		*count = 0;				// indicate no valid value returned

		return;
	}
	else
	{
		// The start counter has hit it's max
		// or there is a non-zero rxCnt
		// or tstValidCnt is > 0
		// i.e. values are now being calculated and returned

		tstStartCnt[chnl] = MR_TST_MAX_START;	// once started ensure it stays sampling

		// Only run this code once
		if (tstFirstSamp[chnl])
		{
			// This is the very first sample on this channel

			// start measurements on all other channels in the ganged list...
			selGang = MAX_LB_CHANNELS_FOR_Y1564;
			for (k = 0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
			{
				// search for the gangList entry that matches this channel
				if (gangMaskList[k] & (0x1 << chnl))
				{
					selGang = k;
					break;
				}
			}
			// force a start on all other channels in the gang
			for (k = 0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
			{
				if (gangMaskList[selGang] & (0x1<<k))
				{
					tstStartCnt[k] = MR_TST_MAX_START;
				}
			}

			// clear the flag so it doesn't occur on the next iteration
			tstFirstSamp[chnl] = False;

			// save the current values to the 'last' value holders
			tstLastSampRxCnt[chnl] = rxCnt;
			tstLastSampTxCnt[chnl] = txCnt;

			// add the current counts to the accumulated counters
			tstAcuRxCnt[chnl] += rxCnt;
			tstAcuTxCnt[chnl] += txCnt - 1;			// compensate for the Seq No starting at 1 not 0

			*count = 0;				// indicate no valid value returned
		}
		else
		{
			// After startup and the 1st value has been received all further
			// calls will be to here to process the FIFO data.

			// calculate the tx and rx counter deltas
			deltaRx = rxCnt - tstLastSampRxCnt[chnl];
			deltaTx = txCnt - tstLastSampTxCnt[chnl];

			if ((tstValidCnt[chnl] < tstDuration[chnl]) || (tstDuration[chnl] == 0))
			{
				// If the number of valid samples read from the FIFO so far is
				// < the test duration or the test duration is 0 (continuous)
				// then carry out all calculations and update the returned values

				tstAcuDeltaRxCnt[chnl] += deltaRx;
				tstAcuDeltaTxCnt[chnl] += deltaTx;

				OneSecEntry->irTx 		= (uint32_t) (deltaTx * ADJUST_FOR_UTILISED_LINE_RATE(pktSize));
				OneSecEntry->irTxMean	= (uint32_t)((tstAcuDeltaTxCnt[chnl] * ADJUST_FOR_UTILISED_LINE_RATE(pktSize)) / (tstValidCnt[chnl] + 1));

				OneSecEntry->irCurr 	= (uint32_t) (deltaRx * ADJUST_FOR_UTILISED_LINE_RATE(pktSize));
				if (OneSecEntry->irCurr < tstIrMin[chnl])
					tstIrMin[chnl] = OneSecEntry->irCurr;
				OneSecEntry->irMin = tstIrMin[chnl];

				if (OneSecEntry->irCurr > tstIrMax[chnl])
					tstIrMax[chnl] = OneSecEntry->irCurr;
				OneSecEntry->irMax = tstIrMax[chnl];

				OneSecEntry->irMean	= (uint32_t)((tstAcuDeltaRxCnt[chnl] * ADJUST_FOR_UTILISED_LINE_RATE(pktSize)) / (tstValidCnt[chnl] + 1));

				OneSecEntry->flCount = tstAcuTxCnt[chnl] - tstAcuRxCnt[chnl];

				// Prevent division by 0 when calculating the sampled FLR value...
				if (deltaTx == 0)
					OneSecEntry->flr = 1;
				else
					OneSecEntry->flr = (double)(((double)(deltaTx - deltaRx)/(deltaTx)));

				// The Frame Loss is acceptable
				// There is a hysterisis type filter on the available counter. e.g.
				// if THRESHOLD = 10 then:
				// AAAAAUAAAAAAAAAAAAUUUAAUUUUUUUUUUAUAUAAAAAAAAAAAAAAAAAAA
				// |--------- Avail -----|              |------ Avail ----|
				//                        |---UnAvail--|
				prevAvailSt = availSt[chnl];

				if (OneSecEntry->flr <= tstFlrThres[chnl])
				{
					// count consecutive available seconds...
					// but clamp the consecutive counter at MR_AVAIL_THRESH + 1 (saves storage)
					if (consecAvailSec[chnl] <= MR_AVAIL_THRESH)
						consecAvailSec[chnl]++;

					// if the consecutive avail count equals or exceeds
					// the threshold make the available True
					if (consecAvailSec[chnl] >= MR_AVAIL_THRESH)
						availSt[chnl] = True;

					// reset the consecutive unAvail counter
					consecUnAvailSec[chnl] = 0;
				}
				else
				{
                    // count consecutive available seconds...
                    // but clamp the consecutive counter at MR_AVAIL_THRESH + 1 (saves storage)
                    if (consecUnAvailSec[chnl] <= MR_AVAIL_THRESH)
                    	consecUnAvailSec[chnl]++;

                    // if the consecutive unAvail count equals or exceeds the
                    // threshold make the available False
                    if (consecUnAvailSec[chnl] >= MR_AVAIL_THRESH)
                    	availSt[chnl] = False;

                    // reset the consecutive avail counter
                    consecAvailSec[chnl] = 0;
				}
				// if the current state is Available = true then continue to count totalAvailSec
				if (availSt[chnl])
					if (prevAvailSt != availSt[chnl] )
						availSecCnt[chnl] += MR_AVAIL_THRESH;
					else
						availSecCnt[chnl]++;
				else
				{
					// when the state changes to false deduct the threshold to
					// compensate for the previous consecutive unavailable seconds
					if (prevAvailSt != availSt[chnl] )
						availSecCnt[chnl] -= MR_AVAIL_THRESH;
				}
				OneSecEntry->availSecCnt = availSecCnt[chnl];

				// inc the valid count for the channel
				tstValidCnt[chnl]++;
				(*count)++;	   // indicate 1 new value returned

			}
			else
			{

				// Stop transmit on all other channels in the ganged list...
				selGang = MAX_LB_CHANNELS_FOR_Y1564;
				for (k = 0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
				{
					// search for the gangList entry that matches this channel
					if (gangMaskList[k] & (0x1 << chnl))
					{
						selGang = k;
						break;
					}
				}

				if (!en1Way)
				{
					// In 1-Way mode the Tx is remotely located and cannot be stopped.
					// This code is only run in loopback operation.

					// Stop Transmit on all other channels in the gang
					disableLB(gangMaskList[selGang] & 0x7FFF, MR_TX_ONLY);

					// once the desired duration is reached stop transmit on the channel. Rx remains enabled.
					// It's possible that selGang (on previous line) selects a null mask (i.e. ganged mode is off)
					// so this line is still required.
					disableLBTxChnl(chnl);
				}

				// all valid samples have been read, now just wait for packets in flight
				if (tstTLag[chnl] > 0)
					tstTLag[chnl]--;

				//*count = 0;	   // indicate no valid value returned
			}

			// In all cases continue to update the following info

			// add the current counts to the accumulated counters
			tstAcuRxCnt[chnl] += deltaRx;
			tstAcuTxCnt[chnl] += deltaTx;

			// save the current values to the 'last' value holders
			tstLastSampRxCnt[chnl] = rxCnt;
			tstLastSampTxCnt[chnl] = txCnt;
		}
	}
}


// -----------------------------------------------------------------------------------------------------
// readY1564Data
// -----------------------------------------------------------------------------------------------------
// This function reads and processes any new values stored in the 1-sec HW FIFO that
// samples the TST and Loopback receive channels. If there are new values they are
// processed and returned in the y1564DataEntry_t data structure.
// The function will stop Transmit data when a pre-set testDuration value has been
// achieved. It will stop processing data after a programmable tstTLag has elapsed,
// allowing time for in-flight packets to arrive at the receiver, and will set a
// testDone flag in the y1564DataEntry_t to True indicating that the test is complete.
// =====================================================================================================
int	readSaData(uint8_t chnl, bool en1Way,y1564DataEntry_t *y1564Data, uint8_t *newHead)
{
	uint32_t		data;
	uint32_t		txCnt;
	uint32_t		rxCnt;
	uint8_t			currHead;
	uint8_t			k;
	uint8_t			count;
	uint16_t		pktSize;
	uint32_t		txPktCount;
	double			ave;
	lbrRamEntry_t	lbrRamEntry;
	int				retVal;

	// set the read count to 0 initially in case there are any errors.
	y1564Data->validCnt = 0;

	// get the current head pointer. This is the next free location in the FIFO.
	mrHalRegRead(CLR_LBR_ERR_REG_ADDR,&data);
	currHead = (uint8_t) ((data >> 5) & 0x7);

	//mrHalRegRead(LB_PKT_SIZE_REG_ADDR,&data);
	//pktSize = (uint16_t)((data & LB_PKT_SIZE_REG_PKT_SIZE_MASK) >> LB_PKT_SIZE_REG_PKT_SIZE_OFFSET);
	pktSize = tstRxPktSize[chnl];

	// tstTailPtr[chnl] is a globally maintained variable indicating the location of the
	// head pointer during the last SW read from the FIFO. It is analogous to the 'tail'
	count = 0;
	if (tstTailPtr[chnl] > currHead)
	{
		// read from tail to top of FIFO
		for (k=tstTailPtr[chnl];k<MR_LBR_1SEC_FIFO_DEPTH;k++)
		{
			readLBRPerSecFiFo(chnl,k,&txCnt, &rxCnt);
			processStats(&count,chnl,txCnt,rxCnt,&y1564Data->irFlData[count],pktSize,en1Way);
		}
		// also read from bottom of FIFO to current head
		for (k=0;k<currHead;k++)
		{
			readLBRPerSecFiFo(chnl,k,&txCnt, &rxCnt);
			processStats(&count,chnl,txCnt,rxCnt,&y1564Data->irFlData[count],pktSize,en1Way);
		}
	}
	else
	{
		// read from tail to head only.
		for (k=tstTailPtr[chnl];k<currHead;k++)
		{
			readLBRPerSecFiFo(chnl,k,&txCnt, &rxCnt);
			processStats(&count,chnl,txCnt,rxCnt,&y1564Data->irFlData[count],pktSize,en1Way);
		}
	}

	y1564Data->validCnt = count;
	tstTailPtr[chnl] = currHead;
	*newHead = currHead;

	if (tstDuration[chnl] == 0)
		// If the test duration is set to 0 then run continuously
		y1564Data->testDone = False;
	else
	{
		if ((tstValidCnt[chnl] >= tstDuration[chnl]) && (tstTLag[chnl] == 0))
		{
			y1564Data->testDone = True;
			// once the test is totally finished and it's not a 1-way test
			// stop the channels Tx and Rx.
			if (!en1Way)
				disableLBChnl(chnl,MR_BOTH_TX_RX);
		}
		else
			y1564Data->testDone = False;
	}

	y1564Data->totalSamples = tstValidCnt[chnl];

	//--------------------- Get the non-sampled info --------------

	// Get the tx count from the Tx Template sequence number, but subtract 1 to get the count!
	if (en1Way)
		// if it's a 1-way test then the txPktCount is not available
		txPktCount = 0;
	else
	{
		if ((retVal = readSeqNumInTxTempl(chnl,&txPktCount,LB_TEMPL)))
			return(retVal);
	}

	if ((retVal = readLBRRam(chnl,&lbrRamEntry)))
		return(retVal);

/*		if ((retVal = mrHalRegRead(LBR_ERR_REG_ADDR,&data)))
			return(retVal);

	lbStats->sequenceErr = (uint8_t) ((data >> lbChnl) & 0x1);
	lbStats->timeoutErr	 = (uint8_t) ((data >> (lbChnl+8)) & 0x1);
*/

	// initialise the LB RAMs....
	if (!en1Way)
	{
		tstAcuTotalTxCnt[chnl] += (txPktCount - tstLastTxCnt[chnl]);
		tstLastTxCnt[chnl] = txPktCount;
		y1564Data->delayData.txCnt = tstAcuTotalTxCnt[chnl];		// Number of transmitted LBM messages
	}
	tstAcuTotalRxCnt[chnl] += (lbrRamEntry.numRxLbr - tstLastRxCnt[chnl]);
	tstLastRxCnt[chnl] = lbrRamEntry.numRxLbr;
	y1564Data->delayData.rxCnt = tstAcuTotalRxCnt[chnl];		// Number of received LBR messages on this loopback channel

	y1564Data->delayData.ftdCurr = lbrRamEntry.lastLat;
	y1564Data->delayData.ftdMin = lbrRamEntry.minLat;			// Measured minimum latency. This value is stored in terms of units of 256ns
	y1564Data->delayData.ftdMax = lbrRamEntry.maxLat;			// Measured maximum latency. This value is stored in terms of units of 256ns
	y1564Data->delayData.fdvCurr = lbrRamEntry.lastJitt;
	y1564Data->delayData.fdvMin = lbrRamEntry.minJitt;			// Measured minimum latency. This value is stored in terms of units of 256ns
	if (y1564Data->delayData.rxCnt <= 1)
	{
		// If only 1 rx packet is received then no FDV was calculated (but initial HW value is All F's)  so force this to 0
		y1564Data->delayData.fdvMax = 0;			// Measured maximum latency. This value is stored in terms of units of 256ns
	}
	else
	{
		y1564Data->delayData.fdvMax = lbrRamEntry.maxJitt;			// Measured maximum latency. This value is stored in terms of units of 256ns
	}
	ave = ((double)lbrRamEntry.sumLatUpp) * pow(2,32);
	ave += lbrRamEntry.sumLatLow;
	//ave = ave * 100;
	ave = ave / tstAcuTotalRxCnt[chnl];
	y1564Data->delayData.ftdMean = (uint32_t) ave;
	ave = ((double)lbrRamEntry.sumJitUpp) * pow(2,32);
	ave += lbrRamEntry.sumJitLow;
	//ave = ave * 100;
	ave = ave / (tstAcuTotalRxCnt[chnl]-1);
	y1564Data->delayData.fdvMean = (uint32_t) ave;
	return(NO_ERR);
}

// -----------------------------------------------------------------------------------------------------
// initY1564
// -----------------------------------------------------------------------------------------------------
// This function is called only for 1 and 2-Way test.
// for 2-way - It initialises context and enables both Tx and Rx on all channels
// in the chnlMask.
// For 1-way only Rx is enabled - this function should only be called for the Rx
// side of a 1-way test! In which case selDir = rx_only
// In both cases the testDuration,pktSize and flThres are
// all used to initialise the context used during following readY1564Data() calls.
// =====================================================================================================
int	initSaTest(uint32_t testDuration,uint16_t pktSize, svcDir_t	selDir, uint16_t chnlMask, double *flThres)
{
	uint8_t		k;
	uint8_t		currHead;
	uint32_t	data;
	uint8_t		selMask;

	selMask = MAX_LB_CHANNELS_FOR_Y1564; //initially set to an invalid value

	// clear the default gangMask so no channels are in the gang.
	gangMaskList[selMask] = 0;

	// Initialise the local parameters for all masked channels
	for (k=0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
	{
		// clear any old channel masks that have bits
		// set that match any enabled channel in this mask
		if (gangMaskList[k] & chnlMask)
			gangMaskList[k] = 0;

		// only allocate a new ganged Mask if the GANGED_OPERATION bit is
		// set in the supplied chnlMask and there is a free entry and
		// it has not already been allocated
		if ((gangMaskList[k] == 0) && (selMask >= MAX_LB_CHANNELS_FOR_Y1564) && (chnlMask & GANGED_OPERATION))
			selMask = k;

		if (chnlMask & (0x1<<k))
		{
			// These Tx related parameters are used for calculations based
			// on the live Tx Seq number.
			tstAcuTotalTxCnt[k] = 0;
			tstLastTxCnt[k] = 1;  //This is 1 because the seq No starts at 1 not 0!

			// These Tx related parameters are used for calculations based
			// on the received sequence number NOT the live Tx Seq number.
			tstAcuTxCnt[k] = 0;
			tstLastSampTxCnt[k] = 0;
			tstAcuDeltaTxCnt[k] = 0;
			tstRxPktSize[k] = pktSize;
			tstAcuRxCnt[k] = 0;
			tstLastSampRxCnt[k] = 0;
			tstValidCnt[k] = 0;
			tstDuration[k] = testDuration;
			tstAcuDeltaRxCnt[k] = 0;
			tstAcuTotalRxCnt[k] = 0;
			consecAvailSec[k] = 0;
			consecUnAvailSec[k] = 0;
			availSecCnt[k] = 0;
			availSt[k] = False;
			tstFirstSamp[k] = True;
			tstIrMin[k] = 0xFFFFFFFF;
			tstIrMax[k] = 0;
			tstFlrThres[k] = flThres[k];
			tstStartCnt[k] = 0;
			tstLastRxCnt[k] = 0;
			tstTLag[k] = 1;				// This means the test will allow 4 sec for pkts in flight

			// Clear the stats on all masked channels before starting
			_lbClearStats(k,1,selDir);
		}
	}

	// save the current channel mask into the selected location
	// This is used to control 'ganged' channel operation
	if (selMask < MAX_LB_CHANNELS_FOR_Y1564)
		gangMaskList[selMask] = chnlMask;

	// get the current head pointer. This is the next free location in the FIFO.
	mrHalRegRead(CLR_LBR_ERR_REG_ADDR,&data);
	currHead = (uint8_t) ((data >> 5) & 0x7);

	// Initialise the tail pointer for all masked channels to the current Head
	for (k=0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
	{
		if (chnlMask & (0x1<<k))
			tstTailPtr[k] = currHead;
	}

	// Enable all masked channels
	enableLB(chnlMask,selDir);

	return(0);
}

static int configureLbForMepTarget(mepIndices_t *mepIndices, uint16_t packetSize, svcDir_t selDir, uint8_t lbChnl, mrPort_t *port, bool forceDe, uint8_t de)
{
	lMepNum_t			lMepNum;
	rMepNum_t			rMepNum;
	uint16_t			dataTlvLength;
	txTemplateEntry_t	txLBMTempl;
	rMepDB_t 			rMepDB;

	// Make sure we only process valid packet sizes
	if ((packetSize < 60) || (packetSize >= (MAX_LBM_PKT_SIZE - 4)))
	{
		DO_LOG_ERROR(log_handle,"configureLbForMepTarget() failed because packet size = %d is not allowed (min = 60, max = %d)",packetSize, (MAX_LBM_PKT_SIZE - 4));
		return (-1);
	}
	//ASSERT(packetSize >= 60 && packetSize < (MAX_LBM_PKT_SIZE - 4),packetSize);

	if (!mepIsLMepIdValid(mepIndices->mdIndex, mepIndices->maIndex, mepIndices->lMepId))
	{
		return (1);
	}

	lMepNum = _mepIdToMepNum(mepIndices->mdIndex, mepIndices->maIndex, mepIndices->lMepId);

	rMepNum = _rMepIdToRMepNum(mepIndices->mdIndex, mepIndices->maIndex, mepIndices->lMepId, mepIndices->rMepId);

	mepInfo_t	mepInfo;
	mepCcInfo_t	mepCcInfo;
	lMepRead(mepIndices->mdIndex, mepIndices->maIndex,mepIndices->lMepId,&mepInfo,&mepCcInfo,NULL,NULL,NULL,NULL,NULL);

	*port = mepInfo.iFace;

	tagEntry_t	innerTag;
	tagEntry_t	outerTag;
	generatePacketTags(
			&innerTag,
			&outerTag,
			getPrimaryVidForMep(mepIndices->mdIndex, mepIndices->maIndex, mepIndices->lMepId),
			(mepIndices->overwritePCP ? mepIndices->prio : mepCcInfo.ccmLtmPriority),
			(forceDe ? de : (mepIndices->overwritePCP ? mepIndices->de : 0)),		// For Y1564 tests we must control the DE to set frame colour
			_maGetTagType(mepIndices->mdIndex, mepIndices->maIndex),
			mepInfo.iFace);

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

	// The Frame length is made up of basic frame (23), dataTLV (3 + dataTlvLength), crc (4) and tags. This means dataTlvlength is:
	dataTlvLength = packetSize - 23 - 3 - 4 - (innerTag.present ? 4 : 0) - (outerTag.present ? 4 : 0);

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

	rMepDBRead(mepIndices->mdIndex, mepIndices->maIndex, mepIndices->lMepId, mepIndices->rMepId, &rMepDB);

	generateLBMTemplate(
			lMepNum,
			rMepNum,
			rMepDB.macAddress,
			mepInfo.iFace,
			dataTlvLength,
			mdMibInfo.level,
			&innerTag,
			&outerTag,
			false,			// No Multicast!
			&txLBMTempl);

	_configLBSvcOnLMep(lMepNum, false, &txLBMTempl, LB_TEMPL, selDir, lbChnl);

	return (0);
}

static int initLbChnl(uint8_t lbChnl, bool targetIsMep, mepIndices_t *targetMep, frameCfg_t *frameCfg, uint16_t packetSize, svcDir_t selDir, mrPort_t *port, bool forceDe, uint8_t de, bool colourAware)
{
	uint32_t	rdData;

	disableLBChnl(lbChnl,selDir);		// Make sure the channel is stopped before configuring it


	mrHalRegRead(LBR_ERR_REG_ADDR,&rdData);
	// Initially clear the ignore DE bit for the channel - i.e. colour aware
	rdData &= ~(LBR_ERR_REG_IGDE_CHNL0_MASK << lbChnl);
	if (!colourAware)
		// if not colour aware then set the ignore DE bit for the channel
		rdData |= (LBR_ERR_REG_IGDE_CHNL0_MASK << lbChnl);
	mrHalRegWrite(LBR_ERR_REG_ADDR,rdData);

	// Configure LB channel for the required packet...
	if (targetIsMep)
	{
		// The target is identified as a MEP, so get the info needed to configure the loopback packet
		return(configureLbForMepTarget(targetMep, packetSize, selDir, lbChnl, port, forceDe, de));
	}
	else
	{
		// TODO: add code to configure the custom packet
	    EGL_ASSERT(0,1);		// NOT supported yet so automatic assert if it happens
		*port = frameCfg->port;
		return(0);
	}
}

static bool testThroughputRate(rfc2544TestType_t testType, uint8_t frameIndex, uint16_t frameSize, uint8_t lbChnl, svcDir_t selDir, mrPort_t port, uint32_t testRate, uint32_t testDuration, uint32_t acceptableLoss, rfc2544Results_t *rfc2544Results)
{
	y1564DataEntry_t	y1564Data;
	double				flrList[MAX_LB_CHANNELS_FOR_Y1564];
	double 				measuredflr;
	uint8_t				k;
	uint8_t 			newHead;
	uint16_t			testRateInMbps;
	bool				testDone = false;

	disableLBChnl(lbChnl, selDir);		// Make sure the channel is stopped before configuring it

	if (testRate == 0)
	{
		if (testType == delayTest)
		{
			rfc2544Results->delayResults.data[frameIndex].frameRate = 0;
		}
		if (testType == frameLossTest)
		{
			rfc2544Results->frameLossResults.data[frameIndex].frameLoss[rfc2544Results->frameLossResults.data[frameIndex].numSteps] = (uint32_t) (FLR_TO_FRAME_LOSS_MILLI_PERCENT(1));
		}
		return(true);
	}

    // rate limit to 100% line rate
    if (testRate > 1000000)
    	testRate = 1000000;

    testRateInMbps = (uint16_t)CONVERT_KBPS_TO_MBPS(testRate);

    // Setup the appropriate rates
    _lbSetRate(lbChnl, testRateInMbps);

    _lbClearStats(lbChnl, 1, selDir);

    _lbSetMode(lbChnl, lb_continuousMode, port);

    for(k=0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
    {
    	flrList[k] = 0;
    }
    flrList[0] = FRAME_LOSS_MILLI_PERCENT_TO_FLR(acceptableLoss);    // Set the Green Channel loss to the user set value

    //
    initSaTest(testDuration, frameSize, selDir, EN_CHNL_0, flrList);

    do
    {
    	osSleepMS(1000);

        // Before proceeding check if the user has requested to cancel the test
        if (saStopFlag)
        {
        	goto STOP_TP_RATE_TEST;
        }

        readSaData(lbChnl, false,&y1564Data, &newHead);		// TODO: consider adding one-way support later

		if (y1564Data.validCnt > 0)
		{
			LOCK_SA_READ(0);
			//DO_LOG_DEBUG(log_handle,"Test result:valid count = %d, txCnt = %llu, rxCnt = %llu \n", y1564Data.validCnt, (unsigned long long)y1564Data.delayData.txCnt, (unsigned long long)y1564Data.delayData.rxCnt);
			if (testType == thruPutTest)
			{
				rfc2544Results->throughPutResults.data[frameIndex].elapsedTime	= y1564Data.totalSamples;
			}

			if (testType == delayTest)
			{
				rfc2544Results->delayResults.data[frameIndex].elapsedTime		= y1564Data.totalSamples;
				//rfc2544Results->delayResults.data[frameIndex].frameRate			= y1564Data.irFlData[y1564Data.validCnt-1].irTxMean;

				rfc2544Results->delayResults.data[frameIndex].frameDelayVarMax	= (uint32_t)round(y1564Data.delayData.fdvMin * 0.256);
				rfc2544Results->delayResults.data[frameIndex].frameDelayVarMax	= (uint32_t)round(y1564Data.delayData.fdvMax * 0.256);
				rfc2544Results->delayResults.data[frameIndex].frameDelayVarAvg	= (uint32_t)round(y1564Data.delayData.fdvMean * 0.256);

				rfc2544Results->delayResults.data[frameIndex].frameDelayMin		= (uint32_t)round(y1564Data.delayData.ftdMin * 0.256);
				rfc2544Results->delayResults.data[frameIndex].frameDelayMax		= (uint32_t)round(y1564Data.delayData.ftdMax * 0.256);
				rfc2544Results->delayResults.data[frameIndex].frameDelayAvg		= (uint32_t)round(y1564Data.delayData.ftdMean * 0.256);
			}

			if(y1564Data.delayData.txCnt == 0)
			{
				measuredflr = 1;
			}
			else
			{
				measuredflr = ((double)(y1564Data.delayData.txCnt - y1564Data.delayData.rxCnt)) / (double)y1564Data.delayData.txCnt;
			}

			if (testType == frameLossTest)
			{
				//rfc2544Results->frameLossResults.data[frameIndex].elapsedTime	= y1564Data.totalSamples;
				// The global result struct (saRfc2544Results) is used because for testType == frameLossTest 'fc2544Results' is a pointer to a temp set of results not the actual result
				saRfc2544Results.frameLossResults.data[frameIndex].elapsedTime	= y1564Data.totalSamples;
				rfc2544Results->frameLossResults.data[frameIndex].frameLoss[rfc2544Results->frameLossResults.data[frameIndex].numSteps] = (uint32_t) (FLR_TO_FRAME_LOSS_MILLI_PERCENT(measuredflr));
			}
			DO_LOG_DEBUG(log_handle,"Elapsed time = %lu",y1564Data.totalSamples);
			UNLOCK_SA_READ();
		}


        if (y1564Data.testDone != 0)
        {
        	LOCK_SA_READ(0);
			// The test is done...validCnt will most likely be 0 for the last few reads but the
			// the non-sampled values (delay/delay var and pkt counts must be read now to
			// get the final test values
        	if (testType == thruPutTest)
			{
				rfc2544Results->throughPutResults.data[frameIndex].elapsedTime	= y1564Data.totalSamples;
			}

			if (testType == delayTest)
			{
				rfc2544Results->delayResults.data[frameIndex].frameDelayVarMax = (uint32_t)round(y1564Data.delayData.fdvMin * 0.256);
				rfc2544Results->delayResults.data[frameIndex].frameDelayVarMax = (uint32_t)round(y1564Data.delayData.fdvMax * 0.256);
				rfc2544Results->delayResults.data[frameIndex].frameDelayVarAvg = (uint32_t)round(y1564Data.delayData.fdvMean * 0.256);

				rfc2544Results->delayResults.data[frameIndex].frameDelayMin = (uint32_t)round(y1564Data.delayData.ftdMin * 0.256);
				rfc2544Results->delayResults.data[frameIndex].frameDelayMax = (uint32_t)round(y1564Data.delayData.ftdMax * 0.256);
				rfc2544Results->delayResults.data[frameIndex].frameDelayAvg = (uint32_t)round(y1564Data.delayData.ftdMean * 0.256);
        	}

			if(y1564Data.delayData.txCnt == 0)
			{
				measuredflr = 1;
			}
			else
			{
				measuredflr = ((double)(y1564Data.delayData.txCnt - y1564Data.delayData.rxCnt)) / (double)y1564Data.delayData.txCnt;
			}

			if (testType == frameLossTest)
			{
				rfc2544Results->frameLossResults.data[frameIndex].frameLoss[rfc2544Results->frameLossResults.data[frameIndex].numSteps] = (uint32_t) (FLR_TO_FRAME_LOSS_MILLI_PERCENT(measuredflr));
				//rfc2544Results->frameLossResults.data[frameIndex].elapsedTime	= y1564Data.totalSamples;
				// The global result struct (saRfc2544Results) is used because for testType == frameLossTest 'fc2544Results' is a pointer to a temp set of results not the actual result
				saRfc2544Results.frameLossResults.data[frameIndex].elapsedTime	= y1564Data.totalSamples;
			}

			// set to zero to indicate that the test is completed on this channel
			testDone = true;

			DO_LOG_DEBUG(log_handle,"Final Elapsed time = %lu",y1564Data.totalSamples);
			UNLOCK_SA_READ();
        }

    } while(!testDone);

	disableLBChnl(lbChnl, selDir);		// Make sure the channel is stopped before configuring it

	DO_LOG_DEBUG(log_handle," Test result: frame size = %d, rate = %dMbps, txCnt = %llu, rxCnt = %llu, FLR = %f",frameSize, testRateInMbps, (unsigned long long)y1564Data.delayData.txCnt, (unsigned long long)y1564Data.delayData.rxCnt, measuredflr);
    if (measuredflr > FRAME_LOSS_MILLI_PERCENT_TO_FLR(acceptableLoss))
    {
    	// The measured Frame Loss is greater than the acceptable loss, so this test fails
STOP_TP_RATE_TEST:
    	return(false);
   	}
    return(true);
}

static int runThroughputBinarySearch(rfc2544TestType_t testType, uint8_t resultIndex, uint16_t frameSize, uint8_t lbChnl, svcDir_t selDir, rfc2544Cfg_t *rfc2544Cfg, rfc2544Results_t *rfc2544Results, mrPort_t	*port)
{
	uint32_t	testRate;
	uint32_t	upSearchLimit;
	uint32_t	loSearchLimit;

	// Configure the loopback channel to send the correct frames (size and content)
	if (initLbChnl(lbChnl, rfc2544Cfg->targetIsMep, &rfc2544Cfg->targetMep, &rfc2544Cfg->frameCfg, frameSize, selDir, port, false, 0, false))
	{
		// There was a problem with the service configuration
    	rfc2544Results->throughPutResults.status = test_failed;
    	return(1);		// Stopped by user
	}

	upSearchLimit = rfc2544Cfg->throughPutCfg.maxRate;
	loSearchLimit = rfc2544Cfg->throughPutCfg.minRate;

NEXT_BINARY_STEP:
	testRate = round(((upSearchLimit + loSearchLimit) / 2) / RATE_RESOLUTION_IN_KBPS) * RATE_RESOLUTION_IN_KBPS; // trim the search Value to the nearest step resolution

	// Update this so results will indicate what frame rate is being tested
	rfc2544Results->throughPutResults.data[resultIndex].frameRate = testRate;
	if (testRate == 0)
	{
		goto BINARY_TEST_DONE;
	}

	// Test if the frame rate is received without any loss
	if (testThroughputRate(testType,resultIndex,frameSize,lbChnl,selDir,*port,testRate, rfc2544Cfg->binaryDuration,rfc2544Cfg->throughPutCfg.acceptedFrameLoss,rfc2544Results))
	{
        // The selected rate PASSED..
        // move the search limits such that:
		DO_LOG_DEBUG(log_handle,"Throughput test: frame size = %d, rate = %dkbps PASSED",frameSize, testRate);

        // move sL up to the last passing value
		loSearchLimit = testRate;

        // If sL = sU and the test just passed then we're done!
       if(loSearchLimit == upSearchLimit)
    	{
    	   goto BINARY_TEST_DONE;
    	}

        // if sU and sL differ by only 1 step then set them both to sU and re-test
        if( loSearchLimit == (upSearchLimit - RATE_RESOLUTION_IN_KBPS))
        {
        	loSearchLimit = upSearchLimit;
        }
        goto NEXT_BINARY_STEP;
	}
	else
	{
		DO_LOG_DEBUG(log_handle,"Throughput test: frame size = %d, rate = %dkbps FAILED",frameSize, testRate);

        // Before proceeding check if the user has requested to cancel the test
        if (saStopFlag)
        {
        	rfc2544Results->throughPutResults.status = test_stopped;
        	return(1);		// Stopped by user
        }

        // The selected rate FAILED...
		// move sU down to the last failing value
        upSearchLimit = testRate;

		// if sU and sL differ by only 1 step then set them both to sL and re-test
		if (upSearchLimit == (loSearchLimit + RATE_RESOLUTION_IN_KBPS))
		{
			upSearchLimit = loSearchLimit;
		}
		else
		{
			// if test fails when sL=sU then we went 1 step too far..step back by 1
			if (loSearchLimit == upSearchLimit)
			{
				loSearchLimit = loSearchLimit - RATE_RESOLUTION_IN_KBPS;
				upSearchLimit = loSearchLimit;
			}
		}
		goto NEXT_BINARY_STEP;
	}

BINARY_TEST_DONE:
	rfc2544Results->throughPutResults.data[resultIndex].elapsedTime = rfc2544Cfg->binaryDuration;
	rfc2544Results->throughPutResults.status = test_completed;
	return (2);				// Test completed
}



static int runThroughPutTest(uint8_t lbChnl, svcDir_t selDir, rfc2544Cfg_t *rfc2544Cfg, rfc2544Results_t *rfc2544Results)
{
	uint8_t		f;
	int			retVal;
	mrPort_t	port;

	rfc2544Results->throughPutResults.startTime = getCurrentRTC();
	rfc2544Results->throughPutResults.status = test_running;

	DO_LOG_DEBUG(log_handle,"Throughput test started (binary test step duration = %d sec)",rfc2544Cfg->binaryDuration);
	f = 0;
	while(rfc2544Cfg->throughPutCfg.frameSizeList[f] != 0 && f < MAX_NUM_FRAME_SIZES)
	{
		retVal = runThroughputBinarySearch(thruPutTest, f, rfc2544Cfg->throughPutCfg.frameSizeList[f], lbChnl, selDir, rfc2544Cfg, rfc2544Results, &port);

		// Before proceeding check if the user has requested to cancel the test
		if (retVal != 2)
		{
			goto EXIT_TP_TEST;
		}

		if (rfc2544Results->throughPutResults.data[f].frameRate > 0)
		{
			// After the binary search re-test at the binary search result rate for the full trial duration.
			DO_LOG_DEBUG(log_handle,"Binary search completed testing throughput (%ul kbps) (trial duration = %lu sec)",rfc2544Results->throughPutResults.data[f].frameRate, rfc2544Cfg->throughPutCfg.trialDuration);
	RE_TEST_RATE:
			if (!testThroughputRate(thruPutTest, f,rfc2544Cfg->throughPutCfg.frameSizeList[f],lbChnl,selDir,port,rfc2544Results->throughPutResults.data[f].frameRate, rfc2544Cfg->throughPutCfg.trialDuration,rfc2544Cfg->throughPutCfg.acceptedFrameLoss,rfc2544Results))
			{

				// If the rate fails try to reduce the rate by our minimum step resolution (1Mb)
				if (rfc2544Results->throughPutResults.data[f].frameRate > 0)
				{
					rfc2544Results->throughPutResults.data[f].frameRate -= 1 * RATE_RESOLUTION_IN_KBPS;
					DO_LOG_DEBUG(log_handle,"thruPutTest failed - re-trying at %lu kbps for full trial duration",rfc2544Results->throughPutResults.data[f].frameRate);
					goto RE_TEST_RATE;
				}
			}
		}

		rfc2544Results->throughPutResults.data[f].status = test_completed;
		f++;
	} // while frameSizeList


	rfc2544Results->throughPutResults.endTime = getCurrentRTC();
	DO_LOG_DEBUG(log_handle,"Throughput test completed");
	sendSaStatusNotificaiton(log_handle, rfc2544ThruTest, test_completed);
	return (2);		// This is the normal exit if the test is left run to completion

EXIT_TP_TEST:
	// If exiting the function early due to users stopping test, then change all pending to stopped
	for (;f < MAX_NUM_FRAME_SIZES; f++)
	{
		if(rfc2544Results->throughPutResults.data[f].status == test_pending)
		{
			rfc2544Results->throughPutResults.data[f].status = test_stopped;
		}
	}
	rfc2544Results->throughPutResults.endTime = getCurrentRTC();
	DO_LOG_DEBUG(log_handle,"Throughput test stopped");
	sendSaStatusNotificaiton(log_handle, rfc2544ThruTest, test_stopped);
	return(1);
}


static int runDelayTest(uint8_t lbChnl, svcDir_t selDir, rfc2544Cfg_t *rfc2544Cfg, rfc2544Results_t *rfc2544Results)
{
	uint8_t				f, t;
	bool				tpDone;
	rfc2544Results_t	rfc2544ResultsTemp;
	int					retVal;
	mrPort_t			port;

	rfc2544Results->delayResults.startTime = getCurrentRTC();
	rfc2544Results->delayResults.status = test_running;

	DO_LOG_DEBUG(log_handle,"Delay test started (trial duration = %lu sec)",rfc2544Cfg->delayCfg.trialDuration);
	f = 0;
	while(rfc2544Cfg->delayCfg.frameSizeList[f] != 0 && f < MAX_NUM_FRAME_SIZES)
	{
		// Configure the loopback channel to send the correct frames (size and content)
		initLbChnl(lbChnl, rfc2544Cfg->targetIsMep, &rfc2544Cfg->targetMep, &rfc2544Cfg->frameCfg, rfc2544Cfg->delayCfg.frameSizeList[f], selDir, &port, false, 0, false);

		// Test all configured frame sizes...
		tpDone = false;
		// See if the throughput test was already run for this frame size...
		for(t = 0; t < MAX_NUM_FRAME_SIZES; t++)
		{
			if (rfc2544Results->throughPutResults.data[t].frameSize == rfc2544Cfg->delayCfg.frameSizeList[f] &&
					rfc2544Results->throughPutResults.data[t].status == test_completed)
			{
				tpDone = true;
				break;
			}
		}
		if (tpDone)
		{
			// update the delay results with the throughput rate that is being used
			rfc2544Results->delayResults.data[f].frameRate = rfc2544Results->throughPutResults.data[t].frameRate;
			// The throughput test was already run on this frame size so re-use the throughput rate but run the test for the delay trialDuration
			if (!testThroughputRate(delayTest,f,rfc2544Cfg->delayCfg.frameSizeList[f],lbChnl,selDir,port,rfc2544Results->throughPutResults.data[t].frameRate, rfc2544Cfg->delayCfg.trialDuration,rfc2544Cfg->delayCfg.acceptedFrameLoss,rfc2544Results))
			{
				//ASSERT(0,1);		// This test should not fail!!
				DO_LOG_ERROR(log_handle,"Delay test failed - **** this should not happen ****");
				rfc2544Results->delayResults.data[f].status = test_failed;
			}
			DO_LOG_DEBUG(log_handle,"Delay test: frame size = %d, rate = %dkbps",rfc2544Cfg->throughPutCfg.frameSizeList[f], rfc2544Results->throughPutResults.data[t].frameRate);
		}
		else
		{
			// The thoughput test was not already run for this frame size to re-run it, using temp results struct...
			retVal = runThroughputBinarySearch(delayTest,f, rfc2544Cfg->delayCfg.frameSizeList[f], lbChnl, selDir, rfc2544Cfg, &rfc2544ResultsTemp, &port);
			if (retVal != 2)
			{
				goto EXIT_DELAY_TEST;
			}
			// update the delay results with the throughput rate that is being used
			rfc2544Results->delayResults.data[f].frameRate = rfc2544ResultsTemp.throughPutResults.data[f].frameRate;
			// Next run the Delay test at the Throughput rate for the delay trial duration...
			if (!testThroughputRate(delayTest,f,rfc2544Cfg->delayCfg.frameSizeList[f],lbChnl,selDir,port,rfc2544ResultsTemp.throughPutResults.data[f].frameRate, rfc2544Cfg->delayCfg.trialDuration,rfc2544Cfg->delayCfg.acceptedFrameLoss,rfc2544Results))
			{
				//ASSERT(0,1);		// This test should not fail!!
				DO_LOG_ERROR(log_handle,"Delay test failed - **** this should not happen ****");
				rfc2544Results->delayResults.data[f].status = test_failed;
			}
			DO_LOG_DEBUG(log_handle,"Delay test: frame size = %d, rate = %dkbps",rfc2544Cfg->throughPutCfg.frameSizeList[f], rfc2544ResultsTemp.throughPutResults.data[f].frameRate);
		}

		// Before proceeding check if the user has requested to cancel the test
		if (saStopFlag)
		{
			goto EXIT_DELAY_TEST;
		}
		// only update the test if it's status is pending...it may have failed so we need to preserve the status in that case
		if (rfc2544Results->delayResults.data[f].status == test_pending)
		{
			rfc2544Results->delayResults.data[f].status = test_completed;
		}
		rfc2544Results->delayResults.data[f].elapsedTime = rfc2544Cfg->delayCfg.trialDuration;

		f++;
	} // while frameSizeList

	rfc2544Results->delayResults.endTime = getCurrentRTC();
	rfc2544Results->delayResults.status = test_completed;
	DO_LOG_DEBUG(log_handle,"Delay test completed");
	sendSaStatusNotificaiton(log_handle, rfc2544LatencyTest, test_completed);
	return (2);		// This is the normal exit if the test is left run to completion

	EXIT_DELAY_TEST:
	// If exiting the function early due to users stopping test, then change all pending to stopped
	for (;f < MAX_NUM_FRAME_SIZES; f++)
	{
		if(rfc2544Results->delayResults.data[f].status == test_pending)
		{
			rfc2544Results->delayResults.data[f].status = test_stopped;
		}
	}
	rfc2544Results->delayResults.endTime = getCurrentRTC();
	rfc2544Results->delayResults.status = test_stopped;
	DO_LOG_DEBUG(log_handle,"Delay test stopped");
	sendSaStatusNotificaiton(log_handle, rfc2544LatencyTest, test_stopped);
	return (1);
}


static int runFrameLossTest(uint8_t lbChnl, svcDir_t selDir, rfc2544Cfg_t *rfc2544Cfg, rfc2544Results_t *rfc2544Results)
{
	uint8_t				f,t;
	uint16_t			stepNo;
	bool 				oneGood, twoGood;
	rfc2544Results_t	rfc2544ResultsTemp;
	int32_t				testRate;
	mrPort_t			port;

	rfc2544Results->frameLossResults.startTime = getCurrentRTC();
	rfc2544Results->frameLossResults.status = test_running;

	DO_LOG_DEBUG(log_handle,"Frame Loss test started (trial duration = %lu sec)",rfc2544Cfg->frameLossCfg.trialDuration);
	f = 0;
	while(rfc2544Cfg->frameLossCfg.frameSizeList[f] != 0 && f < MAX_NUM_FRAME_SIZES)
	{
		// Configure the loopback channel to send the correct frames (size and content)
		initLbChnl(lbChnl, rfc2544Cfg->targetIsMep, &rfc2544Cfg->targetMep, &rfc2544Cfg->frameCfg, rfc2544Cfg->frameLossCfg.frameSizeList[f], selDir, &port, false , 0, false);

		// Test all configured frame sizes...
		rfc2544ResultsTemp.frameLossResults.data[f].numSteps = 0;
		rfc2544ResultsTemp.frameLossResults.data[f].frameSize = rfc2544Cfg->frameLossCfg.frameSizeList[f];

		oneGood = false;
		twoGood = false;
        stepNo = 0;
        for (testRate = rfc2544Cfg->frameLossCfg.maxRate; testRate >= ((int32_t)rfc2544Cfg->frameLossCfg.minRate); testRate -= rfc2544Cfg->frameLossCfg.stepSize)
        {
        	rfc2544ResultsTemp.frameLossResults.data[f].frameRate[rfc2544ResultsTemp.frameLossResults.data[f].numSteps] = testRate;
            if(testThroughputRate(frameLossTest, f,rfc2544Cfg->frameLossCfg.frameSizeList[f],lbChnl,selDir,port,testRate, rfc2544Cfg->frameLossCfg.trialDuration,rfc2544Cfg->frameLossCfg.acceptedFrameLoss,&rfc2544ResultsTemp))
            {
                if (oneGood)
                {
                    // if oneGood is true then the previous test passed
                    // now this test passed so set twoGood to true
                    twoGood = true;
                }
                else
                {
                    // if the previous test did not pass but this one did
                    // then set oneGood to true
                    oneGood = true;
                }
    			DO_LOG_DEBUG(log_handle,"Frame Loss test: frame size = %d, rate = %dkbps PASSED",rfc2544Cfg->frameLossCfg.frameSizeList[f], testRate);
            }
            else
            {
                // the test failed so clear both
                oneGood = false;
                twoGood = false;
       			DO_LOG_DEBUG(log_handle,"Frame Loss test: frame size = %d, rate = %dkbps FAILED",rfc2544Cfg->frameLossCfg.frameSizeList[f], testRate);
            }

            LOCK_SA_READ(0);
    		// Manage the saved step results...
    		stepNo++;
    		if (stepNo > MAX_FRAME_RATE_STEPS)
    		{
    			// we need to re-order results
    			for (t=0;t<MAX_FRAME_RATE_STEPS;t++)
    			{
    				rfc2544Results->frameLossResults.data[f].frameLoss[t] = rfc2544ResultsTemp.frameLossResults.data[f].frameLoss[(rfc2544ResultsTemp.frameLossResults.data[f].numSteps + t) % MAX_FRAME_RATE_STEPS];;
    				rfc2544Results->frameLossResults.data[f].frameRate[t] = rfc2544ResultsTemp.frameLossResults.data[f].frameRate[(rfc2544ResultsTemp.frameLossResults.data[f].numSteps + t) % MAX_FRAME_RATE_STEPS];;
    			}
    			rfc2544Results->frameLossResults.data[f].numSteps = MAX_FRAME_RATE_STEPS;
    		}
    		else
    		{
    			// Results are in order so copy from the temp to the real results...
    			rfc2544Results->frameLossResults.data[f].frameLoss[rfc2544ResultsTemp.frameLossResults.data[f].numSteps] = rfc2544ResultsTemp.frameLossResults.data[f].frameLoss[rfc2544ResultsTemp.frameLossResults.data[f].numSteps];
				rfc2544Results->frameLossResults.data[f].frameRate[rfc2544ResultsTemp.frameLossResults.data[f].numSteps] = rfc2544ResultsTemp.frameLossResults.data[f].frameRate[rfc2544ResultsTemp.frameLossResults.data[f].numSteps];;
    			rfc2544Results->frameLossResults.data[f].numSteps = stepNo;
    		}
    		rfc2544ResultsTemp.frameLossResults.data[f].numSteps = (rfc2544ResultsTemp.frameLossResults.data[f].numSteps + 1) % MAX_FRAME_RATE_STEPS;
    		UNLOCK_SA_READ();

            if (oneGood && twoGood)
            {
            	break;
            }

    		// Before proceeding check if the user has requested to cancel the test
    		if (saStopFlag)
    		{
    			goto EXIT_DELAY_TEST;
    		}
        }
    	rfc2544Results->frameLossResults.data[f].status = test_completed;
    	rfc2544Results->frameLossResults.data[f].elapsedTime = rfc2544Cfg->frameLossCfg.trialDuration;

		f++;
	} // while frameSizeList

	rfc2544Results->frameLossResults.endTime = getCurrentRTC();
	rfc2544Results->frameLossResults.status = test_completed;
	DO_LOG_DEBUG(log_handle,"Frame Loss test completed");
	sendSaStatusNotificaiton(log_handle, rfc2544FrameLossTest, test_completed);
	return (2);		// This is the normal exit if the test is left run to completion

	EXIT_DELAY_TEST:
	// If exiting the function early due to users stopping test, then change all pending to stopped
	for (;f < MAX_NUM_FRAME_SIZES; f++)
	{
		if(rfc2544Results->frameLossResults.data[f].status == test_pending)
		{
			rfc2544Results->frameLossResults.data[f].status = test_stopped;
		}
	}
	rfc2544Results->frameLossResults.status = test_stopped;
	rfc2544Results->frameLossResults.endTime = getCurrentRTC();
	DO_LOG_DEBUG(log_handle,"Frame Loss test stopped");
	sendSaStatusNotificaiton(log_handle, rfc2544FrameLossTest, test_stopped);
	return (1);
}

static bool testB2BBurst(uint8_t frameIndex, uint16_t frameSize, uint8_t lbChnl, svcDir_t selDir, mrPort_t port, uint32_t burstSize, uint32_t testDuration, uint32_t acceptableLoss, rfc2544Results_t *rfc2544Results)
{
	lbStats_t 	lbStats;
	uint32_t	txPktCount;

	disableLBChnl(lbChnl, selDir);		// Make sure the channel is stopped before configuring it

	if (burstSize == 0)
	{
		rfc2544Results->backToBackResults.data[frameIndex].averageBurstSize = 0;
		return(true);
	}


	// Run at 100% line rate
    _lbSetRate(lbChnl, 1000);

    _lbClearStats(lbChnl, 1, selDir);

    _lbSetMode(lbChnl, lb_burstMode, port);

    // The number of pkts per burst is set to max and the burst count is varied depending on the burstSize requested - this limits the step resolution
    _lbConfigureBurst(lbChnl, MAX_LB_PKTS_PER_BURSTS, burstSize/MAX_LB_PKTS_PER_BURSTS, 0);

	// Enable loopback
    enableLBChnl(lbChnl);

    txPktCount = MAX_LB_PKTS_PER_BURSTS * (burstSize/MAX_LB_PKTS_PER_BURSTS);
    do
    {
    	// Don't hog the CPU will waiting for the burst to complete
    	osSleepMS(50);
        // Before proceeding check if the user has requested to cancel the test
        if (saStopFlag)
        {
        	goto STOP_B2B_BURST_TEST;
        }

    } while(isLbActive(lbChnl));

	disableLBChnl(lbChnl, selDir);		// Make sure the channel is stopped before configuring it

	// Always wait 2 seconds after the burst before proceeding
	osSleepMS(2000);

	// Get the results
	readLBStats(lbChnl, &lbStats);

	DO_LOG_DEBUG(log_handle," Test result: frame size = %d, txCnt = %lu, rxCnt = %lu",frameSize, txPktCount, lbStats.numRxPkts);
    if (lbStats.numRxPkts < txPktCount)
    {
    	// Packets were lost so the test fails
STOP_B2B_BURST_TEST:
    	return(false);
   	}
    return(true);
}

#define MAX_B2B_REPEAT_COUNT 100
static int runBackToBackSearch(uint8_t resultIndex, uint16_t frameSize, uint8_t lbChnl, svcDir_t selDir, rfc2544Cfg_t *rfc2544Cfg, rfc2544Results_t *rfc2544Results)
{
	uint32_t	burstSize;
	mrPort_t	port;
	uint32_t	upSearchLimit;
	uint32_t	loSearchLimit;
	uint32_t	numPktsInburst;
	uint32_t	iterResults[MAX_B2B_REPEAT_COUNT];
	int			iterCount;
	double		sumOfBursts;
	bool		firstTest;

	// Configure the loopback channel to send the correct frames (size and content)
	if (initLbChnl(lbChnl, rfc2544Cfg->targetIsMep, &rfc2544Cfg->targetMep, &rfc2544Cfg->frameCfg, frameSize, selDir, &port, false, 0, false))
	{
		// There was a problem with the service configuration
    	rfc2544Results->backToBackResults.status = test_failed;
    	return(1);		// Stopped by user
	}

	sumOfBursts = 0;
	for (iterCount = 0; iterCount < rfc2544Cfg->backToBackCfg.repeat; iterCount++)
	{
		firstTest = true;
		DO_LOG_DEBUG(log_handle,"frame size = %d, Starting trial No. %d -------->", frameSize, iterCount+1);

		// Calculate the required number of packets in burst
		numPktsInburst = (rfc2544Cfg->backToBackCfg.trialDuration * 125000 ) / (frameSize + 20); // this is: 'burst time' / 'packet tx time + IPG' => 'time in ms' / (framesize + 20 / 8ns) => 125000000/1000 * 'time in ms' / (framesize + 20)

		// always round the burst size up to the nearest multiple of MAX_LB_PKTS_PER_BURSTS
		upSearchLimit = ((numPktsInburst / MAX_LB_PKTS_PER_BURSTS) * MAX_LB_PKTS_PER_BURSTS) + MAX_LB_PKTS_PER_BURSTS;

		if (upSearchLimit > MAX_LB_NUM_BURSTS * MAX_LB_PKTS_PER_BURSTS)
		{
			upSearchLimit = MAX_LB_NUM_BURSTS * MAX_LB_PKTS_PER_BURSTS;
		}

		loSearchLimit = MAX_LB_PKTS_PER_BURSTS;

	NEXT_STEP:
		// Before proceeding check if the user has requested to cancel the test
		if (saStopFlag)
		{
			rfc2544Results->backToBackResults.status = test_stopped;
			return(1);		// Stopped by user
		}

		if (firstTest)
		{
			// It will most likely speed up tests to run the max burst size initially, because if that passes we are done!
			burstSize = upSearchLimit;
			firstTest = false;
		}
		else
		{
			burstSize = ( ((upSearchLimit + loSearchLimit) / 2 ) / MAX_LB_PKTS_PER_BURSTS ) * MAX_LB_PKTS_PER_BURSTS;
		}

		// Update this so results will indicate what frame rate is being tested
		rfc2544Results->backToBackResults.data[resultIndex].averageBurstSize = burstSize;
		if (burstSize == 0)
		{
			goto B2B_SEARCH_FINISHED;
		}

		// Test if the frame rate is received without any loss
		if (testB2BBurst(resultIndex,frameSize,lbChnl,selDir,port,burstSize, rfc2544Cfg->binaryDuration,rfc2544Cfg->throughPutCfg.acceptedFrameLoss,rfc2544Results))
		{
			// The selected rate PASSED..
			// move the search limits such that:
			DO_LOG_DEBUG(log_handle,"frame size = %d, burst size = %lu PASSED",frameSize, burstSize);

			// move sL up to the last passing value
			loSearchLimit = burstSize;

			// If sL = sU and the test just passed then we're done!
			if(loSearchLimit == upSearchLimit)
			{
			   goto B2B_SEARCH_FINISHED;
			}

			// if sU and sL differ by only 1 step then set them both to sU and re-test
			if( loSearchLimit == (upSearchLimit - MAX_LB_PKTS_PER_BURSTS))
			{
				loSearchLimit = upSearchLimit;
			}
			goto NEXT_STEP;
		}
		else
		{
			DO_LOG_DEBUG(log_handle,"frame size = %d, burst size = %lu FAILED",frameSize, burstSize);

			// Before proceeding check if the user has requested to cancel the test
			if (saStopFlag)
			{
				rfc2544Results->backToBackResults.status = test_stopped;
				return(1);		// Stopped by user
			}

			// The selected rate FAILED...
			// move sU down to the last failing value
			upSearchLimit = burstSize;

			// if sU and sL differ by only 1 step then set them both to sL and re-test
			if (upSearchLimit == (loSearchLimit + MAX_LB_PKTS_PER_BURSTS))
			{
				upSearchLimit = loSearchLimit;
			}
			else
			{
				// if test fails when sL=sU then we went 1 step too far..step back by 1
				if (loSearchLimit == upSearchLimit)
				{
					loSearchLimit = loSearchLimit - MAX_LB_PKTS_PER_BURSTS;
					upSearchLimit = loSearchLimit;
				}
			}
			goto NEXT_STEP;
		}

B2B_SEARCH_FINISHED:

		DO_LOG_DEBUG(log_handle,"Trial #%d completed, burst size = %lu",iterCount+1,burstSize);
		iterResults[iterCount] = burstSize;
		sumOfBursts += burstSize;
		rfc2544Results->backToBackResults.data[resultIndex].averageBurstSize  = sumOfBursts / (iterCount + 1);
		rfc2544Results->backToBackResults.data[resultIndex].elapsedTime = (rfc2544Results->backToBackResults.data[resultIndex].averageBurstSize * (frameSize + 20)) / 125000;
	}

	DO_LOG_DEBUG(log_handle,"Avg burst size = %lu",iterCount+1,rfc2544Results->backToBackResults.data[resultIndex].averageBurstSize);

	rfc2544Results->backToBackResults.data[resultIndex].elapsedTime = (rfc2544Results->backToBackResults.data[resultIndex].averageBurstSize * (frameSize + 20)) / 125000;
	rfc2544Results->backToBackResults.status = test_completed;
	return (2);				// Test completed
}

static int runBackToBackTest(uint8_t lbChnl, svcDir_t selDir, rfc2544Cfg_t *rfc2544Cfg, rfc2544Results_t *rfc2544Results)
{
	uint8_t				f;
	mrPort_t			port;
	int					retVal;

	rfc2544Results->backToBackResults.startTime = getCurrentRTC();
	rfc2544Results->backToBackResults.status = test_running;

	DO_LOG_DEBUG(log_handle,"Test started");
	f = 0;
	while(rfc2544Cfg->backToBackCfg.frameSizeList[f] != 0 && f < MAX_NUM_FRAME_SIZES)
	{
		// Configure the loopback channel to send the correct frames (size and content)
		initLbChnl(lbChnl, rfc2544Cfg->targetIsMep, &rfc2544Cfg->targetMep, &rfc2544Cfg->frameCfg, rfc2544Cfg->backToBackCfg.frameSizeList[f], selDir, &port, false , 0, false);

		// Test all configured frame sizes...
		retVal = runBackToBackSearch(f, rfc2544Cfg->backToBackCfg.frameSizeList[f], lbChnl, selDir, &saRfc2544Cfg, &saRfc2544Results);

		// Before proceeding check if the user has requested to cancel the test
		if (retVal != 2)
		{
			goto EXIT_B2B_TEST;
		}

    	rfc2544Results->backToBackResults.data[f].status = test_completed;
    	//rfc2544Results->backToBackResults.data[f].elapsedTime = rfc2544Cfg->backToBackCfg.trialDuration;

		f++;
	} // while frameSizeList

	rfc2544Results->backToBackResults.endTime = getCurrentRTC();
	rfc2544Results->backToBackResults.status = test_completed;
	DO_LOG_DEBUG(log_handle,"Test completed");
	sendSaStatusNotificaiton(log_handle, rfc2544Back2Backtest, test_completed);
	return (2);		// This is the normal exit if the test is left run to completion

	EXIT_B2B_TEST:
	// If exiting the function early due to users stopping test, then change all pending to stopped
	for (;f < MAX_NUM_FRAME_SIZES; f++)
	{
		if(rfc2544Results->backToBackResults.data[f].status == test_pending)
		{
			rfc2544Results->backToBackResults.data[f].status = test_stopped;
		}
	}
	rfc2544Results->backToBackResults.status = test_stopped;
	rfc2544Results->backToBackResults.endTime = getCurrentRTC();
	DO_LOG_DEBUG(log_handle,"Test stopped");
	sendSaStatusNotificaiton(log_handle, rfc2544Back2Backtest, test_stopped);
	return (1);
}


/**
 * @brief This should be called immediately after the RFC2544 configuration has been updated
 * @param rfc2544Cfg
 * @param rfc2544Results
 */
void updateRfc2544ResultsStatus(rfc2544Cfg_t *rfc2544Cfg, rfc2544Results_t *rfc2544Results)
{
	uint8_t	f;

	LOCK_SA_LIST(0);

	// First clear all the results to defaults
	setSaRfc2544ResultDefaults(&saRfc2544Results);

	// ------- Throughput test
	if (rfc2544Cfg->enThroughput)
	{
		rfc2544Results->throughPutResults.status = test_pending;
	}
	else
	{
		rfc2544Results->throughPutResults.status = test_disabled;
	}

	for (f = 0; f < MAX_NUM_FRAME_SIZES; f++)
	{
		if (rfc2544Cfg->throughPutCfg.frameSizeList[f] == 0)
		{
			rfc2544Results->throughPutResults.data[f].status	= test_disabled;
		}
		else
		{
			rfc2544Results->throughPutResults.data[f].status	= test_pending;
			rfc2544Results->throughPutResults.data[f].frameSize	= rfc2544Cfg->throughPutCfg.frameSizeList[f];
		}
	}

	// ------- delay test
	if (rfc2544Cfg->enDelay)
	{
		rfc2544Results->delayResults.status = test_pending;
	}
	else
	{
		rfc2544Results->delayResults.status = test_disabled;
	}

	for (f = 0; f < MAX_NUM_FRAME_SIZES; f++)
	{
		if (rfc2544Cfg->delayCfg.frameSizeList[f] == 0)
		{
			rfc2544Results->delayResults.data[f].status		= test_disabled;
		}
		else
		{
			rfc2544Results->delayResults.data[f].status		= test_pending;
			rfc2544Results->delayResults.data[f].frameSize	= rfc2544Cfg->delayCfg.frameSizeList[f];
		}
	}

	// ------- Frame Loss test
	if (rfc2544Cfg->enFrameLoss)
	{
		rfc2544Results->frameLossResults.status = test_pending;
	}
	else
	{
		rfc2544Results->frameLossResults.status = test_disabled;
	}

	for (f = 0; f < MAX_NUM_FRAME_SIZES; f++)
	{
		if (rfc2544Cfg->frameLossCfg.frameSizeList[f] == 0)
		{
			rfc2544Results->frameLossResults.data[f].status		= test_disabled;
		}
		else
		{
			rfc2544Results->frameLossResults.data[f].status		= test_pending;
			rfc2544Results->frameLossResults.data[f].frameSize	= rfc2544Cfg->frameLossCfg.frameSizeList[f];
		}
	}

	// ------- Back to Back test
	if (rfc2544Cfg->enBackToBack)
	{
		rfc2544Results->backToBackResults.status = test_pending;
	}
	else
	{
		rfc2544Results->backToBackResults.status = test_disabled;
	}

	for (f = 0; f < MAX_NUM_FRAME_SIZES; f++)
	{
		if (rfc2544Cfg->backToBackCfg.frameSizeList[f] == 0)
		{
			rfc2544Results->backToBackResults.data[f].status	= test_disabled;
		}
		else
		{
			rfc2544Results->backToBackResults.data[f].status	= test_pending;
			rfc2544Results->backToBackResults.data[f].frameSize	= rfc2544Cfg->backToBackCfg.frameSizeList[f];
		}
	}
	UNLOCK_SA_LIST();
}

/**
 * @brief This is intended to run as an OS task. It runs all the configured tests for RFC2544 in sequence.
 * @param args Not used. Should be set to NULL.
 *
 * This task does not stay active. When all tests have run the task will self terminate.
 * If 'stop' is requested then the task will tidy up and exit without completing any further tests.
 */
void rfc2544TestsTask(void *args)
{
	uint8_t			lbChnl = 0;
	svcDir_t		selDir;
	int 			retVal;

	DO_LOG_NOTICE(log_handle,"RFC2544 tests starting...");

	LOCK_SA_LIST(0);
	saStopFlag = false;
	UNLOCK_SA_LIST();

	// set the results status to reflect which tests are disabled and which are pending
	updateRfc2544ResultsStatus(&saRfc2544Cfg, &saRfc2544Results);

	// Reserve and lock a loopback channel - this will be channel 0 - its the only channel used by CFM loopback!
	EGL_ASSERT(_lbLockChannel(lbChnl,MAX_NUM_MEPS + lbChnl) >= 0, 1);			//confirm that we got assigned a loopback channel

	// Configure the test direction - TWO-WAY or ONE-WAY Tx or Rx (!!!NOTE: only TWO_WAY supported in this release!!!)
	if (saRfc2544Cfg.frameType == y1731_lbm_frames)
	{
		selDir = both_tx_rx;
	}
	else
	{
		//TODO: not supported in this release
		selDir = both_tx_rx;
	}

	if (saRfc2544Cfg.enThroughput)
	{
		retVal = runThroughPutTest(lbChnl, selDir, &saRfc2544Cfg, &saRfc2544Results);
		if (retVal == 1)
		{
			DO_LOG_NOTICE(log_handle,"RFC2544 tests stopped");
			goto EXIT_TASK;
		}
	}

	if (saRfc2544Cfg.enDelay)
	{
		retVal = runDelayTest(lbChnl, selDir, &saRfc2544Cfg, &saRfc2544Results);
		if (retVal == 1)
		{
			DO_LOG_NOTICE(log_handle,"RFC2544 tests stopped");
			goto EXIT_TASK;
		}
	}

	if (saRfc2544Cfg.enFrameLoss)
	{
		retVal = runFrameLossTest(lbChnl, selDir, &saRfc2544Cfg, &saRfc2544Results);
		if (retVal == 1)
		{
			DO_LOG_NOTICE(log_handle,"RFC2544 tests stopped");
			goto EXIT_TASK;
		}
	}

	if (saRfc2544Cfg.enBackToBack)
	{
		retVal = runBackToBackTest(lbChnl, selDir, &saRfc2544Cfg, &saRfc2544Results);
		if (retVal == 1)
		{
			DO_LOG_NOTICE(log_handle,"RFC2544 tests stopped");
			sendSaStatusNotificaiton(log_handle, rfc2544AllTests, test_stopped);
			goto EXIT_TASK;
		}
	}

	rfc2544Status = test_completed;
	DO_LOG_NOTICE(log_handle,"RFC2544 tests completed");
	sendSaStatusNotificaiton(log_handle, rfc2544AllTests, test_completed);
EXIT_TASK:
	// release the loopback channel
	_lbUnLockChannel(lbChnl,MAX_NUM_MEPS + lbChnl);
	_lbFreeChannel(MAX_NUM_MEPS + lbChnl);
	LOCK_SA_LIST(0);
	// Change this flag to show that this test is no longer running
	saActiveFlag = false;
	UNLOCK_SA_LIST();

	// Cleanly exit the task
	osTaskDelete( 0 );
}

/**
 * @brief This will launch a OS task to run the RFC2544 tests if no other SA test is running.
 * @return
 * 1: Operation rejected - Another SA Test is already running
 * 2: Operation successful
 */
int rfc2544RunTests(void)
{
	LOCK_SA_LIST(0);
	if (!saActiveFlag)
	{
		saActiveFlag = true;
		UNLOCK_SA_LIST();
		/* Create the main task. */
		osTaskCreate( rfc2544TestsTask, "rfc2544tst  ", RFC2544_TASK_STACKSIZE, NULL, OS_STACK_THREAD_PRIO - 1, NULL );
		return(2);		// Operation successful
	}
	UNLOCK_SA_LIST();
	DO_LOG_INFO(log_handle,"RFC2544 tests already running!");
	return(1);			// Operation rejected - Another SA Test is already running
}
//==========================================================================================================================
static void copyLatestResults(y1564Data_t *results, y1564DataEntry_t *y1564Data, uint8_t sampIndex)
{
	//double flr;

	results->availableSecCount	= y1564Data->irFlData[sampIndex].availSecCnt;
	results->elapsedTime		= y1564Data->totalSamples;
	//results->frameLossCount		= (y1564Data->delayData.txCnt >= y1564Data->delayData.rxCnt) ? y1564Data->delayData.txCnt - y1564Data->delayData.rxCnt : y1564Data->delayData.txCnt;
	results->frameLossCount		= y1564Data->irFlData[sampIndex].flCount;

	//flr 						= (y1564Data->delayData.txCnt == 0) ? 1 :((y1564Data->delayData.txCnt - y1564Data->delayData.rxCnt) / y1564Data->delayData.txCnt);
	results->frameLossRatio		= (uint32_t) (FLR_TO_FRAME_LOSS_MILLI_PERCENT(y1564Data->irFlData[sampIndex].flr));

	results->ulrMin				= y1564Data->irFlData[sampIndex].irMin * 8;
	results->ulrMax				= y1564Data->irFlData[sampIndex].irMax * 8;
	results->ulrAvg				= y1564Data->irFlData[sampIndex].irMean * 8;
	results->ulrMeasured			= y1564Data->irFlData[sampIndex].irCurr * 8;
	results->txFrameCount		= y1564Data->delayData.txCnt;
	results->rxFrameCount		= y1564Data->delayData.rxCnt;

	if (y1564Data->delayData.rxCnt > 0)
	{
		results->frameDelayVarMax		= (uint32_t)round(y1564Data->delayData.fdvMin * 0.256);
		results->frameDelayVarMax		= (uint32_t)round(y1564Data->delayData.fdvMax * 0.256);
		results->frameDelayVarAvg		= (uint32_t)round(y1564Data->delayData.fdvMean * 0.256);
		results->frameDelayVarMeasured	= (uint32_t)round(y1564Data->delayData.fdvCurr * 0.256);

		results->frameDelayMin			= (uint32_t)round(y1564Data->delayData.ftdMin * 0.256);
		results->frameDelayMax			= (uint32_t)round(y1564Data->delayData.ftdMax * 0.256);
		results->frameDelayAvg			= (uint32_t)round(y1564Data->delayData.ftdMean * 0.256);
		results->frameDelayMeasured		= (uint32_t)round(y1564Data->delayData.ftdCurr * 0.256);
	}
	else
	{
		// If no packets are received then we must zero these as the 'measured' values are not valid
		results->frameDelayVarMax		= 0;
		results->frameDelayVarMax		= 0;
		results->frameDelayVarAvg		= 0;
		results->frameDelayVarMeasured	= 0;

		results->frameDelayMin			= 0;
		results->frameDelayMax			= 0;
		results->frameDelayAvg			= 0;
		results->frameDelayMeasured		= 0;
	}

	results->frameRate			= y1564Data->irFlData[sampIndex].irTxMean * 8;
}

static void copyFinalResults(y1564Data_t *results, y1564DataEntry_t *y1564Data)
{
	double flr;

	results->elapsedTime		= y1564Data->totalSamples;
	results->frameLossCount		= (y1564Data->delayData.txCnt - y1564Data->delayData.rxCnt);

	flr 						= (y1564Data->delayData.txCnt == 0) ? 1 :((double)((y1564Data->delayData.txCnt - y1564Data->delayData.rxCnt)) / ((double)y1564Data->delayData.txCnt));
	results->frameLossRatio		= (uint32_t) (FLR_TO_FRAME_LOSS_MILLI_PERCENT(flr));

	results->txFrameCount		= y1564Data->delayData.txCnt;
	results->rxFrameCount		= y1564Data->delayData.rxCnt;

	if (y1564Data->delayData.rxCnt > 0)
	{
		results->frameDelayVarMax	= (uint32_t)round(y1564Data->delayData.fdvMin * 0.256);
		results->frameDelayVarMax	= (uint32_t)round(y1564Data->delayData.fdvMax * 0.256);
		results->frameDelayVarAvg	= (uint32_t)round(y1564Data->delayData.fdvMean * 0.256);

		results->frameDelayMin		= (uint32_t)round(y1564Data->delayData.ftdMin * 0.256);
		results->frameDelayMax		= (uint32_t)round(y1564Data->delayData.ftdMax * 0.256);
		results->frameDelayAvg		= (uint32_t)round(y1564Data->delayData.ftdMean * 0.256);
	}
	else
	{
		// If no packets are received then we must zero these as the 'measured' values are not valid
		results->frameDelayVarMax		= 0;
		results->frameDelayVarMax		= 0;
		results->frameDelayVarAvg		= 0;

		results->frameDelayMin			= 0;
		results->frameDelayMax			= 0;
		results->frameDelayAvg			= 0;
	}
	// These are not valid at the end of the test - set to 0
	results->frameDelayVarMeasured		= 0;
	results->frameDelayMeasured			= 0;
	results->ulrMeasured					= 0;
}

static int runY1564ConfigTest(uint8_t lbChnl, svcDir_t selDir, uint8_t svcIndex,
		uint16_t greenRate, uint16_t yellowRate,			// These rates are in kbps and are 'utilised line rate' (NOT IR!!)
		uint16_t testDuration, uint16_t frameSize,
		mrPort_t port, uint32_t	acceptableLoss,
		y1564Data_t *greenResults, y1564Data_t *yellowResults)
{

	y1564DataEntry_t	y1564DataGreen;
	y1564DataEntry_t	y1564DataYellow;
	double				flrList[MAX_LB_CHANNELS_FOR_Y1564];
	uint8_t				k;
	uint8_t 			newHeadG;
	uint8_t 			newHeadY;
	uint16_t			greenRateInMbps;
	uint16_t			yellowRateInMbps;
	bool				testDone = false;

	disableLBChnl(lbChnl, selDir);			// Make sure the channel is stopped before configuring it
	disableLBChnl(lbChnl+1, selDir);		// Make sure the channel is stopped before configuring it

    // rate limit to 100% line rate
	if (greenRate > 1000)
		greenRate = 1000;
	if (yellowRate > 1000)
		yellowRate = 1000;

    greenRateInMbps = (uint16_t)greenRate;
    yellowRateInMbps = (uint16_t)yellowRate;

    greenResults->frameSize = frameSize;
    yellowResults->frameSize = frameSize;

    // Setup the appropriate rates - *** The Hardware must be configured with the IR (NOT 'utilised line rate') ***
    _lbSetRate(lbChnl, greenRateInMbps);
    _lbSetRate(lbChnl+1, yellowRateInMbps);

    _lbClearStats(lbChnl, 1, selDir);
    _lbClearStats(lbChnl+1, 1, selDir);

    _lbSetMode(lbChnl, lb_continuousMode, port);
    _lbSetMode(lbChnl+1, lb_continuousMode, port);

    for(k=0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
    {
    	flrList[k] = 0;
    }
    flrList[lbChnl]		= FRAME_LOSS_MILLI_PERCENT_TO_FLR(acceptableLoss);    // Set the Green Channel loss to the user set value
    flrList[lbChnl+1]	= FRAME_LOSS_MILLI_PERCENT_TO_FLR(acceptableLoss);    // Set the Green Channel loss to the user set value

    // initialise the measurement, capture FIFO, etc. and start transmit of Loopback channels.
    initSaTest(testDuration, frameSize, selDir, EN_CHNL_0_1 | GANGED_OPERATION, flrList);

    do
    {
    	osSleepMS(1000);

        // Before proceeding check if the user has requested to cancel the test
        if (saStopFlag)
        {
        	// Disable all active loopback channels
        	disableLB(LB_RESET_REG_ALL_CHNL_RESET_MASK,MR_BOTH_TX_RX);
        	return(1);
        }

        LOCK_SA_READ(0);
        // Read the Green and Yellow results
        readSaData(lbChnl, false, &y1564DataGreen, &newHeadG);		// TODO: consider adding on-way support later
        readSaData(lbChnl+1, false, &y1564DataYellow, &newHeadY);

        // Process the results...
		if (y1564DataGreen.validCnt > 0)
		{
			copyLatestResults(greenResults, &y1564DataGreen, y1564DataGreen.validCnt-1);
		}
		if (y1564DataYellow.validCnt > 0)
		{
			copyLatestResults(yellowResults, &y1564DataYellow, y1564DataYellow.validCnt-1);
		}

        if (y1564DataGreen.testDone != 0)
        {
			// The test is done...validCnt will most likely be 0 for the last few reads but the
			// the non-sampled values (delay/delay var and pkt counts must be read now to
			// get the final test values

        	copyFinalResults(greenResults, &y1564DataGreen);
        	copyFinalResults(yellowResults, &y1564DataYellow);

			// set to zero to indicate that the test is completed on this channel
			testDone = true;
        }
        UNLOCK_SA_READ();
    } while(!testDone);

	disableLBChnl(lbChnl, selDir);		// Make sure the channel is stopped
	disableLBChnl(lbChnl+1, selDir);	// Make sure the channel is stopped
	return(2);
}

static int runCirTest(uint8_t lbChnl, svcDir_t selDir, uint8_t svcIndex, bool step, y1564Cfg_t *y1564Cfg, y1564Results_t *y1564Results)
{
	mrPort_t	port;
	uint16_t	greenRate;
	uint16_t	yellowRate;
	uint16_t	stepRate;
	uint8_t		numSteps;
	uint8_t		stepIndex;
	int			retVal;

	// The CIR test involves sending green packets at the CIR rate and yellow packets at 0 Mbps.
	// The receive rate of both green and yellow packet should be recorded

	// MAX_NUM_CONFIG_STEPS steps can be recorded (min = 4). so if MAX_NUM_CONFIG_STEPS = 4 then
	// First_Step_rate = final_rate - (3 * step_rate)
	// e.g. if step_rate = 25 and final_rate = 100 then
	// first_step_rate = 100 - (25 * 3) = 25
	if (step)
	{
		DO_LOG_DEBUG(log_handle,"CIR Step test started on Service #%d", svcIndex);
		if (y1564Cfg->serviceCfg[svcIndex].configStepSize != 0)
		{
			// The user has chosen the step size to use...try to make sense of it!
			if (y1564Cfg->serviceCfg[svcIndex].configStepSize >= y1564Cfg->serviceCfg[svcIndex].cir)
			{
				// If given a bad stepsize then just run at CIR
				greenRate = y1564Cfg->serviceCfg[svcIndex].cir;
				numSteps = 1;
				stepRate = 0;
			}
			else
			{
				// Some examples:  (to get here StepSize < CIR AND StepSize > 0)
				// CIR   StepSize	Numsteps	Steps
				// (MHz)   (MHz)				(MHz)
				//  2		1			2			1,2
				//  3		1			3			1,2,3
				//  4		1			4			1,2,3,4
				//	5		1			4			2,3,4,5
				//	3		2			2			1,3
				//	11		3			4			2,5,8,11
				// get numSteps by dividing CIR by step size, if there is no remainder - job done, if there is a remainder then add 1 to numSteps
				numSteps = (y1564Cfg->serviceCfg[svcIndex].cir / y1564Cfg->serviceCfg[svcIndex].configStepSize) + ((y1564Cfg->serviceCfg[svcIndex].cir % y1564Cfg->serviceCfg[svcIndex].configStepSize) == 0 ? 0 : 1);

				// Limit numSteps to the storage available
				if (numSteps > MAX_NUM_CONFIG_STEPS)
				{
					numSteps = MAX_NUM_CONFIG_STEPS;
				}

				stepRate = y1564Cfg->serviceCfg[svcIndex].configStepSize;

				// re-adjust the initial green rate so the steps will ensure we finish at the desired CIR
				greenRate = y1564Cfg->serviceCfg[svcIndex].cir - (stepRate * (numSteps - 1));
			}
		}
		else
		{
			// Use the default step size of 25% CIR
			if (y1564Cfg->serviceCfg[svcIndex].cir >= 4)
			{
				greenRate = y1564Cfg->serviceCfg[svcIndex].cir/4;
				numSteps = 4;
				stepRate = greenRate;
				// we are limited by resolution and don't want to have a greenRate=0, so we need to
				// re-adjust the initial green rate so the steps will ensure we finish at the desired CIR
				greenRate = y1564Cfg->serviceCfg[svcIndex].cir - stepRate * (numSteps - 1);
			}
			else
			{
				// For values of CIR < 4 we must reduce the number of steps
				greenRate = 1;
				numSteps = y1564Cfg->serviceCfg[svcIndex].cir;			// TODO: is it ok to have CIR = 0?
				stepRate = 1;
			}
		}
	}
	else
	{
		DO_LOG_DEBUG(log_handle,"CIR test started on Service #%d", svcIndex);
		greenRate = y1564Cfg->serviceCfg[svcIndex].cir;
		numSteps = 1;
		stepRate = 0;
	}

	yellowRate = 0;
	if (!step)
	{
		numSteps = 1;
	}

	for (stepIndex = 0; stepIndex < numSteps; stepIndex++)
	{
		// Increment the 'steps' parameter at the start of the test in order to make the results
		// available while the step is still running. This is to be consistent with all other tests.
		y1564Results->configurationResults[svcIndex].cirResults.steps = stepIndex+1;

		y1564Results->configurationResults[svcIndex].cirResults.status[stepIndex] = test_running;
		// Configure the loopback channel to send the correct frames (size and content)
		initLbChnl(lbChnl,
				y1564Cfg->serviceCfg[svcIndex].targetIsMep,
				&y1564Cfg->serviceCfg[svcIndex].targetMep,
				&y1564Cfg->serviceCfg[svcIndex].frameCfg,
				y1564Cfg->serviceCfg[svcIndex].frameSize,
				selDir, &port, true, 0, true);						// Green frames
		initLbChnl(lbChnl+1,
				y1564Cfg->serviceCfg[svcIndex].targetIsMep,
				&y1564Cfg->serviceCfg[svcIndex].targetMep,
				&y1564Cfg->serviceCfg[svcIndex].frameCfg,
				y1564Cfg->serviceCfg[svcIndex].frameSize,
				selDir, &port, true, 1, true);						// Yellow frames

		retVal = runY1564ConfigTest(lbChnl, selDir, svcIndex,
				greenRate,										// The Green frame rate - Units are Utalised Line rate in kbps
				yellowRate,										// The yellow frame rate - Units are Utalised Line rate in kbps
				y1564Cfg->configStepDuration,
				y1564Cfg->serviceCfg[svcIndex].frameSize,
				port,
				y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss,
				&y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex],
				&y1564Results->configurationResults[svcIndex].cirResults.dataYellow[stepIndex]);


		if (retVal == 1)
		{
			// user cancelled the test.
			y1564Results->configurationResults[svcIndex].cirResults.status[stepIndex]	= test_stopped;
			y1564Results->configurationResults[svcIndex].status							= test_stopped;
			DO_LOG_DEBUG(log_handle,"CIR full/step test stopped on Service #%d", svcIndex);
			return(1);
		}

		y1564Results->configurationResults[svcIndex].cirResults.pass[stepIndex] = true;

		// Test all possible fail conditions...
		if (y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex].txFrameCount == 0)
		{
			DO_LOG_DEBUG(log_handle,"Y1564 CIR test failed because txFrameCount = 0");
			y1564Results->configurationResults[svcIndex].cirResults.pass[stepIndex] = false;
			y1564Results->configurationResults[svcIndex].cirResults.failReason = fail_tx_count_0;
		}
		if (y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex].rxFrameCount == 0)
		{
			DO_LOG_DEBUG(log_handle,"Y1564 CIR test failed because rxFrameCount = 0");
			y1564Results->configurationResults[svcIndex].cirResults.pass[stepIndex] = false;
			y1564Results->configurationResults[svcIndex].cirResults.failReason = fail_rx_count_0;
		}
		if (y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex].frameDelayVarMax > y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh)
		{
			DO_LOG_DEBUG(log_handle,"Y1564 CIR test failed because frameDelayMax (%luus) > frameDelayThresh (%luus)", y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex].frameDelayVarMax, y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh);
			y1564Results->configurationResults[svcIndex].cirResults.pass[stepIndex] = false;
			y1564Results->configurationResults[svcIndex].cirResults.failReason = fail_fdv_thres;
		}
		if (y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex].frameDelayMax > y1564Cfg->serviceCfg[svcIndex].frameDelayThresh)
		{
			DO_LOG_DEBUG(log_handle,"Y1564 CIR test failed because frameDelayMax (%luus) > frameDelayThresh (%luus)", y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex].frameDelayMax, y1564Cfg->serviceCfg[svcIndex].frameDelayThresh);
			y1564Results->configurationResults[svcIndex].cirResults.pass[stepIndex] = false;
			y1564Results->configurationResults[svcIndex].cirResults.failReason = fail_ftd_thres;
		}
		if (y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex].frameLossRatio > y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss)
		{
			DO_LOG_DEBUG(log_handle,"Y1564 CIR test failed because frameLossRatio (%f\%) > acceptedFrameLoss (%f\%)", ((float)y1564Results->configurationResults[svcIndex].cirResults.dataGreen[stepIndex].frameLossRatio) * 0.001, ((float)y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss) * 0.001);
			y1564Results->configurationResults[svcIndex].cirResults.pass[stepIndex] = false;
			y1564Results->configurationResults[svcIndex].cirResults.failReason = fail_loss_thres;
		}

		y1564Results->configurationResults[svcIndex].cirResults.status[stepIndex] = test_completed;
		if (step)
		{
			DO_LOG_DEBUG(log_handle,"CIR step test @%dMbps completed on Service #%d - %s", greenRate, svcIndex, y1564Results->configurationResults[svcIndex].cirResults.pass[stepIndex] ? "Passed" : "Failed");
		}
		greenRate += stepRate;
	}
	if (!step)
	{
		DO_LOG_DEBUG(log_handle,"CIR full test completed on Service #%d - %s", svcIndex, y1564Results->configurationResults[svcIndex].cirResults.pass[0] ? "Passed" : "Failed");
	}
	return(2);	// Success
}

static void combineGreenAndYellow(y1564Data_t *combinedResults, y1564Data_t *greenResults, y1564Data_t *yellowResults)
{
	double flr;

	combinedResults->txFrameCount	= greenResults->txFrameCount + yellowResults->txFrameCount;
	combinedResults->rxFrameCount	= greenResults->rxFrameCount + yellowResults->rxFrameCount;
	if (combinedResults->txFrameCount > 0)
	{
		flr = (combinedResults->txFrameCount - combinedResults->rxFrameCount) / combinedResults->txFrameCount;
		combinedResults->frameLossRatio = (uint32_t) FLR_TO_FRAME_LOSS_MILLI_PERCENT(flr);
	}
	else
	{
		combinedResults->frameLossRatio = (uint32_t)FLR_TO_FRAME_LOSS_MILLI_PERCENT(1);
	}

	if (greenResults->frameDelayMax > yellowResults->frameDelayMax)
	{
		combinedResults->frameDelayMax = greenResults->frameDelayMax;
	}
	else
	{
		combinedResults->frameDelayMax = yellowResults->frameDelayMax;
	}

	if (greenResults->frameDelayVarMax > yellowResults->frameDelayVarMax)
	{
		combinedResults->frameDelayVarMax = greenResults->frameDelayVarMax;
	}
	else
	{
		combinedResults->frameDelayVarMax = yellowResults->frameDelayVarMax;
	}

	combinedResults->ulrAvg = greenResults->ulrAvg + yellowResults->ulrAvg;

	//TODO: I only added the bare minimum to this - add the other params if required!
}

static int runEirTest(uint8_t lbChnl, svcDir_t selDir, uint8_t svcIndex, y1564Cfg_t *y1564Cfg, y1564Results_t *y1564Results)
{
	mrPort_t	port;
	uint16_t	greenRate;
	uint16_t	yellowRate;
	int			retVal;
	y1564Data_t *resultsPtr;
	y1564Data_t combinedResults;

	// The EIR test involves sending green packets at the CIR rate and yellow packets at EIR.
	// The receive rate of both green and yellow packet should be recorded
	DO_LOG_DEBUG(log_handle,"EIR test started on Service #%d", svcIndex);
	if (y1564Cfg->serviceCfg[svcIndex].colourAware)
	{
		greenRate	= y1564Cfg->serviceCfg[svcIndex].cir;
		yellowRate	= y1564Cfg->serviceCfg[svcIndex].eir;
	}
	else
	{
		greenRate	= y1564Cfg->serviceCfg[svcIndex].cir + y1564Cfg->serviceCfg[svcIndex].eir;
		yellowRate	= 0;
	}

	y1564Results->configurationResults[svcIndex].eirResults.status = test_running;
	// Configure the loopback channel to send the correct frames (size and content)
	initLbChnl(lbChnl,
			y1564Cfg->serviceCfg[svcIndex].targetIsMep,
			&y1564Cfg->serviceCfg[svcIndex].targetMep,
			&y1564Cfg->serviceCfg[svcIndex].frameCfg,
			y1564Cfg->serviceCfg[svcIndex].frameSize,
			selDir, &port, true, 0, true);						// Green frames
	initLbChnl(lbChnl+1,
			y1564Cfg->serviceCfg[svcIndex].targetIsMep,
			&y1564Cfg->serviceCfg[svcIndex].targetMep,
			&y1564Cfg->serviceCfg[svcIndex].frameCfg,
			y1564Cfg->serviceCfg[svcIndex].frameSize,
			selDir, &port, true, 1, true);						// Yellow frames

	retVal = runY1564ConfigTest(lbChnl, selDir, svcIndex,
			greenRate,										// The Green frame rate
			yellowRate,										// The yellow frame rate = 0
			y1564Cfg->configStepDuration,
			y1564Cfg->serviceCfg[svcIndex].frameSize,
			port,
			y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss,
			&y1564Results->configurationResults[svcIndex].eirResults.dataGreen,
			&y1564Results->configurationResults[svcIndex].eirResults.dataYellow);


	if (retVal == 1)
	{
		// user cancelled the test.
		y1564Results->configurationResults[svcIndex].eirResults.status = test_stopped;
		DO_LOG_DEBUG(log_handle,"EIR test stopped on Service #%d", svcIndex);
		return(1);
	}

	if(y1564Cfg->serviceCfg[svcIndex].colourAware)
	{
		// If colour aware we are only concerned with checking Green results
		resultsPtr = &y1564Results->configurationResults[svcIndex].eirResults.dataGreen;
	}
	else
	{
		combineGreenAndYellow(&combinedResults,&y1564Results->configurationResults[svcIndex].eirResults.dataGreen,&y1564Results->configurationResults[svcIndex].eirResults.dataYellow);
		resultsPtr = &combinedResults;
	}

	// See if the test criteria pass. If any one fails, the test fails...
	y1564Results->configurationResults[svcIndex].eirResults.pass = true;
	if (resultsPtr->txFrameCount == 0)
	{
		DO_LOG_INFO(log_handle,"Y1564 EIR test failed because txFrameCount = 0");
		y1564Results->configurationResults[svcIndex].eirResults.pass = false;
		y1564Results->configurationResults[svcIndex].eirResults.failReason = fail_tx_count_0;
	}
	if (resultsPtr->rxFrameCount == 0)
	{
		DO_LOG_INFO(log_handle,"Y1564 EIR test failed because rxFrameCount = 0");
		y1564Results->configurationResults[svcIndex].eirResults.pass = false;
		y1564Results->configurationResults[svcIndex].eirResults.failReason = fail_rx_count_0;
	}
	if (resultsPtr->frameDelayVarMax > y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh)
	{
		DO_LOG_INFO(log_handle,"Y1564 EIR test failed because frameDelayMax (%luus) > frameDelayThresh (%luus)", resultsPtr->frameDelayVarMax, y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh);
		y1564Results->configurationResults[svcIndex].eirResults.pass = false;
		y1564Results->configurationResults[svcIndex].eirResults.failReason = fail_fdv_thres;
	}
	if (resultsPtr->frameDelayMax > y1564Cfg->serviceCfg[svcIndex].frameDelayThresh)
	{
		DO_LOG_INFO(log_handle,"Y1564 EIR test failed because frameDelayMax (%luus) > frameDelayThresh (%luus)", resultsPtr->frameDelayMax, y1564Cfg->serviceCfg[svcIndex].frameDelayThresh);
		y1564Results->configurationResults[svcIndex].eirResults.pass = false;
		y1564Results->configurationResults[svcIndex].eirResults.failReason = fail_ftd_thres;
	}
	if (resultsPtr->frameLossRatio > y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss)
	{
		DO_LOG_INFO(log_handle,"Y1564 CIR test failed because frameLossRatio (%f\%) > acceptedFrameLoss (%f\%)", ((float)resultsPtr->frameLossRatio) * 0.001, ((float)y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss) * 0.001);
		y1564Results->configurationResults[svcIndex].eirResults.pass = false;
		y1564Results->configurationResults[svcIndex].eirResults.failReason = fail_loss_thres;
	}

	y1564Results->configurationResults[svcIndex].eirResults.status = test_completed;
	DO_LOG_DEBUG(log_handle,"EIR test completed on Service #%d - %s", svcIndex, y1564Results->configurationResults[svcIndex].eirResults.pass ? "Passed" : "Failed");
	return(2);	// Success
}

static int runPolicerTest(uint8_t lbChnl, svcDir_t selDir, uint8_t svcIndex, y1564Cfg_t *y1564Cfg, y1564Results_t *y1564Results)
{
	mrPort_t	port;
	uint16_t	greenRate;
	uint16_t	yellowRate;
	int			retVal;
	y1564Data_t *resultsPtr;
	y1564Data_t combinedResults;
	bool		irTotalIsLessThanCirPlusAcceptedLoss;
	bool		irTotalIsGreaterThanCirPlusEirPlusM;

	// The EIR test involves sending green packets at the CIR rate and yellow packets at EIR.
	// The receive rate of both green and yellow packet should be recorded
	DO_LOG_DEBUG(log_handle,"Policer test starting on Service #%d", svcIndex);
	if (y1564Cfg->serviceCfg[svcIndex].colourAware)
	{
		greenRate	= y1564Cfg->serviceCfg[svcIndex].cir;
		if (y1564Cfg->serviceCfg[svcIndex].eir < (greenRate / 5))
		{
			// If EIR < 20% CIR then Yellow rate = 25% CIR + EIR		From "ITU_T Y.1564 (03/211)": Chapter 8.2.1 paragraph C.1
			yellowRate	= (greenRate/4) + y1564Cfg->serviceCfg[svcIndex].eir;
		}
		else
		{
			// If EIR > 20% CIR then Yellow rate = 125% EIR				From "ITU_T Y.1564 (03/211)": Chapter 8.2.1 paragraph C.1
			yellowRate	= y1564Cfg->serviceCfg[svcIndex].eir * 1.25;
		}
	}
	else
	{
		if (y1564Cfg->serviceCfg[svcIndex].eir < (y1564Cfg->serviceCfg[svcIndex].cir / 5))
		{
			// If EIR < 20% CIR then Yellow rate = 125%*CIR + EIR		From "ITU_T Y.1564 (03/211)": Chapter 8.2.1 paragraph C.2
			greenRate	= (y1564Cfg->serviceCfg[svcIndex].cir * 1.25) + y1564Cfg->serviceCfg[svcIndex].eir;
		}
		else
		{
			// If EIR > 20% CIR then Yellow rate = 125% EIR				From "ITU_T Y.1564 (03/211)": Chapter 8.2.1 paragraph C.2
			greenRate	= y1564Cfg->serviceCfg[svcIndex].cir + ( y1564Cfg->serviceCfg[svcIndex].eir * 1.25);
		}
	}

	y1564Results->configurationResults[svcIndex].policerResults.status = test_running;
	// Configure the loopback channel to send the correct frames (size and content)
	initLbChnl(lbChnl,
			y1564Cfg->serviceCfg[svcIndex].targetIsMep,
			&y1564Cfg->serviceCfg[svcIndex].targetMep,
			&y1564Cfg->serviceCfg[svcIndex].frameCfg,
			y1564Cfg->serviceCfg[svcIndex].frameSize,
			selDir, &port, true, 0, true);						// Green frames
	initLbChnl(lbChnl+1,
			y1564Cfg->serviceCfg[svcIndex].targetIsMep,
			&y1564Cfg->serviceCfg[svcIndex].targetMep,
			&y1564Cfg->serviceCfg[svcIndex].frameCfg,
			y1564Cfg->serviceCfg[svcIndex].frameSize,
			selDir, &port, true, 1, true);						// Yellow frames

	retVal = runY1564ConfigTest(lbChnl, selDir, svcIndex,
			greenRate,										// The Green frame rate
			yellowRate,										// The yellow frame rate = 0
			y1564Cfg->configStepDuration,
			y1564Cfg->serviceCfg[svcIndex].frameSize,
			port,
			y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss,
			&y1564Results->configurationResults[svcIndex].policerResults.dataGreen,
			&y1564Results->configurationResults[svcIndex].policerResults.dataYellow);


	if (retVal == 1)
	{
		// user cancelled the test.
		y1564Results->configurationResults[svcIndex].policerResults.status = test_stopped;
		DO_LOG_DEBUG(log_handle,"Policer test stopped on Service #%d", svcIndex);
		return(1);
	}

	combineGreenAndYellow(&combinedResults,&y1564Results->configurationResults[svcIndex].policerResults.dataGreen,&y1564Results->configurationResults[svcIndex].policerResults.dataYellow);

	irTotalIsGreaterThanCirPlusEirPlusM = false;
	// Regardless of colour awareness, see if the received IR is > CRI + EIR + M
	if ((combinedResults.ulrAvg > (CONVERT_MBPS_TO_BPS(y1564Cfg->serviceCfg[svcIndex].cir + y1564Cfg->serviceCfg[svcIndex].eir) + CONVERT_KBPS_TO_BPS(y1564Cfg->serviceCfg[svcIndex].mFactor))))
	{
		irTotalIsGreaterThanCirPlusEirPlusM = true;
	}

	irTotalIsLessThanCirPlusAcceptedLoss = false;

	if(y1564Cfg->serviceCfg[svcIndex].colourAware)
	{
		// If colour aware we are only concerned with checking Green results
		resultsPtr = &y1564Results->configurationResults[svcIndex].policerResults.dataGreen;
	}
	else
	{
		// If colour un-aware then we add Green and Yellow and use the combined values
		resultsPtr = &combinedResults;

		// Convert CIR to BPS to compare it with received IR Avg. If irAvg < [CIR * (1-FLR_accepted)] then the test fails
		if (resultsPtr->ulrAvg < (CONVERT_MBPS_TO_BPS(y1564Cfg->serviceCfg[svcIndex].cir) * (1 - FRAME_LOSS_MILLI_PERCENT_TO_FLR(y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss))))
		{
			irTotalIsLessThanCirPlusAcceptedLoss = true;
		}

	}

	// See if the test criteria pass. If any one fails, the test fails...
	y1564Results->configurationResults[svcIndex].policerResults.pass = true;
	if (resultsPtr->txFrameCount == 0)
	{
		DO_LOG_INFO(log_handle,"Y1564 Policer test failed because txFrameCount = 0");
		y1564Results->configurationResults[svcIndex].policerResults.pass = false;
		y1564Results->configurationResults[svcIndex].policerResults.failReason = fail_tx_count_0;
	}
	if (resultsPtr->rxFrameCount == 0)
	{
		DO_LOG_INFO(log_handle,"Y1564 Policer test failed because rxFrameCount = 0");
		y1564Results->configurationResults[svcIndex].policerResults.pass = false;
		y1564Results->configurationResults[svcIndex].policerResults.failReason = fail_rx_count_0;
	}
	if (resultsPtr->frameDelayVarMax > y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh)
	{
		DO_LOG_INFO(log_handle,"Y1564 Policer test failed because frameDelayMax (%luus) > frameDelayThresh (%luus)", resultsPtr->frameDelayVarMax, y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh);
		y1564Results->configurationResults[svcIndex].policerResults.pass = false;
		y1564Results->configurationResults[svcIndex].policerResults.failReason = fail_fdv_thres;
	}
	if (resultsPtr->frameDelayMax > y1564Cfg->serviceCfg[svcIndex].frameDelayThresh)
	{
		DO_LOG_INFO(log_handle,"Y1564 Policer test failed because frameDelayMax (%luus) > frameDelayThresh (%luus)", resultsPtr->frameDelayMax, y1564Cfg->serviceCfg[svcIndex].frameDelayThresh);
		y1564Results->configurationResults[svcIndex].policerResults.pass = false;
		y1564Results->configurationResults[svcIndex].policerResults.failReason = fail_ftd_thres;
	}
	if (resultsPtr->frameLossRatio > y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss)
	{
		DO_LOG_INFO(log_handle,"Y1564 Policer test failed because frameLossRatio (%f\%) > acceptedFrameLoss (%f\%)", ((float)resultsPtr->frameLossRatio) * 0.001, ((float)y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss) * 0.001);
		y1564Results->configurationResults[svcIndex].policerResults.pass = false;
		y1564Results->configurationResults[svcIndex].policerResults.failReason = fail_loss_thres;
	}
	if (irTotalIsGreaterThanCirPlusEirPlusM)	// If irAvg > CIR + EIR + M then test fails
	{
		DO_LOG_INFO(log_handle,"Y1564 Policer test failed because IR-total (%lubps) > CIR + EIR + M (%lubps)", combinedResults.ulrAvg, CONVERT_MBPS_TO_BPS(y1564Cfg->serviceCfg[svcIndex].cir + y1564Cfg->serviceCfg[svcIndex].eir) + CONVERT_KBPS_TO_BPS(y1564Cfg->serviceCfg[svcIndex].mFactor));
		y1564Results->configurationResults[svcIndex].policerResults.pass = false;
		y1564Results->configurationResults[svcIndex].policerResults.failReason = fail_irAvg_high;
	}
	if (irTotalIsLessThanCirPlusAcceptedLoss)	// If irAvg < [CIR * (1-FLR_accepted)] then the test fails (colour-blind only!)
	{
		DO_LOG_INFO(log_handle,"Y1564 Policer test failed because, when colour unaware, IR-total (%lubps) < CIR * acceptedFrameLoss (%lubps)", resultsPtr->ulrAvg,  (CONVERT_MBPS_TO_BPS(y1564Cfg->serviceCfg[svcIndex].cir) * (1 - FRAME_LOSS_MILLI_PERCENT_TO_FLR(y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss))));
		y1564Results->configurationResults[svcIndex].policerResults.pass = false;
		y1564Results->configurationResults[svcIndex].policerResults.failReason = fail_irAvg_low;
	}

	y1564Results->configurationResults[svcIndex].policerResults.status = test_completed;
	DO_LOG_DEBUG(log_handle,"Policer test completed on Service #%d - %s", svcIndex,y1564Results->configurationResults[svcIndex].policerResults.pass ? "Passed" : "Failed");
	return(2);	// Success
}

static int runConfigTests(uint8_t lbChnl, svcDir_t selDir, y1564Cfg_t *y1564Cfg, y1564Results_t *y1564Results)
{
	uint8_t			svc;
	uint8_t			step;
	int				retVal;
	bool 			macValid;
	macAddress_t	macAddress;
	bool			anyTestFail = false;

	DO_LOG_DEBUG(log_handle,"Y1564 configuration tests starting");
	for (svc=0;svc<MAX_NUM_SA_SERVICES;svc++)
	{
		if (y1564Cfg->serviceCfg[svc].enable)
		{
			// Test every enabled service...
			y1564Results->configurationResults[svc].startTime = getCurrentRTC();

			y1564Results->configurationResults[svc].status = test_running;

			// Don't try to test the service if the target MAC is unknown
			rMepGetMAC(y1564Cfg->serviceCfg[svc].targetMep.mdIndex, y1564Cfg->serviceCfg[svc].targetMep.maIndex, y1564Cfg->serviceCfg[svc].targetMep.lMepId, y1564Cfg->serviceCfg[svc].targetMep.rMepId, &macValid, macAddress);
			if (!macValid)
			{
				y1564Results->configurationResults[svc].status = test_failed;
				y1564Results->configurationResults[svc].cirResults.pass[0]			= false;
				y1564Results->configurationResults[svc].cirResults.pass[1]			= false;
				y1564Results->configurationResults[svc].cirResults.pass[2]			= false;
				y1564Results->configurationResults[svc].cirResults.pass[3]			= false;
				y1564Results->configurationResults[svc].cirResults.failReason		= fail_target_unknown;

				y1564Results->configurationResults[svc].eirResults.pass				= false;
				y1564Results->configurationResults[svc].eirResults.failReason		= fail_target_unknown;

				y1564Results->configurationResults[svc].policerResults.pass			= false;
				y1564Results->configurationResults[svc].policerResults.failReason	= fail_target_unknown;
				anyTestFail = true;
				continue;
			}

			// Run the enabled tests for each frame size...

			// Run the CIR test...
			if (y1564Cfg->serviceCfg[svc].enStepTest)
			{
				// In Step mode...
				retVal = runCirTest(lbChnl, selDir, svc, true, y1564Cfg, y1564Results);
				if (retVal == 1)
				{
					goto EXIT_CONFIG_TESTS;
				}
				for (step=0; step < y1564Results->configurationResults[svc].cirResults.steps;step++)
				{
					if (!y1564Results->configurationResults[svc].cirResults.pass[step])
					{
						anyTestFail = true;
					}
				}
			}
			else
			{
				// In full CIR mode
				retVal = runCirTest(lbChnl,  selDir, svc, false, y1564Cfg, y1564Results);
				if (retVal == 1)
				{
					goto EXIT_CONFIG_TESTS;
				}
				if (!y1564Results->configurationResults[svc].cirResults.pass[0])
				{
					anyTestFail = true;
				}
			}

			// Run the EIR test
			if (y1564Cfg->serviceCfg[svc].eir != 0)
			{
				retVal = runEirTest(lbChnl, selDir, svc, y1564Cfg, y1564Results);
				if (retVal == 1)
				{
					goto EXIT_CONFIG_TESTS;
				}
				if (!y1564Results->configurationResults[svc].eirResults.pass)
				{
					anyTestFail = true;
				}
			}

			if (y1564Cfg->serviceCfg[svc].enPolicingTest)
			{
				// Run the Policer test
				retVal = runPolicerTest(lbChnl, selDir, svc, y1564Cfg, y1564Results);
				if (retVal == 1)
				{
					goto EXIT_CONFIG_TESTS;
				}
				if (!y1564Results->configurationResults[svc].policerResults.pass)
				{
					anyTestFail = true;
				}
			}
		}
		y1564Results->configurationResults[svc].status	= anyTestFail ? test_failed :test_completed;
	}
	DO_LOG_DEBUG(log_handle,"Y1564 configuration tests completed");
	sendSaStatusNotificaiton(log_handle, y1564ConfigTest, anyTestFail ? test_failed :test_completed);
	return (2);		// This is the normal exit if the test is left run to completion

EXIT_CONFIG_TESTS:
	// If exiting the function early due to users stopping test, then change all pending to stopped
	for (svc=0;svc<MAX_NUM_SA_SERVICES;svc++)
	{
		if (y1564Cfg->serviceCfg[svc].enable)
		{
			for (step = 0; step < MAX_NUM_CONFIG_STEPS; step++)
			{
				if(y1564Results->configurationResults[svc].cirResults.status[step] == test_pending)
				{
					y1564Results->configurationResults[svc].cirResults.status[step] = test_stopped;
				}
			}
			if(y1564Results->configurationResults[svc].eirResults.status == test_pending)
			{
				y1564Results->configurationResults[svc].eirResults.status = test_stopped;
			}
			if(y1564Results->configurationResults[svc].policerResults.status == test_pending)
			{
				y1564Results->configurationResults[svc].policerResults.status = test_stopped;
			}
			y1564Results->configurationResults[svc].endTime = getCurrentRTC();
		}
		y1564Results->configurationResults[svc].status	= test_stopped;
	}
	DO_LOG_DEBUG(log_handle,"Y1564 configuration tests stopped");
	sendSaStatusNotificaiton(log_handle, y1564ConfigTest, test_stopped);
	return(1);
}

static void configY1564PerfTest(uint8_t lbChnl, svcDir_t selDir,
		uint16_t cirRate, mrPort_t port, uint16_t frameSize)
{

	//disableLBChnl(lbChnl, selDir);			// Make sure the channel is stopped before configuring it

    // rate limit to 100% line rate
    if (cirRate > 1000)
    	cirRate = 1000;

    // Setup the appropriate rates
    _lbSetRate(lbChnl, cirRate);

    _lbSetMode(lbChnl, lb_continuousMode, port);

    _lbClearStats(lbChnl, 1, selDir);
    DO_LOG_DEBUG(log_handle,"Y1564 config chnl #%d CIR=%dMBps,port=%d,framesize=%d",lbChnl,cirRate,port,frameSize);
}

static int runPerformanceTest( svcDir_t selDir, y1564Cfg_t *y1564Cfg, y1564Results_t *y1564Results)
{
	mrPort_t			port;
	uint8_t				svcIndex;
	uint8_t				chnlMask;
	uint8_t				newHead;
	y1564Data_t 		*resultsPtr;
	y1564DataEntry_t	y1564Data;
	double				flrList[MAX_LB_CHANNELS_FOR_Y1564] = {0};
	uint8_t				testDone;
	uint16_t			frameSize;
	bool				macValid;
	macAddress_t		macAddress;
	uint8_t				enabledCount = 0;
	uint8_t				unknownCount = 0;
	bool				anyTestFail = false;
	bool				sentAlready = false;

	y1564Results->performanceResults.startTime = getCurrentRTC();
	y1564Results->performanceResults.status = test_running;
	DO_LOG_DEBUG(log_handle,"Y1564 performance test starting");

	chnlMask = 0;
	for (svcIndex = 0; svcIndex < MAX_NUM_SA_SERVICES; svcIndex++)
	{
		if (y1564Cfg->serviceCfg[svcIndex].enable)
		{
			enabledCount++;

			// Don't try to test the service if the target MAC is unknown
			rMepGetMAC(y1564Cfg->serviceCfg[svcIndex].targetMep.mdIndex, y1564Cfg->serviceCfg[svcIndex].targetMep.maIndex, y1564Cfg->serviceCfg[svcIndex].targetMep.lMepId, y1564Cfg->serviceCfg[svcIndex].targetMep.rMepId, &macValid, macAddress);
			if (!macValid)
			{
				unknownCount++;
				y1564Results->performanceResults.failReason[svcIndex]	= fail_target_unknown;
				y1564Results->performanceResults.pass[svcIndex]			= false;
				DO_LOG_INFO(log_handle,"MAC address unknown for target MEP on service %d",svcIndex+1);		// +1 is because NETCONF using idices from 1 to 8 not 0 to 7.
				continue;
			}

			// !!!!  IMPORTANT !!!!
			// Performance test can not support different frame sizes for each channel. The y1564Cfg->serviceCfg[].frameSize
			// should only be used for configuration testing.
			// The y1564Cfg_t struct did not make provision for a separate frameSize. A temporary scheme is used to encode the
			// performance frame size into the y1564Cfg->serviceCfg[].emix parmeter since emix was not in use otherwise.
			frameSize = (y1564Cfg->serviceCfg[svcIndex].emix[0] * 0x100) + y1564Cfg->serviceCfg[svcIndex].emix[1];

			// Set the appropriate bit for every loopback channel that is in use
			chnlMask |= 1 << svcIndex;
			// Configure the loopback channel to send the correct frames (size and content)
			initLbChnl(svcIndex,
					y1564Cfg->serviceCfg[svcIndex].targetIsMep,
					&y1564Cfg->serviceCfg[svcIndex].targetMep,
					&y1564Cfg->serviceCfg[svcIndex].frameCfg,
					frameSize,
					selDir, &port, true, 0, true);					// Green frames

			// Configure the loopback mode and rate
			configY1564PerfTest(svcIndex, selDir, y1564Cfg->serviceCfg[svcIndex].cir, port, frameSize);

			flrList[svcIndex]		= FRAME_LOSS_MILLI_PERCENT_TO_FLR(y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss);    // Set the Green Channel loss to the user set value

		    y1564Results->performanceResults.data[svcIndex].frameSize = frameSize;
		    DO_LOG_DEBUG(log_handle,"Service %d, has frame size = %d, CIR = %d",svcIndex, frameSize, y1564Cfg->serviceCfg[svcIndex].cir);
		}
	}

	// Only run the test if one or more enabled service has a known target.
	if (enabledCount > unknownCount)
	{
		// initialise the measurement, capture FIFO, etc. and start transmit of Loopback channels.
		initSaTest(((uint32_t)(y1564Cfg->performanceDuration * 60)), frameSize, selDir, chnlMask | GANGED_OPERATION, flrList);

		DO_LOG_DEBUG(log_handle,"Running performance test for %d minutes",y1564Cfg->performanceDuration);

		// For each channel that is in use set the corresponding bit to a 1
		testDone = chnlMask;
		do
		{
			osSleepMS(2000);
			DO_LOG_DEBUG(log_handle,"Processing results");

			// Before proceeding check if the user has requested to cancel the test
			if (saStopFlag)
			{
				// Disable all active loopback channels
				disableLB(LB_RESET_REG_ALL_CHNL_RESET_MASK,MR_BOTH_TX_RX);
				y1564Results->performanceResults.status = test_stopped;
				DO_LOG_DEBUG(log_handle,"Y1564 performance test stopped");
				return(1);
			}

			LOCK_SA_READ(0);
			for (svcIndex = 0; svcIndex < MAX_NUM_SA_SERVICES; svcIndex++)
			{
				if (y1564Cfg->serviceCfg[svcIndex].enable && (y1564Results->performanceResults.failReason[svcIndex]	!= fail_target_unknown))
				{
					// Read the Green and Yellow results
					readSaData(svcIndex, false, &y1564Data, &newHead);		// TODO: consider adding on-way support later

					// Process the results...
					if (y1564Data.validCnt > 0)
					{
						copyLatestResults(&y1564Results->performanceResults.data[svcIndex], &y1564Data, y1564Data.validCnt-1);

						// Check the SLA thresholds that make sense as the test is running and send immediate fail notification
						if (y1564Results->performanceResults.data[svcIndex].frameDelayVarMax	> y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh)
						{
							anyTestFail = true;
							y1564Results->performanceResults.failReason[svcIndex] = fail_fdv_thres;
						}
						if (y1564Results->performanceResults.data[svcIndex].frameDelayMax > y1564Cfg->serviceCfg[svcIndex].frameDelayThresh)
						{
							anyTestFail = true;
							y1564Results->performanceResults.failReason[svcIndex] = fail_ftd_thres;
						}
					}

					if ((y1564Data.testDone != 0) && (testDone & (1 << svcIndex)))
					{
						// The test is done...validCnt will most likely be 0 for the last few reads but the
						// the non-sampled values (delay/delay var and pkt counts must be read now to
						// get the final test values

						copyFinalResults(&y1564Results->performanceResults.data[svcIndex], &y1564Data);

						// As each service completes clear it associated bit
						testDone &= ~(1 << svcIndex);
					}
				}
			}
			UNLOCK_SA_READ();

			if (anyTestFail)
			{
				if (!sentAlready)
				{
					sendSaStatusNotificaiton(log_handle, y1564PerfTest, anyTestFail ? test_failed :test_completed);
					sentAlready = true;
				}
#ifdef STOP_PERFORMANCE_TEST_ON_ERROR		// TODO: decide if the performance test should be stopped now?
				disableLB(LB_RESET_REG_ALL_CHNL_RESET_MASK,MR_BOTH_TX_RX);
				y1564Results->performanceResults.status = test_failed;
				DO_LOG_DEBUG(log_handle,"Y1564 performance test failed");
				return(1);
#endif
			}

		} while(testDone != 0);
		//} while(testDone != 0 && !anyTestFail)  //TODO: add this to stop the test

		for (svcIndex = 0; svcIndex < MAX_NUM_SA_SERVICES; svcIndex++)
		{
			if (y1564Cfg->serviceCfg[svcIndex].enable && (y1564Results->performanceResults.failReason[svcIndex]	!= fail_target_unknown))
			{
				resultsPtr = &y1564Results->performanceResults.data[svcIndex];

				// See if the test criteria pass. If any one fails, the test fails.
				y1564Results->performanceResults.pass[svcIndex] = true;
				if (resultsPtr->txFrameCount == 0)
				{
					DO_LOG_INFO(log_handle,"Y1564 performance test failed because txFrameCount = 0");
					y1564Results->performanceResults.pass[svcIndex] = false;
					y1564Results->performanceResults.failReason[svcIndex] = fail_tx_count_0;
				}
				if (resultsPtr->rxFrameCount == 0)
				{
					DO_LOG_INFO(log_handle,"Y1564 performance test failed because rxFrameCount = 0");
					y1564Results->performanceResults.pass[svcIndex] = false;
					y1564Results->performanceResults.failReason[svcIndex] = fail_rx_count_0;
				}
				if (resultsPtr->frameDelayVarMax	> y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh)
				{
					DO_LOG_INFO(log_handle,"Y1564 performance test failed because frameDelayVarMax (%luus) > frameDelayVarThresh (%luus)", resultsPtr->frameDelayVarMax, y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh);
					y1564Results->performanceResults.pass[svcIndex] = false;
					y1564Results->performanceResults.failReason[svcIndex] = fail_fdv_thres;
				}
				if (resultsPtr->frameDelayMax		> y1564Cfg->serviceCfg[svcIndex].frameDelayThresh)
				{
					DO_LOG_INFO(log_handle,"Y1564 performance test failed because frameDelayMax (%luus) > frameDelayThresh (%luus)", resultsPtr->frameDelayVarMax, y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh);
					y1564Results->performanceResults.pass[svcIndex] = false;
					y1564Results->performanceResults.failReason[svcIndex] = fail_ftd_thres;
				}
				if (resultsPtr->frameLossRatio		> y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss)
				{
					DO_LOG_INFO(log_handle,"Y1564 performance test failed because frameLossRatio (%f%%) > acceptedFrameLoss (%f%%)", ((float)resultsPtr->frameLossRatio) * 0.001, ((float)y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss) * 0.001);
					y1564Results->performanceResults.pass[svcIndex] = false;
					y1564Results->performanceResults.failReason[svcIndex] = fail_loss_thres;
				}
				if (resultsPtr->availableSecCount	< ((float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh * (float)y1564Cfg->performanceDuration) * (60.0/100000.0))
				{
					DO_LOG_INFO(log_handle,"Y1564 performance test failed because availableSecCount (%usec) < Total Time (%usec) * availabiltyThresh (%f%%) (%fsec)", resultsPtr->availableSecCount, y1564Cfg->performanceDuration * 60,(((float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh) * 0.001),((float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh * (float)y1564Cfg->performanceDuration) * (60.0/100000.0));
					//fprintf(stderr,"Fail Details: availableSecCount (%usec) < Total Time (%dsec) * availabiltyThresh (%6.3f%%) (%3.0fsec)\n", resultsPtr->availableSecCount, y1564Cfg->performanceDuration * 60,(((float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh) * 0.001),(((float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh * (float)y1564Cfg->performanceDuration) * (60.0/100000.0)));
					y1564Results->performanceResults.pass[svcIndex] = false;
					y1564Results->performanceResults.failReason[svcIndex] = fail_avail_thres;
				}

				if (y1564Results->performanceResults.pass[svcIndex])
				{
					DO_LOG_DEBUG(log_handle,"Details: frameDelayVarMax (%uus) <= frameDelayVarThresh (%uus)", resultsPtr->frameDelayVarMax, y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh);
					DO_LOG_DEBUG(log_handle,"Details: frameDelayMax (%uus) <= frameDelayThresh (%uus)", resultsPtr->frameDelayVarMax, y1564Cfg->serviceCfg[svcIndex].frameDelayVarThresh);

					DO_LOG_DEBUG(log_handle,"Details: frameLossRatio (%6.3f%%) <=  acceptedFrameLoss (%6.3f%%)", ((float)resultsPtr->frameLossRatio) * 0.001, ((float)y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss) * 0.001);
					//fprintf(stderr,"Details: frameLossRatio (%6.3f%%) <=  acceptedFrameLoss (%6.3f%%)\n", ((float)resultsPtr->frameLossRatio) * 0.001, ((float)y1564Cfg->serviceCfg[svcIndex].acceptedFrameLoss) * 0.001);

					DO_LOG_DEBUG(log_handle,"Details: availableSecCount (%usec) >= Total Time (%dsec) * availabiltyThresh (%6.3f%%) (%3.0fsec)", resultsPtr->availableSecCount, y1564Cfg->performanceDuration * 60,(float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh * 0.001,((float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh * (float)y1564Cfg->performanceDuration) * (60.0/100000.0));
					//fprintf(stderr,"Details: availableSecCount (%usec) >= Total Time (%dsec) * availabiltyThresh (%6.3f%%) (%3.0fsec)\n", resultsPtr->availableSecCount, y1564Cfg->performanceDuration * 60,(((float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh) * 0.001),(((float)y1564Cfg->serviceCfg[svcIndex].availabiltyThresh * (float)y1564Cfg->performanceDuration) * (60.0/100000.0)));
					y1564Results->performanceResults.failReason[svcIndex] = fail_no_reason;
				}
				else
				{
					anyTestFail = true;
				}
			}
		}
	}
	else
	{
		DO_LOG_INFO(log_handle,"Y1564 performance test not run because target MACs not known for all services");
		anyTestFail = true;		// This will happen if the MAC address of all enabled services are unknown
	}
	y1564Results->performanceResults.endTime = getCurrentRTC();
	y1564Results->performanceResults.status = test_completed;
	DO_LOG_DEBUG(log_handle,"Y1564 performance test completed");
	sendSaStatusNotificaiton(log_handle, y1564PerfTest, anyTestFail ? test_failed :test_completed);
	return(2);	// Success
}

/*
 * @brief This should be called when the Y1564 tests are enabled. It will set the initial value of the results status for all tests.
 */
static void initY1564ResultsStatus(y1564Cfg_t *y1564Cfg, y1564Results_t *y1564Results)
{
	uint8_t	svc,k;

	LOCK_SA_LIST(0);

	// First clear all the results to defaults
	setSaY1564ResultDefaults(y1564Results);

	for (svc=0;svc<MAX_NUM_SA_SERVICES;svc++)
	{
		if (!y1564Cfg->enConfigTest)
		{
			y1564Results->configurationResults[svc].status = test_disabled;
		}
		else
		{
			if (y1564Cfg->serviceCfg[svc].enable)
			{
				// Mark every enabled service test as pending...
				y1564Results->configurationResults[svc].status = test_pending;

				for (k = 0; k < MAX_NUM_CONFIG_STEPS; k++)
				{
					if (y1564Cfg->serviceCfg[svc].enStepTest)
					{
						y1564Results->configurationResults[svc].cirResults.status[k] = test_pending;
					}
					else
					{
						y1564Results->configurationResults[svc].cirResults.status[k] = test_disabled;
					}
				}
				y1564Results->configurationResults[svc].cirResults.status[0] = test_pending;

				if (y1564Cfg->serviceCfg[svc].eir == 0)
				{
					y1564Results->configurationResults[svc].eirResults.status = test_disabled;
				}
				else
				{
					y1564Results->configurationResults[svc].eirResults.status = test_pending;
				}

				if (y1564Cfg->serviceCfg[svc].enPolicingTest)
				{
					y1564Results->configurationResults[svc].policerResults.status = test_pending;
				}
				else
				{
					y1564Results->configurationResults[svc].policerResults.status = test_disabled;
				}

			}
			else
			{
				y1564Results->configurationResults[svc].status = test_disabled;
			}
		}


		if(y1564Cfg->enPerformanceTest)
		{
			y1564Results->performanceResults.status = test_pending;
		}
		else
		{
			y1564Results->performanceResults.status = test_disabled;
		}

	}
	UNLOCK_SA_LIST();
}

/**
 * @brief This is intended to run as an OS task. It runs all the configured tests for RFC2544 in sequence.
 * @param args Not used. Should be set to NULL.
 *
 * This task does not stay active. When all tests have run the task will self terminate.
 * If 'stop' is requested then the task will tidy up and exit without completing any further tests.
 */
void y1564TestsTask(void *args)
{
	uint8_t		lbChnl = 0;
	svcDir_t	selDir;
	int 		retVal;

	DO_LOG_NOTICE(log_handle,"Y1564 tests starting");

	LOCK_SA_LIST(0);
	saStopFlag = false;
	UNLOCK_SA_LIST();

	// set the results status to reflect which tests are disabled and which are pending
	LOCK_SA_READ(0);
	initY1564ResultsStatus(&saY1564Cfg, &saY1564Results);
	UNLOCK_SA_READ();

	// Reserve and lock a loopback channel - this will be channel 0 - its the only channel used by CFM loopback!
	EGL_ASSERT(_lbLockChannel(lbChnl,MAX_NUM_MEPS + lbChnl) >= 0, 1);			//confirm that we got assigned a loopback channel

	// Disable all active loopback channels
	disableLB(LB_RESET_REG_ALL_CHNL_RESET_MASK,MR_BOTH_TX_RX);

	// Configure the test direction - TWO-WAY or ONE-WAY Tx or Rx (!!!NOTE: only TWO_WAY supported in this release!!!)
	if (saY1564Cfg.frameType == y1731_lbm_frames)
	{
		selDir = both_tx_rx;
	}
	else
	{
		//TODO: not supported in this release
		selDir = both_tx_rx;
	}

	if (saY1564Cfg.enConfigTest)
	{
		retVal = runConfigTests(lbChnl, selDir, &saY1564Cfg, &saY1564Results);
		if (retVal == 1)
		{
			if (saY1564Cfg.enPerformanceTest)
			{
				saY1564Results.performanceResults.status = test_stopped;
			}
			DO_LOG_NOTICE(log_handle,"Y1564 tests stopped");
			goto EXIT_Y1564_TASK;
		}
	}

	if (saY1564Cfg.enPerformanceTest)
	{
		retVal = runPerformanceTest(selDir, &saY1564Cfg, &saY1564Results);
		if (retVal == 1)
		{
			DO_LOG_NOTICE(log_handle,"Y1564 tests stopped");
			sendSaStatusNotificaiton(log_handle, y1564AllTests, test_stopped);
			goto EXIT_Y1564_TASK;
		}
	}

	y1564Status = test_completed;
	DO_LOG_NOTICE(log_handle,"Y1564 tests completed");
	sendSaStatusNotificaiton(log_handle, y1564AllTests, test_completed);

EXIT_Y1564_TASK:
	// release the loopback channel
	_lbUnLockChannel(lbChnl,MAX_NUM_MEPS + lbChnl);
	_lbFreeChannel(MAX_NUM_MEPS + lbChnl);
	// Change this flag to show that this test is no longer running
	LOCK_SA_LIST(0);
	saActiveFlag = false;
	UNLOCK_SA_LIST();
	// Cleanly exit the task
	osTaskDelete( 0 );
}

/**
 * @brief This will launch a OS task to run the RFC2544 tests if no other SA test is running.
 * @return
 * 1: Operation rejected - Another SA Test is already running
 * 2: Operation successful
 */
int y1564RunTests(void)
{
	LOCK_SA_LIST(0);
	if (!saActiveFlag)
	{
		saActiveFlag = true;
		UNLOCK_SA_LIST();
		/* Create the main task. */
		osTaskCreate( y1564TestsTask, "y1564tst    ", Y1564_TASK_STACKSIZE, NULL, OS_STACK_THREAD_PRIO - 1, NULL );
		return(2);		// Operation successful
	}
	UNLOCK_SA_LIST();
	DO_LOG_INFO(log_handle,"Y1564 tests already running!");
	return(1);			// Operation rejected - Another SA Test is already running
}

int saStopTest(void)
{
	int	count = 10;		// Stall at most 5 seconds (10 x 0.5sec)

	LOCK_SA_LIST(0);
	if (saActiveFlag)
	{
		DO_LOG_DEBUG(log_handle,"SA test 'Stop' requested");
		saStopFlag = true;
		UNLOCK_SA_LIST();

		// The test thread will take up to 3 seconds to terminate so wait for it
		while (saActiveFlag && count-- > 0)
		{
			osSleepMS(500);		// sleep for 0.5 sec
		}
		if (saActiveFlag)
		    EGL_ASSERT(false,1);		// NOT GOOD - the test task failed to stop in 5 sec!
		else
			return(2);			// Operation successful
	}
	UNLOCK_SA_LIST();
	DO_LOG_DEBUG(log_handle,"SA test 'Stop' - nothing to stop");
	return(1);			// Operation rejected - No SA Test running
}

#if 0
// -----------------------------------------------------------------------------------------------------
// startY1564OneWayMeas
// -----------------------------------------------------------------------------------------------------
// This function should only be called for a channel being used on the receive side of a
// 1-way test. It should only be called after Rx has already been enabled on the channel.
// It will clear the context associated with measurement made from the 1-second sampling FIFO
// thus starting/resetting the measurements.
// =====================================================================================================
int	startSaOneWayMeas(uint16_t pktSize, uint16_t chnlMask, double *flThres)
{
	uint8_t		k;
	uint8_t		currHead;
	uint32_t	data;
	//uint8_t	selMask;

	#if defined(MINGOA_ENABLE_DEBUG_LOGGING) && ((defined(MINGOA_DEBUG_LEVEL_MATCH) && (MINGOA_DEBUG_LEVEL == MINGOA_DEBUG_LEVEL_SERVICES)) || (!defined(MINGOA_DEBUG_LEVEL_MATCH) && (MINGOA_DEBUG_LEVEL <= MINGOA_DEBUG_LEVEL_SERVICES)))
		sprintf(msgStr, "INFO::startY1564OneWayMeas: (re)Start measurements for channels in mask = 0x%02X\n",chnlMask);
		writeLog(MINGOA_DEBUG_LEVEL_SERVICES,msgStr);
	#endif

	//selMask = MAX_LB_CHANNELS_FOR_Y1564; //initially set to an invalid value

	// Initialise the local parameters for all masked channels
	for (k=0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
	{
		if (chnlMask & (0x1<<k))
		{
			// These Tx related parameters are used for calculations based
			// on the live Tx Seq number.
			tstAcuTotalTxCnt[k] = 0;
			tstLastTxCnt[k] = 1;  //This is 1 because the seq No starts at 1 not 0!

			// These Tx related parameters are used for calculations based
			// on the received sequence number NOT the live Tx Seq number.
			tstAcuTxCnt[k] = 0;
			tstLastSampTxCnt[k] = 0;
			tstAcuDeltaTxCnt[k] = 0;

			tstRxPktSize[k] = pktSize;
			tstAcuRxCnt[k] = 0;
			tstLastSampRxCnt[k] = 0;
			tstValidCnt[k] = 0;
			tstDuration[k] = 0;			// a value of 0 indicates continuous operation
			tstAcuDeltaRxCnt[k] = 0;
			tstAcuTotalRxCnt[k] = 0;
			consecAvailSec[k] = 0;
			consecUnAvailSec[k] = 0;
			availSecCnt[k] = 0;
			availSt[k] = False;
			tstFirstSamp[k] = True;
			tstIrMin[k] = 0xFFFFFFFF;
			tstIrMax[k] = 0;
			tstFlrThres[k] = flThres[k];
			tstStartCnt[k] = 0;
			tstLastRxCnt[k] = 0;
			tstTLag[k] = 0;				// This means the test start measurement immediately
		}
	}

	// get the current head pointer. This is the next free location in the FIFO.
	mrHalRegRead(CLR_LBR_ERR_REG_ADDR,&data);
	currHead = (uint8_t) ((data >> 5) & 0x7);

	// Initialise the tail pointer for all masked channels to the current Head
	for (k=0;k<MAX_LB_CHANNELS_FOR_Y1564;k++)
	{
		if (chnlMask & (0x1<<k))
			tstTailPtr[k] = currHead;
	}

	return(NO_ERR);
}
#endif

