/******************************************************************************
   Module description: 
   This is the source code for IPMB command tool to send and receive requests
   to the BMC compliant with IPMI 1.5 over the SMBus interface.
   Author: amitoj@fnal.gov		Date: 10/8/2004
   Author: ab@zresearch.com             Date: 6/9/2005

   Installation Notes:	gcc -O2 -o ssif-cmd ssif-cmd.c
   Supporting Software: lm_sensors >= 2.8.7
                        i2c >= 2.8.7
   Both supporting applications available from:
   http://secure.netroedge.com/~lm78/index.html
   Required modules: i2c-dev, i2c-i801, i2c-core

             HELP: HELP: HELP: HELP:
   $> lspci  (in the output look for this entry)
    00:1f.3 SMBus: Intel Corp. 6300ESB SMBus Controller (rev 01)
          Subsystem: Intel Corp.: Unknown device 342f
          Flags: medium devsel,  IRQ 17
          I/O ports at 0400 [size=32]
                       ----
   $> cat /proc/bus/i2c
   i2c-0   smbus    SMBus I801 adapter at 0400         Non-I2C SMBus adapter
                                          ----
       Make sure the "0400" above matches with the "0400" address under proc.
       Also make sure "i2c-0" is not different. If it appears different then
       grep for "i2c-0" in this code "ssif-cmd.c" and change. "i2c-X" is the
       label assigned to each slave device attached on the i2c bus.

   BMC address Locator:
       Refer to the SM BIOS IPMI Device Information Record
       Type 38,  record 06h and 08h. Use the value of record
       06h as the IPMBAddress and load the SMBus controller
       driver at the address value read from record 08h.
       Usual values for record 06h -> 0x42
       Usual values for record 08h -> 0x400
*******************************************************************************/
/* Includes */
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/io.h>
#include <errno.h>
#include <math.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <linux/i2c-dev.h> 

#ifndef I2C_SLAVE
# define I2C_SLAVE      0x0703  /* Change slave address  */
#endif

/* this is for i2c-dev.c        */
#define IPMI_I2C_SLAVE       0x0703  /* Change slave address                 */
                                     /* Attn.: Slave address is 7 or 10 bits */
#define IPMI_I2C_SLAVE_FORCE 0x0706  /* Change slave address                 */
                                     /* Attn.: Slave address is 7 or 10 bits */
                                     /* This changes the address, even if it */
                                     /* is already taken!                    */

#define IPMI_I2C_SMBUS       0x0720  /* SMBus-level access */

/* SMBus transaction types (size parameter in the above functions)
   Note: these no longer correspond to the (arbitrary) PIIX4 internal codes! */
#define IPMI_I2C_SMBUS_QUICK                0
#define IPMI_I2C_SMBUS_BYTE                 1
#define IPMI_I2C_SMBUS_BYTE_DATA            2
#define IPMI_I2C_SMBUS_WORD_DATA            3
#define IPMI_I2C_SMBUS_PROC_CALL            4
#define IPMI_I2C_SMBUS_BLOCK_DATA           5
#define IPMI_I2C_SMBUS_I2C_BLOCK_DATA       6
#define IPMI_I2C_SMBUS_BLOCK_PROC_CALL      7     /* SMBus 2.0 */
#define IPMI_I2C_SMBUS_BLOCK_DATA_PEC       8     /* SMBus 2.0 */
#define IPMI_I2C_SMBUS_PROC_CALL_PEC        9     /* SMBus 2.0 */
#define IPMI_I2C_SMBUS_BLOCK_PROC_CALL_PEC  10    /* SMBus 2.0 */
#define IPMI_I2C_SMBUS_WORD_DATA_PEC        11    /* SMBus 2.0 */

/*
 * Data for SMBus Messages
 */
#define IPMI_I2C_SMBUS_BLOCK_MAX        32      /* As specified in SMBus standard */

#define IPMI_I2C_SMBUS_I2C_BLOCK_MAX    32      /* Not specified but we use same structure */

/* smbus_access read or write markers */
#define IPMI_I2C_SMBUS_READ     1
#define IPMI_I2C_SMBUS_WRITE    0


