/*                  - Mellanox Confidential and Proprietary -
 *
 * Copyright (C) May 2003, Mellanox Technologies Ltd. ALL RIGHTS RESERVED.
 *
 * Except as specifically permitted herein, no portion of the information,
 * including but not limited to object code and source code, may be reproduced,
 * modified, distributed, republished or otherwise exploited in any form or by
 * any means for any purpose without the prior written permission of Mellanox
 * Technologies Ltd. Use of software subject to the terms and conditions
 * detailed in the file "LICENSE.txt".
 *
 * End of legal section ......................................................
 *
 *  eburn.cpp - Command line EEPROM burning tool (Gamla/Anafa)
 *
 *  Version: $Id: spark.cpp 3431 2007-06-05 15:58:50Z orenk $
 *
 *  Standalone compiling:
 *     g++ -g -Wall -I/usr/mst/include -L/usr/mst/lib -o spark spark.cpp -lmtcr
 *
 *  Author: Oren Kladnitsky orenk@mellanox.co.il
 */


#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>


#ifdef DEBUG 
#define mdebug(args) printf("-D- %s: ", __FUNCTION__); printf args;printf("\n")
#else 
#define mdebug(args)
#endif

#ifndef __WIN__ 

//
// Linux
//

#include <endian.h>
#include <unistd.h>
#include <netinet/in.h>
#include <byteswap.h>

#else // __WIN__

//
// Windows (Under DDK)
//

#include <io.h>
#include <Winsock2.h>

// Sleep adaptor
#define	usleep(x) Sleep((x)/1000)
#define sleep(x)  Sleep((x)*1000)

#define vsnprintf      _vsnprintf
#define strtoull       _strtoui64
#define isatty         _isatty

#define COMP_CDECL     __cdecl

#define __LITTLE_ENDIAN 1234
#define __BIG_ENDIAN 4321
#define __BYTE_ORDER __LITTLE_ENDIAN


#if __BYTE_ORDER == __LITTLE_ENDIAN
#define bswap_32(x) ntohl(x)
#define bswap_16(x) ntohs(x)
#else
#error windows is assumed to run a on little endian architecture
#endif


// DEBUG : Internal imp of strtoull for win2k msvcrt does not have _strtoui64


#endif // __WIN__




#include <iostream>
#include <sstream>
#include <map>
#include <vector>
#include <list>
#include <string>

#ifdef USING_IBVSMAD
 #include <infiniband/mad.h>
 #include "ibvsmad.h"
#else
 #include <mtcr.h>
#endif

//
// endianess issues:
//

#ifndef __be32_to_cpu
    #define __be32_to_cpu(x) ntohl(x)
#endif
#ifndef __cpu_to_be32
    #define __cpu_to_be32(x) htonl(x)
#endif

#if __BYTE_ORDER == __LITTLE_ENDIAN
    #ifndef __cpu_to_le32
        #define  __cpu_to_le32(x) (x)
    #endif
    #ifndef __le32_to_cpu
        #define  __le32_to_cpu(x) (x)
    #endif
#elif __BYTE_ORDER == __BIG_ENDIAN
    #ifndef __cpu_to_le32
        #define  __cpu_to_le32(x) bswap_32(x)
    #endif
    #ifndef __le32_to_cpu
        #define  __le32_to_cpu(x) bswap_32(x)
    #endif
#else
    #ifndef __cpu_to_le32
        #define  __cpu_to_le32(x) bswap_32(__cpu_to_be32(x))
    #endif
    #ifndef __le32_to_cpu
        #define  __le32_to_cpu(x) __be32_to_cpu(bswap_32(x))
    #endif
#endif

#if __BYTE_ORDER == __LITTLE_ENDIAN
    #ifndef __cpu_to_le16
        #define  __cpu_to_le16(x) (x)
    #endif
    #ifndef __le16_to_cpu
        #define  __le16_to_cpu(x) (x)
    #endif
    #ifndef __cpu_to_be16
        #define  __cpu_to_be16(x) bswap_16(x)
    #endif
    #ifndef __be16_to_cpu
        #define  __be16_to_cpu(x) bswap_16(x)
    #endif
#elif __BYTE_ORDER == __BIG_ENDIAN
    #ifndef __cpu_to_le16
        #define  __cpu_to_le16(x) bswap_16(x)
    #endif
    #ifndef __le16_to_cpu
        #define  __le16_to_cpu(x) bswap_16(x)
    #endif
    #ifndef __cpu_to_be16
        #define  __cpu_to_be16(x) (x)
    #endif
    #ifndef __be16_to_cpu
        #define  __be16_to_cpu(x) (x)
    #endif
#else
    #ifndef __cpu_to_le16
        #define  __cpu_to_le16(x) bswap_16(__cpu_to_be16(x))
    #endif
    #ifndef __le16_to_cpu
        #define  __le16_to_cpu(x) __be16_to_cpu(bswap_16(x))
    #endif
    #ifndef __cpu_to_be16
        #define  __cpu_to_be16(x) (x)
    #endif
    #ifndef __be16_to_cpu
        #define  __be16_to_cpu(x) (x)
    #endif
#endif

// Version globals
#ifdef VERSION_ID
#define __VFSTR(x)                      #x
#define _VFSTR(x)                       __VFSTR(x)
char* _versionID = _VFSTR( VERSION_ID ) ;
#else
char* _versionID = "MFT 2.0.0 , BUILD 20070618-2135";
#endif

char* _cvsID     = "$Revision: 3431 $";

using namespace std;

typedef std::vector<u_int8_t>       DataVec;

struct EeLoc {
    EeLoc(u_int32_t o = 0, u_int8_t d = 0) : offset(o) , dev(d) {}
    u_int32_t     offset;
    u_int8_t      dev;	
};


class ErrMsg
{
public:
    ErrMsg() : _err(0)       {                           }
    ~ErrMsg()                { err_clear();              }
    const char *err() const  { return _err;              }
    void       err_clear()   { delete [] _err; _err = 0; }

protected:
    
    char *vprint(const char *format, va_list args)
    {
	const int INIT_VAL = 1024;
	int       max_str, max_buf = INIT_VAL;
	char      *out_buf;
	
	while (1)
	{
	    out_buf = new char[max_buf];
	    max_str = max_buf - 1;
	    
	    if (vsnprintf(out_buf, max_str, format, args) < max_str)
		return out_buf;
	    delete [] out_buf;
	    max_buf *= 2;
	}
    } 


    bool errmsg(const char *format, ...)
#ifdef __GNUC__
        __attribute__ ((format (printf, 2, 3)))
#endif
    ; 

    bool warnmsg(const char *format, ...)
#ifdef __GNUC__
        __attribute__ ((format (printf, 2, 3)))
#endif
    ;  
private:

    char       *_err;
};

bool ErrMsg::warnmsg(const char *format, ...) {
    va_list   args;

    printf("-W- ");
    va_start(args, format);
    vprintf(format, args);
    va_end(args);

    printf("\n");
    return true;
}

bool ErrMsg::errmsg(const char *format, ...) {
    va_list   args;

    char* prev_err = _err;

    va_start(args, format);
    _err = vprint(format, args);
    va_end(args);

    delete[] prev_err;

    return false;
}   

typedef pair<EeLoc, u_int32_t> EeRange;

//
// Endianness handler:
//
inline
u_int32_t ImgBytes2Dw(const u_int8_t* p) {
    return __be32_to_cpu(*(u_int32_t*)p);
}

inline
void PatchImgBytes(u_int8_t* p, u_int32_t val) {
    u_int32_t correct_endianess_val = __cpu_to_be32(val);
    memcpy(p , &correct_endianess_val, 4);
}



////////////////////////////////////////////////////////////////////////
//                                                                    //
// ****************************************************************** //
//                        CRC16 CALCULATION                           //
// ****************************************************************** //
//                                                                    //
////////////////////////////////////////////////////////////////////////
class Crc16 {
public:
    Crc16(bool d = false) : _debug(d) { clear();}
    u_int16_t      get()              { return _crc;}
    void           clear()            { _crc = 0xffff;}
    void           operator<<(u_int32_t val) { add(val);}
    void           add(u_int32_t val);
    void           finish();
private:
    u_int16_t      _crc;
    bool           _debug;
};

////////////////////////////////////////////////////////////////////////
void Crc16::add(u_int32_t o)
{
    if (_debug)
        printf("Crc16::add(%08x)\n", o);
    for (int i=0; i<32; i++) {
        if (_crc & 0x8000)
            _crc = (u_int16_t) ((((_crc<<1) | (o>>31)) ^  0x100b) & 0xffff);
        else
            _crc= (u_int16_t) (((_crc<<1) | (o>>31)) & 0xffff);
        o = (o<<1) & 0xffffffff;
    }
} // Crc16::add


////////////////////////////////////////////////////////////////////////
void Crc16::finish()
{
    for (int i=0; i<16; i++) {
        if (_crc & 0x8000)
            _crc=((_crc<<1)  ^  0x100b) & 0xffff;
        else
            _crc=(_crc<<1) & 0xffff;
    }

    // Revert 16 low bits
    _crc = _crc ^ 0xffff;

} // Crc16::finish


//
// Interface class for translations between addr/dev and contiguous address space.
//
class EeAddressConvertor {
public:
    enum {
        EEPROMS = 8
    };

    virtual bool      Init         (const u_int32_t eepromData[EEPROMS]);
    virtual bool      Init         (const u_int8_t* eepromDataBytes);

    virtual u_int32_t Convert      (const EeLoc& ee_loc) const;
    virtual EeLoc     Convert      (u_int32_t    addr  ) const;

    virtual std::vector<EeRange> 
                      ConvertRange (u_int32_t    addr,
                                    u_int32_t    len)    const;

    virtual u_int32_t GetTotalSize ()                    const;

    virtual void      Clear()      {_devmap.clear();}

    virtual ~EeAddressConvertor    () {}

protected:

    // In this vector the offset of the EeLoc represents size of Eeprom
    std::vector<EeLoc> _devmap;

};

//
// FW Data Section
//
struct Section {
    Section(u_int8_t s, u_int32_t o) : 
        location(o, s) ,
        addr(0) {}
    
    Section(const char *n, u_int8_t s, u_int32_t o) :
        name(n),
        location(o, s),
        addr(0) {}
    
    std::string      name;
    EeLoc            location;
    u_int32_t        addr;
    u_int8_t         type;
    
    DataVec          data;
};

typedef std::vector<Section*> SectionVec;


// IoBase
class IoBase : public ErrMsg {
public:
    IoBase() : 
        _conv(NULL),
        _primaryEeprom(0), 
        _secondaryEeprom(0)       {}

    virtual ~IoBase()  {}

    virtual bool IsFile() = 0;

    virtual bool open   (const char *)                  {return false;}

    virtual bool close  ()                                                  = 0;

    virtual bool read   (u_int32_t   addr, 
                         u_int32_t   len, 
                         DataVec&    data);

    virtual bool read   (u_int8_t    i2c_slave, 
                         u_int32_t   offset, 
                         u_int32_t   len,
                         DataVec&    data)                                  = 0;

    virtual bool SetAddressConvertor(EeAddressConvertor* conv)   {_conv = conv; return true;}

    virtual const EeAddressConvertor* 
                 GetAddressConvertor()                           {return _conv;}

    virtual u_int8_t  GetPrimaryEeprom()                {return _primaryEeprom;}
    virtual u_int8_t  GetSecondaryEeprom()              {return _secondaryEeprom;}

    virtual void      SetPrimaryEeprom(u_int8_t eeprom)   {_primaryEeprom   = eeprom;}
    virtual void      SetSecondaryEeprom(u_int8_t eeprom) {_secondaryEeprom = eeprom;}

protected:
    EeAddressConvertor* _conv;

    u_int8_t            _primaryEeprom;
    u_int8_t            _secondaryEeprom;
};

class ImageFile : public IoBase {
public:

    virtual bool open   (const char* imageFile);
    virtual bool close  () {return true;}

    virtual bool read   (u_int8_t    i2c_slave, 
                         u_int32_t   offset, 
                         u_int32_t   len,
                         DataVec&    data);

    virtual const SectionVec&
                 GetSections()  {return _sections;}

    virtual Section*
                 GetSection(const char* name);
    virtual bool AddSection(Section* s);

    virtual const string&
                  FileName()    {return _fileName;}

    virtual bool IsFile()       {return true;}
    
private:

    typedef std::map<u_int8_t, DataVec> DeviceDataMap;

    bool LoadBin        (const char* imageFile) {imageFile = NULL; return false;}
    bool LoadImg        (const char* imageFile);

    DeviceDataMap    _deviceData;
    std::string      _fileName;

    SectionVec       _sections;

};



// ProgressMeter (Base).

class ProgressMeter {
public:
    virtual bool Add    (u_int32_t amount)  = 0;
    virtual bool Reset  (u_int32_t max = 0) = 0;
    virtual ~ProgressMeter     ()                  {}
};

class PercentProgressMeter : public ProgressMeter {
public:
    PercentProgressMeter    (const std::string& msg = "" , u_int32_t max = 0) :
        _add_count(0),
        _current_progress(0),
        _progress_max    (max),
        _done (false),
        _status("OK"),
       _msg (msg) {}

    virtual bool Add   (u_int32_t amount);
    virtual bool Reset (u_int32_t max)    {_add_count = 0; 
                                                  _current_progress = 0; 
                                                  _done = false; 
                                                  if (max) _progress_max = max ;
                                                  return Add(0);}

    void         SetMsg(const std::string& msg)  {_msg = msg;}
    void         Done(const char* status = NULL) {if (status) _status = status; _current_progress = _progress_max; Add(0);}
        
private:
    u_int32_t   _add_count;
    u_int32_t   _current_progress;
    u_int32_t   _progress_max;
    bool        _done;
    const char* _status;

    std::string _msg;
    
    static const char   _spinner[8];
};  


class Eeprom : public IoBase {
public:
    Eeprom() : 
        _prog (NULL),
        _is3IdWord(0) {}

    virtual bool open   (const char* dev);
    virtual bool close  ();

    virtual bool write  (u_int32_t   addr,
                         DataVec&    data);

    virtual bool write  (u_int8_t    i2c_slave, 
                         u_int32_t   offset,
                         DataVec&    data);


    virtual bool read   (u_int8_t    i2c_slave, 
                         u_int32_t   offset, 
                         u_int32_t   len,
                         DataVec&    data);   

    virtual void SetProgressMeter(ProgressMeter* prog) {_prog = prog;}

    
    bool         InitImageAddressesFromUser (u_int8_t primarySlaveAddr, 
                                             u_int8_t secondarySlaveAddr);

    bool         InitImageAddressesFromIs3  (u_int8_t is3SlaveAddr);

