/**************************************************************
$Workfile:   eoclb.c  $
***************************************************************
**
** FIRMWARE, Copyright (C) 2000-2002
** Ikanos Communications, Fremont, CA
** All rights reserved. 
**
** The information and contents of this file are the
** proprietary information of Ikanos Communication and may not
** be disclosed or used without the formal written approval of
** Ikanos Communication
**
$Revision: 1.1 $
$Date: 2006/10/16 04:34:49 $
***************************************************************/

#include <stdio.h>
#include "ipos.h"
#include "eocapp.h"
#include "appconfig.h"
#include "oampdef.h"
#include "oamp.h"
#include "oampstruct.h"
#include "portmap.h"
#include "oampdb.h"
#include "apperrCode.h"
#include "apposmacro.h"
#include "dapi.h"

/* a general comment:
 *
 * several places a parameter is declared to be long but which
 * only receives unsigned short (U16).  the parameter is
 * declared long so the initial value can be set to -1,
 * providing unambigious proof no value has been stored for
 * this parameter.   this avoids having to creat a separate
 * parameter to record whether the value has been initialized
 *
 * another general comment:
 *
 * several places clock ticks are converted to milliseconds
 * or vice versa.  Although one could propose to do this in
 * a routine, unfortunately the exact calculation really
 * should be set up by hand to get the maximum precision
 * and accuracy.  64-bit arithmetic is not avaiable in general.
 *
 * a third general comment:
 *
 * Several places in the source code are bracketed off with
 * #defines specific to this file.  These include REPORT_TIMING,
 * REPORT_INCREMENTAL_LATENCY, REPORT_NACK_BUSY and NO_DELAY_ZERO
 * These represent alternate versions of the programming that at
 * one point were useful and may prove useful again.  None of these
 * #defines need to be defined (or should be defined) on the
 * compiler command line.
 *
 */

/* For EOC Loopback Stress tests */

#define  EOC_LB_MAGIC	0xab
#define  EOC_LB_MAGIC_B		(EOC_CTL_B + 1)
#define  EOC_LB_PORT_B		(EOC_LB_MAGIC_B + 1)
#define  EOC_LB_CO_INDEX_B	(EOC_LB_PORT_B + 1)
#define  EOC_LB_CPE_INDEX_B	(EOC_LB_CO_INDEX_B + sizeof( short ))

/* Sometimes the HIC-BME interface rejects the packet with a
   NACK_BUSY.  In this situation this software retries the
   operation.  LIMIT_REPEATED_ERRORS defines the number of
   times to try the operation (Including the initial attempt).	*/

#define  LIMIT_REPEATED_ERRORS  2

/*******  Local data structure definitions    *************************/

typedef	void  (*completionJob) (U32 arg1, U32 arg2, ipos_msg_buf	*pBuf);

/* This struct MUST match the oamp_cb_msg struct in oampfuncs.c !! */

typedef	struct
{
	ipos_port_id	portId;
	U8				uid;
	int				status;
	int				cntRpt;			/* repeat counter	*/
	int				parLen;
    char			par[128];		/* parameters	*/
} oamp_cb_msg_lb;

/* special note: indicies run from 1 to IPOS_MAX_PORTS ...

   One could adjust the index into eocLbStats and eocLbTestParams,
   but that's a mess, as each developer has to remember to do it
   everytime.  By increasing the size of the array, we do waste
   one slot, but the gain is in fewer bugs from misindexing  */

/* coIndexAck and cpIndexAck are both longs, so they can be
   initialized to -1.  A negative value thus implies no index
   at all has been seen and accept the 1st one w/o comment.  */

typedef struct
{
	U32	countSent;
	U32	countAckInternal;	/* iposEocSendDataStream's callback gets good status */
	U32	countAck;
	U32	outgoingErrors;
	U32	internalErrors;		/* iposEocSendDataStream's callback gets bad status */
	U32	crcErrorsStart;
	long	coIndexAck;
	long	cpeIndexAck;
	U16	coIndex;
	U16	cpeIndex;
	short	repeatedInternalError;

} eoc_lb_stats;

eoc_lb_stats	eocLbStats[ IPOS_MAX_PORTS + 1 ];

typedef struct
{
	appOampFunc	pTheJob;
	U32		totalCount;
	U32		currentCount;
	U32		currentTick;
	U32		startTick;
	int		lbDelayTicks;
} eoc_lb_test_params;

eoc_lb_test_params	eocLbTestParams[ IPOS_MAX_PORTS + 1 ];

#define LATENCY_BIT_WIDTH  8
#define LATENCY_ARRAY_SIZE  (1 << LATENCY_BIT_WIDTH)
#define LATENCY_INDEX_MASK  (LATENCY_ARRAY_SIZE - 1)

typedef struct
{
	long	tickSent[ LATENCY_ARRAY_SIZE ];
	long	cumulativeTime;
	int	countRecv;
	int	countLost;
	U16	worstCase;

   /* helps track how many packets are in the pipeline */

	U16	currentCoIndex;
} eoc_lb_latency;

eoc_lb_latency	eocLbLatency[ IPOS_MAX_PORTS + 1 ];

#ifdef VTU_O
static int	quiet_mode = 0;
#endif

/*******  External Data and Functions    ******************************/

extern oamp_bme oampBme [IPOS_MAX_BME+1];  		/* BME objects for OAMP   */

