/**************************************************************
$Workfile:   eocapi.c  $
***************************************************************
**
** Host Application, Copyright (C) 2000-2002
** Ikanos Communication, 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 $
**************************************************************/

/* These programs are to be used by all applications that work with Ikanos EOC
   protocols and are independent of any particular protocol or message type */

#include <stdio.h>
#include <string.h>

#include "eocapi.h"
#include "eocapp.h"

static iposEocPerProtocol	iposEocProtocolTable[ IPOS_MAX_PROTOCOLS ];

/* notify IPOS of an EOC protocol */

int
iposEocRegHandler( U16 eocProtocol, U8 uid, iposEocHandler pProtocolHandler )
{
	int	iter, found_an_entry = 0, retval = IPOS_SUCCESS;

	for (iter = 0; iter < IPOS_MAX_PROTOCOLS && found_an_entry == 0; iter++)
	{
		if (iposEocProtocolTable[ iter ].eocProtocol == EOC_NO_PROTOCOL)
		{
			U16	linenum = 1;

			iposEocProtocolTable[ iter ].pRemoteCb = pProtocolHandler;
			iposEocProtocolTable[ iter ].eocProtocol = eocProtocol;
			iposEocProtocolTable[ iter ].uid = uid;

			for (linenum = 1; linenum < MAX_NUM_OF_PORTS + 1 ; linenum++)
			{
				pIposEocPerPort	pThisPortEntry =
					 &iposEocProtocolTable[ iter ].perPort[ linenum ];

				getBmePortId( linenum, &(pThisPortEntry->pid) );
				pThisPortEntry->semCmd = iposSemCreate();
				pThisPortEntry->pCmdCb = NULL;
				pThisPortEntry->stateCmd = IPOSEOC_STATE_CMD_IDLE;
				iposCreateTmr( &(pThisPortEntry->ptmrRsnd) );
			}

			found_an_entry = 1;
		}
		else if (iposEocProtocolTable[ iter ].eocProtocol == eocProtocol)
		{
			printf( "\niposEocRegHandler: error, protocol %u already registered\n", eocProtocol );
			found_an_entry = 1;
			retval = IPOS_ERROR;
		}
	}

	if (found_an_entry == 0)
	  retval = IPOS_ERROR;

	return( retval );
}

/*
 * two accessor methods,
 * 1st returns system wide protocol handler
 * 2nd returns per port protocol handler
 *
 */

pIposEocPerProtocol
iposEocGetPerProtocol( U16 eocProtocol )
{
	pIposEocPerProtocol	retAddr = NULL;
	int			iter, found_the_entry = 0;

	for (iter = 0; iter < IPOS_MAX_PROTOCOLS && found_the_entry == 0; iter++)
	{
		if (iposEocProtocolTable[ iter ].eocProtocol == eocProtocol)
		{
			found_the_entry = 1;
			retAddr = &(iposEocProtocolTable[ iter ]);
		}
	}

	return( retAddr );
}

pIposEocPerPort
iposEocGetPerPort( ipos_port_id pid, U16 eocProtocol )
{
	pIposEocPerPort		retAddr = NULL;
	pIposEocPerProtocol	perProtocolAddr = NULL;
	int			lineNum;

	if (getLineNum(pid, &lineNum) != IPOS_SUCCESS)
	{
		return( NULL );
	}

	perProtocolAddr = iposEocGetPerProtocol( eocProtocol );

	if (perProtocolAddr != NULL)
	  retAddr = &(perProtocolAddr->perPort[ lineNum ]);

	return( retAddr );
}

/* method changes the state of the command message system */