    u_int32_t    GetIs3IdWord() {return _is3IdWord;}

    virtual bool IsFile()       {return false;}

private:

    ProgressMeter* _prog;
    u_int32_t      _is3IdWord;

    enum EPROM_CMD {
        EPROM_WRITE, EPROM_READ, EPROM_END  // EEPROM commands
    };
    enum CONSTANTS {
        MAX_ERROR_LEN = 512,         // Maximal length of error string
        EPROM_UNIT = 4,              // EEPROM unit length
        EPROM_PRIMARY_COUNT = 5000,  // Amount of "fast" check
        EPROM_SECONDARY_COUNT = 100, // Amount of "slow" check
        EPROM_REQUEST_PAUSE = 100,   // Microseconds for pause
        EPROM_ALIGN_W = 32,          // Write accesses can't cross this boundary
        EPROM_BURST_W = 32,          // Maximal data burst on I2C bus on write
        EPROM_BURST_R = 64,          // Maximal data burst on I2C bus on read
        MAX_REPETITION = 100,        // Maximal number of transaction repet.
        MAX_I2C_WR_RETRY = 256,      // Maximal number of writing attempts
        MAX_I2C_RD_RETRY = 16        // Maximal number of reading attempts
    };

    bool      read_field (u_int32_t addr, u_int8_t offs, u_int8_t len, u_int32_t* v);
    bool      write_field(u_int32_t addr, u_int8_t offs, u_int8_t len, u_int32_t  v);
    bool      eprom_wait(u_int32_t exp);
    bool      eprom_cmd(EPROM_CMD cmd);
    bool      eprom_check();

    void      micro_sleep(const int n);

    // 
    // PCI access functions (MT43132 only)
    //
    bool      eprom_w_trans(int len, u_int8_t *data);
    bool      eprom_r_trans(int len, u_int8_t *data);
    bool      pci_read(u_int32_t addr, u_int8_t *data, u_int32_t length);
    bool      pci_write(u_int32_t addr, u_int8_t *data, u_int32_t length);

    void      eprom_wdelay(int data_size) { micro_sleep((4700 + data_size*10250 + 4700) / 1000 + 1); };

    mfile       *_mf;
    bool        _was_tmo;
    bool        _verbose;

    const char* _dev;

    bool        _dbg_high;
    bool        _dbg_low;
    bool        _internal_read;
    
public:
    void        wdelay() { eprom_wdelay(EPROM_BURST_W); }

};



#define MISCC_SPM_ADR7  0x3180, 0,  7
#define MISCC_SPM_STS   0x3180, 16, 3
#define MISCC_SPM_VBT   0x3180, 22, 2
#define MISCC_SPM_CMD   0x3180, 29, 3
#define MISCC_SPM_DAT_3 0x3184, 0,  32

#define MASK32(S)         ( ((u_int32_t) ~0L) >> (32-(S)) )
#define BITS32(O,S)       ( MASK32(S) << (O) )
#define EXTRACT32(W,O,S)  ( ((W)>>(O)) & MASK32(S) )
#define INSERT32(W,F,O,S) ((W)= ( ( (W) & (~BITS32(O,S)) ) | (((F) & MASK32(S))<<(O)) ))

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////

bool Eeprom::InitImageAddressesFromIs3(u_int8_t is3SlaveAddr) {

    // The Primary and Secondary I2c slave addresses of the eeproms are taken from
    // strapping options and accessible from cr-space.
    // Primary eeprom  - Cr Address 0x60010 bits 14:8
    // Seconary eeprom - Hard wires 4'b1010 concatenated to Cr Address 0x60010 bits {17,16,26}

    // Need to open another interface because address width for is3 access is 32 bits,
    // and for eeproms it's 16 bits.
    mfile* mf;
    u_int32_t initData;

    if (!_mf)                             
        return errmsg("Internal error: InitImageAddressesFromIs3 before open()");
    
    #ifndef USING_IBVSMAD
    if (!(mf = mopend(_dev , MST_TAVOR))) 
        return errmsg("Failed to open device %s for IS3 access", _dev);
    #else
      mf = _mf;
      mf->portid.lid = strtoul(_dev, NULL, 0);
    #endif
   
    mset_i2c_slave(mf, is3SlaveAddr);

    if (mread4(mf, 0x60010, &initData) != 4 || mread4(mf, 0x60014, &_is3IdWord) != 4) {
        return errmsg("IS3 device access failed" );
    }

    #ifndef USING_IBVSMAD
    mclose(mf);
    #endif

    _primaryEeprom   = EXTRACT32(initData, 8, 7);
    _secondaryEeprom = 0x50 | (EXTRACT32(initData, 16, 2) << 1) | EXTRACT32(initData, 26, 1);

    //printf("-D- InitData:%08x Eeprom addresses: P: %02x S: %02x\n", initData, _primaryEeprom, _secondaryEeprom);
    return true;
}

bool Eeprom::InitImageAddressesFromUser(u_int8_t primarySlaveAddr, u_int8_t secondarySlaveAddr) {
    _primaryEeprom   = primarySlaveAddr;
    _secondaryEeprom = secondarySlaveAddr;
    return true;
}

bool Eeprom::open             (const char* dev) {
    // Open MTCR device
    if ( !(_mf = mopend(dev, MST_GAMLA)) )
    {
        return errmsg(strerror(errno));
    }
    if (_mf->sock != -1)
    {
        return errmsg("Remote operation not supporeted.");
    }

    _dev     = dev;
    return true;
}

bool Eeprom::close            () {
    mclose (_mf);
    _mf = NULL;
    
    return true;
}


bool Eeprom::read_field(u_int32_t addr, u_int8_t offs, u_int8_t len, u_int32_t* v)
{

    if (mread4(_mf, addr, v) != 4)
        return errmsg("CR read failed: %s", strerror(errno));
    *v = __le32_to_cpu(*v);
    *v = EXTRACT32(*v, offs, len);

    if (_dbg_low)
        printf("%sEeprom::read_field(0x%x, %d, %d) -> 0x%x\n",
               _internal_read ? "    " : "", addr, offs, len, *v);
    return true;
} // Eeprom::read_field

////////////////////////////////////////////////////////////////////////
bool Eeprom::write_field(u_int32_t addr, u_int8_t offs, u_int8_t len, u_int32_t val)
{
    _internal_read = true;
    u_int32_t v;
    if (!read_field(addr, 0, 32, &v)) return false;
    _internal_read = false;

    INSERT32(v, val, offs, len);
    v = __cpu_to_le32(v);
    if (_dbg_low)
        printf("Eeprom::write_field(0x%x, %d, %d, 0x%x) /0x%x/\n",
               addr, offs, len, val, v);

    if (mwrite4(_mf, (unsigned int)addr, (unsigned int)v) != 4)
        return errmsg("Eeprom write field failed: %s",  strerror(errno));

    return true;
} // Eeprom::write_field

////////////////////////////////////////////////////////////////////////
bool Eeprom::eprom_wait(u_int32_t exp)
{
    u_int32_t     f;
    u_int32_t     cnt = 0;

    while(1) {
        if (!read_field(MISCC_SPM_CMD, &f )) return false;
        if (f == 0)
            break;
        if (cnt > EPROM_PRIMARY_COUNT)
            micro_sleep(EPROM_REQUEST_PAUSE);
        if (cnt > EPROM_PRIMARY_COUNT+EPROM_SECONDARY_COUNT)
        {
            _was_tmo = true;
            return errmsg("EPROM command timeout (command is 0x%x instead of 0)", f);
        }
        cnt++;
    }

    if (!read_field(MISCC_SPM_STS, &f )) return false;
    if (f != exp)
    {
        return errmsg("EPROM invalid status (status is 0x%x instead of 0x%x)", f , exp);
    }

    return true;
} // Eeprom::eprom_wait

////////////////////////////////////////////////////////////////////////
bool Eeprom::eprom_cmd(EPROM_CMD cmd)
{
    int           exp=0;

    switch (cmd)
    {
    case EPROM_READ:
        if (!write_field(MISCC_SPM_CMD, 1)) return false;
        exp = 7;
        break;
    case EPROM_WRITE:
        if (!write_field(MISCC_SPM_CMD, 2)) return false;
        exp = 7;
        break;
    case EPROM_END:
        if (!write_field(MISCC_SPM_CMD, 3)) return false;
        exp = 0;
        break;
    }
    if (!eprom_wait(exp)) return false;

    return true;
} // Eeprom::eprom_cmd

////////////////////////////////////////////////////////////////////////
bool Eeprom::eprom_check(void)
{
    u_int32_t     f;

    if (!read_field(MISCC_SPM_STS, &f)) return false;
    if (f != 0) {
        if (!eprom_cmd(EPROM_END)) return false;
    }

    return true;
} // Eeprom::eprom_check

////////////////////////////////////////////////////////////////////////
bool Eeprom::eprom_w_trans(int len, u_int8_t *data)
{
    int           vbt=-1;   // Initialized w/ invalid value
    u_int32_t     val = 0;

    switch (len)
    {
    case 1:
        vbt = 0;
        val = (*data << 24) & 0xff000000;
        break;
    case 2:
        vbt = 1;
        val = ((*data << 24) & 0xff000000) | ((data[1] << 16) & 0xff0000);
        break;
    case 3:
        vbt = 2;
        val = ((*data << 24) & 0xff000000) | ((data[1] << 16) & 0xff0000) |
            ((data[2] << 8) & 0xff00);
        break;
    case 4:
        vbt = 3;
        val = ((*data << 24) & 0xff000000) | ((data[1] << 16) & 0xff0000) |
            ((data[2] << 8) & 0xff00) | (data[3] & 0xff);
        break;
    default:
        return errmsg("Invalid EEPROM unit length: %d", len);
    }
    if (_dbg_high)
        printf("eprom_w_trans; len:%d val:%08x\n", len, val);

    if (!write_field(MISCC_SPM_VBT, vbt))     return false;
    if (!write_field(MISCC_SPM_DAT_3, val))   return false;
    if (!eprom_cmd(EPROM_WRITE))              return false;

    return true;
} // Eeprom::eprom_w_trans

////////////////////////////////////////////////////////////////////////
bool Eeprom::eprom_r_trans(int len, u_int8_t *data)
{
    int           vbt=-1;   // Initialized w/ invalid value
    u_int32_t     val = 0;

    switch (len)
    {
    case 1:
        vbt = 0;
        break;
    case 2:
        vbt = 1;
        break;
    case 3:
        vbt = 2;
        break;
    case 4:
        vbt = 3;
        break;
    default:
        return errmsg("Invalid EEPROM unit length: %d", len);
    }

    if (!write_field(MISCC_SPM_VBT, vbt))      return false;
    if (!eprom_cmd(EPROM_READ))                return false;
    if (!read_field(MISCC_SPM_DAT_3, &val))    return false;

    switch (len)
    {
    case 1:
        *data = (val >> 24) & 0xff;
        break;
    case 2:
        *data++ = (val >> 24) & 0xff;
        *data   = (val >> 16) & 0xff;
        break;
    case 3:
        *data++ = (val >> 24) & 0xff;
        *data++ = (val >> 16) & 0xff;
        *data   = (val >> 8)  & 0xff;
        break;
    case 4:
        *data++ = (val >> 24) & 0xff;
        *data++ = (val >> 16) & 0xff;
        *data++ = (val >> 8)  & 0xff;
        *data   = val         & 0xff;
        break;
    default:
        return errmsg("Invalid EEPROM unit length: %d", len);
    }
    if (_dbg_high)
        printf("eprom_r_trans; len:%d %08x\n", len, val);

    return true;
}

////////////////////////////////////////////////////////////////////////
bool Eeprom::pci_read(u_int32_t addr, u_int8_t *data, u_int32_t length)
{
    u_int16_t addr16 = (u_int16_t) (addr & 0xffff);
    int       orig_len = length, repeat=1;
    u_int8_t  *orig_data = data;

    _was_tmo = false;
    while (repeat)
    {

        addr16 = __cpu_to_be16(addr16);
        if (!eprom_check()  ||                          // Check precondition
            !eprom_w_trans(2, (u_int8_t *)&addr16))  {  // Write address
            goto PCI_READ_ERROR;
        }
        
        // Read reply
        while (length)
        {
            int unit_len = length > EPROM_UNIT ? (int)EPROM_UNIT : length;
            if (!eprom_r_trans(unit_len, data)) {
                goto PCI_READ_ERROR;
            }
            data += unit_len;
            length -= unit_len;
        }

        eprom_cmd(EPROM_END);   // End transaction
        repeat = 0;             // If error wasn't set everything is OK

        continue;

        // Error handling:
        PCI_READ_ERROR:
            if (_was_tmo)
            {
                if (_dbg_high)
                    printf("pci_read - transaction repeated\n");
                _was_tmo = false;
                eprom_cmd(EPROM_END);
                addr16 = __cpu_to_be16(addr16);
                length = orig_len;
                data = orig_data;
                if (++repeat > MAX_REPETITION)
                    return errmsg("Pci read failed %d times: %s", MAX_REPETITION, err());
            }
            else
                return false;
    }
  
    return true;
} // Eeprom::pci_read

////////////////////////////////////////////////////////////////////////
bool Eeprom::pci_write(u_int32_t addr, u_int8_t *data, u_int32_t length)
{
    u_int16_t addr16 = (u_int16_t) (addr & 0xffff);
    int       orig_len = length, repeat=1;
    u_int8_t  *orig_data = data;

    _was_tmo = false;
    while (repeat)
    {

        addr16 = __cpu_to_be16(addr16);
        if (!eprom_check() ||                          // Check precondition
            !eprom_w_trans(2, (u_int8_t *)&addr16)) {    // Write address
            goto PCI_WRITE_ERROR;
        }

        // Write data
        while (length)
        {
            int unit_len = length > EPROM_UNIT ? (int)EPROM_UNIT : length;
            eprom_w_trans(unit_len, data);
            data += unit_len;
            length -= unit_len;
        }
        
        eprom_cmd(EPROM_END);   // End transaction
        eprom_wdelay(orig_len);
        repeat = 0;             // If error wasn't set everything is OK

        continue;

        PCI_WRITE_ERROR:

            if (_was_tmo)
            {
                if (_dbg_high)
                    printf("pci_write - transaction repeated, cnt=%d\n", repeat);
                _was_tmo = false;
                eprom_cmd(EPROM_END);
                eprom_wdelay(orig_len);
                addr16 = __cpu_to_be16(addr16);
                length = orig_len;
                data = orig_data;
                if (++repeat > MAX_REPETITION)
                    return errmsg("Pci readwrite failed %d times: %s", MAX_REPETITION, err());
            }
            else
                return false;
        
    }

    return true;
}