extern void	addCompletionJob(ipos_port_id portId, U8 UID, U32 uPar, int status,
				 U8 *par, int len,  completionJob  pFunJob);
extern int	getPortNum (ipos_port_id *pid);
extern void	reportPortErrorCounts( int portIndex );
extern void	resetPortErrorCounts( int portIndex );

/******  Forward Declarations    **************************************/

#ifdef VTU_O
static int	appEocLbStressReq(ipos_port_id	pid, int len );
#endif
static void	appEocLbResetLatency( int lineNum );

static U32
getCurrentCrcErrors( ipos_port_id pid )
{
	U32	retval;

	if (oampGetBandPlan == 4)
	  retval = (oampBme[pid.bmeNum].oampPort[pid.portNum].Counters.NeCRC_F + 1)/2;
	else
	  retval = (oampBme[pid.bmeNum].oampPort[pid.portNum].Counters.NeCRC_F);

	return( retval );
}

#ifdef VTU_O

/* if a packet fails to transfer to the BME initially, the latency will already be
   set up for that packet.  Before calling appEocLbStressReq again, we need to first
   erase the latency entry for that packet so that storeInitialLatency will not be
   confused and display a spurious error message					*/ 

static void
eraseInitialLatency( int portIndex, U16 coIndex )
{
	int	indexLatencyArray = (coIndex & LATENCY_INDEX_MASK);

	eocLbLatency[ portIndex ].tickSent[ indexLatencyArray ] = -1;
}

static void
storeInitialLatency( int portIndex, U16 coIndex, U32 currentTick )
{
	int	indexLatencyArray = (coIndex & LATENCY_INDEX_MASK);

	if (eocLbLatency[ portIndex ].tickSent[ indexLatencyArray ] >= 0)
	{
#ifdef REPORT_TIMING
		if (quiet_mode == 0)
		  printf(
	    "port %d, loopback ack never received for index %d, new index is %d at %ld\n",
			 portIndex, indexLatencyArray, coIndex, iposTickGet()
		  );
		eocLbLatency[ portIndex ].countLost++;
#else
		if (quiet_mode == 0)
		  printf(
	    "port %d, loopback ack never received for index %d, new index is %d\n",
			 portIndex, indexLatencyArray, coIndex
		  );
		eocLbLatency[ portIndex ].countLost++;
#endif
		
	}

	eocLbLatency[ portIndex ].tickSent[ indexLatencyArray ] = (currentTick & 0xFFFF);
	eocLbLatency[ portIndex ].currentCoIndex = coIndex;
}

static void
storeLatencyResult( int portIndex, U16 coIndex, U32 currentTick )
{
	int	indexLatencyArray = (coIndex & LATENCY_INDEX_MASK);

	if (eocLbLatency[ portIndex ].tickSent[ indexLatencyArray] < 0)
	{
		if (quiet_mode == 0)
		  printf( "port %d, loopback latency never set up for latency index %d, index is %d\n",
			 portIndex, indexLatencyArray, coIndex
		  );
	}
	else
	{
		U16	maskedTick = (currentTick & 0xFFFF);
		U16	startTick = (U16) (eocLbLatency[ portIndex ].tickSent[ indexLatencyArray] & 0xFFFF);
		U16	thisLatency = maskedTick - startTick;

		if (thisLatency > eocLbLatency[ portIndex ].worstCase)
		  eocLbLatency[ portIndex ].worstCase = thisLatency;
		eocLbLatency[ portIndex ].countRecv++;
		eocLbLatency[ portIndex ].cumulativeTime += thisLatency;

	    /*activate next line to see individual loopback packet latency, but beware
	      the message can be verbose and might disrupt the timing of the task that
	      calls storeLatencyResult (most likely BME Handler)	*/
#ifdef REPORT_INCREMENTAL_LATENCY
#define LATENCY_REPORT_FREQUENCY  64
	    if (coIndex % LATENCY_REPORT_FREQUENCY == 0)
	    printf( "port: %d, index: %d, loopback latency: %d, current CO index: %d\n",
		     portIndex, coIndex, thisLatency, eocLbLatency[ portIndex ].currentCoIndex );
#endif
		eocLbLatency[ portIndex ].tickSent[ indexLatencyArray ] = -1;
	}
}