/* Typedefs */
typedef unsigned char BYTE;
typedef unsigned int  WORD;
typedef unsigned long DWORD;
typedef unsigned int  UINT;
typedef unsigned long ULONG;
/* function error codes */
#define NO_ERROR                0
#define UNDEF_ERROR             0xFFFF
#define UNKNOWN_INTERFACE       0x1000
#define ISA_MESSAGE_OVERFLOW    0x1100
#define BAD_ISA_STATE           0x1200
#define BMC_WRITE_STATE_FAIL    0x1300
#define BMC_READ_STATE_FAIL     0x1400
#define SMS_READY_TIMEOUT       0x1500
#define ERROR_READING_SMS       0x1600
#define PACKET_NUMBER_MISMATCH  0x1700
#define PACKET_SIZE_MISMATCH    0x1800
#define I2C_RETRY_ERROR         0x1900
#define SMS_SEND_ERROR_ON_FLUSH 0x2000
#define ISA_TIMEOUT_IN_IBF      0x2100
#define ISA_TIMEOUT_IN_OBF      0x2200
/* Response Packet Offsets */
#define RSP_OFFSET_COMPCODE 0x03
/* SMBus */
#define SMB_IPMI_REQUEST 2
#define SMB_IPMI_RESPONSE 3

/*
 * Data for SMBus Messages
 */
#define IPMI_I2C_SMBUS_BLOCK_MAX        32      /* As specified in SMBus standard */

#define IPMI_I2C_SMBUS_I2C_BLOCK_MAX    32      /* Not specified but we use same structure */

/* smbus_access read or write markers */
#define IPMI_I2C_SMBUS_READ     1
#define IPMI_I2C_SMBUS_WRITE    0

union ipmi_i2c_smbus_data {
  uint8_t byte;
  uint16_t word;
  uint8_t block[IPMI_I2C_SMBUS_BLOCK_MAX + 3]; /* block[0] is used for length */
  /* one more for read length in block process call */
  /* and one more for PEC */
};

/* Note: 10-bit addresses are NOT supported! */
/* This is the structure as used in the I2C_SMBUS ioctl call */
struct ipmi_i2c_smbus_ioctl_data {
  char read_write;
  uint8_t command;
  int size;
  union ipmi_i2c_smbus_data *data;
};

#define PrintInterfaceError(status) printf ("Status: %d", status);
int InterfaceInit (BYTE);
int SendRequest  (int,  unsigned char *,  int);
int ReadResponse (int,  unsigned char *);

static inline int32_t
ipmi_i2c_smbus_access (int file, char read_write, uint8_t command, int size,
                       union ipmi_i2c_smbus_data *data)
{
  struct ipmi_i2c_smbus_ioctl_data args;

  args.read_write = read_write;
  args.command = command;
  args.size = size;
  args.data = data;
  return ioctl (file, IPMI_I2C_SMBUS, &args);
}

/* ipmi_i2c_smbus_read_block_data is based on
linux/i2c-dev.h:i2c_smbus_read_block_data. It is duplicated here to
reduce dependencies. -- Anand Babu */
static inline int32_t
ipmi_i2c_smbus_read_block_data (int file, uint8_t command, uint8_t *values)
{
  union ipmi_i2c_smbus_data data;
  int i;
  if (ipmi_i2c_smbus_access (file, IPMI_I2C_SMBUS_READ, command,
			     IPMI_I2C_SMBUS_BLOCK_DATA, &data))
    return -1;
  else {
    for (i = 1; i <= data.block[0]; i++)
      values[i-1] = data.block[i];
    return data.block[0];
  }
}

/* ipmi_i2c_smbus_write_block_data is based on
linux/i2c-dev.h:i2c_smbus_write_block_data. It is duplicated here to
reduce dependencies. -- Anand Babu */
static inline int32_t
ipmi_i2c_smbus_write_block_data (int file, uint8_t command, uint8_t length, uint8_t *values)
{
  union ipmi_i2c_smbus_data data;
  int i;
  if (length > 32)
    length = 32;
  for (i = 1; i <= length; i++)
    data.block[i] = values[i-1];
  data.block[0] = length;
  return ipmi_i2c_smbus_access (file, IPMI_I2C_SMBUS_WRITE, command,
				IPMI_I2C_SMBUS_BLOCK_DATA, &data);
}


/*******************************************************************************
*  Routine: ExitWithUsage
*
*  Arguments:
*     N/A
*
*  Returns:
*     N/A
*
*  Description:
*     This routine exits back to the OS with a message displaying the correct
*     usage for the command line arguments.
*     
*******************************************************************************/
void ExitWithUsage (void)
{
   printf ("\n");
   printf ("SSIF-CMD (IPMI 1.5 using the SMBus Interface)\n");
   printf ("Disclaimer:\n");
   printf ("Software is provided \"AS IS\" with no warranties whatsoever.\n");
   printf ("\n");
   printf ("Usage: ssif-cmd IPMBAddr NetFnLUN Cmd [Data1 ... DataN]\n");
   printf ("  IPMBAddr IPMB address of microcontroller (e.g.,  BMC = 42)\n");
   printf ("  NetFnLUN Command net function (high 6 bits) and LUN (low 2 bits)\n");
   printf ("  Cmd      Command micro to perform\n");
   printf ("  Data1..N Command data\n");
   exit (0xFFFF);
}