int
iposEocCmdStateUpdate( pIposEocPerPort perPort, int Event )
{
	U8	curState;

	if (Event == IPOSEOC_EVNT_RESET)
	  perPort->stateCmd = IPOSEOC_STATE_CMD_IDLE;
	else {
		curState = perPort->stateCmd;

		switch (curState)
		{
		  case IPOSEOC_STATE_CMD_IDLE:

			if(Event== IPOSEOC_EVNT_CMD_SENT)
			{
				 perPort->stateCmd = IPOSEOC_STATE_CMD_INPROGRESS;
			}
			break;

		  case IPOSEOC_STATE_CMD_INPROGRESS :

			if (Event == IPOSEOC_EVNT_RESP_RCVD || Event == IPOSEOC_EVNT_RESP_EXPIRED)
			{
				perPort->stateCmd = IPOSEOC_STATE_CMD_IDLE;
			}
			break;

		default:
			break;
		}
	}

	return	IPOS_SUCCESS;
}

void
iposEocCmdCoreReset( pIposEocPerPort perPort )
{
	perPort->pCmdCb = NULL;
	perPort->msgLen = 0;
	memset( &(perPort->msgRsn[ 0 ]), 0, sizeof( perPort->msgRsn ) );
}

static void
eocCmdCoreReportError( pIposEocPerPort perPort, int errCode )
{
	if (errCode == IPOS_SUCCESS)
	  errCode = IPOS_ERROR;

	iposSemGive( perPort->semCmd );

	if (perPort->pCmdCb != NULL)
	{
		(perPort->pCmdCb)(
			perPort->pid, perPort->uid, errCode,
			NULL, 0
		);
	}

	iposEocCmdCoreReset( perPort );
}

void
iposEocCmdCoreResend( U32 arg1, U32 arg2 )
{
	int		proceed = 1;
	int		errCode = IPOS_SUCCESS;
	pIposEocPerPort perPort = (pIposEocPerPort) arg1;

	if (perPort == NULL)
	{
		printf( "\nProgramming error in EOC Command Core Resend, per port handle not valid\n" );
		return;
	}

	if (perPort->resendCount > 0)
	{
		perPort->resendCount--;

		errCode = iposEocSendDataStream(
			  perPort->pid,
			  perPort->msgLen,
			&(perPort->msgRsn[ 0 ]),
			  perPort->uid,
			  0, NULL
		);

		if (errCode != IPOS_SUCCESS)
		{
			proceed = 0;
		}

		if (proceed) {
			errCode = iposSetTmr(
				perPort->ptmrRsnd,
				perPort->resendInterval,
				iposEocCmdCoreResend, (U32) perPort, (U32) 0
			);
			if (errCode != IPOS_SUCCESS)
			{
				proceed = 0;
			}
		}
	}
	else
	{
		errCode = IPOSEOC_ERROR_TIMEOUT;
		proceed = 0;
	}

	if (proceed == 0)
	  eocCmdCoreReportError( perPort, errCode );
}