void
appEocLbReportStats( ipos_port_id pid )
{
	int	changeCrcErrors, lineNum;
	int	averageLatency, worstLatency;
	U32	currentCrcErrors;
	U32	intermediate;

	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{
		printf( "failed to get a line number for port b%dp%d in appEocLbReportStats\n",
			 pid.bmeNum, pid.portNum );
		return;
	}

	currentCrcErrors = getCurrentCrcErrors( pid );
	changeCrcErrors = (int) (currentCrcErrors - eocLbStats[ lineNum ].crcErrorsStart);

	printf( "Port b%dp%d, sent %ld, %ld internal acks, %ld external acks\n",
		 pid.bmeNum, pid.portNum,
		 eocLbStats[ lineNum ].countSent,
		 eocLbStats[ lineNum ].countAckInternal,
		 eocLbStats[ lineNum ].countAck
	);
	printf( "with %ld internal errors, %ld outgoing errors, %d CRC errors\n", 
		 eocLbStats[ lineNum ].internalErrors,
		 eocLbStats[ lineNum ].outgoingErrors,
		 changeCrcErrors
	);

	if (eocLbLatency[ lineNum ].countRecv > 0)
	{
		int	ticksPerSecond = sysClkRateGet();

	    /* average latency */

		intermediate = (U32) (eocLbLatency[ lineNum ].cumulativeTime);

	    /* short cumulative time: multiply by 1000 first to avoid loss of precision */

		if (intermediate < ticksPerSecond * 100)
		{
			intermediate = intermediate * 1000 / ticksPerSecond;
		}

	    /* long cumulative time: divide by ticks per second first
	       to help prevent integer overflow */

		else
		{
			intermediate = (intermediate / ticksPerSecond) * 1000;
		}
		averageLatency = (int) (intermediate / eocLbLatency[ lineNum ].countRecv);

	    /* worst case latency */

		intermediate = (U32) (eocLbLatency[ lineNum ].worstCase * 1000);
		worstLatency = (int) (intermediate / sysClkRateGet());
		printf( "average latency: %d ms for %d packets, worst case: %d ms\n",
			 averageLatency, eocLbLatency[ lineNum ].countRecv, worstLatency
		);
		printf( "%d packets confirmed lost\n", eocLbLatency[ lineNum ].countLost );
	}
	else
	{
		printf( "no loopback packet acknowledged, cannot compute latency statistics\n" );
	}
}
#endif

#ifdef VTU_R
/*static void
internalCpeAckComplete( ipos_port_id pid, U8 UID, U32 uPar,  int status, void *par)
{
	int	lineNum;

	extern port_error_counters	portErrorCounters[ IPOS_MAX_PORTS+1 ];

	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{
		printf(
	    "failed to get a line number for port b%dp%d in internalCpeAckComplete\n",
	     pid.bmeNum, pid.portNum
		);
		return;
	}
	if (status != IPOS_SUCCESS)
	{
		if (status == NACK_BUSY)
		{
			portErrorCounters[ lineNum ].countNackBusy++;
		}

		portErrorCounters[ lineNum ].totalErrors++;
	}
}
*/
/* most likely on the CPE side ... */

void
appEocLbStressResp( ipos_port_id pid, int len, U8 *data )
{
	U16	thisIndex;
	U16	newCoIndex;
	int	lineNum, lineNumOrig;

	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{
		printf( "failed to get a line number for port b%dp%d in appEocLbStressResp\n",
			 pid.bmeNum, pid.portNum );
		return;
	}

	lineNumOrig = data[ EOC_LB_PORT_B ];
	memcpy( &newCoIndex, &data[ EOC_LB_CO_INDEX_B ], sizeof( U16 ));

	if (eocLbStats[ lineNum ].coIndexAck >= 0)
	{
		if (eocLbStats[ lineNum ].coIndexAck + 1 != (long) newCoIndex)
		{
			printf(
		   "for port %d, discrepancy in CO index: old: %ld, new: %d, sent on port %d\n",
		    lineNum, eocLbStats[ lineNum ].coIndexAck, newCoIndex, lineNumOrig
			);
		}
	}

	eocLbStats[ lineNum ].coIndexAck = (long) newCoIndex;

	thisIndex = (U16) eocLbStats[ lineNum ].cpeIndex;
	memcpy( &data[ EOC_LB_CPE_INDEX_B ], &thisIndex, sizeof( thisIndex ) );
	eocLbStats[ lineNum ].cpeIndex++;

	iposEocSendDataStream(pid, len, data, 0,0, NULL);
}
#endif

#ifdef VTU_O
void
appEocLbStressResult( ipos_port_id pid, int len, U8 *data )
{
	U16	newCoIndex;
	U16	newCpeIndex;
	int	lineNum, lineNumOrig;
	int	coIndexDiscrepancy = 0, cpeIndexDiscrepancy = 0;

	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{
		printf( "failed to get a line number for port b%dp%d in appEocLbStressResult\n",
			 pid.bmeNum, pid.portNum );
		return;
	}

	lineNumOrig = data[ EOC_LB_PORT_B ];
	memcpy( &newCoIndex, &data[ EOC_LB_CO_INDEX_B ], sizeof( U16 ));
	memcpy( &newCpeIndex, &data[ EOC_LB_CPE_INDEX_B ], sizeof( U16 ));
#if 0
	printf( "Received special stress loopback message of length %d, for port b%dp%d\n",
				 len, pid.bmeNum, pid.portNum );
	printf( "old CO index: %ld, new CO index: %d, old CPE index: %ld, new CPE index: %d\n",
		 eocLbStats[ lineNum ].coIndexAck, newCoIndex,
		 eocLbStats[ lineNum ].cpeIndexAck, newCpeIndex );
#endif

	storeLatencyResult( lineNum, newCoIndex, iposTickGet() );

	if (eocLbStats[ lineNum ].coIndexAck >= 0)
	{
		if (eocLbStats[ lineNum ].coIndexAck + 1 != (long) newCoIndex)
		{
			coIndexDiscrepancy = 1;
		}
	}

	if (eocLbStats[ lineNum ].cpeIndexAck >= 0)
	{
		if (eocLbStats[ lineNum ].cpeIndexAck + 1 != (long) newCpeIndex)
		{
			cpeIndexDiscrepancy = 1;
		}
	}

	if (coIndexDiscrepancy && cpeIndexDiscrepancy)
	{
		if (quiet_mode == 0)
		{
			printf( "discrepancy in both CO and CPE indexes for port %d, sent from port %d\n",
				 lineNum, lineNumOrig );
			printf( "old CO: %ld, new CO: %d; old CPE: %ld, new CPE: %d\n",
				 eocLbStats[ lineNum ].coIndexAck, newCoIndex,
				 eocLbStats[ lineNum ].cpeIndexAck, newCpeIndex
			);
		}
	}
	else if (coIndexDiscrepancy)
	{
		if (quiet_mode == 0)
		  printf( "discrepancy in CO index for port %d: old: %ld, new: %d, sent from port %d\n",
			   lineNum, eocLbStats[ lineNum ].coIndexAck, newCoIndex, lineNumOrig
		  );
	}
	else if (cpeIndexDiscrepancy)
	{
		if (quiet_mode == 0)
		  printf( "discrepancy in CPE index for port %d: old: %ld, new: %d, sent from port %d\n",
			   lineNum, eocLbStats[ lineNum ].cpeIndexAck, newCpeIndex, lineNumOrig
		  );
	}

	eocLbStats[ lineNum ].coIndexAck = (long) newCoIndex;
	eocLbStats[ lineNum ].cpeIndexAck = (long) newCpeIndex;
	eocLbStats[ lineNum ].countAck++;
}

