
/*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/smic.c.v  $
 * 
 *    Rev 1.2   07 Oct 1999 15:28:08   jpaiasno
 * Added  new BMC transfer state machine code
 *  to KCS interface for IPMI 1.0 conformance
 * 
 *    Rev 1.1   08 Dec 1998 10:55:46   jpaiasno
 * Added code to support async.
 * DPC events from the EMP console.
 * 
 *    Rev 1.7   24 Mar 1998 15:51:14   fkhanx
 * Added function headers
 * 
 *    Rev 1.6   24 Mar 1998 14:41:40   fkhanx
 * Added copyright header
*/

#include "imb_os.h"
#include "imb_if.h"
#include "imb_drv.h"
#include "smic.h"
//
// Exported functions
//
//	None
//
// Imported functions
//
//		void ReadPortUchar()
//		void WritePortUchar()
//		void DelayRetryFunc()
//		BOOL ImbReqTimedOut()
//		void ZeroMemory()
//
// Exported data
//
//		ImbIf SmicImbIf
//

#ifdef DO_HIST
//
// This code can be used to histogram delay times 
// You also need to up NO_RETRY_DELAY_COUNT in imb_drv.h above the
// max histogram bucket count
//
#define HIST_MAX	100

extern DWORD	ImbDelayHist[HIST_MAX];
DWORD			ImbHistDivider	= 1;

static void
DoHist( DWORD value )
{
	value /= ImbHistDivider;

	if( value >= HIST_MAX )
		value = HIST_MAX - 1;	// saturate the count

	ImbDelayHist[value]++;
}

#else // DO_HIST

#define DoHist( x )

#endif // DO_HIST
//
/////////////////////////////////////////////////////////////////////////////////////////////////
// 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 )
{
	UCHAR val;

	ReadPortUchar( SMIC_FLAG_REGISTER, &val );

	return (val & (SMS_AVAIL|SMIC_BUSY)) == SMS_AVAIL;
}

static BOOL
smicCtlReady()
{
	UCHAR val;

	ReadPortUchar( SMIC_FLAG_REGISTER, &val );

	return (val & (TX_DATA_RDY|SMIC_BUSY)) == TX_DATA_RDY;
}

static BOOL
smicBusy()
{
	UCHAR val;

	ReadPortUchar( SMIC_FLAG_REGISTER, &val );

	return (val & SMIC_BUSY) == SMIC_BUSY;
}

static BOOL
smicDataReady()
{
	UCHAR val;

	ReadPortUchar( SMIC_FLAG_REGISTER, &val );

	return (val & (RX_DATA_RDY|SMIC_BUSY)) == RX_DATA_RDY;
}
//////////////////////////////////////////////////////////////////////////////////
// writeSmicCommandAndData 
//////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       writeMismicCommandAndData 
//  Purpose:    This function writes a command and a data byte to the BMC
//              via mismic mailbox interface
//  Context:    called by imbp message processing functions like
//                              sendBmcMessage and sendImbpMessage
//  Returns:    TRUE if write operation was successfull else FALSE
//  Parameters: 
//              command			command byte to be written
//              data			data byte to be written
//              context         OS specific context
//
//  Notes:      none
*F*/
static BOOL
writeSmicCommandAndData( BYTE command, BYTE data, void *context )
{
	DWORD	retryCount = 0;
	UCHAR	val;

	//
	// Wait for the interface to become ready
	//
	while ( ! smicCtlReady() ) {

		DelayRetryFunc( retryCount++, SMIC_READY_DELAY, context );

		if ( ImbReqTimedOut( context ) ) {
			DoHist( retryCount );
			return False;
		}
	}
	DoHist( retryCount );
	//
	// Write the command and data to the interface
	//
	WritePortUchar( SMIC_CNTL_REGISTER, command );
	WritePortUchar( SMIC_DATA_REGISTER, data );
	//
	// Mark the interface as busy
	//
	ReadPortUchar( SMIC_FLAG_REGISTER, &val );
	WritePortUchar( SMIC_FLAG_REGISTER, (UCHAR)(val | SMIC_BUSY) );
	//
	// wait until the command/data has been accepted by the BMC
	//
	retryCount = 0;

	while( smicBusy() ) {
		DelayRetryFunc( retryCount++, SMIC_READY_DELAY, context );

		if ( ImbReqTimedOut( context ) ) {
			DoHist( retryCount );
			return False;
		}
	}
	DoHist( retryCount );
	return True;
}