/*******************************************************************************
*  Routine: main
*
*  Arguments:
*     argc - argument count
*     argv - array of arguments
*
*  Returns:
*     Errorlevel of 0 to O/S if all went well
*     Nonzero errorlevel otherwise
*
*  Description:
*     main function for the IMPB tool
*
*******************************************************************************/
int main (int argc,  char *argv[])
{
	WORD count;
	WORD temp;
	BYTE CmdBuffer[256];
	BYTE IPMBAddress;
	BYTE ResponseBuffer[256];
	int BytesRead = 0;
	WORD status;
	int  ioFile;

	//minimum three commands line inputs
	if (argc < 4)
	{
	  ExitWithUsage ();
	}

	if (sscanf (argv[1],  "%x",  &temp) != 1) {
		ExitWithUsage ();
	}

	IPMBAddress = (BYTE) (temp & 0xFF);
	for (count = 2; count < (WORD)argc; count++)
	{
		if (sscanf (argv[count],  "%x",  &temp) != 1)
		{
			ExitWithUsage ();
		}
		CmdBuffer[count - 2] = (BYTE) (temp & 0xFF);
	}
	
        if (! (ioFile = InterfaceInit (IPMBAddress)))
        {
               PrintInterfaceError (status);
               exit (0xFFFF);
        }

	if ( (status = SendRequest (ioFile,  CmdBuffer,  (BYTE) (argc - 2))) < 0)
	{
		printf ("Error sending command to IPMB.\n");
		PrintInterfaceError (status);
		exit (0xFFFF);
	}

	if ( (BytesRead = ReadResponse (ioFile,  ResponseBuffer)) < 0)
	{
		printf ("Error receving response from IPMB.\n");
		PrintInterfaceError (status);
		exit (0xFFFF);
	}

	for (count = 0; count < BytesRead; count++)
	{
		if (count != 0) printf (" ");
		printf ("%02X",  ResponseBuffer[count]);
	}
	
	printf ("\n");
	exit (ResponseBuffer[RSP_OFFSET_COMPCODE]);
}

/*******************************************************************************
*  Routine: SendRequest
*
*  Arguments:
*     ioIpmb - pointer to the BMC device file
*     buf - array of unsigned chars which holds the ipmi command and vals
*     len - length of buf 
* 
*  Returns:
*     the number of bytes sent to the BMC
*     0 means failed to send data to the BMC
*
*
*******************************************************************************/
int SendRequest (int ioIpmb,  unsigned char *buf,  int len)
{ 
  return (ipmi_i2c_smbus_write_block_data (ioIpmb, SMB_IPMI_REQUEST, len, buf));
}

/*******************************************************************************
*  Routine: ReadResponse
*
*  Arguments:
*     ioIpmb - pointer to the BMC device file
*     buf - array of unsigned chars which holds the response from the BMC
* 
*  Returns:
*     the number of bytes received from the BMC
*     0 means failed to get respons from the BMC
*
*
*******************************************************************************/
int ReadResponse (int ioIpmb,  BYTE *buf)
{
  return (ipmi_i2c_smbus_read_block_data (ioIpmb, SMB_IPMI_RESPONSE, buf));
}

/*******************************************************************************
*  Routine: InterfaceInit
*
*  Arguments:
*     IPMBAddress - slave address of the BMC on the SMBus (usually 0x42)
*
*  Returns:
*     a the BMC device file pointer
*     retuns zero otherwise
*
*
*******************************************************************************/
int InterfaceInit (BYTE IPMBAddress)
{
   char fileName[20];
   int ioctlFile,  e1;
   sprintf (fileName, "/dev/i2c-0");
   if ( (ioctlFile = open (fileName, O_RDWR)) < 0) {
     e1 = errno;
     if (e1 != ENOENT) {
       fprintf (stderr, "Error: Could not open file '%s' : %s\n", fileName, strerror (e1));
        if (e1 == EACCES)
         fprintf (stderr, "Run as root?\n");
     }
     return (0);
   }

   if (ioctl (ioctlFile, I2C_SLAVE, IPMBAddress) < 0) {
     if (errno == EBUSY) {
       printf ("device is busy. ");
     }
     printf ("failed communication\n");
     return (0);
   }
   return ioctlFile;
}