static void
makeErrorReport( int portIndex, char *errorMessage )
{
	if (errorMessage == NULL)
	  errorMessage = "(null)";

	printf( "EOC stress loopback test stopped: %s\n", errorMessage );
	printf( "Sent %ld packets, with %ld acknowledged\n",
		 eocLbStats[ portIndex ].countSent, eocLbStats[ portIndex ].countAck
	);
}

static void
makeFinalReport( int portIndex )
{
	int		elapsedTicks, elapsedTime, lbDelay;
	U32		currentTick;
	U32		intermediate;
	ipos_port_id	pid;

	if (getBmePortId( portIndex, &pid) != IPOS_SUCCESS)
	{
		printf( "\nCannot get BME port ID for port index %d\n", portIndex );	
		return;
	}

	/*appEocLbReportStats( pid );	no. this is confusing.  */
				/* more loopbacks may be coming */
	currentTick = iposTickGet();
	elapsedTicks = currentTick - eocLbTestParams[ portIndex ].startTick;
	intermediate = (U32) (elapsedTicks);
	elapsedTime = (int) ((intermediate * 1000) / sysClkRateGet());

	intermediate = (U32) (eocLbTestParams[ portIndex ].lbDelayTicks);
	lbDelay = (int) ((intermediate * 1000) / sysClkRateGet());
		
	printf( "\nAll EOC loopback packets have been sent for test on port %d\n", portIndex );
#if 0
	printf( "Start tick: %ld, current tick: %ld, elapsed time: %d ms, delay was: %d ms\n",
		 eocLbTestParams[ portIndex ].startTick, currentTick, elapsedTime, lbDelay );
#else
	printf( "Elapsed time: %d ms, delay was: %d ms\n", elapsedTime, lbDelay );
#endif
}

static void
internalCoAckComplete( ipos_port_id portId, U8 UID, U32 uPar,  int status, void *par)
{
	int	lineNum;

    /*printf( "\ninternalCoAckComplete starts, port: b%dp%d, UID: %d, upar: %lu, status: %d, par: %ld",
				portId.bmeNum, portId.portNum, UID, uPar, status, (long) par );*/
	if (getLineNum( portId, &lineNum ) != IPOS_SUCCESS)
	{
		printf( "\nfailed to get a line number for port b%dp%d in internalCoAckComplete\n",
			 portId.bmeNum, portId.portNum );
		return;
	}

	if (status != IPOS_SUCCESS)
	{
		char    errorMsg[ 36 ];
		int     errCode = status;
		int     msgLen = (int) uPar;    /* see appEocLbStressReq */

		eocLbStats[ lineNum ].internalErrors++;
		if (status == (int) NACK_BUSY &&
		    eocLbStats[ lineNum ].repeatedInternalError < LIMIT_REPEATED_ERRORS)
		{

		  /* decrement coIndex for this port to avoid introducing a discrepancy
		     in the CO index that would generate an error message when the packet
		     is received (the CPE would make a similar error report)		*/

			eocLbStats[ lineNum ].coIndex--;
#ifdef REPORT_NACK_BUSY
#ifdef REPORT_TIMING
			printf(
		    "Received NACK_BUSY for index %d on port b%dp%d at %ld, trying again\n",
		    eocLbStats[ lineNum ].coIndex, portId.bmeNum, portId.portNum, iposTickGet()
			);
#else
			printf(
		    "Received NACK_BUSY for index %d on port b%dp%d, trying again\n",
		    eocLbStats[ lineNum ].coIndex, portId.bmeNum, portId.portNum
			);
#endif
#endif
			eraseInitialLatency( lineNum, eocLbStats[ lineNum ].coIndex );
			iposTaskDelay( 1 );
			errCode = appEocLbStressReq( portId, msgLen );
			if (errCode == IPOS_SUCCESS)
			  return;

		   /* if appEocLbStressReq fails, fall through to make error report */
		}
		sprintf( &errorMsg[ 0 ], "error %d on port b%dp%d.",
			  errCode, portId.bmeNum, portId.portNum);
		makeErrorReport( lineNum, &errorMsg[ 0 ] );
		return;
	}
	else {
		int		msgLen = (int) uPar;
		int		len = sizeof( msgLen );
		U32		cntRpt = 0;
		U32		currentCount = 0;
		U32		totalCount = eocLbTestParams[ lineNum ].totalCount;
		appOampFunc	pTheJob;

                eocLbStats[ lineNum ].repeatedInternalError = 0;
		eocLbStats[ lineNum ].countAckInternal++;

		eocLbTestParams[ lineNum ].currentTick = iposTickGet();
		eocLbTestParams[ lineNum ].currentCount++;
		currentCount = eocLbTestParams[ lineNum ].currentCount;

		if (currentCount > totalCount)
		  cntRpt = 0;
		else
		  cntRpt = totalCount - currentCount;

		pTheJob = eocLbTestParams[ lineNum ].pTheJob;
#ifdef NO_DELAY_ZERO
		if (eocLbTestParams[ lineNum ].lbDelayTicks < 1)
		{
			if (cntRpt < 1)
			  makeFinalReport( lineNum);
			else
			  appEocLbStressReq( portId, msgLen );
		}
		else
		{
			if (pTheJob != NULL)
			  addCompletionJob(portId, UID, cntRpt, status, (U8 *) &msgLen, len, pTheJob );
		}
#else
		if (pTheJob != NULL)
		  addCompletionJob(portId, UID, cntRpt, status, (U8 *) &msgLen, len, pTheJob );
#endif
	}
}

