
/*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/imb_drv.c.v  $
 * 
 *    Rev 1.11   07 Oct 1999 15:28:06   jpaiasno
 * Added  new BMC transfer state machine code
 *  to KCS interface for IPMI 1.0 conformance
 * 
 *    Rev 1.10   02 Sep 1999 10:51:50   jpaiasno
 * reverted back to revision 1.6 and fixed a logic
 * problem related to copying I2C response
 * 
 *    Rev 1.6   27 Jul 1999 10:18:14   jpaiasno
 * Changed the BMC communication buffer
 * size to 64 to support all IPMI 1.0 based
 * commands.  From now on it is the responsibility 
 * of the firmware to carry out consistency checks 
 * on request and expected response packet lengths.
 * 
 *    Rev 1.5   30 Apr 1999 14:35:14   jpaiasno
 * Fixed logic realted to checking and copying  IPMI 1.0 realted 
 * IPMB responses to handle the extra channel number
 * byte inserted by BMC firmware
 * 
 *    Rev 1.4   28 Apr 1999 11:13:14   jpaiasno
 * Fixed the response length logic for
 * IPMB messages
 * 
 *    Rev 1.3   21 Jan 1999 11:44:24   jpaiasno
 *  
 * 
 *    Rev 1.2   21 Jan 1999 11:40:36   jpaiasno
 * Added Support for IPMI 1.0 firmware
 * 
 *    Rev 1.1   08 Dec 1998 10:55:44   jpaiasno
 * Added code to support async.
 * DPC events from the EMP console.
 * 
 *    Rev 1.9   24 Mar 1998 15:30:34   fkhanx
 * Added function headers
 * 
 *    Rev 1.8   24 Mar 1998 14:39:32   fkhanx
 * Added copyright header
*/

#include "imb_os.h"
#include "imb_drv.h"
//
// This structure provides the linkage to the IMB interfaces that we support
//
extern ImbInterface     SmicImbIf;      // SMIC interface
extern ImbInterface     KcsImbIf;       // KCS interface

ImbInterface * ImbInterfaces[] = {
	& SmicImbIf,
	& KcsImbIf,
	NULL
};