////////////////////////////////////////////////////////////////////////
void Eeprom::micro_sleep(const int n)
{
    usleep(n);
} // Eeprom::micro_sleep


////////////////////////////////////////////////////////////////////////


bool Eeprom::write       (u_int32_t   addr,
                          DataVec&    data) {

    if (!_conv) {
        return errmsg("Internal error: read() when _conv not initialized");
    }

    vector<EeRange> eer = _conv->ConvertRange(addr, data.size());
    
    if (eer.size() > 1) {
        u_int32_t totWritten = 0;
        for (vector<EeRange>::iterator it = eer.begin() ; it != eer.end(); ++it ) {
            DataVec curData;
            
            u_int32_t chunkSize = it->second;
            curData.insert(curData.begin(), 
                           data.begin() + totWritten, 
                           data.begin() + totWritten + chunkSize);

            if (!write(it->first.dev, it->first.offset, curData)) 
                return false;

            totWritten += chunkSize;
        }
    } else if (eer.size() == 1) {
        if (!write(eer[0].first.dev, eer[0].first.offset, data)) 
                return false;
    } else {
        // eer.size() == 0 => no conversion:
        return errmsg("No address/range conversion to eeprom address/size = %x/%x . Total eproms size: %x",
                      addr,
                      (u_int32_t)data.size(), 
                      _conv->GetTotalSize());

    }

    return true;
}

bool Eeprom::write      (u_int8_t    i2c_slave, 
                         u_int32_t   offset,
                         DataVec&    data) {

    u_int32_t len   = data.size();
    u_int8_t* pdata = &(data[0]); 
    u_int32_t addr  = offset;


    while (len)
    {
        int i2c_wr_retry=0;
        int burst_len = len > EPROM_BURST_W ? (int)EPROM_BURST_W : len;
        int till_next_boundary = EPROM_ALIGN_W - (addr % EPROM_ALIGN_W);
        if (burst_len > till_next_boundary)
            burst_len = till_next_boundary;

        if (_mf)
        {
            _mf->i2c_slave = i2c_slave;
            switch(_mf->tp)
            {

#ifdef USING_IBVSMAD
            case ACCSES_VIA_IBVSMAD:
                if (mwrite64(_mf, addr, pdata, burst_len) != burst_len)
                {
                    return errmsg("Write error: I2C slave:0x%02x, ADDR:0x%x - %s\n",
                                  i2c_slave, addr, strerror(errno));
                }
                usleep(5000);
                break;
#endif
            case MST_CALBR:
                if (mwrite64(_mf, addr, pdata, burst_len) != burst_len)
                {
                    return errmsg("Write error: I2C slave:0x%02x, ADDR:0x%x - %s\n",
                                  i2c_slave, addr, strerror(errno));

                }
                break;
            case MST_USB:
            case MST_USB_DIMAX:
            case MST_IB:
            case MST_IF:
                while (mwrite64(_mf, addr, pdata, burst_len) != burst_len)
                {
                    if (++i2c_wr_retry > MAX_I2C_WR_RETRY)
                    {
                        return errmsg("Write error: I2C slave:0x%02x, ADDR:0x%x - %s\n",
                                      i2c_slave, addr, strerror(errno));
                    }
                }
                break;
            case MST_PPC:
            case MST_PCI:
            case MST_PCICONF:
                if (!write_field(MISCC_SPM_ADR7, _mf->i2c_slave & 0x7f)) return false;
                if (!pci_write(addr, pdata, burst_len))                  return false;
                break;
            }
        }
        else
            eprom_wdelay(burst_len);

        if (_prog) {
            _prog->Add(burst_len);
        }

        pdata += burst_len;
        len   -= burst_len;
        addr  += burst_len;
    }

    return true;

} // burn


////////////////////////////////////////////////////////////////////////
bool Eeprom::read   (u_int8_t    i2c_slave, 
                     u_int32_t   offset, 
                     u_int32_t   len,
                     DataVec&    data) {

    u_int32_t  currSize = data.size();
    data.resize(currSize + len );

    u_int8_t* pdata = &(data[currSize]);
    u_int32_t addr  = offset;

    u_int8_t vdata[EPROM_BURST_R];

    while (len)
    {
        int i2c_rd_retry=0;
        int burst_len = len > EPROM_BURST_R ? (int)EPROM_BURST_R : len;

        if (_mf)
        {
            _mf->i2c_slave = i2c_slave;
            switch(_mf->tp)
            {
#ifdef USING_IBVSMAD
            case ACCSES_VIA_IBVSMAD:
                //ibvsmad::mread64
                if (mread64(_mf, addr, vdata, burst_len) != burst_len)
                {
                    return errmsg("Read error: I2C slave:0x%02x, ADDR:0x%x - %s\n",
                                  i2c_slave, addr, strerror(errno));
                }
                break;
#endif
            case MST_CALBR:
                if (mread64(_mf, addr, vdata, burst_len) != burst_len)
                {
                    return errmsg("Read error: I2C slave:0x%02x, ADDR:0x%x - %s\n",
                                  i2c_slave, addr, strerror(errno));
                }
                break;
            case MST_USB:
            case MST_USB_DIMAX:
            case MST_IB:
            case MST_IF:
                while (mread64(_mf, addr, vdata, burst_len) != burst_len)
                {
                    if (++i2c_rd_retry > MAX_I2C_RD_RETRY)
                    {
                        return errmsg("Read error: I2C slave:0x%02x, ADDR:0x%x - %s\n",
                                      i2c_slave, addr, strerror(errno));
                    }
                }
                break;
            case MST_PPC:
            case MST_PCI:
            case MST_PCICONF:
                if (!write_field(MISCC_SPM_ADR7, _mf->i2c_slave & 0x7f)) return false;
                if (!pci_read(addr, vdata, burst_len))                   return false;
                break;
            }

            memcpy(pdata, vdata, burst_len);

        }
        else
        {
            return errmsg("Internal error: mfile is null\n");
        }

        if (_prog) {
            _prog->Add(burst_len);
        }

        pdata += burst_len;
        len   -= burst_len;
        addr  += burst_len;
    }

    return true;
}



//
// ProgressBar: 
//

const char  PercentProgressMeter::_spinner[8] = {'-','\\','|','/','-','\\','|','/'};

bool PercentProgressMeter::Add (u_int32_t amount) {
    _current_progress += amount;
    _add_count++;
    u_int32_t progress_percent = (_current_progress * 100) / _progress_max;
    u_int32_t spin_pos         = _add_count % sizeof(_spinner);

    if (_current_progress >= _progress_max) {
        if (_done) {
            return true;
        }

        _done    = true;
        spin_pos = 0;
    }

    printf("\r");
    printf("%c %s - ", _spinner[spin_pos], _msg.c_str());
    if (_done) {
        printf(" %s \n", _status);
    } else {
        printf("%%%02d", progress_percent);
        fflush(stdout);
    }

    return true;
}



// Algorithm


////////////////////////////////////////////////////////////////////////
//
// Burn Operations functions
//
////////////////////////////////////////////////////////////////////////

class Operations : public ErrMsg {
public:

    Operations() :
        _useDefaultAnswer(false)
    {
        InitNames();
    }

    //
    // IS3 FW IMAGE DATA
    //

    enum {
        MAX_IMAGE_SIZE = (128 * 1024),
        PSID_CR_ADDR   = 0x3ff0c,
        FWVER_CR_ADDR  = 0x3ffa0
    };


        // Data initialization types
    enum SectionType {
        TYPE_IWI = 1,   // Individual words initialization
        TYPE_NBI = 2,   // Non-homogeneous block initialization
        TYPE_HBI = 3,   // Homogeneous block initialization
        TYPE_RMW = 4,   // Read-modify-write block initialization
        TYPE_JMP = 5,   // Jump address initialization
        TYPE_COD = 97,  // Code. Same as TYPE_NBI
        TYPE_SPC = 98,  // Special structire
        TYPE_LST = 99,  // Last data record

        // Following typed are not "real" type field values (meaning
        // that they are not recognized by FW as a valid "type" fiels in 
        // a section). 
        TYPE_PLL = 200,
        TYPE_BOOT= 201,   // Boot section - different structure
        TYPE_UNKNOWN= 0,
    };

    // Special STRUCT ID
    enum FieldId {
        FIELD_UNKNOWN= 0,
        SYS_GUID_ID  = 1,
        NODE_DESC_ID = 2,
        NODE_GUID_ID = 5,
        BRD_ID       = 3,
        VS_CLS_EN_ID = 4,
        BSN_ID       = 6,

        // Not in Special Section:
        PSID         = 20,
        CRC          = 21,
        FWVER        = 22
    };

    // Simly burn the ImageFile content to rom. No checks or replaces.
    bool RawBurn           (Eeprom& eeprom, ImageFile& imageFile);

    bool CompareContent    (Eeprom& eeprom, ImageFile& imageFile);

    // Failsafe burn flow:
    // 1. Read and check imagefile (Primary Image). Exit if bad.
    // 2. Read and verify full image on eeproms.
    //    Remember image state (which is valid). Exit if both are bad.
    // 3. ? Compare AddressConvertors and Make sure they're the same.
    // 4. Read PSID and Special section data from eeprom.
    // 5. Get PSID and Special section data + adresses from image. Duplicate them for both images).
    // 6. Patch the image with the data from eeprom (or from user).
    // 7. Failsafe burn patched image according to the eeprom image state.

    struct ReplaceFieldData {
        ReplaceFieldData() :
            type(FIELD_UNKNOWN),
            addr(0),
            size(0) {}
        FieldId    type;
        u_int32_t  addr;
        u_int32_t  size;
        DataVec    data;
    };

    typedef std::map<FieldId, ReplaceFieldData> SpecialFieldsMap;

    struct FwImageData {
        SectionVec           _sections;
        EeAddressConvertor   _conv;
        SpecialFieldsMap     _specialFields;

        void Clear() {
            for (SectionVec::iterator it = _sections.begin(); it != _sections.end(); ++it) {
                delete *it;
            }          

            _conv.Clear();
            _specialFields.clear();
        }

        ~FwImageData() {
            Clear();
        }
    };

    bool IS3FwBurn         (Eeprom& eeprom, ImageFile& imageFile, const SpecialFieldsMap& userFields, bool fsBurn);

    bool CheckFullImage    (IoBase&      img, 
                            u_int8_t     primaryI2cDev, 
                            u_int8_t     secondaryI2cDev = 0, 
                            bool         report          = true);

    bool CheckImage        (IoBase&      img, 
                            u_int8_t     i2cDev, 
                            FwImageData& fwData, 
                            bool         report, 
                            bool         onlyDataSections = false,
                            const char*  header           = NULL,
                            bool         maybeNofs        = false);
    
    bool QueryImage        (IoBase& img, u_int8_t primaryI2cDev, u_int8_t secondaryI2cDev);
    bool DumpImage         (Eeprom& eeprom, u_int8_t primaryI2cDev, u_int8_t secondaryI2cDev, const char* filename);

    bool ReadBlock         (IoBase& img, u_int8_t i2cDev, u_int32_t offset, u_int32_t size,  const char* filename);

    bool GetFieldFromString(SpecialFieldsMap& fields, FieldId fid, const char* str);

    void UseDefaultAnswers() {_useDefaultAnswer = true;}

private:

    bool AskUser           (const char* msg, bool def = true);

    bool GetSpecialData    (const SectionVec& sectVec, SpecialFieldsMap& specialData);
    bool ExtractSpecial    (Section* s, SpecialFieldsMap& specialData);

    bool CheckAndReportFwVersion(Eeprom&      eeprom,
                                 FwImageData& curImageData,
                                 FwImageData& newImageData);

    bool DumpSections      (FILE* f, Operations::FwImageData& imgData, bool convertRange = true);
    bool DumpSpecial       (FILE* f, Operations::FwImageData& imgData);

    bool GetFieldStringRepresentation(const DataVec& data, FieldId t, string& str);
    

    bool FsBurnImage       (Eeprom& eeprom, Operations::FwImageData& image, u_int32_t imgOffset);

    bool PatchNewImage     (FwImageData& newImageData, 
                            const SpecialFieldsMap& curFields, 
                            const SpecialFieldsMap& userFields,
                            bool  ignoreCurrent = false);
    
    bool BurnSections      (Eeprom&            eeprom, 
                            const SectionVec&  sectVec, 
                            bool               useAddress,  
                            u_int32_t          imgOffset,
                             const char*       msg);

    bool CompareSections   (Eeprom&            eeprom, 
                            const SectionVec&  sectVec,
                            bool               useAddress,
                            u_int32_t          imgOffset, 
                            const char*        msg);

    bool ReplaceField      (FwImageData& image, FieldId fid, const DataVec& fieldData);
    bool FixSpecialCrc     (FwImageData& image);

    // Verify CRC of the section. CRC is assumed to be found in the lase
    // DW (BE) in the vector, and calculated over all the vector except
    // last DW.
    // Returns true ic CRC is ok, false otherwise.
    bool CheckCrc          (const DataVec& section);

    u_int32_t CalcCrc      (const DataVec& data, u_int32_t size = 0);

    bool RecalcCrc         (Section* s);


    // Section check methods. The section is read from the given eeprom/addr.
    // Read data is stores in the given sect struct, allocated by the caller.
    bool CheckPlls         (IoBase& img, u_int8_t eeprom, Section* sect);
    bool CheckBoot         (IoBase& img, u_int8_t eeprom, Section* sect);
    bool CheckSection      (IoBase& img, u_int32_t addr,  Section* sect);


    // Printing methods for verified image
    bool ReportSection     (Section* sect, const char* status = NULL);
    bool ReportSectionHeader();


    bool PrintSpecialData       (Operations::SpecialFieldsMap& fields, const char* src);
    bool PrettyPrintSpecialData (Operations::SpecialFieldsMap& fields);

    std::map<SectionType, const char*> _sectionTypeNames;
    std::map<FieldId,    const char*>  _fieldIdNames;
    std::map<FieldId,    u_int32_t>    _fieldIdSizes;
    
    bool _useDefaultAnswer;