int
iposEocCmdCoreSend(
	ipos_port_id pid,
	U8 *eocMsg,
	int lenMsg,
	U8 uid,
	int resendCount,
	int resendInterval,
	iposEocCb pCmdCb
) {
	U16		eocProtocol;
	int		errCode, retval = IPOS_SUCCESS;
	pIposEocPerPort perPortEntry = NULL;

	if (lenMsg < 1 || lenMsg > MAX_MSG_SIZE || eocMsg == NULL) {
		return( IPOS_ERROR );
	}

/* iposEocCmdCoreSend only works with real protocols, i.e. a
   protocol type as expressed in the 1st byte of the message */

	eocProtocol = (U16) eocMsg[ EOC_CTL_B ];
	perPortEntry = iposEocGetPerPort( pid, eocProtocol );
	if (perPortEntry == NULL)
	{
		printf(
	    "\nError in EOC Command Core Send, no per port object for protocol %d, port b%dp%d\n",
	       eocProtocol, pid.bmeNum, pid.portNum
		);
		return( IPOS_ERROR );
	}

	errCode = iposSemTake( perPortEntry->semCmd, IPOS_WAIT_FOREVER );
	if (errCode != OS_SUCCESS)
	{
		return( IPOS_ERROR );
	}

	errCode = iposEocSendDataStream(pid, lenMsg, eocMsg, uid, 0, NULL);

	if (errCode != IPOS_SUCCESS) {
		iposSemGive( perPortEntry->semCmd );
		iposEocCmdStateUpdate( perPortEntry, IPOSEOC_EVNT_RESET );
		retval = errCode;
	}
	else {
		retval = IPOS_SUCCESS;

	  /* Next test is for the (unlikely) situation that the application expects no response */

		if (resendCount < 1 && resendInterval < 1) {
			iposSemGive( perPortEntry->semCmd );
			iposEocCmdStateUpdate( perPortEntry, IPOSEOC_EVNT_RESET );
		}
		else			/* successfully sent to iPOS */
		{			/* do not give the semaphore until ack received or timeout */
			perPortEntry->uid = uid;
			perPortEntry->resendInterval = resendInterval;
			perPortEntry->resendCount = resendCount;
			perPortEntry->pCmdCb = pCmdCb;
			memcpy( &(perPortEntry->msgRsn[ 0 ]), eocMsg, lenMsg );
			perPortEntry->msgLen = lenMsg;

			iposEocCmdStateUpdate( perPortEntry, IPOSEOC_EVNT_CMD_SENT );

			errCode = iposSetTmr(
				perPortEntry->ptmrRsnd,
				resendInterval,
				iposEocCmdCoreResend, (U32) perPortEntry, (U32) 0
			);
		}
	}

	return( retval );
}

void
iposEocCmdSetCtrlHdr( ipos_ctrl_hdr *pHdr, U8 eocProtocol, U8 msgType, U8 ackType, U8 uid )
{
	pHdr->genericHdr.pt = eocProtocol;
	pHdr->genericHdr.msgType = msgType;
	pHdr->genericHdr.length = 5;
	pHdr->uid = uid;
	pHdr->ack = ackType;
}

int
iposEocEncodeCtrlHdr( ipos_ctrl_hdr *pHdr, U8 **pPmsg )
{
	int size = 0;

    /* Msg header - Protocol Type */

	**pPmsg = pHdr->genericHdr.pt ;
	*pPmsg += 1 ;
	size++ ;

    /* Msg header - Message Type */

	**pPmsg = pHdr->genericHdr.msgType ;
	*pPmsg += 1 ;
	size++ ;
	  
    /* Msg header - Length */

	**pPmsg = pHdr->genericHdr.length ;
	*pPmsg += 1 ;
	size++ ;

    /* User Id */

	**pPmsg = pHdr->uid ;
	*pPmsg += 1 ;
	size++ ;

    /* Ack */

	**pPmsg = pHdr->ack ;
	*pPmsg += 1 ;
	size++ ;

	return size ;
}

int
iposEocGetMsgClass( U8 *pEocMsg )
{
	U8	msgType, ack;
	int	retval;

	msgType = pEocMsg[ IPOS_MSGHDR_MSGTYPE ];
	ack = pEocMsg[ IPOS_MSGHDR_ACK ];

	if (msgType==IPOS_MSG_DATA_BLOCK || msgType==IPOS_MSG_DATA_ACK)
	{
		retval = IPOS_MSG_FILE;
	}
	else if (ack == IPOS_ACK_CMD)
	{
		retval = IPOS_MSG_COMMAND;
	}
	else if (ack == IPOS_ACK_ACK || ack == IPOS_ACK_NACK)
	{
		retval = IPOS_MSG_RESPONSE;
	}
	else {
		retval = IPOS_MSG_UNKNOWN;
	}

	return( retval );
}

/* sanity check.  enforce protocol standards  */

