//==============================================================================
// FLI TX/RX UART Interface
//
// Set up socket, transmit/receive string to/from UART.
//
// This library is free software; you can redistribute it and/or modify it 
// under the terms of the GNU Lesser General Public License as published 
// by the Free Software Foundation; either version 2.1 of the License, or 
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.   See the GNU Lesser General Public 
// License for more details.   See http://www.gnu.org/copyleft/lesser.txt
//
//------------------------------------------------------------------------------
// Version   Author          Date          Changes
// 0.1       Hans Tiggeler   03 Aug 2003   Tested on Modelsim SE 5.7e
//==============================================================================
#include <stdio.h>
#include <math.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>

#include <mti.h>                    // MTI Headers & Prototypes

//========================== Sockets Definitions ======================
#define WIN32
#define ASK_SERVER_NAME

#ifndef ASK_SERVER_NAME
    #define PORTNUMBER  2000   
    #define SERVERNAME  "achilles"
#endif

#ifdef WIN32
    #include <winsock.h>
#else
    #include <unistd.h>
    #include <sys/time.h>
    #include <sys/param.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>              // defines gethostbyname()
#endif

#ifdef WIN32
    #define MAXHOSTNAMELEN MAXGETHOSTSTRUCT
#else
    #define SOCKET_ERROR -1
    #define INVALID_SOCKET -1
    #define NO_ERROR    0
    typedef int SOCKET;
#endif

#define MAXBUFFER       64          // Maximum number of characters per packet

typedef enum {                      // std_logic enumerated type
    STD_LOGIC_U,
    STD_LOGIC_X,
    STD_LOGIC_0,
    STD_LOGIC_1,
    STD_LOGIC_Z,
    STD_LOGIC_W,
    STD_LOGIC_L,
    STD_LOGIC_H,
    STD_LOGIC_D
} StdLogicType;

typedef struct {                    // Entity ports 
     mtiSignalIdT clk ;                        
     mtiSignalIdT resetn;                        
     mtiSignalIdT tdre;             // '1' if ready to transmit
     mtiSignalIdT rdrf;             // '1' if character received
     mtiSignalIdT dbusout;          // RX databus 

     mtiDriverIdT wr;
     mtiDriverIdT rd;

     mtiSignalIdT dbusin;           // TX databus    
     mtiDriverIdT * drv_elems;

     SOCKET       sd;               // Socket Descriptor         
     SOCKET       sd_current;       // Socket Descriptor
} inst_rec;

int debug=0;                        // set to 1 to enable debug

//char rxchar;                      // Receive character
//int  rxflag=0;                        // Receive character flag (!=0 if char received)
int  tx_in_progress=0;              // TX character in progress
int  rx_in_progress=0;              // RX character in progress
int  quit=0;                        // set to !0 to quit

char *buffer;                       // Receive buffer (FIFO)
int  fifocount=0;                   
int  rdptr=0;                       // Read Pointer
int  wrptr=0;                       // Write Pointer

// Prototypes
static void tx_fli(void *param);    // From terminal to UART
static void rx_fli(void *param);    // From UART to terminal            
int init_sockets(char *hostname, int port, inst_rec *ip);
int  send_packet(SOCKET sd,char *msg,int msg_size); 
int  rec_packet(SOCKET sd, char *msg, int msg_size); 
void close_socket(inst_rec *ip);

static long char2stdlogic(char value);
char *  stdlogic2char(char sigval);
mtiUInt32T conv_std_logic_vector(mtiSignalIdT stdvec);
void drive_stdlogicvector(mtiSignalIdT bus, mtiDriverIdT * drivers, char *strarray, mtiDelayT delay);
void sockCB(void * sock );          // Receive socket callback
void loadDoneCB(void * sock);       // Add socket callback
void restartCallback(void *param);  // User types quit/restart 


char * char2str(char ch);           // Convert char to boolean string