/////////////////////////////////////////////////////////////////////////////////////
// writeSmicCommand 
/////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       writeSmicCommand 
//  Purpose:    This function writes a command byte to the BMC
//              via mismic mailbox interface
//  Context:    called by imbp message processing functions like
//                              sendBmcMessage and sendImbpMessage
//  Returns:    TRUE if write operation was successfull else FALSE
//  Parameters: 
//              command		command byte tobe written
//              context		pointer to OS specific context
//
//  Notes:      none
*F*/
static BOOL
writeSmicCommand( BYTE command, void * context )
{
	BYTE	val;
	DWORD	retryCount = 0;
	//
	// Wait for the interface to become ready
	//
	while( ! smicCtlReady() )  {
		DelayRetryFunc( retryCount++, SMIC_READY_DELAY, context );

		if ( ImbReqTimedOut( context ) ) {
			DoHist( retryCount );
			return False;
		}
	}
	DoHist( retryCount );
	//
	// Write the command to the interface
	//
	WritePortUchar( SMIC_CNTL_REGISTER, command );
	//
	// Mark the interface as busy
	//
	ReadPortUchar( SMIC_FLAG_REGISTER, &val );
	WritePortUchar( SMIC_FLAG_REGISTER, (UCHAR)(val | SMIC_BUSY) );
	//
	// wait until the command has been accepted by the BMC
	//
	retryCount = 0;

	while( smicBusy() ) {
		DelayRetryFunc( retryCount++, SMIC_READY_DELAY, context );

		if ( ImbReqTimedOut( context ) ) {
			DoHist( retryCount );
			return False;
		}
	}
	DoHist( retryCount );
	return True;

 }



/////////////////////////////////////////////////////////////////////////////////////////////////
// getBmcData
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       getBmcData 
//  Purpose:    This function checks whether the smic mailbox 
//              related data ready flag is set or not and then 
//              reads the mismic control and data register
//  Context:    called by imbp message processing functions like
//              sendBmcMessage and sendImbpMessage
//  Returns:    TRUE if ready flag was set and the specified mismic
//                              registers could be read else FALSE
//  Parameters: 
//              readStatus		pointer to the status byte to be read
//              bufPtr			pointer to the data byte to be read
//              context			driver device OS context
//
//  Notes:      none
*F*/