    void InitNames() {
        // Initialize the (static) names map.
        if (_sectionTypeNames.empty()) {
            _sectionTypeNames[TYPE_PLL]  = "PLL";
            _sectionTypeNames[TYPE_BOOT] = "BOOT";
            _sectionTypeNames[TYPE_IWI]  = "IWI";
            _sectionTypeNames[TYPE_NBI]  = "NBI";
            _sectionTypeNames[TYPE_HBI]  = "HBI";
            _sectionTypeNames[TYPE_RMW]  = "RMW";
            _sectionTypeNames[TYPE_JMP]  = "JUMP";
            _sectionTypeNames[TYPE_COD]  = "CODE";
            _sectionTypeNames[TYPE_SPC]  = "SPECIAL";
            _sectionTypeNames[TYPE_LST]  = "LAST";
            _sectionTypeNames[TYPE_UNKNOWN]  = "UNKNOWN";

            _fieldIdNames[SYS_GUID_ID]  = "System Image GUID" ;
            _fieldIdNames[NODE_GUID_ID] = "Node GUID" ;
            _fieldIdNames[NODE_DESC_ID] = "Node Description";
            _fieldIdNames[BSN_ID]       = "Board Serial Number";
            _fieldIdNames[PSID]         = "PSID";
            _fieldIdNames[FWVER]        = "FW Version";

            _fieldIdSizes[SYS_GUID_ID]  = 8;
            _fieldIdSizes[NODE_GUID_ID] = 8;
            _fieldIdSizes[NODE_DESC_ID] = 64;
            _fieldIdSizes[BSN_ID]       = 64;
            _fieldIdSizes[BRD_ID]       = 16;
            _fieldIdSizes[VS_CLS_EN_ID] = 4;
            _fieldIdSizes[PSID]         = 16;
            _fieldIdSizes[FWVER]        = 4;
        }
    }


};


bool  EeAddressConvertor::Init  (const u_int8_t* eepromDataBytes) {
    u_int32_t eepromData[EEPROMS];
    for (u_int32_t i = 0 ; i < EEPROMS ; i++ ) {
        eepromData[i] = ImgBytes2Dw(eepromDataBytes + i * 4);
    }

    return Init(eepromData);
}

bool  EeAddressConvertor::Init  (const u_int32_t    eepromData[EEPROMS]) {
    _devmap.clear();
    for (u_int32_t i = 0 ; i < EEPROMS ; i++ ) {
        u_int8_t  d = eepromData[i] >> 24;
        u_int32_t o = eepromData[i] & 0xffffff;

        _devmap.push_back(EeLoc(o,d));
    }
    
    return true;
}


u_int32_t EeAddressConvertor::GetTotalSize () const {
    u_int32_t res = 0;
    for (vector<EeLoc>::const_iterator it = _devmap.begin(); it != _devmap.end() ; ++it) {
        res += it->offset;
    }

    return res;
}


EeLoc EeAddressConvertor::Convert   (u_int32_t addr) const {
    vector<EeRange> eer = ConvertRange(addr,1);

    if (eer.empty()) {
        // TODO: Check return value for all calls of this function.
        return EeLoc(0xffffffff, 0xff);
    }

    return eer[0].first;   
}


vector<EeRange> EeAddressConvertor::ConvertRange   (u_int32_t addr, u_int32_t len) const {
    vector<EeRange> res;
    //printf("-D- Converted (addr,size) => (dev,offset,size) : (%06x, %06x) = > ",addr, len); // NOTE - remove below printf too!!!
    for (u_int32_t i = 0; len && i < _devmap.size() ; i++) {
        u_int32_t devSize = _devmap[i].offset;

        if (addr < devSize) {
            if (addr + len <= devSize) {
                res.push_back(EeRange(EeLoc(addr, _devmap[i].dev), len));

                //for (vector<EeRange>::iterator it = res.begin(); it != res.end(); ++it) 
                //    printf("(%02x,%06x,%06x) ", it->first.dev, it->first.offset, it->second);
                // printf("\n");

                return res;
            }

            u_int32_t curr_len = devSize - addr;
            res.push_back(EeRange(EeLoc(addr, _devmap[i].dev), curr_len));
            len -= curr_len;
            addr = 0;
            
        } else {
            addr -= _devmap[i].offset;
        }
    }

    // If we're here - no mapping found.
    // IBADM_THROW("Addr2OffsetDev: No mapping for the given addr (" << hex << addr << ").");   
    res.clear();
    return res;

}


u_int32_t    EeAddressConvertor::Convert(const EeLoc& ee_loc) const {
    if (_devmap.empty() && ee_loc.dev == 0) {
        return ee_loc.offset;
    }
    
    u_int32_t addr = 0;

    for (u_int32_t i = 0; i < _devmap.size() ; i++) {
        if (_devmap[i].dev && (_devmap[i].dev == ee_loc.dev)) {
            return addr + ee_loc.offset;
        } else {
            addr += _devmap[i].offset;
        }
    }

    // If we're here - no mapping found.
    return (u_int32_t)(-1);
}



bool ImageFile::open    (const char* imageFile) {
    
    string tmp = imageFile;
    string::size_type dotPos = tmp.rfind(".");
    string ext;


    if (dotPos == string::npos) {
        ext = "";
    } else {
        ext = tmp.substr(dotPos, tmp.size() - dotPos);
    }

    if        (ext == ".img" || ext == ".eeprom") {
        return LoadImg(imageFile);
//    } else if (ext == ".bin") {
//        return LoadBin(imageFile);
    } else {
        return errmsg("Unsupported file format (%s). Supported formats: .img ",
                      ext.c_str());
    }
}

    
bool ImageFile::LoadImg(const char* imageFile) {
    // Read and parse image file

    // 1. Load and Store IMG sections in _sections vector
    // 2. Move data to the _devceData map.
    // 3. Mark _primaryEeprom (the first section).


    FILE *fp;
    if ((fp = fopen(imageFile, "r")) == NULL)
    {
        return errmsg(strerror(errno));
    }

    u_int32_t lineNum = 0;

    Section *curr_sect = 0;
    while(1)
    {
        const int   MAX_STR = 1024;
        u_int32_t   curr_addr, curr_eepr, curr_value;
        static char name[MAX_STR], str[MAX_STR], st[MAX_STR];
        char        *endp;

        lineNum++;

        if (!curr_sect)
        {
            /*
             * Outside section
             */
            if (!fgets(str, MAX_STR, fp))
                break;

            // Skip comments and empty lines
            if (str[strspn(str, " \t")] == '#')
                continue;
            if (str[strspn(str, " \t")] == '\n')
                continue;

            sscanf(str, "%s %s %x %x", st, name, &curr_addr, &curr_eepr);
            if (strcmp(st, "START"))
                continue;
            curr_sect = new Section(name, (u_int8_t)curr_eepr, curr_addr);
        }
        else
        {
            /*
             * Inside section
             */
            if (fscanf(fp, "%s", str) != 1)
            {
                return errmsg("%s:%d: Wrong EEPROM image format - unexpected EOF",
                              _fileName.c_str(),
                              lineNum);
            }
            if (!strcmp(str, "END"))
            {
                if (fscanf(fp, "%s", name) != 1)
                {
                    return errmsg(
                            "Wrong EEPROM image format - after END name of "
                            "the input section is expected.");
                }
                if (strcmp(curr_sect->name.c_str(), name))
                {
                    return errmsg(
                            "Wrong EEPROM image format - end of section \"%s\" is"
                            " expected,\nwhile end of section \"%s\" is detected.",
                           curr_sect->name.c_str(), name);
                }
                _sections.push_back(curr_sect);
                curr_sect = 0;
            }
            else
            {
                curr_value = strtoul(str, &endp, 16);
                if (*endp != '\0')
                    return errmsg("Wrong EEPROM image - hexa constant %s"
                                  "has invalid characters.", str);

                curr_sect->data.push_back((u_int8_t)curr_value);
            }
        }
    }
    fclose(fp);


    //for (vector<Section*>::iterator it = _sections.begin(); it != _sections.end(); ++it) {
    //    Section* s = *it;
    //    printf("-D- %-30s %02x %08x\n",s->name.c_str(), s->location.dev, s->location.offset);
    //}


    if (_sections.empty()) {
        return errmsg("No image sections found.");
    }

    // The first section in the given file is written to the primary eeprom.
    _primaryEeprom = (*_sections.begin())->location.dev;

    // Put the data in the deviceData map
    for (vector<Section*>::iterator it = _sections.begin(); it != _sections.end(); ++it) {

        Section* s = *it;
        DeviceDataMap::iterator dit = _deviceData.find(s->location.dev);

        if (dit == _deviceData.end()) {
            // Add this device
            _deviceData[s->location.dev];

            dit = _deviceData.find(s->location.dev);
        }

        DataVec& devData = dit->second;

        if (devData.size() < s->location.offset) {
            // new section starts beyond the place where prev section ended - resize to start:
            devData.resize(s->location.offset, 0);
        }

        if (devData.size() == s->location.offset) {
            // The section continues previous section - add to end.
            devData.insert(devData.end(), s->data.begin(), s->data.end());
        } else if (devData.size() > s->location.offset + s->data.size() ) {
            for (u_int32_t i = 0; i < s->data.size() ; i++) {
                devData[s->location.offset + i] = s->data[i];
            }
        } else {
            return errmsg("Failed to process sections: Found section %s (%02x,$08x) of size %x bytes, but device current size is %x bytes.",
                          s->name.c_str(),
                          s->location.dev,
                          s->location.offset,
                          (u_int32_t)devData.size());
        }
    }
    
    //for (DeviceDataMap::iterator it = _deviceData.begin(); it != _deviceData.end(); ++it) {
    //    printf("-D- Dev %02x  Size %08x\n", it->first, it->second.size());
    //}

    return true;
}


bool IoBase::read      (u_int32_t   addr, 
                         u_int32_t   len, 
                         DataVec&    data) {

    if (!_conv) {
        return errmsg("Internal error: read() when _conv not initialized");
    }

    vector<EeRange> eer = _conv->ConvertRange(addr, len);

    for (vector<EeRange>::iterator it = eer.begin() ; it != eer.end(); ++it ) {
        if (!read(it->first.dev, it->first.offset, it->second, data)) 
            return false;
    }

    return true;
}


Section* ImageFile::GetSection (const char* name) {
    for (SectionVec::iterator sit = _sections.begin();
          sit != _sections.end();
          ++sit) {
        
        Section* s = *sit;
        if (s->name == name) {
            return s;
        }
    }

    return NULL;
}

bool ImageFile::AddSection(Section* s) {
    _sections.push_back(s);
    return true;
}



// read() : Insert the extraced data to the given data vector.
//          Note that if data is not empty, new the new data is appended to it.
bool ImageFile::read    (u_int8_t    i2c_slave, 
                         u_int32_t   offset, 
                         u_int32_t   len,
                         DataVec&    data) {


    DeviceDataMap::iterator dit = _deviceData.find(i2c_slave);

    if (dit == _deviceData.end()) {
        return errmsg("Tried to read %x bytes from offset %x from a non existing device: %02x.",
                      len,
                      offset,
                      i2c_slave);
    }

    DataVec& devData = dit->second;

    if (offset + len > devData.size()) {
        return errmsg("Tried to read %x bytes from offset %x - Beyond device %02x size - %x.",
                      len,
                      offset,
                      i2c_slave,
                      (u_int32_t)devData.size());
    }


    data.insert(data.end(), devData.begin() + offset, devData.begin() + offset + len); 

    return true;
}




//
// Operations class implementation
//

bool Operations::CheckPlls(IoBase& img, u_int8_t eeprom, Section* sect) {
    sect->type = TYPE_PLL;
    if (!img.read(eeprom, 0x0, 0x28 , sect->data)) return errmsg(img.err());

    // Verify Checksum
    const u_int8_t expectedCheckSum = 0xAB;
    u_int8_t       actualCheckSum = 0;
    for (u_int32_t i = 0; i < 16 ; i++) {
        actualCheckSum += sect->data[i];
    }

    if (actualCheckSum != expectedCheckSum) {
        return errmsg("Bad PLL checksum: Expected: %02x, Actual: %02x", expectedCheckSum, actualCheckSum);
    }

    return true;
}


bool Operations::CheckBoot(IoBase& img, u_int8_t eeprom, Section* sect) {

    DataVec data;
    sect->type = TYPE_BOOT;

    // Read boot2 section size
    if (!img.read(eeprom, 0x2c, 4, data)) return errmsg(img.err());

    u_int32_t bootSectSize = ImgBytes2Dw(&data[0]);

    // check reasonable size to make sure we're not working on a 
    // corrupted data (e.g. 0xffffffff of blank eeprom). The boot 
    // section size (and its crc + PLL boot record) can not cross 32KB.

    if (bootSectSize * 4 >= 32*1024 - 0x38) {
        return errmsg("Boot section check failed. Read boot size from eeprom %02x, "
                      "offset %x is %x bytes, which is larger than 32KB.",
                      eeprom,
                      0x2c,
                      bootSectSize * 4);
    }

    u_int32_t bootSize = bootSectSize * 4 + 0x10;

    if (!img.read(eeprom, 0x28, bootSize, sect->data)) return errmsg(img.err());

    if (!CheckCrc(sect->data)) {
        return false;
    }

    return true;
}

bool Operations::CheckCrc(const DataVec& data) {

    u_int32_t calc_crc = CalcCrc(data, data.size() - 4);
    u_int32_t sect_crc = ImgBytes2Dw(&data[data.size() - 4]);

    if (sect_crc != calc_crc) {
        return errmsg("Bad CRC: Aclual: %04x, Expected: %04x",
                      sect_crc,
                      calc_crc);
    }

    return true;
}


u_int32_t Operations::CalcCrc(const DataVec& data, u_int32_t size) {
    if (size == 0) {
        size = data.size();
    }

    assert((size % 4) == 0);

    Crc16 crc;
    for (u_int32_t i = 0; i < size ; i += 4) {
        u_int32_t w = ImgBytes2Dw(&data[i]);
        crc.add(w);
    }
    crc.finish();
    
    return crc.get();
}

bool Operations::CheckSection(IoBase& img, u_int32_t addr, Section* sect) {
    DataVec data;

    if(!img.read(addr + 4, 4, data)) return errmsg(img.err());
    u_int32_t type_size = ImgBytes2Dw (&data[0]);

    sect->type          = type_size >> 24;
    u_int32_t sectSize  = (type_size & 0xffff) * 4 + 8;

    if(!img.read(addr, sectSize, sect->data)) return errmsg(img.err());

    if (!CheckCrc(sect->data)) {
        return false;
    }

    return true;
}

bool Operations::ReadBlock (IoBase& img, u_int8_t i2cDev, u_int32_t offset, u_int32_t size, const char* filename) {
    FwImageData readData;

    Section*  sect = new Section("ReadBlock" , i2cDev, offset);
    readData._sections.push_back(sect);

    //printf("-D- bool Operations::ReadBlock ( , %x, %x, %x, %s)\n", i2cDev, offset, size,filename);

    if (!img.read(i2cDev, offset, size, sect->data)) 
        return errmsg("Read failed: %s", img.err());

    FILE* of = fopen(filename, "w");
    if (of == NULL) {
         return errmsg("Failed to open file %s for writing: %s",
                       filename,
                       strerror(errno));
    }

    if (!DumpSections(of, readData, false)) {
        return false;
    }

    fclose(of);

    return true;
}