void cif_init( mtiRegionIdT region, char *param, mtiInterfaceListT *generics, mtiInterfaceListT *ports)
{
    inst_rec    *ip;                                // Declare ports            
    mtiProcessIdT rxproc,txproc;                    // current process id
    char        str[80];                            // used for stdinput 
    char        hostname[80];
    int         port=2000;
    mtiDriverIdT drvid;


    ip = (inst_rec *)mti_Malloc(sizeof(inst_rec));  // allocate memory for ports
    mti_AddRestartCB(mti_Free, ip);                 // restart entry point

    ip->clk     = mti_FindPort(ports, "clk");       // Get entity ports
    ip->resetn  = mti_FindPort(ports, "resetn");                        
    ip->tdre    = mti_FindPort(ports, "tdre");
    ip->rdrf    = mti_FindPort(ports, "rdrf");
    ip->wr      = mti_CreateDriver(mti_FindPort(ports, "wr"));
    ip->rd      = mti_CreateDriver(mti_FindPort(ports, "rd"));

    ip->dbusout = mti_FindPort(ports, "dbusout");

    ip->dbusin  = mti_FindPort(ports, "dbusin");                        
    drvid       = mti_CreateDriver(ip->dbusin);
    ip->drv_elems=mti_GetDriverSubelements(drvid, 0);

    rxproc = mti_CreateProcess("rx_fli", rx_fli, ip);
    mti_Sensitize(rxproc, ip->clk, MTI_EVENT);      // Add sensitivity signals
    mti_Sensitize(rxproc, ip->resetn, MTI_EVENT);
    
    txproc = mti_CreateProcess("tx_fli", tx_fli, ip);
    mti_Sensitize(txproc, ip->clk, MTI_EVENT);      // Add sensitivity signals
    mti_Sensitize(txproc, ip->resetn, MTI_EVENT);

// Ask the user for the machinename & portnumber 
#ifdef ASK_SERVER_NAME
    mti_AskStdin(str,"\nEnter hostname:");
    strcpy(hostname,str);
    mti_AskStdin(str,"Enter port id:");
    port=atoi(str);
#else                   
// Use fixed server and portnumber               
    strcpy(hostname,SERVERNAME);
    port=PORTNUMBER;
#endif

    mti_PrintFormatted("Opening socket %d on %s\nWaiting for connection.....\n",port,hostname);

    if ((init_sockets(hostname,port,ip))==SOCKET_ERROR) {   // Get socket descriptor
        mti_PrintMessage("*** Socket init error ***\n");
        mti_FatalError();                           // Do not continue if fail
    }                               
    
    // Allocate memory for Receive buffer
    if ((buffer=(char *)mti_Malloc(MAXBUFFER*sizeof(char)))==NULL) {
        mti_PrintMessage("*** MTI Memory Allocation Failure for Receive Buffer***\n");
        mti_FatalError();                           // Do not continue
    }

    mti_AddQuitCB(restartCallback, ip);
    mti_AddRestartCB(restartCallback, ip);          // Add quit or restart CallBack
    
    mti_AddLoadDoneCB((mtiVoidFuncPtrT)loadDoneCB, (void *)ip->sd_current);

    if (debug) mti_PrintMessage("\ncif_init (socket version) called\n");

}

void restartCallback(void *param)                   // User types quit/restart 
{
    fifocount=0;                                    // Receive character flag (!=0 if char received)
    rdptr=0;                                    // Read Pointer
    wrptr=0;                                    // Write Pointer
    
    tx_in_progress=0;
    rx_in_progress=0;

    close_socket(param);
    free(param);
}

static void rx_fli(void *param)                     // from uart to terminal (No fifo)  
{
    inst_rec * ip = (inst_rec *)param;
    mtiUInt32T i;

    if (mti_GetSignalValue(ip->resetn)==STD_LOGIC_0) {  // Check reset asserted

        mti_ScheduleDriver(ip->rd, STD_LOGIC_1, 0, MTI_INERTIAL); // rd<='1';

    } else  if (mti_GetSignalValue(ip->clk)==STD_LOGIC_1) { // rising edge
        if (rx_in_progress) {                   // second rising edge                      
        
            i=conv_std_logic_vector(ip->dbusout);       // if 0x03-> CTRL-C

            if (debug) mti_PrintFormatted("**** UART->TERM %c(%d) ****\n",(char) i,(char) i);
            send(ip->sd_current,(const char *)(&i),1,0); 
            mti_ScheduleDriver(ip->rd, STD_LOGIC_1, 0, MTI_INERTIAL); // rd<='1';
            rx_in_progress=0;
        } else {
            if (mti_GetSignalValue(ip->rdrf)==STD_LOGIC_1) { // character pending
                mti_ScheduleDriver(ip->rd, STD_LOGIC_0, 0, MTI_INERTIAL);  // rd<='0'
                rx_in_progress=1;
            }
        }
    }
}