static BOOL
getBmcData( BYTE *readStatus, BYTE *bufPtr, void *context ) 
{
	//
	// the main code related to data ready is bypassed because of firmware bugs.
	// once the proper firmware is available then the commented code should be
	// activated
	//
	int retryCount = 0;
	//
	// Wait until the interface has more data (or a timeout)
	//
    while( ! smicDataReady() ) {
		DelayRetryFunc( retryCount++, SMIC_READY_DELAY, context );

		if ( ImbReqTimedOut( context ) ) {
			DoHist( retryCount );
			return False;
		}
	}
	DoHist( retryCount );
	//
	// get the data
	//
	ReadPortUchar( SMIC_CNTL_REGISTER, readStatus );
	ReadPortUchar( SMIC_DATA_REGISTER, bufPtr );

	return True;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// checkSmicStatusInBmcMode 
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       checkSmicStatusInBmcMode 
//  Purpose:    This function checks the status of the  previous
//              SMIC mail box transaction and changes
//              the  state of BMC message processing state machine accordingly
//  Context:    called by BMC message processing function
//              sendBmcMessage.
//
//  Returns:    TRUE if status of previous  transaction was ok else
//              FALSE
//  Parameters: 
//              
//    machineState     pointer to the machine state to be changed, as
//                     per the status of last SMIC transaction
//
//    context		   OS specific data, used by delay and timeout functions
//
//  Notes:      none
*F*/


static BOOL
checkSmicStatusInBmcMode( int *machineState, void *context )
{
	BYTE			status;
	BYTE			errorVal;
	DWORD			retryCount = 0;
	//
	// Wait until the interface has more data (or a timeout)
	//
    while( smicBusy() ) {
		DelayRetryFunc( retryCount++, SMIC_READY_DELAY, context );

		if ( ImbReqTimedOut( context ) ) {
			DoHist( retryCount );
			return False;
		}
	}
	DoHist( retryCount );

	ReadPortUchar( SMIC_CNTL_REGISTER, &status );
	ReadPortUchar( SMIC_DATA_REGISTER, &errorVal );

	switch ( *machineState ) {

	case  TRANSFER_INIT:
		break;

	case  TRANSFER_START:
	case  RECEIVE_START:

		if ( status != SMS_SC_READY )
			*machineState = TRANSFER_ABORT;

		break;

	case  TRANSFER_NEXT:
	case  TRANSFER_END:

		if ( status != SMS_SC_WRITE_NEXT &&	status != SMS_SC_WRITE_START )
			*machineState = TRANSFER_ABORT;

		break;

	case   RECEIVE_INIT:

		if ( status != SMS_SC_WRITE_END )
			*machineState = TRANSFER_ABORT;

		break;  

	case   RECEIVE_NEXT:

		if ( status != SMS_SC_READ_START &&
			 status != SMS_SC_READ_NEXT &&
			 status != SMS_SC_READ_END )

			*machineState = TRANSFER_ABORT;
		break;

	case   RECEIVE_END:
		if  ( status != SMS_SC_READ_END )
			*machineState = TRANSFER_ABORT;

		break;

	default:

		break;
	}

	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   //dummy flag to support compilation
									  // for SMIC interface.
	)
{
    IMB_STATUS	errStatus = IMB_SUCCESS;
    BYTE		invalidRespCount = 0;
	BYTE		status;
	BYTE *		receiveBuf;
	BYTE *		transmitBuf;

	int			machineState = TRANSFER_INIT;
	DWORD		i = 0;

	response->cCode = CCODE_UNKNOWN;

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

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

				if ( machineState > TRANSFER_ABORT )
					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;
		}

		if ( machineState <  RECEIVE_START )
			checkSmicStatusInBmcMode( &machineState, context ); 

		switch (machineState) {

		case  TRANSFER_INIT:
			i = 0;
			machineState = TRANSFER_START;
			(void) writeSmicCommand( SMS_CC_GET_STATUS, context );
			break;

		case  TRANSFER_START:
			machineState = TRANSFER_NEXT;
			(void) writeSmicCommandAndData( SMS_CC_START_TRANSFER, transmitBuf[i], context);
			++i;
			break;

		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;
			}
			(void) writeSmicCommandAndData( SMS_CC_NEXT_TRANSFER, transmitBuf[i], context );
			++i;

			break;

		case  TRANSFER_END:
			//
			// end of request, now transition to receiving the response
			//
    		machineState = RECEIVE_INIT;

	    	(void) writeSmicCommandAndData( SMS_CC_END_TRANSFER, transmitBuf[i], context );
		    break;

		case  RECEIVE_INIT:
			//
			// setup to start receiving
			//
			i = 0; 
			ZeroMemory( receiveBuf, *respLength );
			machineState = RECEIVE_START;
			break;

		case  RECEIVE_START:
			//
			// wait a little while before actively checking for a response
			//
			DelayRetryFunc( FORCE_DELAY, BMC_RESPONSE_DELAY, context );

			if ( ! smicDataReady() )
				break; 

			if ( ! writeSmicCommand( SMS_CC_START_RECEIVE, context) )
				break;

			machineState = RECEIVE_NEXT;
			break;

	   case  RECEIVE_NEXT:
			//
			// check for overrun
			//
			if (  i >= *respLength ) {
				errStatus		= IMB_RESPONSE_DATA_OVERFLOW;
				machineState	= TRANSFER_ABORT;
				break;
			}
			//
			// get the data from the BMC
			//
			if ( ! getBmcData( &status, &receiveBuf[i], context) )
				break;
			++i;

			if ( status == SMS_SC_READ_END ) {
				machineState = RECEIVE_END;
				break;

			} else if ( status != SMS_SC_READ_NEXT && status != SMS_SC_READ_START ) {
				machineState = TRANSFER_ABORT;
				break;
			}
			writeSmicCommandAndData( SMS_CC_NEXT_RECEIVE, (BYTE) 0, context );
			break;
												
	   case  RECEIVE_END:
		   //
		   // check for size, appropriate reponse netfunction, 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;
			
			if( request->cmd != WRITE_I2C )
				writeSmicCommand( SMS_CC_END_RECEIVE, context );
			break;

		case  TRANSFER_ABORT:
		default:
			//
			// we've failed somehow, or maybe it was his fault.
			// try again, as per comm spec. allow 60 ms. for controllers to recover
			//
			DelayRetryFunc( FORCE_DELAY, BMC_RETRY_DELAY, context );
			i = 0;
			machineState = TRANSFER_INIT;
			break;
       } // switch
	} // while( TRUE )

	return errStatus;
}
//
// Try to find the SMIC interface
//
static BOOL
SmicProbe( void )
{
	UCHAR status;

	ReadPortUchar( SMIC_CNTL_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 SMIC
//
ImbInterface SmicImbIf = {

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

};


