/********************************************************
**
*   ibvsmad.c
*
*   the ibvsmad library.
*   this library provides vendor specific mad capabilities.
*  
*   mread64, mwrite64 - IS3 I2C bus access MAD - ExtPortAccess -> I2C compliant port #1
*   mread4 , mwrite4  - IS3 CR-Space access MAD - ConfigSpaceAccess 
*
*
*   author: yoniy@mellanox.co.il , Apr. 16th 2007
*/ 

#if HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>

#define __BUILD_VERSION_TAG__ 1.1
#include <infiniband/common.h>
#include <infiniband/umad.h>
#include <infiniband/mad.h>
#include "ibvsmad.h"

#undef DEBUG
#define	DEBUG	if (verbose) IBWARN
#define IBERROR(fmt, args...)	iberror(__FUNCTION__, fmt, ## args)

// BIT Slicing macros
#define ONES32(size)                    ((size)?(0xffffffff>>(32-(size))):0)
#define MASK32(offset,size)             (ONES32(size)<<(offset))

#define EXTRACT_C(source,offset,size)   ((((unsigned)(source))>>(offset)) & ONES32(size))
#define EXTRACT(src,start,len)          (((len)==32)?(src):EXTRACT_C(src,start,len))

#define MERGE_C(rsrc1,rsrc2,start,len)  ((((rsrc2)<<(start)) & (MASK32((start),(len)))) | ((rsrc1) & (~MASK32((start),(len)))))
#define MERGE(rsrc1,rsrc2,start,len)    (((len)==32)?(rsrc2):MERGE_C(rsrc1,rsrc2,start,len))

#define SWAPL(l) bswap_32(l)


#define IB_VENDOR_SPECIFIC_CLASS_0xA    0x0A
#define IB_VENDOR_SPECIFIC_CLASS_0x9    0x09

#define IB_VS_ATTR_EXT_PORT_INFO    0x13
#define IB_VS_ATTR_CR_ACCESS        0x50

#define IB_VS_ATTRMOD_CR            0x00008000
#define IB_VS_ATTR_EPI_ATTRMOD_I2C1 0x02

#define IB_VS_CR_NUM_OF_RECORDS     1
#define IB_VS_CR_ONE_RECORD_SIZE    4

#define I2C_CHUNK_SIZE  64
#define I2C_DEVICE_ID   0x56
#define I2C_MEMORY_ADDR 0

static int verbose;

extern int ibdebug;

static void
iberror(const char *fn, char *msg, ...)
{
	char buf[512], *s;
	va_list va;
	int n;
    char *argv0 = "ibvsmad";

	va_start(va, msg);
	n = vsprintf(buf, msg, va);
	va_end(va);
	buf[n] = 0;

	if ((s = strrchr(argv0, '/')))
		argv0 = s + 1;

	if (verbose)
		printf("%s: iberror: [pid %d] %s: failed: %s\n", argv0, getpid(), fn, buf);
	else                 
		printf("%s: iberror: failed: %s\n", argv0, buf);

	exit(-1);
}



/********************************************************
**
*    Management class :     0x0A VendorSpecific
*    Management attribute : 0x13 ExtPortAccess
*    Attribute Modifier :   0x02 I2C Compliant Port #2
*
*/
static uint64_t
ibvsmad_i2c_rw(mfile *h, u_int i2c_device_id, u_int32_t memory_address, u_int size, int method, char *data)
{
    u_int8_t vsmad_i2c_data[IB_VENDOR_RANGE2_DATA_SIZE] = {0};
    ib_vendor_call_t call;
    int i;

    /* set MAD parameters to : VendorSpecific.ExtPortInfo.I2cCompliantPort1.Get */
	call.method     = method;
    call.mgmt_class = IB_VENDOR_SPECIFIC_CLASS_0xA;
	call.attrid     = IB_VS_ATTR_EXT_PORT_INFO;
	call.mod        = IB_VS_ATTR_EPI_ATTRMOD_I2C1;
	call.oui        = IB_OPENIB_OUI;
	call.timeout    = 0;
	memset(&call.rmpp, 0, sizeof call.rmpp);

    /* set i2c transaction parameters into the MAD data section */
    vsmad_i2c_data[11] = size;
    vsmad_i2c_data[15] = i2c_device_id;
    for (i=0; i<4; i++)
        vsmad_i2c_data[19-i] = EXTRACT(memory_address,8*i,8);
    
    
    if (method == IB_MAD_METHOD_SET) 
        memcpy(vsmad_i2c_data+20, data, size);

    //ibdebug = 9;
    if (!ib_vendor_call(vsmad_i2c_data, &h->portid, &call))
		return ~0llu;

    //exit(0);

    if (data)
        memcpy(data, vsmad_i2c_data+20, size);
    else 
        return ~0llu;

    return 0;
}