static void tx_fli(void *param)                     // from terminal to uart (Fifo)
{
    inst_rec * ip = (inst_rec *)param;

    if (mti_GetSignalValue(ip->resetn)==STD_LOGIC_0) {  // Check reset 
        
        mti_ScheduleDriver(ip->wr, STD_LOGIC_1, 0, MTI_INERTIAL); // wr<='1';
        drive_stdlogicvector(ip->dbusin, ip->drv_elems, "HHHHHHHH",0);

    } else if (mti_GetSignalValue(ip->clk)==STD_LOGIC_1) {  

        if (tx_in_progress) {   // Transmit in progress wait for tdre to go low
            mti_ScheduleDriver(ip->wr, STD_LOGIC_1, 0, MTI_INERTIAL); // wr<='1'
            if (mti_GetSignalValue(ip->tdre)==STD_LOGIC_0) { // WR acknowledged?
                tx_in_progress=0;
            }
        } else {
            if (fifocount) {                    // fifo is not empty
                if (mti_GetSignalValue(ip->tdre)==STD_LOGIC_1) { // Ready to tx?
                if (debug) mti_PrintFormatted("**** TERM->UART %c(%d) FIFO=%d ****\n",buffer[rdptr],buffer[rdptr],fifocount);
                    mti_ScheduleDriver(ip->wr, STD_LOGIC_0, 0, MTI_INERTIAL);  // wr<='0'
                    drive_stdlogicvector(ip->dbusin, ip->drv_elems, char2str(buffer[rdptr]),2);
                    tx_in_progress=1;
                    rdptr=(rdptr+1)%MAXBUFFER;
                    fifocount--;
                }   
            }
        }           
    }
}

void sockCB(void * sock )                           // Triggered when socket character available
{
    int i;
    char rxchar;
    
    #ifdef WIN32
        i = recv((SOCKET)sock, &rxchar, sizeof(rxchar),0);
    #else
        i = read((SOCKET)sock, &rxchar, sizeof(rxchar));
    #endif
    if (i == 0)  {                                  // terminate if 0 characters received
        #ifdef WIN32
            mti_AddSocketInputReadyCB((SOCKET)sock, (mtiVoidFuncPtrT)0, 0);
        #else
            mti_AddInputReadyCB((SOCKET)sock, (mtiVoidFuncPtrT)0, 0);
        #endif
        mti_PrintMessage("Closing socket\n");
        close((SOCKET)sock );
    } else {

        if (fifocount==MAXBUFFER) {
            mti_PrintMessage("*** FIFO FULL ****");
        } else {
            if (debug) mti_PrintFormatted("RX(%d)%c i=%d ",rxchar,rxchar,i);
            buffer[wrptr]=rxchar;
            wrptr=(wrptr+1)%MAXBUFFER;
            fifocount++;
        }
    }
}

char * char2str(char ch)                            // Convert char to boolean string
{
    int t;
    unsigned int shift;
    static char str[9]="HHHHHHHH";

    shift=0x80;

    for (t=0;t<8;t++) {
        if (ch & (shift >> t)) str[t]='1'; else str[t]='0';
    }
    str[9]='\0';
    return str;
}