#define MAX_STRESS_LB_LEN       200

/* the completion function for multiple EOC loopbacks running in the oamp task	*/

static void
nextLbStressReqJob(U32 arg1, U32 arg2, ipos_msg_buf *pBuf)
{
	ipos_port_id	pid;
	int		lineNum;

	U32		 cntRpt;
	U8		 cmdTag;
	int		 status;
	int		 msglen, *pMsglen;
	/*char		*funcName="iposEocSendDataStream";  not used currently */
	oamp_cb_msg_lb	*pMsg;			/* See key comment where oamp_cb_msg_snr */
						      /* and oamp_cb_msg_snr are defined */
					 /* It's no coincidence the two are identical !! */
    /*printf( "\nnextLbStressReqJob starts" );*/
	if(pBuf==NULL)
	{
		printf("Invalid msg buffer in nextLbStressReqJob OAMP job\n");
		return;
	}

	/* parse the message	*/

	pMsg=(oamp_cb_msg_lb *) pBuf->pDataBuf;
	if (pMsg == NULL)
	{						   /* squawk instead of bus trap */
		printf( "\nNo loopback callback message in nextLbStressReqJob OAMP job !!\n");
		iposMsgFree(pBuf);
		return;
	}

	pid = pMsg->portId;
	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{
		printf( "in nextLbStressReqJob, cannot get line number for port b%dp%d\n",
			 pid.bmeNum, pid.portNum );
		iposMsgFree(pBuf);
		return;
	}

	cntRpt	= (U32) pMsg->cntRpt;
	cmdTag	= pMsg->uid;
	status = pMsg->status;
	pMsglen = (int *) (pMsg->par);
	memcpy( &msglen, pMsglen, 4 );			/* just in case par not word aligned */
	if (msglen < 5)
	  msglen = 5;
	else if (msglen > MAX_STRESS_LB_LEN)
	  msglen = MAX_STRESS_LB_LEN;
	
    /*printf( "\nport: b%dp%d, count: %lu, message length: %d, status: %d\n",
	       pid.bmeNum, pid.portNum, cntRpt, msglen, status );*/

	if (cntRpt > 0)
	{
		U32	oldTick, currentTick;
		int	lbDelayTicks, currentDelay;

		oldTick = eocLbTestParams[ lineNum ].currentTick;
		currentTick = iposTickGet();
		lbDelayTicks = eocLbTestParams[ lineNum ].lbDelayTicks;
		currentDelay = lbDelayTicks - (int) (currentTick - oldTick);

		if (currentDelay > 0)
		  iposTaskDelay( currentDelay );

		appEocLbStressReq( pid, msglen );
	}
	else
	{
		makeFinalReport( lineNum );
	}

	/* release the message	*/

	iposMsgFree(pBuf);
}

/* Initiating EOC stress loopback request	*/

static int
appEocLbStressReq(ipos_port_id	pid, int len )
{
	int	lineNum;
	U8	eocLbStressMsg[ 256 ];

	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{

   /* Should not happen, pid was itself obtained with getLineNum */

		printf( "in appEocLbStressReq, cannot get line number for port b%dp%d\n",
			 pid.bmeNum, pid.portNum );
		return( APP_ERROR_PORT_ID );
	}

	if (oampGetPortState(pid)==uSHOWTIME)
	{

    /* construct the stress test data stream msg 	*/

		eocLbStressMsg[EOC_CTL_B]=EOC_LOOPBACK_REQ;
		eocLbStressMsg[ EOC_LB_MAGIC_B ]=EOC_LB_MAGIC;	/* magic notifies the callback */
    						     /* this is a stress test loopback message */
		eocLbStressMsg[EOC_LB_PORT_B ] = lineNum;	/* assume lineNum < 256 */
		memcpy( &eocLbStressMsg[ EOC_LB_CO_INDEX_B ], &eocLbStats[ lineNum ].coIndex,
						sizeof( eocLbStats[ lineNum ].coIndex ) );
		storeInitialLatency( lineNum, eocLbStats[ lineNum ].coIndex, iposTickGet() );
		eocLbStats[ lineNum ].coIndex++;
		if (iposEocSendDataStream(pid, len, eocLbStressMsg, 0, len, internalCoAckComplete) != IPOS_SUCCESS)
		{
			eocLbStats[ lineNum ].outgoingErrors++;
		}
		else 
		  eocLbStats[ lineNum ].countSent++;
	}
	else
	{
		char	errorMsg[ 36 ];

		sprintf( &errorMsg[ 0 ], "b%dp%d is not in showtime.",pid.bmeNum, pid.portNum);
		makeErrorReport( lineNum, &errorMsg[ 0 ] );
		return( OAMP_ERROR_PORT_NOT_READY );
	}	

	return( IPOS_SUCCESS );
}