/********************************************************
**
*    Management class :     0x09 VendorSpecific
*    Management attribute : 0x50 ConfigSpaceAccess
*
*/
static uint64_t
ibvsmad_craccess_rw(mfile *h, u_int32_t memory_address, int method, u_int8_t num_of_dwords, u_int32_t *data)
{
    u_int8_t vsmad_data[IB_VENDOR_RANGE2_DATA_SIZE] = {0};
    ib_vendor_call_t call;
    int i;

	call.method     = method;
    call.mgmt_class = IB_VENDOR_SPECIFIC_CLASS_0x9;
	call.attrid     = IB_VS_ATTR_CR_ACCESS;
    
    if (num_of_dwords > 0x12) {
        IBERROR("size is too big, maximum num of dwords is 0x12"); // TBD try max size
        return ~0llu;

    }
    call.mod        = ( (EXTRACT(memory_address,0,16)<<00)&0x0000ffff ) | 
                      ( (num_of_dwords<<16)&0x00ff0000 ) |
                      ( (EXTRACT(memory_address,16,8)<<24)&0xff000000 );

    DEBUG("attrmod = %08x\n", call.mod);

	call.oui        = IB_OPENIB_OUI;
	call.timeout    = 0;
	memset(&call.rmpp, 0, sizeof call.rmpp);

    DEBUG("memory_address = 0x%08x\n", memory_address);

    /* set MAD data section */

    // TBD: currently work for 1 record only, fix for more than one.
    // building one record:

    for (i=0; i<8; i++)  
        vsmad_data[i] = 0;    // V_KEY = 0 (8 bytes)

    for (i=0; i<4; i++)
        vsmad_data[11-i] = 0; // I/O dword


    if (method == IB_MAD_METHOD_SET) 
        for (i=0; i<4; i++)
        {
            vsmad_data[11-i] = EXTRACT(*data,8*i,8); // data to write
            vsmad_data[15-i] = 0xff; // mask
        }

    //ibdebug = 9;
	if (!ib_vendor_call(vsmad_data, &h->portid, &call))
		return ~0llu;

    if (data)
    {
        *data = 0;
        for (i=0; i<4; i++)
            *data |= vsmad_data[11-i] << (i*8);
    }
    else 
        return ~0llu;

    return 0;
}







/********************************************************
**
*    Initialize madrpc; Register the vendor specific management classes under 
*    the libibmad library; Resolve destination lid from input.
*
*/
mfile*
mopend(const char *lid, int for_mst_interface)
{
    char *ca = 0;
    int mgmt_classes[3] = {IB_SMI_CLASS, IB_SMI_DIRECT_CLASS, IB_SA_CLASS};
    ib_portid_t *sm_id = 0;
    int ca_port = 0;
    int dest_type = IB_DEST_LID;
    mfile *h;

    if (!(h=(mfile*)malloc(sizeof(mfile))))
    {
        IBERROR("can't allocate ibvsmad_mfile");
        return 0;
    }

	madrpc_init(ca, ca_port, mgmt_classes, 3);

    if (mad_register_client(IB_VENDOR_SPECIFIC_CLASS_0xA, 0) < 0)
    {
        IBERROR("can't register vsmad class %d on this port", IB_VENDOR_SPECIFIC_CLASS_0xA);
        return 0;
    }

    if (mad_register_client(IB_VENDOR_SPECIFIC_CLASS_0x9, 0) < 0)
    {
        IBERROR("can't register vsmad class %d on this port", IB_VENDOR_SPECIFIC_CLASS_0x9);
        return 0;
    }

    if (ib_resolve_portid_str(&h->portid, (char*)lid, dest_type, sm_id) < 0)
    {
		IBERROR("can't resolve destination port %s", lid);
        return 0;
    }

    
    h->sock = -1;                  /* for spark */
    h->tp = ACCSES_VIA_IBVSMAD;    /* for spark */

    return h;
}






/********************************************************
**
*    Free resources
*
*/
int
mclose(mfile *h)
{
    free(h);
    return 0;
}






/********************************************************
**
*    Read a data block from an I2C slave on the IS3 bus using 
*    a vendor specific MAD sent to the IS3
*
*/
int
mread64(mfile *h, u_int32_t memory_address, u_int8_t *data, u_int32_t size)
{
    if (size%4) {
        IBERROR("Size must be 4 aligned, got %d\n", size);
        return -1;
    }
    if (ibvsmad_i2c_rw(h, h->i2c_slave, memory_address, size, IB_MAD_METHOD_GET, (char*)data) == ~0ull) {
			IBERROR("i2c_read to %s failed", portid2str(&h->portid));
            return -1;
    }
    return size;
    
}





/********************************************************
**
*    Write a data block to an I2C slave on the IS3 bus using 
*    a vendor specific MAD sent to the IS3
*
*/
int
mwrite64(mfile *h, u_int32_t memory_address, u_int8_t *data, u_int32_t size)
{
    if (size%4) {
        IBERROR("Size must be 4 aligned, got %d\n", size);
        return -1;
    }
    if (ibvsmad_i2c_rw(h, h->i2c_slave, memory_address, size, IB_MAD_METHOD_SET, (char*)data) == ~0ull) {
			IBERROR("i2c_write to %s failed", portid2str(&h->portid));
            return -1;
    }
    return size;
}




void
mset_i2c_slave(mfile *h, u_int8_t is3_slave_addr)
{
    h->i2c_slave = is3_slave_addr;
}




/********************************************************
**
*    Read an IS3 CR-Space 32 bit register
*
*/
int
mread4(mfile *h, u_int32_t memory_address, u_int32_t *data)
{
    /*
      
    */

    DEBUG("mread4 of 0x%08x\n", memory_address);
    if (ibvsmad_craccess_rw(h, memory_address, IB_MAD_METHOD_GET, 1, data) == ~0ull) {
			IBERROR("cr access read to %s failed", portid2str(&h->portid));
            return -1;
    }
    return 4;
}





/********************************************************
**
*    Write an IS3 CR-Space 32 bit register
*
*/
int
mwrite4(mfile *h, u_int32_t memory_address, u_int32_t _data)
{
    u_int32_t data = _data;
    DEBUG("mwrite4 of 0x%08x\n", memory_address);

    if (ibvsmad_craccess_rw(h, memory_address, IB_MAD_METHOD_SET, 1, &data) == ~0ull) {
			IBERROR("cr access write to %s failed", portid2str(&h->portid));
            return -1;
    }
    return 4;
}



