
/*M*                     
//                    INTEL CORPORATION PROPRIETARY INFORMATION
//      This software is supplied under the terms of a license agreement or
//      nondisclosure agreement with Intel Corporation and may not be copied
//      or disclosed except in accordance with the terms of that agreement.
//            Copyright (c) 1995-2000 Intel Corporation.  All Rights Reserved.
//
*M*/
/*
 * $Log:   //cospublic004/shareddata/isc/ISC2x/common/src/imb/kcs.c.v  $
 * 
 *    Rev 1.7   02 Dec 1999 14:50:42   jpaiasno
 * Fixed logic problems in KCS receive state
 * machine to resolve timing dependencies on
 * BMC firmware
 * 
 * 
 *    Rev 1.6   24 Nov 1999 11:34:40   jpaiasno
 * bypassed ipmi 1.0 changes for receive
 * state  in the KCS state machine . Temporary
 * fix till  the firmware issues are resolved
 * 
 *    Rev 1.5   15 Nov 1999 16:50:50   jpaiasno
 * Added code to kcs state machine to fulfill
 * new IPMI1.0 conformance requirements
 * 
 *    Rev 1.4   07 Oct 1999 15:28:08   jpaiasno
 * Added  new BMC transfer state machine code
 *  to KCS interface for IPMI 1.0 conformance
 * 
 *    Rev 1.3   02 Mar 1999 15:00:02   jrdidner
 * Changed the generic new delay in RECEIVE_INIT to specific
 * delays when OBF is not set and when default is used.
 * This will avoid an unnecessary delay when OBF is set and
 * the data is ready.
 * 
 * 
 *    Rev 1.2   01 Mar 1999 16:39:38   jrdidner
 * Added a new state to the driver state machine.
 * This state implements a delay (which gives up the CPU) 
 * when appropriate.
 * 
 * This modification significantly reduces the CPU Utilization
 * impact of ISC on KCS systems (lancewood, cypress, sitka).
 * 
 * 
 *    Rev 1.1   08 Dec 1998 10:55:46   jpaiasno
 * Added code to support async.
 * DPC events from the EMP console.
 * 
 *    Rev 1.5   24 Mar 1998 15:48:00   fkhanx
 * Added function headers
 * 
 *    Rev 1.4   24 Mar 1998 14:40:16   fkhanx
 * Added copyright header
*/

#include "imb_os.h"
#include "imb_if.h"
#include "imb_drv.h"
#include "kcs.h"

//