int init_sockets(char *hostname, int port, inst_rec *ip)
{
    int     statusFlags;
    int     status;
    int     addrlen;
    struct  sockaddr_in sin;
    struct  sockaddr_in pin;
    struct  hostent *hp;

    // Do some windows stuff, copied from FLI manual
    #ifdef WIN32
        WORD wVersionRequested;                     
        WSADATA wsaData;
        int err;

        wVersionRequested = MAKEWORD( 1, 1 );
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            mti_PrintMessage("**** Cannot find a useable winsock.dll ***\n" );
            return SOCKET_ERROR;
        }

        /* Confirm that the Windows Sockets DLL supports 1.1. Note that if
         * the DLL supports versions greater than 1.1 in addition to 1.1,
         * it will still return 1.1 in wVersion since that is the version
         * we requested. */
        if ( (LOBYTE( wsaData.wVersion ) != 1) || (HIBYTE( wsaData.wVersion ) != 1) ) {
            mti_PrintMessage("*** Cannot find a useable winsock.dll ***\n" );
            WSACleanup();
            return SOCKET_ERROR;
        }
        /* The Windows Sockets DLL is acceptable. Proceed. */
    #endif


    // Request Stream socket from OS using default protocol
    if ((ip->sd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        #ifdef WIN32
            DWORD le = GetLastError();
            mti_PrintFormatted("*** Error opening socket. Error=%d ***\n", le );
        #else
            mti_PrintMessage("*** Error opening socket ***\n" );
        #endif
        return SOCKET_ERROR;
    }

    // get host machine info
    if ((hp = gethostbyname(hostname)) == 0) {
        mti_PrintFormatted( "%s: Unknown host.\n", hostname );
        close_socket(ip);
        return SOCKET_ERROR;
    }

    // fill in the socket structure with host information 
    memset(&sin, 0, sizeof(sin));                       // Clear Structure
    sin.sin_family = AF_INET;                           // Specify Address format
    sin.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;
    sin.sin_port = htons((unsigned short)port);                     // set port number

    // bind the socket to the port number 
    if (bind(ip->sd, (struct sockaddr *) &sin, sizeof(sin)) == -1) {
        mti_PrintMessage("*** Error: Bind\n");
        close_socket(ip);
        return SOCKET_ERROR;
    }

    // Enable connection request on the socket, 5 should be enough :-)
    if (listen(ip->sd, 5) == -1) {
        mti_PrintMessage("*** Error: Listen\n");
        close_socket(ip);
        return SOCKET_ERROR;
    }

    // Accept connection from client, create new socket for communication
    addrlen = sizeof(struct sockaddr_in);
    if ((ip->sd_current = accept(ip->sd, (struct sockaddr *)  &pin, &addrlen)) == -1) {
        mti_PrintMessage("*** Error: Accept\n");
        close_socket(ip);
        return SOCKET_ERROR;
    }
    
    #ifdef WIN32
    {
        unsigned long non_blocking = 1;
        status = ioctlsocket(ip->sd, FIONBIO, &non_blocking );
        if (status == SOCKET_ERROR ) {
            perror("Setting tx socket status");
        }
        status = ioctlsocket(ip->sd_current, FIONBIO, &non_blocking );
        if (status == SOCKET_ERROR ) {
            perror( "Setting listen socket status" );
        }
    }
    #else
        statusFlags = fcntl(ip->sd, F_GETFL );
        if ( statusFlags == SOCKET_ERROR ) {
            perror( "Getting socket status" );
        } else {
            int ctlValue;
            statusFlags |= O_NONBLOCK;
            ctlValue = fcntl( ip->sd, F_SETFL, statusFlags );
            if (ctlValue == SOCKET_ERROR ) {
                perror("Setting listen socket status");
            }
        }


        statusFlags = fcntl(ip->sd_current, F_GETFL );
        if ( statusFlags == SOCKET_ERROR ) {
            perror("Getting listen socket status");
        } else {
            int ctlValue;
            statusFlags |= O_NONBLOCK;
            ctlValue = fcntl( ip->sd_current, F_SETFL, statusFlags );
            if (ctlValue == SOCKET_ERROR ) {
                perror("Setting listen socket status");
            }
        }
    #endif

    // Display some info
    mti_PrintFormatted("Server: connect from host %s, port %hd.\n",
              inet_ntoa (pin.sin_addr), ntohs (pin.sin_port));

    return (NO_ERROR);                                  // Return OK
}