static int
eocMatchCmdResp( ipos_port_id pid, U8 *pResp, U8 *pCmd )
{
	U8	uidCmd = pCmd[ IPOS_MSGHDR_UID ];
	U8	uidResp = pResp[ IPOS_MSGHDR_UID ];
	U8	msgCmd = pCmd[ IPOS_MSGHDR_MSGTYPE ];
	U8	msgResp = pResp[ IPOS_MSGHDR_MSGTYPE ];

	if (iposEocGetMsgClass( pCmd ) != IPOS_MSG_COMMAND)
	{
		printf( "\nProgramming error for EOC protocol %u, message sent was not a command\n",
			   pCmd[ IPOS_MSGHDR_PT ]
		);   
		return IPOS_ERROR;
	}

	if (iposEocGetMsgClass( pResp ) != IPOS_MSG_RESPONSE)
	{
		return IPOS_ERROR;
	}

	if (msgCmd != msgResp)
	{
		printf( "\nmismatch in EOC message type for protocol %u, sent %u, received %u\n",
			 pCmd[ IPOS_MSGHDR_PT ], msgCmd, msgResp
		);
		return IPOS_ERROR;
	}

	if (uidCmd != uidResp)
	{
		printf( "\nmismatch in uid for EOC protocol %u, message type %u, sent %u, received %u\n",
			 pCmd[ IPOS_MSGHDR_PT ], msgCmd, uidCmd, uidResp
		);

	   /* not enforced ... yet */
	}

	return( IPOS_SUCCESS );
}

int
iposEocDecodeCtrlHdr( U8 *pMsgBuf, ipos_ctrl_hdr *pCtrlHdr )
{
	int size = 0;
	
	/* msg header - protocol type */
	pCtrlHdr->genericHdr.pt = *pMsgBuf ;
	pMsgBuf++ ;
	size++ ;

	/* msg header - message type */
	pCtrlHdr->genericHdr.msgType = *pMsgBuf ;
	pMsgBuf++ ;
	size++ ;

	/* msg header - length of the message */
	pCtrlHdr->genericHdr.length = *pMsgBuf ;
	pMsgBuf++ ;
	size++ ;

	/* User Id */
	pCtrlHdr->uid = *pMsgBuf ;
	pMsgBuf++ ;
	size++ ;

	/* Ack */
	pCtrlHdr->ack = *pMsgBuf ;
	pMsgBuf++ ;
	size++ ;

	return size ;
}

static U8
eocGetCmdState( pIposEocPerPort pPortEntry )
{
	return( pPortEntry->stateCmd );
}

int
iposEocCmdCoreResp( ipos_port_id pid, U16 eocProtocol, U8 *pMsg, int lenMsg )
{
	pIposEocPerPort	pThisPortEntry = iposEocGetPerPort( pid, eocProtocol );
	U8		cmdState = IPOSEOC_STATE_CMD_IDLE;

	if (pThisPortEntry == NULL)
	{
		printf(
	    "\nError in EOC Command Core Response, no per port object for protocol %d, port b%dp%d\n",
	       eocProtocol, pid.bmeNum, pid.portNum
		);
		return( IPOS_ERROR );
	}

	cmdState = eocGetCmdState( pThisPortEntry );
	if (cmdState == IPOSEOC_STATE_CMD_IDLE )
	{
		printf( "\nport b%dp%d, EOC protocol %u, response to command called in idle state\n",
			 pThisPortEntry->pid.bmeNum, pThisPortEntry->pid.portNum, eocProtocol
		);
	}
	else
	{
		int	errCode;

		if (eocMatchCmdResp(
			pThisPortEntry->pid, pMsg, &(pThisPortEntry->msgRsn[ 0 ]) ) == IPOS_SUCCESS
		)
		{
			iposEocCmdStateUpdate( pThisPortEntry, IPOSEOC_EVNT_RESP_RCVD );

			errCode=iposResetTmr( pThisPortEntry->ptmrRsnd );
			iposSemGive( pThisPortEntry->semCmd );
			if (pThisPortEntry->pCmdCb != NULL)
			{
				(pThisPortEntry->pCmdCb)(
					pThisPortEntry->pid,
					pThisPortEntry->uid, IPOS_SUCCESS,
					pMsg, lenMsg
				);
			}

			iposEocCmdCoreReset( pThisPortEntry );
		}
	}

	return( IPOS_SUCCESS );
}