bool Operations::DumpImage     (Eeprom& eeprom, u_int8_t primaryI2cDev, u_int8_t secondaryI2cDev, const char* filename) {

    bool primaryOk   = false;
    bool secondaryOk = false;

    primaryI2cDev   = 0; //COMPILER WARNING;
    secondaryI2cDev = 0; //COMPILER WARNING;
    
    FwImageData primaryImageData;
    FwImageData secondaryImageData;

    PercentProgressMeter prog("Reading primary image    " , MAX_IMAGE_SIZE);
    eeprom.SetProgressMeter(&prog);

    primaryOk = CheckImage(eeprom, eeprom.GetPrimaryEeprom(), primaryImageData, false);

    if (primaryOk) {
        prog.Done();
    } else {
        prog.Done(err());
    }


    PercentProgressMeter progSec("Reading secondary image  " , MAX_IMAGE_SIZE);
    eeprom.SetProgressMeter(&progSec);
    secondaryOk = CheckImage(eeprom, eeprom.GetSecondaryEeprom(), secondaryImageData, false);
    if (secondaryOk) {
        progSec.Done();
    } else {
        progSec.Done(err());
    }
    
    // Get special fields on eeprom and image file :
    GetSpecialData(primaryImageData._sections,   primaryImageData._specialFields);
    GetSpecialData(secondaryImageData._sections, secondaryImageData._specialFields);

    FILE* of = fopen(filename, "w");

    if (of == NULL) {
        return errmsg("Failed to open file %s for writing: %s",
                      filename,
                      strerror(errno));
    }

    printf("- Writing image file ...\n");


    DumpSpecial(of, primaryImageData);
    DumpSpecial(of, secondaryImageData);

    DumpSections(of, primaryImageData);
    DumpSections(of, secondaryImageData);


    fclose(of);

    if (!(primaryOk && secondaryOk)) {
        return errmsg("Errors detected in the eeprom image - Image file may not be full");
    }


    return true;
}


bool Operations::DumpSpecial(FILE* f, Operations::FwImageData& imgData) {
    //fprintf(f, "# Operations::DumpSpecial - NOT YET IMPLEMENTED\n\n");
    f=NULL;
    imgData._sections.size();
    return true;
}

bool Operations::DumpSections(FILE* f, Operations::FwImageData& imgData, bool convertRange) {
    for (SectionVec::const_iterator it = imgData._sections.begin() ; it != imgData._sections.end() ; ++it) {
        Section* s = *it;

        // Sections are dumped in physical device boundaries:
        vector<EeRange> eer;

        if (convertRange)
            eer = imgData._conv.ConvertRange(s->addr, s->data.size());
        else {
            eer.push_back(EeRange(s->location, s->data.size()));          
        }

        u_int32_t currOffset = 0;

        for (u_int32_t i = 0 ; i < eer.size() ; ++i) {
            const char* sectName;
            char sectNameBuff[64];
            SectionType t = (SectionType)s->type;

            if (eer.size() > 1) {
                sprintf(sectNameBuff, "%s-%d", _sectionTypeNames[t], i);
                sectName = sectNameBuff;
            } else {
                sectName = _sectionTypeNames[t];
            }

            fprintf(f , 
                    "START %s %08x %02x\n",
                    sectName,
                    eer[i].first.offset, 
                    eer[i].first.dev);

            const int bytesInLine = 4;
            u_int32_t j;
            for (j = 0 ; j < eer[i].second ; ++j) {
               fprintf(f , " %02x", s->data[currOffset + j]);
               if (((j+1) % bytesInLine) == 0) fprintf(f , "\n");
            }

            if (j % bytesInLine) fprintf(f , "\n");

            fprintf(f , 
                    "END %s\n\n",
                    sectName);

            currOffset += eer[i].second;
        }
    }
    return true;
}

bool Operations::CheckFullImage(IoBase& img, u_int8_t primaryI2cDev, u_int8_t secondaryI2cDev, bool report) {
    bool ret;
    FwImageData primaryImage;
    FwImageData secondaryImage;

    ret = CheckImage(img, primaryI2cDev, primaryImage, report, false, "Primary Image:\n");

    if (ret == false && img.GetAddressConvertor() == NULL && secondaryI2cDev == 0) {
        // Bad primary image - Address convertor not initialized, and secondary
        // eeprom is not given - Can't check secondary image.
        // TODO: See if to return errmsg here;
        return false;
    }

    // CheckImage Initiates address convertor:
    if (secondaryI2cDev == 0) {
        // Auto detect secondary I2c slave address - It is located in the middle 
        // of the eeproms size:
        u_int32_t secondaryImageAddr = primaryImage._conv.GetTotalSize() / 2 ;
        secondaryI2cDev = primaryImage._conv.Convert(secondaryImageAddr).dev;


    }

    // TODO: The NOFS check can be done here - check that the secondary I2C dev points to an
    // existing PLL section.
    if (!CheckImage(img, secondaryI2cDev, secondaryImage, report, false, "\nSecondary Image:\n", img.IsFile())) {
        return false;
    }

    return true;
}

bool Operations::QueryImage(IoBase& img, u_int8_t primaryI2cDev, u_int8_t secondaryI2cDev) {

    bool ret;
    FwImageData  primaryImage;
    FwImageData  secondaryImage;

    FwImageData* validImage = NULL;

    ret = CheckImage(img, primaryI2cDev, primaryImage, false, false);
    if (ret)
        validImage = &primaryImage;

    if (ret == false && img.GetAddressConvertor() == NULL && secondaryI2cDev == 0) {
        // Bad primary image - Address convertor not initialized, and secondary
        // eeprom is not given - Can't check secondary image.
        // TODO: See if to return errmsg here;
        return false;
    }

    if (validImage == NULL) {
        // Check secondary image
        if (secondaryI2cDev == 0) {
            // Auto detect secondary I2c slave address - It is located in the middle 
            // of the eeproms size:
            u_int32_t secondaryImageAddr = primaryImage._conv.GetTotalSize() / 2 ;
            secondaryI2cDev = primaryImage._conv.Convert(secondaryImageAddr).dev;
        }

        ret = CheckImage(img, secondaryI2cDev, secondaryImage, false , true);
        if (ret)
            validImage = &secondaryImage;
    }

    if (validImage == NULL) {
        return errmsg("No valid image.");
    }

    // Print the selected fields:
    printf("\nQuery:\n");
    if (!GetSpecialData(validImage->_sections, validImage->_specialFields)) {
        return false;
    }

    if (!PrettyPrintSpecialData(validImage->_specialFields))
        return false;

    return true;
}


bool Operations::PrettyPrintSpecialData(Operations::SpecialFieldsMap& fields) {
    
    FieldId queriedFields[] = {FWVER, NODE_GUID_ID, SYS_GUID_ID, NODE_DESC_ID, BSN_ID, PSID};

    for (u_int32_t i = 0 ; i < sizeof(queriedFields)/sizeof(queriedFields[0]) ; i++) {
        FieldId fid = queriedFields[i];

        ReplaceFieldData& rep = fields[fid];
        string val;

        if (!GetFieldStringRepresentation (rep.data, rep.type, val)) {
            return false;
        }

        if (_fieldIdNames[fid]) {
            printf("  %-20s   %s\n", (string(_fieldIdNames[fid]) + ":").c_str() , val.c_str());
        }
    }

    return true;
}


bool Operations::PrintSpecialData(Operations::SpecialFieldsMap& fields, const char* src) {
    printf("  Special data fields of %s:\n", src);
    printf("%-20s  %-8s  %-s4 %s\n", "FIELD", "ADDR", "SIZE", "VALUE"); 
    for (SpecialFieldsMap::iterator it = fields.begin();
          it != fields.end();
          ++it) {

        ReplaceFieldData& rep = it->second;

        string val;

        if (!GetFieldStringRepresentation (rep.data, rep.type, val)) {
            return false;
        }

        printf("%-20s  %8x  %4d %s\n", 
               _fieldIdNames[it->first],
               rep.addr,
               (u_int32_t)rep.data.size(),
               val.c_str());

    }

    return true;
}


bool Operations::CheckImage(IoBase&                  img, 
                            u_int8_t                 i2cDev, 
                            Operations::FwImageData& imageData, 
                            bool                     report, 
                            bool                     onlyDataSections,
                            const char*              header,
                            bool                     maybeNofs) {

    Section*  sect;
    bool stat;

    // PLL Section
    sect = new Section(i2cDev, 0);
    sect->addr = 0;

    stat = CheckPlls(img, i2cDev, sect);

    if (!stat && maybeNofs && !img.read(i2cDev, 0x0, 0x28 , sect->data)) {
        // Secondary image is optional for NOFS burn.
        // Check that it can be read. If not, it's a NOFS img.
        if (report) printf("\n  NON-FAILSAFE IMAGE\n\n");
        return true;
    } else {
        if (header && report) printf(header);
        if (report) ReportSectionHeader();
    }

    if (!stat) {
        if (report) ReportSection(sect, err());
        return false;
    }
    imageData._sections.push_back(sect);
    if (report) ReportSection(sect);

    sect = new Section(i2cDev, 0x28);
    sect->addr = 0x28;

    if (!CheckBoot(img, i2cDev, sect)) {
        if (report) ReportSection(sect, err());
        return false;
    }
    imageData._sections.push_back(sect);
    if (report) ReportSection(sect);

    if (!imageData._conv.Init(&sect->data[8])) {
        return errmsg("Address convertor initiation failed");
    }
    img.SetAddressConvertor(&imageData._conv);

    //                      boot start   boot size (in bytes)
    u_int32_t currAddress = 0x28 +       sect->data.size();
    u_int32_t imageOffset = imageData._conv.Convert(EeLoc(0, i2cDev));

    // Add the imageOffset to the sections already checked:
    for (SectionVec::iterator it = imageData._sections.begin(); it != imageData._sections.end(); ++it) {
        Section* s = *it;
        s->addr += imageOffset;
    }

    u_int8_t stopAtType = onlyDataSections ? TYPE_JMP : TYPE_LST;
    
    while(sect->type != stopAtType && currAddress < MAX_IMAGE_SIZE) {
        u_int32_t absAddress = imageOffset + currAddress;
        EeLoc     loc        = imageData._conv.Convert(absAddress);

        sect                 = new Section(loc.dev , loc.offset);
        sect->addr           = absAddress;

        if (!CheckSection(img, absAddress, sect)) {
            if (report) ReportSection(sect, err());

            return false;
        }

        if (report) ReportSection(sect);

        currAddress += sect->data.size();
        imageData._sections.push_back(sect);
    }

    return true;
}


bool Operations::ReportSectionHeader() {
    printf("  %-3s %-6s %-6s %-10s %s\n",
           "DEV",
           "OFFSET",
           "SIZE",
           "TYPE",
           "STATUS");

    return true;
}

bool Operations::ReportSection(Section* sect, const char* status) {
    char unknownNameBuff[64];
    const char* sectName;

    if (status == NULL) {
        status = "OK";
    }

    map<SectionType, const char*>::iterator it = _sectionTypeNames.find((SectionType)sect->type);

    if (it != _sectionTypeNames.end()) {
        sectName = it->second;
    } else {
        sprintf(unknownNameBuff, "UNKNOWN(%d)" , sect->type);
        sectName = unknownNameBuff;
    }

    printf("  %02x  %06x %06x %-10s %s\n", 
           sect->location.dev,
           sect->location.offset,
           (u_int32_t)sect->data.size(),
           sectName,
           status);

    return true;

}



bool Operations::GetSpecialData    (const SectionVec& sectVec, SpecialFieldsMap& specialData) {
    Section* psidSect    = NULL;
    Section* specialSect = NULL;
    Section* fwverSect   = NULL;
    
    // Find special section and PSID section.
    for (SectionVec::const_iterator it = sectVec.begin(); 
          it != sectVec.end() && (psidSect == NULL || specialSect == NULL || fwverSect == NULL) ; ++it) {
        Section* s = *it;

        if (s->type == TYPE_SPC) {
            specialSect = s;
        } else if (s->type == TYPE_NBI && s->data.size() == 32) {
            // Check if this section initiates the PSID according to its CR address.
            u_int32_t initAddr = ImgBytes2Dw(&s->data[8]);
            if (initAddr == PSID_CR_ADDR) {
                psidSect = s;
            }
        } else if (s->type == TYPE_RMW) {
            fwverSect = s;
        }
    }

    // Parse the sections
    if (!psidSect) {
        return errmsg("Failed to find PSID section in the given image");
    } else {
        specialData[PSID]; // Add the entry to the map.
        ReplaceFieldData& psidField = specialData[PSID];

        psidField.type = PSID; 
        psidField.addr = psidSect->addr + 12;
        psidField.size = 16;
        psidField.data.insert(psidField.data.begin(),
                              psidSect->data.begin() + 12,
                              psidSect->data.begin() + 12 + 16);


    }

    if (fwverSect) {
        // Find FWVER field
        u_int32_t addr; 
        u_int32_t i;
        for (i = 8; i < fwverSect->data.size(); i += 12) {
            addr = ImgBytes2Dw(&fwverSect->data[i]);
            if (addr == FWVER_CR_ADDR) {
                specialData[FWVER]; // Add the entry to the map.

                ReplaceFieldData& fwverField = specialData[FWVER];
                
                fwverField.type = FWVER; 
                fwverField.addr = 0xffffffff; // This field is not to be replaced - address is irrelevant.
                fwverField.size = 4;
                fwverField.data.insert(fwverField.data.begin(),
                                       fwverSect->data.begin() + i + 4,
                                       fwverSect->data.begin() + i + 8);

                break;
            }
        }
    }

    if (!specialSect) {
        return errmsg("Failed to find Special section in the given image");
    }

    return ExtractSpecial(specialSect, specialData);

}


