/*****************************************************************
******************************************************************
***                                                            ***
***        (C)Copyright 2008, American Megatrends Inc.         ***
***                                                            ***
***                    All Rights Reserved                     ***
***                                                            ***
***       5555 Oakbrook Parkway, Norcross, GA 30093, USA       ***
***                                                            ***
***                     Phone 770.246.8600                     ***
***                                                            ***
******************************************************************
******************************************************************
******************************************************************
*
* Filename: libipmi_fru.c
*
******************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "libipmi_fru.h"
#include "com_BmcType.h"
#include "com_IPMI_App.h"

#define SAFE_FREE(ptr) { if((ptr)) { free(ptr); ptr=NULL; } }
#define IPMI_FRU_VALID_VERSION  0x01
#define CUSTOM_AREA_FIELD       0xC1


const char * buf2str(uint8_t * buf, int len)
{
    static char str[1024];
    int i;

    if (len <= 0 || (len*2) > 1024)
        return NULL;

    memset(str, 0, 1024);

    for (i=0; i<len; i++)
        sprintf(str+i+i, "%2.2x", buf[i]);

    str[len*2] = '\0';

    return (const char *)str;
}

uint16_t LIBIPMI_HL_GetFRUChassisData(IPMI20_UDS_SESSION_T *pUDSSession,
                    uint8_t fru_device_id,
                    uint32_t* p_fru_size,
                    OemFRUChassisInfo_T* chassis_data,
                    int timeout)
{
    uint8_t*                    fru_res_data = NULL;
    uint32_t                    fru_res_data_len = 0;
    FRUReadReq_T                fru_read_req;

    uint32_t                    wRet = 0;
    uint32_t                    fru_size = 0;
    uint8_t                     BLOCK_OF_DATA_TO_READ = 128;
    FRUCommonHeader_T *pTmp = NULL;
  
    //! Read complete FRU Data now
    fru_read_req.FRUDeviceID = fru_device_id;
    fru_read_req.Offset      = 0x00; //! go to start of FRU area

    fru_res_data_len         =  sizeof(FRUReadRes_T) + BLOCK_OF_DATA_TO_READ;
    fru_res_data             = (uint8_t* ) malloc ( fru_res_data_len );
    if( fru_res_data == NULL)
    {
         printf ("libipmi_fru.c: Error allocating memory for tempFRUBuff\n");
         return -1; 
    }

    fru_read_req.CountToRead = sizeof (FRUCommonHeader_T);
    wRet = IPMICMD_ReadFRUData (pUDSSession,
                                &fru_read_req,
                                (FRUReadRes_T *) fru_res_data,
                                timeout);
    if (wRet != 0)
    {
        printf("---> wRet = %d\n", wRet);
        SAFE_FREE (fru_res_data);
        return wRet;
    }
    else
    {
        pTmp = (FRUCommonHeader_T *) (fru_res_data + sizeof(FRUReadRes_T));
        if(pTmp->CommonHeaderFormatVersion != IPMI_FRU_VALID_VERSION)
        {
            SAFE_FREE (fru_res_data);
            wRet = FRU_INVALID_HEADER_VERSION;
            return wRet;
        }
        if(pTmp->ChassisInfoAreaStartOffset == 0)
        {
            SAFE_FREE (fru_res_data);
            wRet = FRU_INVALID_AREA;
            return wRet;
        }
        fru_read_req.Offset  = pTmp->ChassisInfoAreaStartOffset * 8;
        *p_fru_size = fru_read_req.CountToRead = sizeof(OemFRUChassisInfo_T);
        wRet = IPMICMD_ReadFRUData (pUDSSession,
                                &fru_read_req,
                                (FRUReadRes_T *) fru_res_data,
                                timeout);
        if (wRet != 0)
        {
            printf("%s %d,wRet = %d\n",__FILE__, __LINE__, wRet);
            SAFE_FREE (fru_res_data);
            return wRet;
        }

        memcpy (chassis_data, (fru_res_data + sizeof(FRUReadRes_T)), 
            fru_read_req.CountToRead);
    }
    SAFE_FREE (fru_res_data);
    return 0;
    
}

uint16_t IPMC_GetFRUChassisData(IPMI20_UDS_SESSION_T *pUDSSession, uint8_t IpmbAddr, 
                    uint8_t fru_device_id,
                    uint32_t* p_fru_size,
                    OemFRUChassisInfo_T* chassis_data,
                    int timeout)
{
    uint16_t    wRet = 0;
    uint8_t     Req[20] = {0};
    uint8_t     Res[130];
    uint32_t    dwResLen;
    FRUCommonHeader_T *pTmp = NULL;
  
   
    //get header
    Req[0] = 0x40;  //Track | channel 0
    Req[1] = IpmbAddr;
    Req[2] = NETFNLUN_IPMI_STORAGE;
    Req[3] = 0x100 - (Req[1] + Req[2])&0xff;    //checksum1
    Req[4] = 0x20;
    Req[5] = 0x10;
    Req[6] = CMD_READ_FRU_DATA;  
    Req[7] = fru_device_id;             //FRU ID
    Req[8] = 0;             //offset_LSB
    Req[9] = 0;             //offset_MSB         
    Req[10] = sizeof (FRUCommonHeader_T);   //read len
    Req[11] = 0x100 - (Req[4] + Req[5] + Req[6] + Req[7] + Req[8] + Req[9] + Req[10])&0xff;

    //dwResLen = sizeof(GetDevIDRes_T);
    wRet = LIBIPMI_Send_RAW_IPMI2_0_Command(pUDSSession, 
                                            DEFAULT_NET_FN_LUN, CMD_SEND_MSG,
                                            Req, 12,
                                            Res, &dwResLen,
                                            timeout);
    if ((wRet != 0) || (Res[6] != 0))
    {
        printf("%s %d: wRet = %d, ccode = %d\n", __FILE__, __LINE__, wRet, Res[6]);
        return wRet;
    }
    else
    {
        pTmp = (FRUCommonHeader_T *) &Res[8];   //Res[6]: ccode, Res[7]: countReturned
        if(pTmp->CommonHeaderFormatVersion != IPMI_FRU_VALID_VERSION)
        {
            wRet = FRU_INVALID_HEADER_VERSION;
            return wRet;
        }
        if(pTmp->ChassisInfoAreaStartOffset == 0)
        {
            wRet = FRU_INVALID_AREA;
            return wRet;
        }
        
        //get chassis fru info
        Req[0] = 0x40;  //Track | channel 0
        Req[1] = IpmbAddr;
        Req[2] = NETFNLUN_IPMI_STORAGE;
        Req[3] = 0x100 - (Req[1] + Req[2])&0xff;    //checksum1
        Req[4] = 0x20;
        Req[5] = 0x10;
        Req[6] = CMD_READ_FRU_DATA;  
        Req[7] = 0;             //FRU ID
        Req[8] = (pTmp->ChassisInfoAreaStartOffset * 8)&0xff;             //offset_LSB
        Req[9] = ((pTmp->ChassisInfoAreaStartOffset * 8)>>8)&0xff;             //offset_MSB         
        Req[10] = sizeof(OemFRUChassisInfo_T);   //read len
        Req[11] = 0x100 - (Req[4] + Req[5] + Req[6] + Req[7] + Req[8] + Req[9] + Req[10])&0xff;

        //dwResLen = sizeof(GetDevIDRes_T);
        wRet = LIBIPMI_Send_RAW_IPMI2_0_Command(pUDSSession, 
                                                DEFAULT_NET_FN_LUN, CMD_SEND_MSG,
                                                Req, 12,
                                                Res, &dwResLen,
                                                timeout);
        if ((wRet != 0) || (Res[6] != 0))
        {
            printf("%s %d,wRet = %d, ccode = %d\n",__FILE__, __LINE__, wRet, Res[6]);
            return wRet;
        }

        memcpy (chassis_data, &Res[8], sizeof(OemFRUChassisInfo_T));
        *p_fru_size = sizeof(OemFRUChassisInfo_T);
    }
    return 0;
    
}


  
uint16_t LIBIPMI_HL_GetFRUBoardData(IPMI20_UDS_SESSION_T *pUDSSession,
                    uint8_t fru_device_id,
                    uint32_t* p_fru_size,
                    OemFRUBoardInfo_T* board_data,
                    int timeout)
{
    uint8_t*                    fru_res_data = NULL;
    uint32_t                    fru_res_data_len = 0;
    FRUReadReq_T                fru_read_req;

    uint32_t                    wRet = 0;
    uint32_t                    fru_size = 0;
    uint8_t                     BLOCK_OF_DATA_TO_READ = 128;
    FRUCommonHeader_T *pTmp = NULL;
  
    //! Read complete FRU Data now
    fru_read_req.FRUDeviceID = fru_device_id;
    fru_read_req.Offset      = 0x00; //! go to start of FRU area

    fru_res_data_len         =  sizeof(FRUReadRes_T) + BLOCK_OF_DATA_TO_READ;
    fru_res_data             = (uint8_t* ) malloc ( fru_res_data_len );
    if( fru_res_data == NULL)
    {
         printf ("libipmi_fru.c: Error allocating memory for tempFRUBuff\n");
         return -1; 
    }

    fru_read_req.CountToRead = sizeof (FRUCommonHeader_T);
    wRet = IPMICMD_ReadFRUData (pUDSSession,
                                &fru_read_req,
                                (FRUReadRes_T *) fru_res_data,
                                timeout);
    if (wRet != 0)
    {
        SAFE_FREE (fru_res_data);
        return wRet;
    }
    else
    {
        pTmp = (FRUCommonHeader_T *) (fru_res_data + sizeof(FRUReadRes_T));
        if(pTmp->CommonHeaderFormatVersion != IPMI_FRU_VALID_VERSION)
        {
            SAFE_FREE (fru_res_data);
            wRet = FRU_INVALID_HEADER_VERSION;
            return wRet;
        }
        if(pTmp->BoardInfoAreaStartOffset == 0)
        {
            SAFE_FREE (fru_res_data);
            wRet = FRU_INVALID_AREA;
            return wRet;
        }

        fru_read_req.Offset  = pTmp->BoardInfoAreaStartOffset * 8;
        *p_fru_size = fru_read_req.CountToRead = sizeof(OemFRUBoardInfo_T);
        wRet = IPMICMD_ReadFRUData (pUDSSession,
                                &fru_read_req,
                                (FRUReadRes_T *) fru_res_data,
                                timeout);
        if (wRet != 0)
        {
            SAFE_FREE (fru_res_data);
            return wRet;
        }

        memcpy (board_data, (fru_res_data + sizeof(FRUReadRes_T)), 
            fru_read_req.CountToRead);
    }

    SAFE_FREE (fru_res_data);
    return 0;
    
}

uint16_t IPMC_GetFRUBoardData(IPMI20_UDS_SESSION_T *pUDSSession, uint8_t IpmbAddr, 
                    uint8_t fru_device_id,
                    uint32_t* p_fru_size,
                    OemFRUBoardInfo_T* board_data,
                    int timeout)
{
    uint16_t    wRet = 0;
    uint8_t     Req[20] = {0};
    uint8_t     Res[130];
    uint32_t    dwResLen;
    FRUCommonHeader_T *pTmp = NULL;
  
    //get header
    Req[0] = 0x40;  //Track | channel 0
    Req[1] = IpmbAddr;
    Req[2] = NETFNLUN_IPMI_STORAGE;
    Req[3] = 0x100 - (Req[1] + Req[2])&0xff;    //checksum1
    Req[4] = 0x20;
    Req[5] = 0x10;
    Req[6] = CMD_READ_FRU_DATA;  
    Req[7] = fru_device_id;             //FRU ID
    Req[8] = 0;             //offset_LSB
    Req[9] = 0;             //offset_MSB         
    Req[10] = sizeof (FRUCommonHeader_T);   //read len
    Req[11] = 0x100 - (Req[4] + Req[5] + Req[6] + Req[7] + Req[8] + Req[9] + Req[10])&0xff;

    //dwResLen = sizeof(GetDevIDRes_T);
    wRet = LIBIPMI_Send_RAW_IPMI2_0_Command(pUDSSession, 
                                            DEFAULT_NET_FN_LUN, CMD_SEND_MSG,
                                            Req, 12,
                                            Res, &dwResLen,
                                            timeout);
    if ((wRet != 0) || (Res[6] != 0))
    {
        printf("%s %d,wRet = %d, ccode = %d\n",__FILE__, __LINE__, wRet, Res[6]);
        return wRet;
    }
    else
    {
        pTmp = (FRUCommonHeader_T *) &Res[8];   //Res[6]: ccode, Res[7]: countReturned
        if(pTmp->CommonHeaderFormatVersion != IPMI_FRU_VALID_VERSION)
        {
            wRet = FRU_INVALID_HEADER_VERSION;
            return wRet;
        }
        if(pTmp->BoardInfoAreaStartOffset == 0)
        {
            wRet = FRU_INVALID_AREA;
            return wRet;
        }

        //get chassis fru info
        Req[0] = 0x40;  //Track | channel 0
        Req[1] = IpmbAddr;
        Req[2] = NETFNLUN_IPMI_STORAGE;
        Req[3] = 0x100 - (Req[1] + Req[2])&0xff;    //checksum1
        Req[4] = 0x20;
        Req[5] = 0x10;
        Req[6] = CMD_READ_FRU_DATA;  
        Req[7] = 0;             //FRU ID
        Req[8] = (pTmp->BoardInfoAreaStartOffset * 8)&0xff;             //offset_LSB
        Req[9] = ((pTmp->BoardInfoAreaStartOffset * 8)>>8)&0xff;             //offset_MSB         
        Req[10] = sizeof(OemFRUBoardInfo_T);   //read len
        Req[11] = 0x100 - (Req[4] + Req[5] + Req[6] + Req[7] + Req[8] + Req[9] + Req[10])&0xff;

        //dwResLen = sizeof(GetDevIDRes_T);
        wRet = LIBIPMI_Send_RAW_IPMI2_0_Command(pUDSSession, 
                                                DEFAULT_NET_FN_LUN, CMD_SEND_MSG,
                                                Req, 12,
                                                Res, &dwResLen,
                                                timeout);
         if ((wRet != 0) || (Res[6] != 0))
        {
            printf("%s %d,wRet = %d, ccode = %d\n",__FILE__, __LINE__, wRet, Res[6]);
            return wRet;
        }

        memcpy (board_data, &Res[8], sizeof(OemFRUBoardInfo_T));
        *p_fru_size = sizeof(OemFRUBoardInfo_T);
    }

    return 0;
}

uint16_t LIBIPMI_HL_GetFRUProductData(IPMI20_UDS_SESSION_T *pUDSSession,
                    uint8_t fru_device_id,
                    uint32_t* p_fru_size,
                    OemFRUProductInfo_T* product_data,
                    int timeout)
{
    uint8_t*                    fru_res_data = NULL;
    uint32_t                    fru_res_data_len = 0;
    FRUReadReq_T                fru_read_req;

    uint32_t                    wRet = 0;
    uint32_t                    fru_size = 0;
    uint8_t                     BLOCK_OF_DATA_TO_READ = 128;
    FRUCommonHeader_T *pTmp = NULL;
  
    //! Read complete FRU Data now
    fru_read_req.FRUDeviceID = fru_device_id;
    fru_read_req.Offset      = 0x00; //! go to start of FRU area

    fru_res_data_len         =  sizeof(FRUReadRes_T) + BLOCK_OF_DATA_TO_READ;
    fru_res_data             = (uint8_t* ) malloc ( fru_res_data_len );
    if( fru_res_data == NULL)
    {
         printf ("libipmi_fru.c: Error allocating memory for tempFRUBuff\n");
         return -1; 
    }

    fru_read_req.CountToRead = sizeof (FRUCommonHeader_T);
    wRet = IPMICMD_ReadFRUData (pUDSSession,
                                &fru_read_req,
                                (FRUReadRes_T *) fru_res_data,
                                timeout);
    if (wRet != 0)
    {
        SAFE_FREE (fru_res_data);
        return wRet;
    }
    else
    {
        pTmp = (FRUCommonHeader_T *) (fru_res_data + sizeof(FRUReadRes_T));
        if(pTmp->CommonHeaderFormatVersion != IPMI_FRU_VALID_VERSION)
        {
            SAFE_FREE (fru_res_data);
            wRet = FRU_INVALID_HEADER_VERSION;
            return wRet;
        }
        if(pTmp->ProductInfoAreaStartOffset == 0)
        {
            SAFE_FREE (fru_res_data);
            wRet = FRU_INVALID_AREA;
            return wRet;
        }

        fru_read_req.Offset  = pTmp->ProductInfoAreaStartOffset * 8;
        *p_fru_size = fru_read_req.CountToRead = sizeof(OemFRUProductInfo_T);
        wRet = IPMICMD_ReadFRUData (pUDSSession,
                                &fru_read_req,
                                (FRUReadRes_T *) fru_res_data,
                                timeout);
        if (wRet != 0)
        {
            SAFE_FREE (fru_res_data);
            return wRet;
        }

        memcpy (product_data, (fru_res_data + sizeof(FRUReadRes_T)), 
            fru_read_req.CountToRead);
    }

    SAFE_FREE (fru_res_data);
    return 0;
    
}

uint16_t IPMC_GetFRUProductData(IPMI20_UDS_SESSION_T *pUDSSession, uint8_t IpmbAddr,
                    uint8_t fru_device_id,
                    uint32_t* p_fru_size,
                    OemFRUProductInfo_T* product_data,
                    int timeout)
{
    uint16_t    wRet = 0;
    uint8_t     Req[20] = {0};
    uint8_t     Res[130];
    uint32_t    dwResLen;
    FRUCommonHeader_T *pTmp = NULL;
  
    //get header
    Req[0] = 0x40;  //Track | channel 0
    Req[1] = IpmbAddr;
    Req[2] = NETFNLUN_IPMI_STORAGE;
    Req[3] = 0x100 - (Req[1] + Req[2])&0xff;    //checksum1
    Req[4] = 0x20;
    Req[5] = 0x10;
    Req[6] = CMD_READ_FRU_DATA;  
    Req[7] = fru_device_id;             //FRU ID
    Req[8] = 0;             //offset_LSB
    Req[9] = 0;             //offset_MSB         
    Req[10] = sizeof (FRUCommonHeader_T);   //read len
    Req[11] = 0x100 - (Req[4] + Req[5] + Req[6] + Req[7] + Req[8] + Req[9] + Req[10])&0xff;

    //dwResLen = sizeof(GetDevIDRes_T);
    wRet = LIBIPMI_Send_RAW_IPMI2_0_Command(pUDSSession, 
                                            DEFAULT_NET_FN_LUN, CMD_SEND_MSG,
                                            Req, 12,
                                            Res, &dwResLen,
                                            timeout);
    if ((wRet != 0) || (Res[6] != 0))
    {
        printf("%s %d,wRet = %d, ccode = %d\n",__FILE__, __LINE__, wRet, Res[6]);
        return wRet;
    }
    else
    {
        pTmp = (FRUCommonHeader_T *) &Res[8];   //Res[6]: ccode, Res[7]: countReturned
        if(pTmp->CommonHeaderFormatVersion != IPMI_FRU_VALID_VERSION)
        {
            wRet = FRU_INVALID_HEADER_VERSION;
            return wRet;
        }
        if(pTmp->ProductInfoAreaStartOffset == 0)
        {
            wRet = FRU_INVALID_AREA;
            return wRet;
        }

        //get chassis fru info
        Req[0] = 0x40;  //Track | channel 0
        Req[1] = IpmbAddr;
        Req[2] = NETFNLUN_IPMI_STORAGE;
        Req[3] = 0x100 - (Req[1] + Req[2])&0xff;    //checksum1
        Req[4] = 0x20;
        Req[5] = 0x10;
        Req[6] = CMD_READ_FRU_DATA;  
        Req[7] = 0;             //FRU ID
        Req[8] = (pTmp->ProductInfoAreaStartOffset * 8)&0xff;             //offset_LSB
        Req[9] = ((pTmp->ProductInfoAreaStartOffset * 8)>>8)&0xff;             //offset_MSB         
        Req[10] = sizeof(OemFRUProductInfo_T);   //read len
        Req[11] = 0x100 - (Req[4] + Req[5] + Req[6] + Req[7] + Req[8] + Req[9] + Req[10])&0xff;

        //dwResLen = sizeof(GetDevIDRes_T);
        wRet = LIBIPMI_Send_RAW_IPMI2_0_Command(pUDSSession, 
                                                DEFAULT_NET_FN_LUN, CMD_SEND_MSG,
                                                Req, 12,
                                                Res, &dwResLen,
                                                timeout);
         if ((wRet != 0) || (Res[6] != 0))
        {
            printf("%s %d,wRet = %d, ccode = %d\n",__FILE__, __LINE__, wRet, Res[6]);
            return wRet;
        }

        memcpy (product_data, &Res[8], sizeof(OemFRUProductInfo_T));
        *p_fru_size = sizeof(OemFRUProductInfo_T);
    }
    return 0;
}