int
appEocLbTestInProgress( ipos_port_id pid, int *presult )
{
	int	lineNum, result;

	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{
		printf( "failed to get a line number for port b%dp%d in appEocTestInProgress\n",
			 pid.bmeNum, pid.portNum );
		return( APP_ERROR_PORT_ID );
	}

	if (eocLbTestParams[ lineNum ].totalCount != eocLbTestParams[ lineNum ].currentCount)
	  result = 1;
	else
	  result = 0;

	*presult = result;
	return( IPOS_SUCCESS );
}

/* helper programs */

int
appEocSetupLbTest(
	ipos_port_id pid,
	int requestCompletionJob,
	int totalCount,
	int lbDelayTicks
)
{
	int	lineNum;

	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{
		printf( "failed to get a line number for port b%dp%d in appEocSetupLbTest\n",
			 pid.bmeNum, pid.portNum );
		return( APP_ERROR_PORT_ID );
	}

	if (requestCompletionJob)
	  eocLbTestParams[ lineNum ].pTheJob = nextLbStressReqJob;
	else
	  eocLbTestParams[ lineNum ].pTheJob = NULL;
	eocLbTestParams[ lineNum ].totalCount = totalCount;
	eocLbTestParams[ lineNum ].currentCount = 0;
	eocLbTestParams[ lineNum ].lbDelayTicks = lbDelayTicks;
	eocLbTestParams[ lineNum ].currentTick = iposTickGet();
	eocLbTestParams[ lineNum ].startTick = eocLbTestParams[ lineNum ].currentTick;

	eocLbStats[ lineNum ].crcErrorsStart = getCurrentCrcErrors( pid );
	appEocLbResetLatency( lineNum );
	eocLbStats[ lineNum ].repeatedInternalError = 0;

	return( IPOS_SUCCESS );
}

void
appEocGetLbCounts( ipos_port_id pid, int *pcurrent, int *ptotal )
{
	int	lineNum;

	if (getLineNum( pid, &lineNum ) != IPOS_SUCCESS)
	{
		printf( "failed to get a line number for port b%dp%d in appEocSetupLbTest\n",
			 pid.bmeNum, pid.portNum );
		*pcurrent = 0;
		*ptotal = 0;
	}
	else {
		*pcurrent = eocLbTestParams[ lineNum ].currentCount;
		*ptotal = eocLbTestParams[ lineNum ].totalCount;
	}
}
#endif

static void
appEocLbResetPortStats( int portIndex )
{
	ipos_port_id	pid;

	eocLbStats[ portIndex ].countSent = 0;
	eocLbStats[ portIndex ].countAck = 0;
	eocLbStats[ portIndex ].countAckInternal = 0;
	eocLbStats[ portIndex ].outgoingErrors = 0;
	eocLbStats[ portIndex ].internalErrors = 0;
	eocLbStats[ portIndex ].coIndex = 0;
	eocLbStats[ portIndex ].cpeIndex = 0;
	eocLbStats[ portIndex ].coIndexAck = -1;
	eocLbStats[ portIndex ].cpeIndexAck = -1;

	if (getBmePortId( portIndex, &pid ) != IPOS_SUCCESS)
	  eocLbStats[ portIndex ].crcErrorsStart = 0;
	else
	  eocLbStats[ portIndex ].crcErrorsStart = getCurrentCrcErrors( pid );
}

/* called from uitask.c */

void
appEocLbResetStats( int lineNum )
{
	int	portIndex;

	if (lineNum > 0 && lineNum <= IPOS_MAX_PORTS)
	  appEocLbResetPortStats( lineNum );
	else {
		for (portIndex = 0; portIndex <= IPOS_MAX_PORTS; portIndex++)
		{
			appEocLbResetPortStats( portIndex );
		}
	}
}

static void
appEocLbResetPortTestParams( int portIndex )
{
	eocLbTestParams[ portIndex ].pTheJob = NULL;
	eocLbTestParams[ portIndex ].totalCount = 0;
	eocLbTestParams[ portIndex ].currentCount = 0;
	eocLbTestParams[ portIndex ].currentTick = 0;
	eocLbTestParams[ portIndex ].lbDelayTicks = 0;
}

/* called from uitask.c */