bool Operations::ExtractSpecial(Section* s, SpecialFieldsMap& specialData) {

    // Extract the data out of the dpecial section:

    u_int32_t fieldOffset = 8;
    mdebug(("Parsing special sect at %x %x", s->location.dev, s->location.offset));

    while (fieldOffset < s->data.size() - 4) {
        u_int16_t type = (u_int16_t)ImgBytes2Dw(&s->data[fieldOffset]);
        u_int32_t fieldSize;

        std::map<FieldId,u_int32_t>::iterator it = _fieldIdSizes.find((FieldId)type);

        mdebug(("Found field id %d at offset %d from section start", type, fieldOffset ));

        if (it == _fieldIdSizes.end()) {
            return warnmsg("Failed to parse Special section: Unknown special field id: %x", type);
        } else {
            fieldSize = it->second;
        }

        if (fieldOffset + fieldSize + 8 > s->data.size()) {
            return warnmsg("Failed to parse Special section: Field \"%s\" of size %d bytes from offset 0x%x "
                          "exceeds section size 0x%x", 
                          _fieldIdNames[(FieldId)type], 
                          fieldSize, 
                          fieldOffset,
                          (u_int32_t)s->data.size());
        }

        // Old IS3BURN and MIC put the sys image guid twice in the spectial section. Ignore the second one.
        if (specialData.find((FieldId)type) == specialData.end()) {

            specialData[(FieldId)type]; // Add the entry to the map.
            ReplaceFieldData& f = specialData[(FieldId)type];
            f.data.clear();
            
            f.type = (FieldId)type; 
            f.addr = s->addr + fieldOffset + 4;
            f.size = fieldSize;
            f.data.insert(f.data.begin(),
                          s->data.begin() + fieldOffset + 4,
                          s->data.begin() + fieldOffset + 4 + fieldSize);
            
            // printf("-D- Extracted special %d %-20s %06x %d\n", type, _fieldIdNames[f.type], f.addr, f.size);
        } else {
            return warnmsg("Ignoring duplicated field id %d at offset %d from section start", type, fieldOffset);
        }

        fieldOffset = fieldOffset + fieldSize + 4;
    }
    return true;
}

bool Operations::AskUser  (const char* msg, bool def)  {
    printf(msg);
    if  (_useDefaultAnswer) {
        printf("%s\n", def ? "y" : "n");
        return def;
    } else {
        char ansbuff[32];
        ansbuff[0] = '\0';

        if (!isatty(0)) {
            return errmsg("Not on tty - Can't interact. assuming \"no\" for question \"%s\"", msg);
        }
        fflush(stdout);
        fgets(ansbuff, 30, stdin);

        if (  strcmp(ansbuff, "y\n") &&
              strcmp(ansbuff, "Y\n") &&
              strcmp(ansbuff, "yes\n") &&
              strcmp(ansbuff, "Yes\n") &&
              strcmp(ansbuff, "YES\n"))
            return errmsg("Aborted by user");
    }
    return true;
}

bool Operations::GetFieldFromString(Operations::SpecialFieldsMap& fields, Operations::FieldId fid, const char* str) {
    
    u_int64_t guid;
    char* eptr;

    u_int32_t len = strlen(str);

    switch (fid) {
    case SYS_GUID_ID:
    case NODE_GUID_ID:
        guid = strtoull(str, &eptr, 16);
        if (*eptr != '\0' || (guid == 0xffffffffffffffffULL && errno == ERANGE)) {
            return errmsg("Failed to convert string \"%s\" to %s: %s",
                          str,
                          _fieldIdNames[fid],
                          errno ? strerror(errno) : "Bad format" );
        } else {
            ReplaceFieldData& f = fields[fid]; // Creates the field in the fields map.
            f.type = fid;
            f.data.clear();
            f.data.resize(8,0);
            
            ::PatchImgBytes(&f.data[0], guid >> 32);
            ::PatchImgBytes(&f.data[4], guid & 0xffffffff);
        }
        break;

    case BSN_ID:
    case NODE_DESC_ID:

        if (len > 64) {
            return errmsg("Field %s size must be less than 64 characters. Given string is %d characters long.",
                          _fieldIdNames[fid],
                          len);
        } else {
            ReplaceFieldData& f = fields[fid]; // Creates the field in the fields map.
            f.type = fid;
            f.data.clear();
            f.data.resize(64,0);
            
            for (u_int32_t i = 0; i < len; i++) {
                f.data[i] = str[i];
            }
        }

        break;

    default:
        return errmsg("Failed to convert the given string \"%s\" to field id %d", str, fid);
    }

    return true;
}


bool Operations::GetFieldStringRepresentation(const DataVec& data, FieldId t, string& str) {

    char buff[65];
    u_int32_t guid_high;
    u_int32_t guid_low;
    u_int32_t tmp;
    u_int32_t i;

    //printf("-D- To string: type:%d, size:%d\n", t, (int)data.size());

    switch (t) {
    case SYS_GUID_ID:
    case NODE_GUID_ID:
        guid_high = ImgBytes2Dw(&data[0]);
        guid_low  = ImgBytes2Dw(&data[4]);

        sprintf(buff, "0x%08x%08x", guid_high, guid_low);
        break;

    case BSN_ID:
    case NODE_DESC_ID:
    case PSID:

        for (i = 0; i < data.size(); i++) {
            buff[i] = data[i];
        }

        buff[i] = '\0';
        break;

    case FWVER:
        tmp = ImgBytes2Dw(&data[0]);
        sprintf(buff, "%d.%d.%d",
                EXTRACT32(tmp, 16, 8),
                EXTRACT32(tmp,  8, 8),
                EXTRACT32(tmp,  0, 8));
        break;
    case BRD_ID:
        sprintf(buff, "FIELD NOT PRINTABLE"); // TODO - Print break and print the 4 dwords of theis field
        break;
    case VS_CLS_EN_ID:
        tmp = ImgBytes2Dw(&data[0]);
        sprintf(buff, "0x%08x", tmp);
        break;


    default:
        return errmsg("No string representation fot the given type ID (%d)", t);
    }

    str = buff;
    return true;
}


bool Operations::PatchNewImage     (Operations::FwImageData& newImageData, 
                                    const Operations::SpecialFieldsMap& curFields, 
                                    const Operations::SpecialFieldsMap& userFields,
                                    bool  ignoreCurrent) {

    // Patch fields from eeproms to image.
    FieldId keepFields[] = {SYS_GUID_ID, NODE_DESC_ID, NODE_GUID_ID, BSN_ID };

    for (u_int32_t i = 0 ; i < sizeof(keepFields)/sizeof(keepFields[0]); i++) {
        // Check if the field is forced by user
        FieldId fid = keepFields[i];

        const DataVec* fieldData;

        SpecialFieldsMap::const_iterator userFieldIt = userFields.find(fid);
        SpecialFieldsMap::const_iterator curFieldIt  = curFields.find(fid);

        if (ignoreCurrent) {
            // Take what ever field we can from current image
            // If not in image (when burning a blank eeprom), take from user defined values.
            // If not in user defined values, leave as is in new image.

            if        (userFieldIt != userFields.end()) {
                fieldData = &userFieldIt->second.data;
            } else if (curFieldIt != curFields.end()){
                fieldData = &curFieldIt->second.data;
            } else {
                // Keep the current value of the field in the image:
                continue;
            }
        } else {

            //
            // Consider current fields - If a field is not found in the current image, error.
            // Prompt user for user defined values.
            //

            if (curFieldIt == curFields.end()) {
                return errmsg("Can not read field \"%s\" from the eeprom", _fieldIdNames[fid]);
            }
            
            const ReplaceFieldData& curField  = curFieldIt->second; 
            
            if (userFieldIt != userFields.end()) {
                string curVal;
                string userVal;
                
                const ReplaceFieldData& userField = userFieldIt->second;
                
                GetFieldStringRepresentation(curField.data,  fid, curVal);
                GetFieldStringRepresentation(userField.data, fid, userVal);
                
                
                printf("\n    You are about to replace %s in eeprom:\n"
                   " Current value: %s\n"
                       " New value:     %s\n",
                       _fieldIdNames[fid],
                       curVal.c_str(),
                       userVal.c_str()
                       );
                
                if (!AskUser("\n    Is it OK ? (y/n) [n] : "))
                    return errmsg("Aborted by user");
                
                fieldData = &userField.data;
                
            } else {
                fieldData = &curField.data;
            }
        }

        // Apply the field to the new image:
        if (!ReplaceField(newImageData, fid, *fieldData)) {
            return false;
        }
    }

    // Fix CRC after applying current/user supplied fields.
    if (!FixSpecialCrc(newImageData)) {
        return false;
    }

    return true;
}

bool Operations::ReplaceField(Operations::FwImageData& image, Operations::FieldId fid, const DataVec& fieldData) {

    ReplaceFieldData& f = image._specialFields[fid];

    Section* targetSection = NULL;

    // Looking for the section to patch with new data.
    // Assuming that the new data occupies a single section, 
    // and that sections are ordered by aecending addr
    for (SectionVec::iterator it = image._sections.begin(); it != image._sections.end(); ++it) {
        Section* s = *it;

        if (s->addr <= f.addr && (s->addr + s->data.size()) > f.addr ) {
            targetSection = s;
            break;
        }

    }

    u_int32_t fieldSectOffset = f.addr - targetSection->addr;
    if (targetSection->data.size() < fieldSectOffset + fieldData.size()) {
        return errmsg("Bad image structure: Replaced field %s at address %x of size %d "
                      "does not fit in the section starting at address %x , size %x",
                      _fieldIdNames[fid],
                      f.addr,
                      (u_int32_t)fieldData.size(),
                      targetSection->addr,
                      (u_int32_t)targetSection->data.size());
    }

    // Patch the section data:
    for (u_int32_t i = 0; i < fieldData.size() ; i++) {
        targetSection->data[fieldSectOffset + i] = fieldData[i];
    }

    return true;
}

bool Operations::FixSpecialCrc(Operations::FwImageData& image) {
    Section* specialSect = NULL;

    // Find special section and PSID section.
    for (SectionVec::const_iterator it = image._sections.begin(); 
          it != image._sections.end() ; ++it) {
        Section* s = *it;

        if (s->type == TYPE_SPC) {
            specialSect = s;
            break;
        }
    }

    if (specialSect == NULL) {
        return errmsg("Special section not found");
    }

    return RecalcCrc(specialSect);
}


bool Operations::RecalcCrc(Section* s) {
    u_int32_t crc = CalcCrc(s->data, s->data.size() - 4);

    ::PatchImgBytes(&s->data[s->data.size()-4], crc);
    return true;
}

bool Operations::CheckAndReportFwVersion(Eeprom&      eeprom,
                                         FwImageData& curImageData,
                                         FwImageData& newImageData) {
    // Print FW version and verify IS3-A1 compatibility - 
    // FW >= 1.0.0 can not be burnt on IS3-A0

    u_int32_t newFwVer      = 0;
    printf("\n");
    printf("   Current FW Version:  ");
    if (curImageData._specialFields.find(FWVER) != curImageData._specialFields.end()) {
        string fwver;
        if (!GetFieldStringRepresentation(curImageData._specialFields[FWVER].data, FWVER, fwver) ) {
            return errmsg("Failed getting FW Version of the image on the eeprom.");
        }
        printf(fwver.c_str());
    } else {
        printf("N/A");
    }
    printf("\n");
    
    printf("   New FW Version:      ");
    if (newImageData._specialFields.find(FWVER) != newImageData._specialFields.end()) {
        string fwver;
        if (!GetFieldStringRepresentation(newImageData._specialFields[FWVER].data, FWVER, fwver) ) {
            return errmsg("Failed getting FW Version of the image on eeprom.");
        }
        printf(fwver.c_str());

        // Check IS3 Version:
        newFwVer = ImgBytes2Dw(&(newImageData._specialFields[FWVER].data[0]));
    } else {
        printf("N/A");
    }
    printf("\n\n");

    if (newFwVer > 0x000806) {
        if (eeprom.GetIs3IdWord() == 0) {
            // Eeproms are given directly - No access to the IS3 itself.   
            printf(
                   "-W- Can not auto detect the InfiniScale III device revision.\n"
                   "    Burning this firmware version (or any newer than 0.8.6) to a\n"
                   "    Step A0 device will prevent the device from booting.\n"
                   "    Make sure that the device to be burnt is NOT a Step A0 device.\n");

            return AskUser("Do you want to continue ?");
        } else {
            u_int32_t is3Rev = EXTRACT32(eeprom.GetIs3IdWord(), 16, 8);
            if (is3Rev == 0xA0) {
                printf("-E- Can not burn firmware version > 0.8.6 to a A0 Step Infiniscale III device.\n"
                       "    Burning this firmware version (or any newer than 0.8.6) to a\n"
                       "    Step A0 device will prevent the device from booting.\n");

                return errmsg("Incompatible FW/HW version detected");
            }
        }
    }

    return true;
}

bool Operations::IS3FwBurn           (Eeprom&                  eeprom, 
                                      ImageFile&               imageFile, 
                                      const SpecialFieldsMap&  userFields, 
                                      bool                     fsBurn) {
    // Failsafe burn flow:
    // 1. Read and check imagefile (Primary Image). Exit if bad.
    // 2. Read and verify full image on eeproms.
    //    Remember image state (which is valid). Exit if both are bad.
    // 3. ? Compare AddressConvertors and Make sure they're the same.
    // 4. Read PSID and Special section data from eeprom.
    // 5. Get PSID and Special section data + adresses from image file. Duplicate them for both images).
    // 6. Patch the image with the data from eeprom (or from user).
    // 7. Failsafe burn patched image according to the eeprom image state

    u_int8_t primaryI2cDev = imageFile.GetPrimaryEeprom();

    FwImageData newImageData;
    if (!CheckImage(imageFile, primaryI2cDev, newImageData, false)) {
        return errmsg("Bad image in file %s: %s", imageFile.FileName().c_str(), err());
    }

    // Get special fields in image file :
    if (!GetSpecialData(newImageData._sections, newImageData._specialFields)) {
        return errmsg("Failed get special fields from the given image file: %s", err());
    }

    bool primaryOk   = false;
    bool secondaryOk = false;

    FwImageData curImageData;

    PercentProgressMeter prog("Checking primary image    " , MAX_IMAGE_SIZE);
    eeprom.SetProgressMeter(&prog);

    primaryOk = CheckImage(eeprom, eeprom.GetPrimaryEeprom(), curImageData, false, !fsBurn);

    if (primaryOk) {
        prog.Done();
    } else {
        prog.Done(err());
    }

    if (!primaryOk) {
        curImageData.Clear();
        PercentProgressMeter progSec("Checking secondary image  " , MAX_IMAGE_SIZE);
        eeprom.SetProgressMeter(&progSec);
        secondaryOk = CheckImage(eeprom, eeprom.GetSecondaryEeprom(), curImageData, false, !fsBurn);
        if (secondaryOk) {
            progSec.Done();
        } else {
            progSec.Done(err());
        }
    } 

    if (!primaryOk && !secondaryOk) {
        if (fsBurn) {
            return errmsg("Both images on eeproms %02x and %02x are not valid. Can not perform failsafe burn.",
                          eeprom.GetPrimaryEeprom(),
                          eeprom.GetSecondaryEeprom());
        } else {
            eeprom.SetAddressConvertor(&newImageData._conv);
        }
    }

    if (!GetSpecialData(curImageData._sections, curImageData._specialFields) && fsBurn) {
        return errmsg("Failed get special fields from the eeprom image: %s", err());
    }

    //PrintSpecialData(newImageData._specialFields, "Image file");
    //PrintSpecialData(curImageData._specialFields, "Eeprom");

    if (!CheckAndReportFwVersion(eeprom, curImageData, newImageData)) {
        return false;
    }

    // Check PSIDs
    string curPsid;
    string newPsid;
    if (!GetFieldStringRepresentation(curImageData._specialFields[PSID].data, PSID, curPsid) ) {
        return errmsg("Failed getting PSID of the image on eeprom.");
    }

    if (!GetFieldStringRepresentation(newImageData._specialFields[PSID].data, PSID, newPsid) ) {
        return errmsg("Failed getting PSID of the given image file.");
    }

    if (curPsid != newPsid) {        
        printf("\n    You are about to replace current PSID in eeprom - \"%s\" with a different PSID - \"%s\".\n", 
               curPsid.c_str(), 
               newPsid.c_str());

        if (!AskUser("\n    Is it OK ? (y/n) [n] : "))
            return errmsg("Aborted by user");
    }

    if (!PatchNewImage(newImageData, curImageData._specialFields , userFields, !fsBurn)) {
        return false;
    }

    //
    // Burn:
    //

    u_int32_t primaryOffset   = 0;
    u_int32_t secondaryOffset = newImageData._conv.Convert(EeLoc(0, eeprom.GetSecondaryEeprom()));

    if (secondaryOffset == 0xffffffff) {
        return errmsg("Can't map secondary eeprom i2c address (%02x).", eeprom.GetSecondaryEeprom());
    }

    //printf("\n-I- Burning image with the following values:\n");
    //PrettyPrintSpecialData(newImageData._specialFields);
    //printf("\n\n");

    if (fsBurn) {
        if (secondaryOk) {
            // Burn primary image first
            if (!FsBurnImage(eeprom, newImageData, primaryOffset  )) return false;
            if (!FsBurnImage(eeprom, newImageData, secondaryOffset)) return false;
        } else {
            // burn primary image first.
            if (!FsBurnImage(eeprom, newImageData, secondaryOffset)) return false;
            if (!FsBurnImage(eeprom, newImageData, primaryOffset  )) return false;
        }
    } else {
        if (!FsBurnImage(eeprom, newImageData, primaryOffset  )) return false;
    }
    return true;
}