/////////////////////////////////////////////////////////////////////////////////////////////////
// checkImbPacket
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       checkImbPacket
//  Purpose:    Validate the checksum in the packet
//  Context:    
//  Returns:    TRUE ,if checksum validates ,else FALSE
//  Parameters:
//              respData   Buffer containing IMB packet
//                              respLength Length of IMB packet              
//
//  Notes:      
//                              
*F*/
static BOOL
checkImbPacket(
		BYTE *  respData,
		DWORD   respLength
	)
{
	BYTE *          d;
	BYTE            sum;
	DWORD           i;

	if( respLength < MIN_IMB_RESPONSE_SIZE )
		return FALSE;
	//
	// checksum the packet, a good packet consists of two sections - head and body
	// that each sum to 0
	//
	for( d = respData, i = 0, sum = 0; i < respLength ; i++, d++ ) {
		//
		// check for good header checksum
		//
		if( i == IMB_HEAD_SIZE && sum != 0 )
			return FALSE;
		sum += *d;
	}
	if( sum != 0 )
		return FALSE;

	return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// compareImbResponse
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       compareImbResponse
//  Purpose:    Check if the response is for the current request
//  Context:    
//  Returns:    TRUE ,if response is for current request ,
//                              else FALSE
//  Parameters:
//              bmcReq   Current Request
//                              bmcResp  Current Response
//
//  Notes:      
//                              
*F*/
static BOOL
compareImbResponse (
		BmcRequest *    bmcReq,
		BmcResponse *   bmcResp,
		DWORD  ipmiVersionFlag
	)
{
		ImbRespPacket *resp;
	//
	// The original request is a Write I2C command that has the bus id
	// as its first byte. We point the packet pointer to one byte passed.
	//

	ImbPacket *             req             = (ImbPacket *) & bmcReq->data[1];


	if (ipmiVersionFlag)
	 resp    = (ImbRespPacket *) bmcResp->data;
	else
	 resp    = (ImbRespPacket *) (&(bmcResp->data[1]));     
	//
	// compare sequence number, command, and netFn
	//
	return  resp->rqSa                       == req->rsSa &&
			resp->cmd                        == req->cmd &&
			NETFN_OF(resp->nfLn) == RESPONSE_NETFN(NETFN_OF(req->nfLn)) &&
			SEQ_OF(resp->seqLn)  == SEQ_OF(req->seqLn) ;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// copyImbResponse
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       copyImbResponse
//  Purpose:    Reformat the response
//  Context:    
//  Returns:    TRUE ,if success
//                              else FALSE
//  Parameters:
//                              bmcResp  Current Response
//                              bmcLength Length of response
//
//  Notes:      
//                              
*F*/
static BOOL
copyImbResponse (
		BmcResponse *           bmcResp,
		DWORD                           bmcLength,
		ImbResponseBuffer *     response,
		DWORD *                         respLength,
		DWORD                   responseFlag
	)
{
	ImbRespPacket *         packet ;
       // DWORD                          dataLength = bmcLength - (MIN_BMC_RESPONSE_SIZE + MIN_IMB_RESPONSE_SIZE);
	DWORD                           dataLength;       
	if (responseFlag)
		{
			 packet  = (ImbRespPacket *) (bmcResp->data);
			 dataLength = bmcLength - (MIN_BMC_RESPONSE_SIZE + MIN_IMB_RESPONSE_SIZE);       
		 }
	 else
		{
			 packet  = (ImbRespPacket *) (&(bmcResp->data[1]));
			 dataLength = bmcLength - (MIN_BMC_RESPONSE_SIZE + MIN_IMB_RESPONSE_SIZE+1);       
		}

	//
	// check for response buffer size
	//
	if( *respLength < dataLength )
		return FALSE;
	//
	// copy the completion code and data into the response packet
	// we assume the packet has been validated
	//
	response->cCode = packet->cCode;

	if( dataLength )
		{

			CopyMemory( response->data, packet->data, dataLength );
			*respLength = (dataLength-1); //account for completion code to be already copied

		
	} else

			*respLength = 0;
	 

	return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// copyBmcResponse
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       copyBmcResponse
//  Purpose:    Reformat the response
//  Context:    
//  Returns:    TRUE ,if success
//                              else FALSE
//  Parameters:
//                              bmcResp  Current Response
//                              bmcLength Length of response
//
//  Notes:      
//                              
*F*/
static BOOL
copyBmcResponse(
		BmcResponse *           bmcResp,
		DWORD                           bmcLength,
		ImbResponseBuffer *     response,
		DWORD *                         respLength
	)
{
	DWORD dataLength = bmcLength - MIN_BMC_RESPONSE_SIZE;
	//
	// check for response buffer size
	//
	if( *respLength < dataLength )
		return FALSE;
	//
	// copy the completion code and data into the response packet
	// we assume the packet has been validated
	//
	response->cCode = bmcResp->cCode;

	if( dataLength )
		CopyMemory( response->data, bmcResp->data, dataLength );

	*respLength = dataLength;

	return TRUE;
}
//

// 
//
/////////////////////////////////////////////////////////////////////////////////////////////////
// createBmcRequest
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       createBmcRequest
//  Purpose:    construct a BMC request from an IMB request
//  Context:    
//  Returns:    void
//  Parameters:
//                              req  IMB Request
//                              bmcReq BMC Request
//                              DataLength      Length of request
//
//  Notes:      
//                              
*F*/
static void
createBmcRequest (
		ImbRequestBuffer *      req,
		BmcRequest *            bmcReq,
		DWORD *                         dataLength
	)
{
	bmcReq->nfLn    = NETFN_LUN( req->req.netFn, req->req.rsLun );
	bmcReq->cmd             = req->req.cmd;

	if( req->req.dataLength )
		CopyMemory( bmcReq->data, req->req.data, req->req.dataLength );

	*dataLength = req->req.dataLength + MIN_BMC_REQUEST_SIZE;
}
//
// 
//
/////////////////////////////////////////////////////////////////////////////////////////////////
// createI2cRequest
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       createI2cRequest
//  Purpose:    Create an Write I2C command to send to the BMC
//  Context:    
//  Returns:    void
//  Parameters:
//                              req  IMB Request
//                              bmcReq BMC Request
//                              bmcLength       Length of request
//                              context OS dependent request
//
//  Notes:      
//                              
*F*/
static void
createI2cRequest (
		ImbRequestBuffer *      req,
		BmcRequest *            bmcReq,
		DWORD *                         bmcLength,
		void *                          context
	)
{
	WriteI2cCmd *   i2cCmd = (WriteI2cCmd *) bmcReq->data;
	ImbPacket *             imbReq = (ImbPacket *) i2cCmd->data;
	BYTE sum;

	bmcReq->nfLn    = NETFN_LUN( APP_NETFN, BMC_LUN );


	//now provide support for new IPMI 1.0 and above versions (pai 1/14)

	if ( isIpmiOldVersion(context))
	{
		bmcReq->cmd             = WRITE_I2C;
		i2cCmd->busType = PUBLIC_BUS;
	}else {

		bmcReq->cmd             = SEND_MESSAGE_CMD;
		i2cCmd->busType = IPMB_CHANNEL_NUMBER;

	}
	//
	// init the header
	//
	imbReq->rsSa    = req->req.rsSa;
	imbReq->nfLn    = NETFN_LUN( req->req.netFn, req->req.rsLun );
	imbReq->cSum1   = -(char) (imbReq->rsSa + imbReq->nfLn);
	//
	// now the body
	//
	imbReq->rqSa    = BMC_SA;
	imbReq->seqLn   = SEQ_LUN( GetNextImbSeq(context), SMS_LUN );
	imbReq->cmd             = req->req.cmd;

	sum = imbReq->rqSa + imbReq->seqLn + imbReq->cmd;
	//
	// copy any data
	//
	{
		BYTE *s = req->req.data;
		BYTE *d = imbReq->data;
		BYTE i;

		for( i = req->req.dataLength ; i-- ;  ) {
			sum += *s;
			*d++ = *s++;
		}
		*d = -(char) sum;
	}

	*bmcLength = req->req.dataLength + MIN_BMC_REQUEST_SIZE + MIN_IMB_PACKET_SIZE + MIN_WI2C_CMD_LEN;
}

//
// 
//
/////////////////////////////////////////////////////////////////////////////////////////////////
// GetSmsBuffer
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       GetSmsBuffer
//  Purpose:    read the SMS buffer
//  Context:    
//  Returns:    IMB_SUCCESS , if packet available
//                              IMB_CANT_GET_SMS_BUFFER if not
//  Parameters:
//                              bmcResp         BMC Response
//                              bmcLength       Length of request
//                              context OS dependent request
//
//  Notes:      
//                              
*F*/
IMB_STATUS
GetSmsBuffer (
		BmcResponse *   bmcResp,
		DWORD *                 bmcLength,
		void *                  context
	)
{
	BYTE                    reqBuffer[MAX_BMC_REQUEST_SIZE];
	BmcRequest *    bmcReq = (BmcRequest *)reqBuffer;
	IMB_STATUS              status;
	DWORD                             i,j;
	//
	// make sure there's something to do
	//
	if( ! ImbIf(context)->smsBufferAvail() )
		return IMB_MSG_NOT_AVAILABLE;
	//
	// create and send a GetSmsBuffer request
	//
	bmcReq->nfLn    = NETFN_LUN( APP_NETFN, BMC_LUN );

	//Add support for the new IPMI 1.0 based systems ( pai 1/15)
    i = isIpmiOldVersion(context);
	 if (i)
		bmcReq->cmd             = READ_SMS_BUFFER;
	else 
		bmcReq->cmd             = GET_MESSAGE_CMD;

	status = ImbIf(context)->sendBmcRequest( bmcReq, 2, bmcResp, bmcLength, context,i );

	if( status != IMB_SUCCESS || bmcResp->cCode != CCODE_OK )
		return IMB_CANT_GET_SMS_BUFFER;


	    // This code is written based on the discussion with the firmware team. It was decided
		// that for the IPMI 1.0 based messages,  firmware will drop the first byte (BMC slave address) for standard IPMB messages
		// sent over channel 0, but will retain it for the EMP channel 1. This sounds logical because
		// data from any other channels except the IPMB  cannel need not adehere to IPMI protocol
		// It is responsibilty of upper layer software to make sense out of the received data (pai 1/15)
		// (pai 4/28/99)
     

    if (        (i)  ||  
			(bmcResp->data[CHANNEL_NUMBER_OFFSET] == IPMB_CHANNEL_NUMBER)
		
		)  //old IPMI version of BMC or IPMB channel base IPMI 1.0 compliant data(pai 1/20)

		{


		//
		// copy the response down and insert the BMC's SA so that the data
		// will look like a real IMB packet. Bump the packet length to compensate.
		//
		 //                              if (i)
						j = *bmcLength - MIN_BMC_RESPONSE_SIZE;
		 //                             else
		 //                                       j = *bmcLength - ( MIN_BMC_RESPONSE_SIZE+1);



				for(  ; j > 0 ; j-- ) {
					bmcResp->data[j] = bmcResp->data[j-1];

				}

				if (i)
					bmcResp->data[0] = BMC_SA;

				 else
					bmcResp->data[1] = BMC_SA;

				(*bmcLength)++;
				if (i)
				{
					if( ! checkImbPacket( bmcResp->data, *bmcLength - MIN_BMC_RESPONSE_SIZE ) )
						return IMB_INVALID_PACKET;
				} else {

					if( ! checkImbPacket( &(bmcResp->data[1]), *bmcLength - (MIN_BMC_RESPONSE_SIZE+1) ) )
						return IMB_INVALID_PACKET;
				}

		} else {
		 //it's  message from non IPMB channel return success (pai 1/20)
		 // place holder for any checks
		  ;

		}
	
		
		return IMB_SUCCESS;

	}



/////////////////////////////////////////////////////////////////////////////////////////////////
// SendImbRequest
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       SendImbRequest
//  Purpose:    Send a request to the BMC or to another controller through the BMC
//  Context:    
//  Returns:    
//                              IMB_SUCCESS          , if success
//                              IMB_RECEIVE_TIMEOUT , if Timeout error
//                              IMB_INVALID_REQUEST , if bad request
//  Parameters:
//                              req         BMC Request
//                              resp            BMC Response
//                              bmcLength       Length of request
//                              context OS dependent request
//
//  Notes:      
//                              
*F*/
IMB_STATUS
SendImbRequest (
		ImbRequestBuffer *      req,
		ImbResponseBuffer *     resp,
		DWORD *                         respLength,
		void *                          context         // info used by OS specific utility routines
	)
{
//      BYTE                    reqBuffer[MAX_REQUEST_SIZE];
//      BYTE                    respBuffer[MAX_BMC_RESPONSE_SIZE];
	
	BYTE                    reqBuffer[MAX_BUFFER_SIZE];
	BYTE                    respBuffer[MAX_BUFFER_SIZE];
	BmcRequest *    bmcReq = (BmcRequest *)reqBuffer;
	BmcResponse *   bmcResp = (BmcResponse *)respBuffer;
	DWORD                   reqLength = 0;
	DWORD                   length;
	IMB_STATUS              status;
	DWORD                   versionFlag;
	DWORD					i;
	//
	// validate the datalength
	//
	//bypass all IPMI based buffer checks and just check for max buffer length
	//(pai 7/16)
//      if( req->req.dataLength + MIN_IMB_REQ_BUF_SIZE > MAX_BMC_REQUEST_SIZE ) {
//              return IMB_INVALID_REQUEST;
//      }



	
	if( req->req.dataLength > MAX_BUFFER_SIZE) {
		return IMB_INVALID_REQUEST;
	}

	i = isIpmiOldVersion(context);

	//
	// Determine controller this request is going to
	//
	if( req->req.rsSa == BMC_SA ) {
		//
		// validate the lun and netfun
		//
		if( req->req.rsLun != BMC_LUN || ! IS_REQUEST(req->req.netFn) )
			return IMB_INVALID_REQUEST;
		//
		// construct a BMC request from the IMB request
		//
		createBmcRequest( req, bmcReq, &reqLength );
		//
		// send the request to the BMC
		//
		length = *respLength;

	

		status = ImbIf(context)->sendBmcRequest( bmcReq, reqLength, bmcResp, &length, context,i );

		if( status == IMB_SUCCESS ) {
			//
			// package up the response
			//
			if( copyBmcResponse( bmcResp, length, resp, respLength) != TRUE )
				return IMB_RESPONSE_DATA_OVERFLOW;
			//
			// account for the condition code
			//
			*respLength += 1;
		}
		return status;

	} else {
		int retryCount = 0;
		//
		// This is a request to a controller other than the BMC
		// we need to construct a Write I2C command to send to the BMC
		//
		createI2cRequest( req, bmcReq, &reqLength, context );
		//
		// Send the command and wait for our response to come back
		//
		length = sizeof(respBuffer);

		status = ImbIf(context)->sendBmcRequest( bmcReq, reqLength, bmcResp, &length, context,i );

		if( status != IMB_SUCCESS )
			return status;

		if( bmcResp->cCode != CCODE_OK )
			return IMB_SEND_ERROR;
		//
		// see if he wants to wait for a response
		//
		if( req->flags & NO_RESPONSE_EXPECTED ) {
			//
			// nope, return success and include the IMB sequence number for
			// reference
			//
			resp->cCode             = CCODE_OK;
			resp->data[0]   = SEQ_OF( ((ImbPacket *)bmcResp->data)->seqLn );
			*respLength             = 2;    // cCode and seqNo

			return IMB_SUCCESS;
		}
		//
		// wait for a response
		//
		while( ! ImbReqTimedOut( context ) ) {

			if( ImbIf(context)->smsBufferAvail() ) {
				//
				// something there for us
				//
				length = sizeof(respBuffer);

				status = GetSmsBuffer( bmcResp, &length, context );

				if( status != IMB_SUCCESS )
					continue;
				//
				// is it really for us
				//
				versionFlag = isIpmiOldVersion(context);
				if( compareImbResponse( bmcReq, bmcResp, versionFlag ) ) {
					//
					// yup, copy the data and return
					//
					if( copyImbResponse( bmcResp, length, resp, respLength,versionFlag) != TRUE ) {
						return IMB_RESPONSE_DATA_OVERFLOW;

					} else {
						//
						// account for the completion code
						//
						*respLength += 1;
						return IMB_SUCCESS;
					}
				} else {
					//
					// some other response/request
					// put it on the async queue
					//
					ImbAsyncQueueMessage( bmcResp, length, context );
				}
			}
			DelayRetryFunc( FORCE_DELAY, SMS_READY_DELAY, context );
		}
		return IMB_RECEIVE_TIMEOUT;
	}
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// ImbShutdownSystem
/////////////////////////////////////////////////////////////////////////////////////////////////

/*F*
//  Name:       ImbShutdownSystem
//  Purpose:    This function either resets or shuts the power off.
//  Context:    Called by dhut down function during system exit.
//  Returns:    STATUS_SUCCESS
//  Parameters:
//              deviceExtension   Pointer to device extension.
//              
//  Notes:      This function assumes that the OS specific code has started
//                              a request level timer.
*F*/

void
ImbShutdownSystem(
		DWORD shutdownCode,
		DWORD delayTime,
		void *context
	)
{
	BYTE                    reqBuffer[MAX_BMC_REQUEST_SIZE];
	BYTE                    respBuffer[MAX_BMC_RESPONSE_SIZE];
	BmcRequest *    bmcReq  = (BmcRequest *)reqBuffer;
	BmcResponse *   bmcResp = (BmcResponse *)respBuffer;
	DWORD                   bmcLength = 0,i;
	WatchdogCmd *   wdCmd   = (WatchdogCmd *) bmcReq->data;
	IMB_STATUS              status;
	//
	// create a command frame to be sent to the bmc
	// to set the watchdog timeout and related action
	//
	bmcReq->nfLn    = NETFN_LUN( APP_NETFN, BMC_LUN );
	bmcReq->cmd             = SET_WATCHDOG_TIMER;

	wdCmd->timerUse                 = WD_DONT_LOG | WD_SMS;
	wdCmd->preTimeOut               = 0;
	wdCmd->timerUseClear    = 0;

	wdCmd->countLow         = (BYTE)(delayTime & 0xff);
	wdCmd->countHigh        = (BYTE)((delayTime >> 8) & 0xff);

	switch ( shutdownCode ) {

	case SD_POWER_OFF:
		wdCmd->timerActions     = WD_PRE_ACT_NONE | WD_ACT_PWR_DOWN;
		break;

	case SD_RESET   :
		wdCmd->timerActions     = WD_PRE_ACT_NONE | WD_ACT_RESET;
		break;

	case SD_NO_ACTION:
	default:
		wdCmd->timerActions     = WD_PRE_ACT_NONE | WD_ACT_NONE;
		break;
	}
	//
	// send the set request
	//
	bmcLength = sizeof(respBuffer);

	i = isIpmiOldVersion(context);


	status = ImbIf(context)->sendBmcRequest( bmcReq,
											 MIN_BMC_REQUEST_SIZE + sizeof(WatchdogCmd),
											 bmcResp,
											 & bmcLength,
											 context,
											 i
											);

	if( status != IMB_SUCCESS || bmcResp->cCode != CCODE_OK )
		return;
	//
	// create a reset watchdog command
	// this should cause the action programmed above to be executed
	//
	bmcReq->nfLn    = NETFN_LUN( APP_NETFN, BMC_LUN );
	bmcReq->cmd             = RESET_WATCHDOG_TIMER;

	bmcLength = sizeof(respBuffer);

	(void) ImbIf(context)->sendBmcRequest(  bmcReq,
											MIN_BMC_REQUEST_SIZE,
											bmcResp,
											& bmcLength,
											context,
											i
										 );
}