//
/////////////////////////////////////////////////////////////////////////////////////////////////
// SmsBufferAvail
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       SmsBufferAvail
//  Purpose:    Check to see if the SMS buffer has anything in it
//  Context:    
//  Returns:    TRUE ,if SMS_ATN flag set , else FALSE
//  Parameters:  
//  Notes:      none
*F*/
static BOOL
SmsBufferAvail( void )
{
	BYTE status;

	ReadPortUchar( KCS_STATUS_REGISTER, &status );

	return (status & KCS_SMS_ATN) != 0;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// GetKcsState
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       GetKcsState
//  Purpose:    Get the STATE bits of the STATUS flag
//  Context:    
//  Returns:    STATE bits
//  Parameters:  
//  Notes:      none
*F*/
static BYTE GetKcsState()
{
	BYTE status;

	ReadPortUchar( KCS_STATUS_REGISTER, &status );

	return status & KCS_STATE_MASK;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// ReadKcsData
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       ReadKcsData
//  Purpose:    Read the data port of the KCS interface
//  Context:    
//  Returns:    Kcs data
//  Parameters:  
//  Notes:      none
*F*/
static BYTE ReadKcsData()
{
	BYTE data;

	ReadPortUchar( KCS_DATAOUT_REGISTER, &data );

	return data;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// WriteKcsData
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       WriteKcsData
//  Purpose:    Write to the data port of the KCS interface
//  Context:    
//  Returns:    void
//  Parameters:  
				data Data to Write
//  Notes:      none
*F*/
static void	WriteKcsData( BYTE data )
{
	WritePortUchar( KCS_DATAIN_REGISTER, data );
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// WriteKcsCmd
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       WriteKcsCmd
//  Purpose:    Write to the command port of the KCS interface
//  Context:    
//  Returns:    void
//  Parameters:  
				cmd Command to Write
//  Notes:      none
*F*/
static void WriteKcsCmd( BYTE cmd )
{
	WritePortUchar( KCS_COMMAND_REGISTER, cmd );
}


/////////////////////////////////////////////////////////////////////////////////////////////////
// WaitOnIBF
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       WaitOnIBF
//  Purpose:    Check to see if Input Buffer Full Flag is reset
//  Context:    
//  Returns:    TRUE if IBF reset,else if Timeout FALSE
//  Parameters:  
				context OS Dependent context
//  Notes:      none
*F*/
static BOOL WaitOnIBF( void *context )
{
	DWORD	retryCount = 0;
	BYTE	status;

	while( 1 ) {
		ReadPortUchar( KCS_STATUS_REGISTER, &status );
		
		if( (status & KCS_IBF) == 0 )
			break;

		DelayRetryFunc( retryCount++, KCS_READY_DELAY, context );

		if ( ImbReqTimedOut( context ) )
			return False;
	}
	return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// OBFset
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       OBFset
//  Purpose:    Get the OBF bit in the KCS interface
//  Context:    
//  Returns:    TRUE if OBF set,else  FALSE
//  Parameters:  
//  Notes:      none
*F*/
static BOOL OBFset()
{
	BYTE	status;

	ReadPortUchar( KCS_STATUS_REGISTER, &status );

	return (status & KCS_OBF) == KCS_OBF;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
// WaitOnOBF
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       WaitOnOBF
//  Purpose:    Check to see if OutPut Buffer Full Flag is set
//  Context:    
//  Returns:    TRUE if OBF set,else if Timeout FALSE
//  Parameters:  
				context OS Dependent context
//  Notes:      none
*F*/
static BOOL WaitOnOBF( void *context )
{
	DWORD	retryCount = 0;

	while( 1 ) {

		if( OBFset() )
			break;

		DelayRetryFunc( retryCount++, KCS_READY_DELAY, context );

		if ( ImbReqTimedOut( context ) )
			return False;
	}
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// SendBmcRequest
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       SendBmcRequest 
//  Purpose:    This function sends Imbp compliant BMC message  via smic interface
//              It also gets the response (if expected) from BMC and returns it 
//              back to the calling application
//  Context:    called by bmcThread 
//  Returns:    STATUS_SUCESS if the message was processed properly else error status
//  Parameters:  
//              mesgStructInPtr         pointer to the input message data structure
//              mesgStructOutPtr        pointer to the response data structure.
//              deviceExtension         driver device extension pointer
//              information             pointer to the number of response bytes copied to
//                                      output buffer
//  Notes:      none
*F*/

static IMB_STATUS
SendBmcRequest (

		BmcRequest *	request,
		DWORD 			reqLength,
		BmcResponse *	response,
		DWORD *			respLength,
		void *			context,
		DWORD			ipmiOldFlag
	)
{
    IMB_STATUS	errStatus = IMB_SUCCESS;
    BYTE		invalidRespCount = 0;
	BYTE *		receiveBuf;
	BYTE *		transmitBuf;
	int			machineState = TRANSFER_INIT;
	DWORD		i = 0;

		
	//use the new state machine for IPMI 1.0 compliant BMC.
	// I have added new function because the driver should handle
	// the IPMI 1.0 protocol properly in its transfer state machine
	// and I do not want this change to affect the older 0.9 based systems.

	if (!ipmiOldFlag)
	  return	SendBmcRequestNew (	request,
									reqLength,
									response,
									respLength,
									context
									
		);

	response->cCode = CCODE_UNKNOWN;

	transmitBuf	= (BYTE *) request;
	receiveBuf	= (BYTE *) response;

    while( 1 ) {
		//
		// check for timeout
		//
		if ( ImbReqTimedOut( context ) ) {
			//
			// determine the cause
			//
			if ( errStatus == IMB_SUCCESS ) {

				if ( machineState > TRANSFER_END )
					errStatus = IMB_IF_RECEIVE_TIMEOUT;
				else
					errStatus = IMB_IF_SEND_TIMEOUT;
			}                       
			break;
		//
		// check for successful termination
		//
		} else if ( machineState == MACHINE_END ) {
			break;
		//
		// check for too many bad responses
		//
		} else if ( invalidRespCount > MAX_INVALID_RESPONSE_COUNT ) {
			errStatus = IMB_INVALID_IF_RESPONSE;
			break;
		}

		switch (machineState) {

		case  TRANSFER_INIT:
			i = 0;
			machineState = TRANSFER_START;

			(void) WaitOnIBF( context );
			
			//
			// fall through
			//
		case  TRANSFER_START:
			machineState = TRANSFER_NEXT;

			WritePortUchar( KCS_COMMAND_REGISTER, WRITE_START );
			(void) WaitOnIBF( context );
			//
			// fall through, again
			//
		case  TRANSFER_NEXT:
			//
			// check for end of request, transition to the END state which will transfer
			// our last byte
			//
			if ( i == (reqLength - 1) ) {
				machineState = TRANSFER_END ;
				break;
			}

			if( GetKcsState() != KCS_WRITE_STATE ) {
				machineState = TRANSFER_ERROR;
				break;
			}
			WriteKcsData( transmitBuf[i++] );

			(void) WaitOnIBF( context );

			break;

		case  TRANSFER_END:
			//
			// Transfer the last byte of the request and
			// transition to Reading.
			//
			(void) WaitOnIBF( context );

			WriteKcsCmd( WRITE_END );

			(void) WaitOnIBF( context );

			if( GetKcsState() != KCS_WRITE_STATE ) {
				machineState = TRANSFER_ERROR;
				break;
			}
			WriteKcsData( transmitBuf[i++] );

			(void) WaitOnIBF( context );

    		machineState = RECEIVE_START;
			//
			// fall through
			//

		case  RECEIVE_START:
			//
			// end of request, now transition to receiving the response
			//
			switch( GetKcsState() ) {

			case KCS_ERROR_STATE:
				machineState = TRANSFER_ERROR;
				break;

			case KCS_WRITE_STATE:
			case KCS_IDLE_STATE:
				//
				// not ready with our response, hang out for a bit
				//
				DelayRetryFunc( FORCE_DELAY, BMC_RESPONSE_DELAY, context );
				break;

			case KCS_READ_STATE:
				//
				// ready with our response, setup to receive
				//
				i = 0; 
				ZeroMemory( receiveBuf, *respLength );
    			machineState = RECEIVE_INIT;
				break;
			}
		    break;

		case  RECEIVE_INIT:
			//
			// wait for a data byte to come available
			//
			switch( GetKcsState() ) {

			case KCS_ERROR_STATE:
			case KCS_WRITE_STATE:
				machineState = TRANSFER_ERROR;
				break;

			case KCS_IDLE_STATE:
				//
				// he's done sending us data
				//
				machineState = RECEIVE_END;
				break;

			case KCS_READ_STATE:
				if( OBFset() )
    				machineState = RECEIVE_NEXT;
				else
					DelayRetryFunc( FORCE_DELAY, WAIT_1_MS, context );
				break;

			default:
				DelayRetryFunc( FORCE_DELAY, WAIT_1_MS, context );
				break;
			}

			break;

	   case  RECEIVE_NEXT:
			//
			// Read the next data byte from the BMC
			//
			// check for overrun
			//
			if (  i >= *respLength ) {
				errStatus		= IMB_RESPONSE_DATA_OVERFLOW;
				machineState	= TRANSFER_ERROR;
				break;
			}
			//
			// get the data from the BMC
			//
			receiveBuf[i++] = ReadKcsData();

			(void) WaitOnIBF( context );

			WriteKcsData( READ );

			machineState = RECEIVE_INIT2;

			break;
												
		case  RECEIVE_INIT2:
			//
			// wait for a data byte to come available
			//
			switch( GetKcsState() ) {

			case KCS_ERROR_STATE:
			case KCS_WRITE_STATE:
				machineState = TRANSFER_ERROR;
				break;

			case KCS_IDLE_STATE:
				//
				// he's done sending us data
				//
			
				machineState = RECEIVE_END;
				break;

			case KCS_READ_STATE:
				if( OBFset() )
    				machineState = RECEIVE_NEXT;
				break;
			}
			break;

	   case  RECEIVE_END:
		   //
		   // check for size, appropriate response netfun, and command
		   //
		   if ( i < MIN_BMC_RESPONSE_SIZE ||
			    NETFN_OF(response->nfLn) != RESPONSE_NETFN(NETFN_OF(request->nfLn)) ||
				response->cmd != request->cmd ) {
				//
			    // bad response, wait for a while and try sending it again
			    //
				DelayRetryFunc( FORCE_DELAY, BMC_RETRY_DELAY, context );
				machineState = TRANSFER_INIT;

				++invalidRespCount;
				break;
			}
			//
		    // all done, return triumphant
		    //
			*respLength	 = i;
			errStatus	 = STATUS_SUCCESS;
			machineState = MACHINE_END;

			break;

		case  TRANSFER_ERROR:
		default:
			//
			// We've failed somehow, or maybe it was his fault.
			// Try again, as per comm spec. allow 60 ms. for controllers to recover
			// Can we force him into a known state?
			//
			DelayRetryFunc( FORCE_DELAY, BMC_RETRY_DELAY, context );
			i = 0;
			machineState = TRANSFER_INIT;
			break;
       } // switch
	} // while(2)

	return errStatus;
}





static IMB_STATUS
SendBmcRequestNew (

		BmcRequest *	request,
		DWORD 			reqLength,
		BmcResponse *	response,
		DWORD *			respLength,
		void *			context
	)
{
    IMB_STATUS	errStatus = IMB_SUCCESS;
    BYTE		invalidRespCount = 0;
	BYTE *		receiveBuf;
	BYTE *		transmitBuf;
	int			machineState = TRANSFER_INIT;
	DWORD		i = 0;

	response->cCode = CCODE_UNKNOWN;

	transmitBuf	= (BYTE *) request;
	receiveBuf	= (BYTE *) response;

    while( 1 ) {
		//
		// check for timeout
		//
		if ( ImbReqTimedOut( context ) ) {
			//
			// determine the cause
			//
			if ( errStatus == IMB_SUCCESS ) {

				if ( machineState > TRANSFER_END )
					errStatus = IMB_IF_RECEIVE_TIMEOUT;
				else
					errStatus = IMB_IF_SEND_TIMEOUT;
			}                       
			break;
		//
		// check for successful termination
		//
		} else if ( machineState == MACHINE_END ) {
			break;
		//
		// check for too many bad responses
		//
		} else if ( invalidRespCount > MAX_INVALID_RESPONSE_COUNT ) {
			errStatus = IMB_INVALID_IF_RESPONSE;
			break;
		}

		switch (machineState) {

		case  TRANSFER_INIT:
			i = 0;
			machineState = TRANSFER_START;

			(void) WaitOnIBF( context );

			if ( ClearOBF(context) != TRUE)
				{
					machineState = TRANSFER_ERROR;
					break;

				}
			//
			// fall through
			//
		case  TRANSFER_START:
			machineState = TRANSFER_NEXT;

			WritePortUchar( KCS_COMMAND_REGISTER, WRITE_START );
			(void) WaitOnIBF( context );

			if( GetKcsState() != KCS_WRITE_STATE ) {
				machineState = TRANSFER_ERROR;
				break;

			}

			if ( ClearOBF(context) != TRUE)
				{
					machineState = TRANSFER_ERROR;
					break;

				}

		
			//
			// fall through, again
			//
		case  TRANSFER_NEXT:
			//
			// check for end of request, transition to the END state which will transfer
			// our last byte
			//
			if ( i == (reqLength - 1) ) {
				machineState = TRANSFER_END ;
				break;
			}

	/*		if( GetKcsState() != KCS_WRITE_STATE ) {
				machineState = TRANSFER_ERROR;
				break;
			} */
			WriteKcsData( transmitBuf[i++] );

			
			(void) WaitOnIBF( context );


			if( GetKcsState() != KCS_WRITE_STATE ) {
				machineState = TRANSFER_ERROR;
				break;

			}

			if ( ClearOBF(context) != TRUE)
				{
					machineState = TRANSFER_ERROR;
					break;

				}

			break;

		case  TRANSFER_END:
			//
			// Transfer the last byte of the request and
			// transition to Reading.
			//
			/*
			(void) WaitOnIBF( context );
				if ( ClearObf(context) != TRUE)
				{
					machineState = TRANSFER_ERROR;
					break;

				}
				*/

			WriteKcsCmd( WRITE_END );

			(void) WaitOnIBF( context );

			if( GetKcsState() != KCS_WRITE_STATE ) {
				machineState = TRANSFER_ERROR;
				break;
			} 

			if ( ClearOBF(context) != TRUE)
				{
					machineState = TRANSFER_ERROR;
					break;

				}
			WriteKcsData( transmitBuf[i++] );

			(void) WaitOnIBF( context );

    		machineState = RECEIVE_START;
			//
			// fall through
			//

		case  RECEIVE_START:
			//
			// end of request, now transition to receiving the response
			//
			switch( GetKcsState() ) {

									case KCS_ERROR_STATE:
											machineState = TRANSFER_ERROR;
											break;

									case KCS_WRITE_STATE:
									case KCS_IDLE_STATE:
									//
									// not ready with our response, hang out for a bit
									//
											DelayRetryFunc( FORCE_DELAY, BMC_RESPONSE_DELAY, context );
											break;

									case KCS_READ_STATE:
									//
									// ready with our response, setup to receive
									//
											i = 0; 
											ZeroMemory( receiveBuf, *respLength );
    										machineState = RECEIVE_INIT;
											break;
							}
							break;

		case  RECEIVE_INIT:
			//
			// wait for a data byte to come available
			//
				switch( GetKcsState() ) {

										case KCS_ERROR_STATE:
										case KCS_WRITE_STATE:
											machineState = TRANSFER_ERROR;
											break;

										case KCS_IDLE_STATE:
										//
										// he's done sending us data
										//
											machineState = RECEIVE_END;
											break;

										case KCS_READ_STATE:
												if( OBFset() )
    											machineState = RECEIVE_NEXT;
													else
													DelayRetryFunc( FORCE_DELAY, WAIT_1_MS, context );
												break;

										default:
												DelayRetryFunc( FORCE_DELAY, WAIT_1_MS, context );
												break;
							}

							break;

	   case  RECEIVE_NEXT:
			//
			// Read the next data byte from the BMC
			//
			// check for overrun
			//
			if (  i >= *respLength ) {
				errStatus		= IMB_RESPONSE_DATA_OVERFLOW;
				machineState	= TRANSFER_ERROR;
				break;
			}
			//
			// get the data from the BMC
			//
			receiveBuf[i++] = ReadKcsData();

			WriteKcsData( READ );

			(void) WaitOnIBF( context );

			

			machineState = RECEIVE_INIT2;

			break;
												
		case  RECEIVE_INIT2:
			//
			// wait for a data byte to come available
			//
			switch( GetKcsState() ) {

			case KCS_ERROR_STATE:
			case KCS_WRITE_STATE:
				machineState = TRANSFER_ERROR;
				break;

			case KCS_IDLE_STATE:
				//
				// he's done sending us data
				//

				//added to support new IPMI 1.0 conformance requirements.
//                            

				if ( WaitOnOBF( context ) == TRUE)
					{
					    ClearOBF(context);
						machineState = RECEIVE_END;
						
					}
				else

						machineState =  TRANSFER_ERROR;;

				break;

			case KCS_READ_STATE:

					if ( WaitOnOBF( context ) == TRUE)
					{
                                         
						machineState = RECEIVE_NEXT;
						
					}
					else

							machineState =  TRANSFER_ERROR;;

				break;
/*
				if( OBFset() )
    				machineState = RECEIVE_NEXT;
				break;

*/

			}
			break;

	   case  RECEIVE_END:
		   //
		   // check for size, appropriate response netfun, and command
		   //
		   if ( i < MIN_BMC_RESPONSE_SIZE ||
			    NETFN_OF(response->nfLn) != RESPONSE_NETFN(NETFN_OF(request->nfLn)) ||
				response->cmd != request->cmd ) {
				//
			    // bad response, wait for a while and try sending it again
			    //
				DelayRetryFunc( FORCE_DELAY, BMC_RETRY_DELAY, context );
				machineState = TRANSFER_INIT;

				++invalidRespCount;
				break;
			}
			//
		    // all done, return triumphant
		    //
			*respLength	 = i;
			errStatus	 = STATUS_SUCCESS;
			machineState = MACHINE_END;

			break;

		case  TRANSFER_ERROR:
		default:
			//
			// We've failed somehow, or maybe it was his fault.
			// Try again, as per comm spec. allow 60 ms. for controllers to recover
			// Can we force him into a known state?
			//
			DelayRetryFunc( FORCE_DELAY, BMC_RETRY_DELAY, context );
			i = 0;
			machineState = TRANSFER_INIT;
			break;
       } // switch
	} // while(2)

	return errStatus;
}
//
// 

/////////////////////////////////////////////////////////////////////////////////////////////////
// ClearOBF
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       
//  Purpose:    Clears OBF flag if set
//  Context:    
//  Returns:    TRUE if OBF flag gets cleared else FALSE
//  Parameters:  
				context OS Dependent context
//  Notes:      none
*F*/
static BOOL ClearOBF( void *context )
{
	DWORD	retryCount = 0;
	BYTE	status;


    //return TRUE;
	//if( !OBFset() )
	//		return TRUE;

	//dummy output data register read to clear OBF flag
	ReadKcsData();
    /*
	while( 1 ) {

			if( !OBFset() )
				break;

	
			DelayRetryFunc( retryCount++, KCS_READY_DELAY, context );

			if ( ImbReqTimedOut( context ) )
				return False;
	}
	*/
	return TRUE;
}














// 
//
/////////////////////////////////////////////////////////////////////////////////////////////////
// KcsProbe
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       KcsProbe
//  Purpose:    Try to find the KCS interface
//  Context:    
//  Returns:    TRUE if KCS found,else  FALSE
//  Parameters:  
//  Notes:      none
*F*/
static BOOL
KcsProbe( void )
{
	UCHAR status;

	ReadPortUchar( KCS_STATUS_REGISTER, &status );

	if( status == 0xff )
		return FALSE;
	//
	// should we check the status for sanity ???
	// should we try to put the interface in a known state ???
	//

	return TRUE;
}

//
// This structure is the only symbol exported by this module
// It provides the interface to discover and access the KCS if
//
ImbInterface KcsImbIf = {

	SendBmcRequest,		// sends BMC request and return response
	SmsBufferAvail,		// indicates whether the SMS buffer is full
	KcsProbe			// indicates whether there is a KCS IF on this platform

};