bool Operations::FsBurnImage(Eeprom& eeprom, Operations::FwImageData& image, u_int32_t imgOffset) {
    
    // Invalidate the PLL boot record checksum.

    SectionVec& sectVec = image._sections;

    DataVec origData(4);
    DataVec& pllSectData = sectVec[0]->data;
    for (u_int32_t i = 0 ; i < 4 ;i++) {
        origData[i] = pllSectData[i];
    }

    pllSectData[3] = pllSectData[3] + 0xA;

    const char *burnMsg;
    const char *verifyMsg;
    if (imgOffset == 0) {
        burnMsg   = "Burning primary image     ";
        verifyMsg = "Verifying primary image   ";
    } else {
        burnMsg   = "Burning secondary image   ";
        verifyMsg = "Verifying secondary image ";
    }

    if (!BurnSections   (eeprom, sectVec, true, imgOffset, burnMsg)) {
        return false;
    }

    if (!CompareSections(eeprom, sectVec, true, imgOffset, verifyMsg)) {
        return false;
    }

    // Restore PLL boot record section with correct Checksum.
    // TODO: Verify:
    if (!eeprom.write(imgOffset , origData)) {
        return errmsg("EEprom write failed: %s", eeprom.err());
    }

    // Restore the image in memory for future use
    for (u_int32_t i = 0 ; i < 4 ;i++) {
         pllSectData[i] = origData[i];
    }

    return true;
}


bool Operations::BurnSections(Eeprom& eeprom, const SectionVec& sectVec, bool useAddress, u_int32_t imgOffset, const char* msg ) {

    u_int32_t totSize = 0;
    SectionVec::const_iterator it;

    for (it = sectVec.begin() ; it != sectVec.end() ; ++it) {
        Section* s = *it;
        totSize += s->data.size();
    }

    PercentProgressMeter prog(msg, totSize);
    eeprom.SetProgressMeter(&prog);

    for (it = sectVec.begin() ; it != sectVec.end(); ++it) {
        Section* s = *it;
        if (useAddress) {
            eeprom.write(s->addr + imgOffset, s->data);
        } else {
            eeprom.write(s->location.dev, s->location.offset, s->data);
        }
    }
    
    eeprom.SetProgressMeter(NULL);
    return true;
}

bool Operations::CompareSections(Eeprom& eeprom, const SectionVec& sectVec, bool useAddress, u_int32_t imgOffset, const char* msg ) {

    u_int32_t totSize = 0;
    SectionVec::const_iterator it;

    for (it = sectVec.begin() ; it != sectVec.end() ; ++it) {
        Section* s = *it;
        totSize += s->data.size();
    }

    PercentProgressMeter prog(msg, totSize);
    eeprom.SetProgressMeter(&prog);

    for (SectionVec::const_iterator it = sectVec.begin() ;
          it != sectVec.end();
          ++it) {

        Section* s = *it;
        DataVec readData;
        bool ret;
        if (useAddress) {
            ret = ((IoBase*)(&eeprom))->read(s->addr + imgOffset, s->data.size(), readData);
        } else {
            ret = eeprom.read(s->location.dev, s->location.offset, s->data.size(), readData);
        }
        if (!ret) {
            return errmsg(eeprom.err());
        }

        // Compare:
        for (u_int32_t i = 0; i < readData.size(); i++) {
            if (readData[i] != s->data[i]) {
                if (useAddress) {
                    //for (u_int32_t j = 0; j < 100 ; j++ ) {
                    //    printf("%06x  %02x  %02x\n",  
                    // }
                    return errmsg("Address %06x: Expected %02x, got %02x",
                                  s->addr + imgOffset + i,
                                  s->data[i],
                                  readData[i]);
                } else {
                    return errmsg("Eeprom %02x, Offset %x: Expected %02x, got %02x",
                                  s->location.dev,
                                  s->location.offset + i,
                                  s->data[i],
                                  readData[i]);
                }
            }
        }
    }
    
    eeprom.SetProgressMeter(NULL);
    return true;
}



bool Operations::RawBurn(Eeprom& eeprom, ImageFile& imageFile) {
    const SectionVec& sectVec = imageFile.GetSections();

    return BurnSections(eeprom, sectVec, false, 0, "Burning Image");
}

bool Operations::CompareContent(Eeprom& eeprom, ImageFile& imageFile) {
    const SectionVec& sectVec = imageFile.GetSections();

    return CompareSections(eeprom, sectVec, false, 0,     "Verifying    ");
}




////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////


#ifndef USING_IBVSMAD
#ifndef SPARK_NAME
#define SPARK_NAME        "spark"
#endif
#define DEV_MST_EXAMPLE1  "/dev/mst/mtusb-1"
#define DEV_MST_EXAMPLE2  "/dev/mst/mtusb-1"
#else
#ifndef SPARK_NAME
#define SPARK_NAME        "ibspark"
#endif
#define DEV_MST_EXAMPLE1  "0x0003"
#define DEV_MST_EXAMPLE2  "0x0248"
#endif

void usage(const char *sname, bool full = false)
{
    const char *descr =
    "\n"
    "               " SPARK_NAME " - Eeprom FW burning tool for Mellanox IS3 switches.\n"
    "\n"
    "\n"
    "Usage:\n"
    "------\n"
    "\n"
    "    " SPARK_NAME " [switches...] <command> [parameters...]\n"
    "\n"
    "\n"
    "Switches summary:\n"
    "-----------------\n"
    "\n"
//    "    -crc               - Print CRC after each section when verify.\n"
//    "\n"

#ifndef USING_IBVSMAD
    "    -d[evice] <device> - The device the eeprom is connected to.\n"
#else

    "    -d[evice] <LID>    - The LID of the target IS3 in the IB subnet.\n"
    "                         A list of the IS3 devices and their LIDs in the subnet can be found in the\n"
    "                         opensm list file (located by default in /tmp/subnet.lst). You can also run \"ibdiagnet\"\n"
    "                         (under OFED regular installation) and see /tmp/ibdiagnet.lst file.\n"                         
#endif
    "                         Commands affected: all\n"
    "\n"
    "    -i[mage] <image>   - Image file in \".img\" format.\n"
    "                         Commands affected: burn, verify\n"
    "\n"
    "    -guid <GUID>       - Use the given guid as the node guid of the burnt image.\n"
    "                         By default, the guid is taken from the image on the eeprom\n"
    "\n"
    "                         Commands affected: burn\n"
    "\n"
    "    -sysguid  <GUID>   - Use the given guid as the system image guid of the burnt image.\n"
    "                         By default, this value is taken from the current image on the eeprom\n"
    "\n"
    "                         Commands affected: burn\n"
    "\n"
    "    -bsn <BSN>         - Mellanox Board Serial Number (BSN).\n"
    "                         Valid BSN format is:\n"
    "                                 MTxxxxx[-]R[xx]ddmmyy-nnn[-cc]\n"
    "                         By default, this value is taken from the current image on the eeprom\n"
    "\n"
    "                         Commands affected: burn\n"
    "\n"
    "    -ndesc <Descr>     - Use the given string (max 64 characters) as the node description.\n"
    "                         By default, this value is taken from the current image on the eeprom\n"
    "\n"
    "                         Commands affected: burn\n"
    "\n"
    "    -is3_i2c <i2c_addr> - Provides the I2C address of the switch device. If this flag is not specified,\n"
    "                          then the the default address for Mellanox switch devices is: 0x6c.\n"
    "\n"
    "    -pe_i2c <i2c_addr> - Provides the I2C address of the primary EEPROM. By default, this address is read\n"
    "                         from the Mellanox switch device. Use this flag only if the switch device is not\n"
    "                         accessible.\n"
    "\n"
    "    -se_i2c <i2c_addr> - Provides the I2C address of the secondary EEPROM. By default, this address is read \n"
    "                         from the Mellanox switch device. Use this flag only if the switch device is not\n"
    "                         accessible.\n"
    "\n"

    "    -h[elp]            - Prints this message and exits\n"
    "    -hh                - Prints extended command help\n"
    "\n"
    "    -nofs              - Burn image not in a failsafe manner.\n"
    "\n"
    "    -is1               - Burn an InfiniScale device (default is InfiniScaleIII).\n"
    "\n"
//    "    -s[ilent]          - Do not print burn progress flyer.\n"
//    "                         Commands affected: burn\n"
//    "\n"
    "    -y[es]             - Non interactive mode - assume answer\n"
    "                         \"yes\" to all questions.\n"
    "                         Commands affected: all\n"
    "\n"
    "    -v                 - Version info.\n"
    "\n"
    "Commands summary (use -hh flag for full commands description):\n"
    "-----------------\n"
    "    b[urn]   - Burn flash\n"
    "    q[uery]  - Query misc. eeprom/FW characteristics\n"
    "    v[erify] - Verify entire eeprom\n"
    "    bb       - Burn Block - Burns the given image as is. No checks are done.\n"
    "    ri       - Read the fw image on the flash.\n"
//    "    dc       - Dump Configuration: print fw configuration file for the given image.\n"
    "\n";



    const char* full_descr =
    "\n"
    "Command descriptions:\n"
    "----------------------------\n"
    "\n"
    "* Burn eeprom\n"
    "  Burns entire flash from raw binary image.\n"
    "\n"
    "    Command:\n"
    "        b[urn]\n"
    "    Parameters:\n"
    "        None\n"
    "    Examples:\n"
    "        " SPARK_NAME " -d " DEV_MST_EXAMPLE1 " -i image1.img burn\n"
    "        " SPARK_NAME " -d " DEV_MST_EXAMPLE2 " -guid 0x2c9000100d050 -i image1.img b\n"
    "\n"
    "\n"
    "* Burn Block\n"
    "  Burns the entire eeprom from raw image as is. No checks are done on the eeprom or\n"
    "  on the given image file. No fields (such as BSN or Guids) are read from eeprom. \n"
    "  This command can bu used to burn Infiniscale devices.\n"
    "\n"
    "    Command:\n"
    "        bb\n"
    "    Parameters:\n"
    "        None\n"
    "    Examples:\n"
    "        " SPARK_NAME " -d " DEV_MST_EXAMPLE1 " -i image1.img bb\n"
    "\n"
    "\n"
    "* Query miscellaneous FW and eeprom parameters\n"
    "\n"
    "    Command:\n"
    "        q[uery]\n"
    "    Parameters:\n"
    "        None\n"
    "    Example:\n"
    "        " SPARK_NAME " -d " DEV_MST_EXAMPLE1 " query\n"
    "\n"
    "* Verify entire flash.\n"
    "\n"
    "    Command:\n"
    "        v[erify]\n"
    "    Parameters:\n"
    "        None\n"
    "    Example:\n"
    "        " SPARK_NAME " -d " DEV_MST_EXAMPLE1 " v\n"
    "\n"
    "* Read the FW image from eeprom and write it to a file.\n"
    "\n"
    "    Command:\n"
    "        ri\n"
    "    Parameters:\n"
    "        file - filename to write the image to (in .img format).\n"
    "    Example:\n"
    "        " SPARK_NAME " -d " DEV_MST_EXAMPLE1 " ri file.img\n"
    "\n"
    "\n";

    printf(descr, sname);

    if (full) {
        printf(full_descr, sname);
    }
}


//
// Commands database and parsing methods
//
enum CommandInput {
    CI_NONE             = 0x01,
    CI_IMG_ONLY       = 0x02,
    CI_DEV_ONLY       = 0x04,
    CI_IMG_OR_DEV   = 0x06,
    CI_IMG_AND_DEV  = 0x08
};

enum CommandType {
    CMD_UNKNOWN,
    CMD_BURN,
    CMD_BURN_BLOCK,
    CMD_QUERY,
    CMD_VERIFY,
    CMD_READ_BLOCK,
    CMD_READ_IMAGE,
};

struct CommandInfo {
    CommandType  cmd;
    const char*  cmdName;
    bool         requireExactMatch;
    int          maxArgs;
    CommandInput requiredInput;
    const char*  cmdDescription;
 
};

CommandInfo const g_commands[] = {
    { CMD_BURN           , "burn"  ,false , 0 , CI_IMG_AND_DEV , ""},
    { CMD_BURN_BLOCK     , "bb"    ,true  , 0 , CI_IMG_AND_DEV , ""},
    { CMD_QUERY          , "query" ,false , 0 , CI_IMG_OR_DEV  , ""},
    { CMD_VERIFY         , "verify",false , 0 , CI_IMG_OR_DEV  , ""},            
    { CMD_READ_BLOCK     , "rb"    ,true  , 4 , CI_DEV_ONLY  , ""},
    { CMD_READ_IMAGE     , "ri"    ,true  , 1 , CI_DEV_ONLY  , ""},
};