//==============================================================================
// Drive std_logic_vector
// Example (bus, drvbus,"01110ZZU", 5); // bus<=01110ZZU after 5*time_resolution
// Length bus must be equal to length("01110ZZU")
//==============================================================================
void drive_stdlogicvector(mtiSignalIdT bus, mtiDriverIdT * drivers, char *strarray, mtiDelayT delay)
{
    char    * sigval;
    int     i;      

    sigval = (char *)mti_GetArraySignalValue(bus, 0);
    for (i=0;i<8;i++) {
        sigval[i]=char2stdlogic(strarray[i]);           // set bit to '0'/'1'/'z' etc 
        mti_ScheduleDriver( drivers[i], (long)sigval[i], delay, MTI_INERTIAL );
    }

}

void close_socket(inst_rec *ip)
{
#ifdef WIN32
    closesocket(ip->sd_current);
    closesocket(ip->sd);
#else
    close(ip->sd_current);
    close(ip->sd);
#endif
    mti_PrintMessage("**** Socket removed ****\n");
}

//==============================================================================
// Convert character to StdLogicType
// example input 'Z' output STD_LOGIC_Z
//==============================================================================
static long char2stdlogic(char value)               
{
    switch ( value) {        // toupper()
        case 'U': 
        case 'u':return STD_LOGIC_U;
        case 'X': 
        case 'x':return STD_LOGIC_X;
        case '0':return STD_LOGIC_0;
        case '1':return STD_LOGIC_1;
        case 'Z': 
        case 'z':return STD_LOGIC_Z;
        case 'W': 
        case 'w':return STD_LOGIC_W;
        case 'L': 
        case 'l':return STD_LOGIC_L;
        case 'H':
        case 'h':return STD_LOGIC_H;
        case 'D': 
        case 'd':return STD_LOGIC_D;
        default : {
            mti_PrintFormatted("*** ERROR: Incorrect input char (%c) in drive_array()\n");
            mti_FatalError();                           // Do not continue
        }
    }
}

//==============================================================================
// Convert StdLogicType to character 
// example input STD_LOGIC_Z output 'Z' 
//==============================================================================
char * stdlogic2char(char sigval)
{
    char * retval;
    switch (sigval) {
        case STD_LOGIC_U: retval = "U"; break;
        case STD_LOGIC_X: retval = "X"; break;
        case STD_LOGIC_0: retval = "0"; break;
        case STD_LOGIC_1: retval = "1"; break;
        case STD_LOGIC_Z: retval = "Z"; break;
        case STD_LOGIC_W: retval = "W"; break;
        case STD_LOGIC_L: retval = "L"; break;
        case STD_LOGIC_H: retval = "H"; break;
        case STD_LOGIC_D: retval = "-"; break;
        default: retval = "?"; break;
    }
    return retval;
}

// Convert std_logic_vector into an integer
mtiUInt32T conv_std_logic_vector(mtiSignalIdT stdvec)
{
    mtiSignalIdT *  elem_list;
    mtiTypeIdT      sigtype;
    mtiInt32T       i,num_elems;
    mtiUInt32T      retvalue,shift; 

    sigtype = mti_GetSignalType(stdvec);            // signal type
    num_elems = mti_TickLength(sigtype);            // Get number of elements
//  if (debug) mti_PrintFormatted("\nSignal X has %d elements\n",num_elems);

    elem_list = mti_GetSignalSubelements(stdvec, 0);

    shift=(mtiUInt32T) pow(2.0,(double)num_elems-1);// start position
    
    retvalue=0;
    for (i=0; i < num_elems; i++ ) {
        if (mti_GetSignalValue(elem_list[i])==3) {
            retvalue=retvalue+shift;
        } 
        shift=shift>>1;
    }

    mti_VsimFree(elem_list);

    return(retvalue);
}


void loadDoneCB(void * sock)                        // Add socket callback
{
    mti_PrintMessage("Load Done: Adding socket callback.\n");
    #ifdef WIN32
        mti_AddSocketInputReadyCB((SOCKET)sock, (mtiVoidFuncPtrT)sockCB, sock);
    #else
        mti_AddInputReadyCB((SOCKET)sock, (mtiVoidFuncPtrT)sockCB, sock);
    #endif
}