void
appEocLbResetTestParams( int lineNum )
{
	int		portIndex;

	if (lineNum > 0 && lineNum <= IPOS_MAX_PORTS)
	  appEocLbResetPortTestParams( lineNum );
	else {
		for (portIndex = 0; portIndex <= IPOS_MAX_PORTS; portIndex++)
		{
			appEocLbResetPortTestParams( portIndex );
		}
	}
}

static void
appEocLbResetLatency( int lineNum )
{
	int		portIndex, iter;

	if (lineNum > 0 && lineNum <= IPOS_MAX_PORTS)
	{
		for (iter = 0; iter < LATENCY_ARRAY_SIZE; iter++)
		  eocLbLatency[ lineNum ].tickSent[ iter ] = -1;
		eocLbLatency[ lineNum ].countRecv = 0;
		eocLbLatency[ lineNum ].countLost = 0;
		eocLbLatency[ lineNum ].worstCase = 0;
		eocLbLatency[ lineNum ].cumulativeTime = 0;
	}
	else
	{
		for (portIndex = 0; portIndex <= IPOS_MAX_PORTS; portIndex++)
		{
			for (iter = 0; iter < LATENCY_ARRAY_SIZE; iter++)
			  eocLbLatency[ portIndex ].tickSent[ iter ] = -1;
			eocLbLatency[ portIndex ].countRecv = 0;
			eocLbLatency[ portIndex ].countLost = 0;
			eocLbLatency[ portIndex ].worstCase = 0;
			eocLbLatency[ portIndex ].cumulativeTime = 0;
		}
	}
}

void
appEocLbResetStress( int portIndex )
{
	appEocLbResetStats( portIndex );
	appEocLbResetTestParams( portIndex );
	appEocLbResetLatency( portIndex );
}

/* #defines for length include the whole message, including internal bookkeeping */
 
#define MIN_STRESS_LB_LEN	16	/* subject to change w/o notice */
#define MAX_STRESS_LB_LEN	200

#define MAX_STRESS_LB_COUNT	30000
#define DEFAULT_STRESS_LB_COUNT	100

/* no check for supervisor mode */

#ifdef VTU_O

static void
uiEocLbStressReq( ipos_port_id pid, int flowControl )
{
 	char		str[256];
	int		lbLen = MAX_STRESS_LB_LEN;
	int		lbCount = DEFAULT_STRESS_LB_COUNT;
	int		lbDelayMs = 0;
	int		lbDelayTicks = 0;
	int		errCode, iter;
	U32		intermediate;

	str[ 0 ] = '\0';
	printf( "Enter length of loopback message (max %d): ", MAX_STRESS_LB_LEN );
	gets(str);
	sscanf(str,"%i",&lbLen);
	if (lbLen < MIN_STRESS_LB_LEN || lbLen > MAX_STRESS_LB_LEN)
	  lbLen = MAX_STRESS_LB_LEN;

	printf( "Enter count of loopbacks to send: " );
	gets(str);
	sscanf(str,"%i",&lbCount);
	if (lbCount > MAX_STRESS_LB_COUNT)
	  lbCount = DEFAULT_STRESS_LB_COUNT;
	else if (lbCount < 1)
	  lbCount = 1;

	printf( "Enter delay in milliseconds between messages: " );
	gets(str);
	sscanf(str,"%i",&lbDelayMs);

/* Guarantee full 32-bit precision arithmetic */

	intermediate = (U32) lbDelayMs;
	intermediate *= sysClkRateGet();	/* fast system might tick 1000 / second */
	intermediate /= 1000;			/* integer overflow at 4e6 or 4000 seconds */

	lbDelayTicks = (int) intermediate;
	if (lbDelayMs > 0 && lbDelayTicks < 1)	/* if operator asked for a non-zero delay */
	  lbDelayTicks = 1;			/* make sure that happens */

	printf( "Will send %d messages of length %d, waiting %d ticks between each\n",
		 lbCount, lbLen, lbDelayTicks );
	printf( "using port %d on BME %d\n", pid.portNum, pid.bmeNum );

	if (flowControl)			/* only test with flow control is documented */
	{
		appEocSetupLbTest( pid, 1 /* request completion job */, lbCount, lbDelayTicks );
		errCode = appEocLbStressReq( pid, lbLen );
		if (errCode != IPOS_SUCCESS)
		{
			printf( "\nProblem startng loopback stress test with flow control\n" );
		}
	}
	else				/* no flow control is not documented, may break BME */
	{
		appEocSetupLbTest( pid, 0 /* No completion job */, 1, lbDelayTicks );

		for (iter = 0; iter < lbCount; iter++)
		{
			errCode = appEocLbStressReq( pid, lbLen );
			if (errCode != IPOS_SUCCESS)
			{
				if (iter == 0)
				  printf( "Error on first iteration\n" );
				else
				  printf( "\nFailed to send EOC loopback message on the %d iteration\n",
					   iter + 1
				  );
				break;
			}
			else
			{
				if (lbDelayTicks > 0)
				  iposTaskDelay( lbDelayTicks );
			}
		}
	}
}