#define numbel(x) (sizeof(x)/sizeof((x)[0]))


const CommandInfo* GetCommandInfo(CommandType cmd) {
    for (u_int32_t i = 0 ; i < numbel(g_commands); i++ ) {
        if (cmd == g_commands[i].cmd) {
            return &g_commands[i];
        }
    }

    return NULL;
}

CommandType ParseCommand(const char* cmd) {
    u_int32_t cmdLenGiven = strlen(cmd);

    for (u_int32_t i = 0 ; i < numbel(g_commands); i++ ) {
        if (g_commands[i].requireExactMatch ) {
            if (!strcmp(cmd, g_commands[i].cmdName)) {
                return g_commands[i].cmd;
            }
        } else {
            // Match if given cmd maches the beginning of the checked cmd 
            if (!strncmp(cmd, g_commands[i].cmdName, cmdLenGiven )) {
                return g_commands[i].cmd;
            }
        }
    }
    return CMD_UNKNOWN;
}


bool CheckCommandInputs(const char* dev, 
                        const char* img, 
                        CommandType cmd) {

    const CommandInfo* cmdInfo = GetCommandInfo(cmd);

    if (!cmdInfo) {
        printf("*** INTERNAL ERROR *** Unknown command given to CheckCommandInputs() (%d)\n", cmd);
        return false;
    }

    char* inputDesStr [] = {
        NULL,
        "nither device nor an image file",    // CI_NONE
        "only an image file",                 // CI_IMG_ONLY,
        NULL,
        "only a device",                      // CI_DEV_ONLY,
        NULL,
        "either an image file or a device",   // CI_IMG_OR_DEV,
        NULL,
        "both an image file and a device"     // CI_IMG_AND_DEV
    };

    CommandInput given;

    if        ( dev && img) {
        given = CI_IMG_AND_DEV;
    } else if (!dev && img) {
        given = CI_IMG_ONLY;
    } else if (dev && !img) {
        given = CI_DEV_ONLY;
    } else {
        given = CI_NONE;
    }

    if ((given & cmdInfo->requiredInput) == 0) {
        printf("-E- Command \"%s\" requires %s, but %s %s given.\n",
               cmdInfo->cmdName,
               inputDesStr[cmdInfo->requiredInput],
               inputDesStr[given],
               given == CI_IMG_AND_DEV ? "are" : "is");
        return false;
    }

    return true;
}

bool CheckMaxCmdArguments(CommandType cmd, int numArgs) {
    const CommandInfo* cmdInfo = GetCommandInfo(cmd);
    if (!cmdInfo) {
        printf("*** INTERNAL ERROR *** Unknown command given to CheckMaxCmdArguments (%d)\n", cmd);
        return false;
    }

    if (cmdInfo->maxArgs >= 0 && numArgs > cmdInfo->maxArgs) {
        printf("-E- Command \"%s\" requires %d arguments, but %d arguments were given\n",
               cmdInfo->cmdName,
               cmdInfo->maxArgs,
               numArgs);
        return false;
    }
    return true;
}




#define SETERR(args) do { printf("-E- "); printf args ; printf("\n"); return 1; } while(0)

////////////////////////////////////////////////////////////////////////
#define NEXT(s) do {                                                  \
    if (i+1 >= ac)                                                    \
    {                                                                 \
        SETERR(("Missing parameter after \"%s\" switch", s));         \
        exit(1);                                                      \
    } else i++;} while(0)

#define NEXTC(c,param) do {                                                  \
    if (++i >= ac)                                                    \
    {                                                                 \
        SETERR(("Command \"%s\": Missing parameter <%s> ", c , param)); \
        exit(1);                                                      \
    }} while(0)

int main(int ac, char *av[])
{
    bool      verbose = true;

    bool      fsBurn  = true;
    bool      useDefaultAnswers = false;

    bool      is3Mode = true;

    char      *image_fname=0, *dev=0;

    const char* userNodeGuid = NULL;
    const char* userSysGuid  = NULL;
    const char* userBsn      = NULL;
    const char* userNodeDesc = NULL;
    
    const char* cmdStr = NULL;

    u_int8_t    is3I2cSlave          = 0x6C;  // Default IS3 I2C address in Mellanox switch systems.
    u_int8_t    userPrimaryI2cAddr   = 0;
    u_int8_t    userSecondaryI2cAddr = 0;

    int i;

    // Command line parameters
    if (ac < 2)
        usage(av[0]);
    for (i = 1; i < ac; i++)
    {
        if (*av[i] == '-')
        {
            int switchLen = strlen(av[i]);

            if (!strncmp(av[i], "-device", switchLen)) {
                NEXT("-device");
                dev = av[i];
            }            
            else if (!strncmp(av[i], "-image", switchLen)) {
                NEXT("-i");
                image_fname = av[i];
            }
            else if (!strcmp(av[i], "-v") || !strcmp(av[i], "-vv")) {
                printf("%s: %s .",
                       av[0],
                       _versionID);

                if (!strcmp(av[i], "-vv")) {
                    printf(" SVN %s", _cvsID + 1); 
                }

                printf("\n");
                return 0;
            }
            else if (!strcmp(av[i], "-s"))
                verbose = false;
            else if (!strncmp(av[i], "-yes", switchLen))
                useDefaultAnswers = true;
            else if (!strcmp(av[i], "-nofs"))
                fsBurn = false;
            else if (!strcmp(av[i], "-is1"))
                is3Mode = false;
            else if (!strcmp(av[i], "-guid")) {
                NEXT("-guid");
                userNodeGuid = av[i];      
            }
            else if (!strcmp(av[i], "-sysguid")) {
                NEXT("-sysguid");
                userSysGuid = av[i];      
            }
            else if (!strcmp(av[i], "-bsn")) {
                NEXT("-bsn");
                userBsn = av[i];      
            }
            else if (!strcmp(av[i], "-ndesc")) {
                NEXT("-ndesc");
                userNodeDesc = av[i];      
            }
            else if (!strcmp(av[i], "-is3_i2c")) {
                NEXT("-is3_i2c");
                char* ep;
                is3I2cSlave = strtoul(av[i], &ep, 16);
                if (*ep) {
                    SETERR(("Bad argument for %s flag (%s). Expecting a hexadecimal integer.", av[i-1], av[i]));
                }
            }
            else if (!strcmp(av[i], "-pe_i2c") || !strcmp(av[i], "-pe")) {
                NEXT("-pe_i2c");
                char* ep;
                userPrimaryI2cAddr = strtoul(av[i], &ep, 16);
                if (*ep) {
                    SETERR(("Bad argument for %s flag (%s). Expecting a hexadecimal integer.", av[i-1], av[i]));
                }
            }
            else if (!strcmp(av[i], "-se_i2c") || !strcmp(av[i], "-se") ) {
                NEXT(av[i]);
                char* ep;
                userSecondaryI2cAddr = strtoul(av[i], &ep, 16);
                if (*ep) {
                    SETERR(("Bad argument for %s flag (%s). Expecting a hexadecimal integer.", av[i-1], av[i]));
                }
            }
            else if (!strncmp(av[i], "-hh", 3)) {
                usage(av[0], true);
                return 1;
            }
            else if (!strncmp(av[i], "-help", switchLen) ||  !strncmp(av[i], "--h", 3)) {
                usage(av[0]);
                return 1;
            
            } 
            else {
                SETERR(("Invalid switch \"%s\" is specified.\n", av[i]));
            }
        } else {
            cmdStr = av[i];
            break;
        }
    }

    if (cmdStr == NULL) {
        SETERR(("No command given. See help for details."));
    }

    IoBase*    img       = NULL;
    
    Eeprom*    eeprom    = NULL;
    ImageFile* imageFile = NULL;
    
    Operations::SpecialFieldsMap userFields;
    
    Operations op;

    if (useDefaultAnswers) {
        op.UseDefaultAnswers();
    }

    //
    // Check command
    //
    CommandType cmd = CMD_UNKNOWN;
    cmd = ParseCommand(av[i]);
    if (cmd == CMD_UNKNOWN) {
        SETERR(("Invalid command \"%s\".\n", av[i]));
    }

    if (!CheckCommandInputs(dev, image_fname, cmd)) {
        return 1;
    }

    if (!CheckMaxCmdArguments(cmd, ac - i - 1 )) {
        return 1;
    }


    //
    // Open device and image file
    //
    if (dev) {
        eeprom = new Eeprom;
        img = eeprom;
        if (!img->open(dev)) {
            SETERR(("Failed to open device %s: %s", dev, img->err()));
        }

        if (!(cmd == CMD_BURN_BLOCK ||
              cmd == CMD_READ_BLOCK || 
             (cmd == CMD_BURN && !is3Mode))) {
            if (userPrimaryI2cAddr) {
                eeprom->InitImageAddressesFromUser(userPrimaryI2cAddr, userSecondaryI2cAddr);
            } else {
                if (!eeprom->InitImageAddressesFromIs3(is3I2cSlave)) {
                    SETERR(("Failed getting eeproms I2C addresses from IS3 device on I2C address %02x : %s", 
                            is3I2cSlave, 
                            eeprom->err()));
                }
            }
        }
    }
    
    if (image_fname) {
        imageFile = new ImageFile;
        img = imageFile;
        if (!img->open(image_fname)) {
            SETERR(("Failed to open image file %s: %s", image_fname, img->err()));
        }
    }
    
    //
    // Parse and perform commands
    //
    switch (cmd) {
    case CMD_VERIFY:
        if (!op.CheckFullImage(*img, img->GetPrimaryEeprom(), img->GetSecondaryEeprom())) {
            SETERR(("Image check failed: %s", op.err()));
        }
        break;

    case CMD_QUERY:
        if (!op.QueryImage(*img, img->GetPrimaryEeprom() , img->GetSecondaryEeprom()  )) {
            SETERR(("Image query failed: %s", op.err()));
        }
        break;



    case CMD_READ_IMAGE:
    {

        NEXTC("ri" , "output image file");
        const char* outFile = av[i];

        if (!eeprom) {
            SETERR(("A device should be given for ri command\n"));
        }

        if (!op.DumpImage(*eeprom, eeprom->GetPrimaryEeprom(), eeprom->GetSecondaryEeprom(), outFile)) {
            SETERR((op.err()));
        }
    }
    break;

    case CMD_READ_BLOCK:
    {
        // rb <dev> <offs> <size> <out-file>
        u_int32_t args[3];
        char*     argNames[3]  = {"i2c slave", "offset", "size"};

        for (int argIdx = 0; argIdx < 3 ; argIdx++ ) {
            char* ep;
            NEXTC("rb" , argNames[argIdx]);
            args[argIdx] = strtoul(av[i], &ep, 16);
            if (*ep) {
                SETERR(("Bad argument #%d (%s) for rb command (%s). Expecting a hexadecimal integer.", 
                       argIdx,
                       argNames[argIdx],
                       av[i]));
            }
        }

        NEXTC("rb" , "output image file");
        const char* outFile = av[i];

        if (!op.ReadBlock (*eeprom, (u_int8_t) args[0], args[1], args[2], outFile)) {
            SETERR((op.err()));
        }
    }
    break;

    case CMD_BURN:

        // Get user 'setable' fields
        if (userNodeGuid) {
            if (!op.GetFieldFromString(userFields, Operations::NODE_GUID_ID, userNodeGuid)) {
                SETERR((op.err()));
            }
        }
        
        if (userBsn) {
            if (!op.GetFieldFromString(userFields, Operations::BSN_ID, userBsn)) {
                SETERR((op.err()));
            }
        }

        if (is3Mode) {
     
            if (userSysGuid) {
                if (!op.GetFieldFromString(userFields, Operations::SYS_GUID_ID, userSysGuid)) {
                    SETERR((op.err()));
                }
            }
            
            if (userNodeDesc) {
                if (!op.GetFieldFromString(userFields, Operations::NODE_DESC_ID , userNodeDesc)) {
                    SETERR((op.err()));
                }
            }
            
            
            if (!op.IS3FwBurn(*eeprom, *imageFile, userFields, fsBurn)) {
                SETERR(("%sBurn failed: %s\n", 
                        fsBurn ? "Failsafe " : "" ,
                        op.err()));
            }

        } else {
            // Infiniscale 1 mode
            #ifdef USING_IBVSMAD
            SETERR(("ibspark does not support \"-is1\" flag"));
            #else
            const char  *GUID_NAME = "EEPROM_CONFIG_GUID";
            Section     *guidSect = imageFile->GetSection(GUID_NAME);

            if (userNodeGuid) {
                if (!op.GetFieldFromString(userFields, Operations::NODE_GUID_ID, userNodeGuid)) {
                    SETERR((op.err()));
                }

                if (guidSect == NULL) {
                    // Create GUID sect

                    u_int32_t conf_offset = 0;
                    u_int8_t  conf_eepr = 0;                
                    const Section *sect = imageFile->GetSection("EEPROM_CONFIG_GPFLAGS");
                    if (!sect)
                    {
                        SETERR(("Section EEPROM_CONFIG_GPFLAGS not found. Make sure this is a valid InfiniScale FW image."));
                    }

                    conf_offset = sect->location.offset - 8;
                    conf_eepr   = sect->location.dev;

                    guidSect = new Section(GUID_NAME, conf_eepr, conf_offset);

                    imageFile->AddSection(guidSect);

                    //printf("-D- Created %s sect, dev %x, offs %x\n", GUID_NAME, guidSect->location.dev, guidSect->location.offset);
                }
                guidSect->data = userFields[Operations::NODE_GUID_ID].data;
            } else {
                if (guidSect) {
                    guidSect->data.clear();
                }
            }

            // Burn (No failesafe option for IS1)
            if (!op.RawBurn(*eeprom, *imageFile)) {
                SETERR(("Image burn failed: %s", op.err()));
            }

            if (!op.CompareContent(*eeprom, *imageFile)) {
                SETERR(("Image check failed: %s", op.err()));
            }
          
            #endif //USING_IBVSMAD
        }

        break;

    case CMD_BURN_BLOCK:
        if (!op.RawBurn(*eeprom, *imageFile)) {
            SETERR(("Image burn failed: %s", op.err()));
        }

        if (!op.CompareContent(*eeprom, *imageFile)) {
            SETERR(("Image check failed: %s", op.err()));
        }
        break;  


        

    default:
        SETERR(("Internal error: Unknown command %d given. See help for details.\n", cmd));
    }
    
    return 0;
}