void
uiEocLbReportStats( void )
{
	int		linenum = 0;
	char		str[ 256 ];			/* called at same level as getPortNum, */
	ipos_port_id	pid;				/* which also has char str[ 256 ] */

	printf ("Enter port number (press Enter for all): ");
	gets( str );
	sscanf(str,"%i",&linenum);

	if (linenum < 1)
	{
		for (linenum = 1; linenum <= IPOS_MAX_PORTS; linenum++)
		  if (getBmePortId(linenum, &pid) == IPOS_SUCCESS)
		  {
			appEocLbReportStats( pid );
			reportPortErrorCounts( linenum );
		  }

		/* ignore failures from getBmePortId */
	}
	else
	{
		if (getBmePortId(linenum, &pid) != IPOS_SUCCESS)
		{
			printf("\nInvalid port number %d", linenum);
		}
		else {
			appEocLbReportStats( pid );
			reportPortErrorCounts( linenum );
		}
	}
}

void
uiEocLbStressMenu( void )
{
	char		str[ 256 ];
	int		stressLbCmd = 0, stopIt = 0, printMenu = 1, testInProgress = 0;
	int		portNum = 0;
	ipos_port_id	pid;

	while ( stopIt == 0 )
	{
		stressLbCmd = 0;				/* force new command selection */
		if (printMenu)
		{
			printf( "\nStress Looback Test Menu\n");
			printf( "\n   1) Perform multiple EOC loopback test");
			printf( "\n   2) Report multiple EOC loopback test statistics for a port");
			printf( "\n   3) Reset multiple EOC loopback test statistics for a port");
			printf( "\n   4) Reset multiple EOC loopback latency statistics for a port");
			printf( "\n   5) Reset multiple EOC loopback test parameters for a port");
			printf( "\n   6) Report error counts for a port");
			printf( "\n   7) Reset error counts for a port");
			printf( "\n   8) Turn quiet mode on");
			printf( "\n   9) Turn quiet mode off");
			printf( "\n   99) Exit from menu");
		}
		printf ("\n\nSelect option: ");
		gets( str );
		if (strlen( str ) < 1)
		{
			printMenu = 0;
			continue;
		}
		sscanf( str,"%i",&stressLbCmd ); 

		switch (stressLbCmd)
		{
		  case 1:
			if (getPortNum(&pid) < 0)
			{
				break;
			}

			if (appEocLbTestInProgress( pid, &testInProgress ) != IPOS_SUCCESS)
			{
				break;	/* appEocLbTestInProgress reports the error */
			}
			if (testInProgress)
			{
				printf( "Test in progress on port b%dp%d, cannot continue\n",
					 pid.bmeNum, pid.portNum
				);
				break;
			}

			uiEocLbStressReq( pid, 1 );
			stopIt = 0;
			printMenu = 0;
			break;

		  case 2:
			uiEocLbReportStats();
			stopIt = 0;
			printMenu = 0;
			break;

		  case 3:
		  case 4:
		  case 5:
		  case 6:
		  case 7:
			printf( "Enter port number: " );
  			gets (str);
			portNum = 0;
			sscanf(str,"%i",&portNum);

			switch (stressLbCmd)
			{
			  case 3:
				appEocLbResetStats( portNum );
				printf( "\nEOC Loopback test statistics reset\n" );
				break;

			  case 4:
				appEocLbResetLatency( portNum );
				printf( "\nEOC Loopback latency statistics reset\n" );
				break;

			  case 5:
				appEocLbResetTestParams( portNum );
				break;

			  case 6:
				reportPortErrorCounts( portNum );
				break;

			  case 7:
				resetPortErrorCounts( portNum );
				break;

			  default:
				printf( "\nProgramming error, unexpected command index of %d\n",						   stressLbCmd );
				break;
			}
			stopIt = 0;
			if (stressLbCmd == 6)	/* reportPortErrorCounts */
			  printMenu = 0;
			else
			  printMenu = 1;    /* other options reset various stats and counters */
			break;

		  case 99:
			stopIt = 1;
			break;

		/* stress request with no flow control is not documented */

		  case 90:
			if (getPortNum(&pid) < 0)
			{
				break;
			}

		/* no check for test in progress ... */

			uiEocLbStressReq( pid, 0 );
			stopIt = 0;
			printMenu = 1;
			break;

		  case 8:
			quiet_mode = 1;
			break;

		  case 9:
			quiet_mode = 0;
			break;

		  default:
			printf( "\nInput of %s not recognized, try again\n", str );
			stopIt = 0;
			printMenu = 1;
			break;
		}
	}
}
#endif

/* CPE version only works with error counts for the ports */

#ifdef VTU_R
void
uiEocLbStressMenu( void )
{
	int	portNum = 0;
	int	portErrorCmd = 0;
	char	str[ 256 ];

	printf( "\n   1) Report error counts for a port");
	printf( "\n   2) Reset error counts for a port");
	printf( "\n   99) Exit from menu");
	printf ("\n\nSelect option: ");
	gets( str );
	if (strlen( str ) < 1)
	{
		return;
	}
	sscanf( str,"%i",&portErrorCmd ); 

	switch (portErrorCmd)
	{
	  case 1:
	  case 2:
		printf( "Enter port number: " );
  		gets (str);
		portNum = 0;
		sscanf(str,"%i",&portNum);

		switch (portErrorCmd)
		{
		  case 1:
			reportPortErrorCounts( portNum );
			break;

		  case 2:
			resetPortErrorCounts( portNum );
			break;

		  default:
			printf( "\nProgramming error, unexpected command index of %d\n",
				   portErrorCmd );
			break;
		}
		break;

	  case 99:
		break;

	  default:
		printf( "Invalid selection %s\n", str );
		break;
	}
}
#endif
