/* 
 * This software is licensed under terms of the GNU GENERAL PUBLIC LICENSE
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software  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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Source code is available at http://ppcenter.free.fr
 */

#include "parlink.h"





/*--------------------------------------------------------------------------*/
/* Saturn interface functions                                               */
/*--------------------------------------------------------------------------*/

/**
 * Synchronize with PAR.
 * Returns
 *  0 when no error.
 *  1 when synchronisation timeouted.
**/
//From ssfexes.lzh\WIN95\SSFEXE.cpp
//  //
//  // 送信＆受信
//  //
//  UINT CMainDlg::SendRece(UINT sdata)
//  {
//  	UINT result;
//  
//  	// 高速化
//  	__asm {
//  		mov	edx,m_dataport
//  		mov	eax,sdata
//  		out	dx,al
//  
//  		xor	eax,eax
//  		mov	edx,m_flagport
//  		in	al,dx
//  		and	al,01h
//  		mov	result,eax
//  	}
//  
//  	// ＢＵＳＹチェック
//  	if (result) {
//  		BusyChk();
//  		if (m_abort)
//  			return (UINT)NULL;
//  	}
//  
//  	// 高速化
//  	__asm {
//  		xor	eax,eax
//  		mov	edx,m_dataport
//  		in	al,dx
//  		mov	result,eax
//  	}
//  
//  	return result;
//  }
int par_sync(void)
{
    scl_log(SCLT_DLLPG, 7, "[par_sync]start (I>D, N>O)");

    unsigned long i = 0;
    unsigned char tmp;
    while (1)
    {
        if(i > _loop_max)
        {
            scl_log(SCLT_DLLPG, 0, "[par_sync]Synchronize timeout ! (_loop_max=%d)", _loop_max);
            return 1;
        }
        i++;

        //__dpmi_yield();
        tmp = par_exchange_byte('I');// scl_log(SCLT_DLLPG, 7, "[par_sync]tmpD->I = %02X (%c)", tmp, char2pchar(tmp));
        if (tmp != 'D')
            continue;
        tmp = par_exchange_byte('N');// scl_log(SCLT_DLLPG, 7, "[par_sync]tmpN->O = %02X (%c)", tmp, char2pchar(tmp));
        if (tmp == 'O')
            break;
    }
    scl_log(SCLT_DLLPG, 7, "[par_sync]OK");
    return 0;
}


/**
 * Attempt to re-synchronize with PAR after an unsuccessful download (function $01).
 * Let's suppose we forgot to receive some bytes (1~2).
 * In order to try to cancel download, send `\0' bytes until
 * `OK' (download cancel success) is received.
 */
int par_abort_download(void)
{
    unsigned long i = 0;
    unsigned char ok;
    while (1)
    {
        if(i > _loop_max)
        {
            scl_log(SCLT_DLLPG, 0, "[par_abort_download]Cancel download timeout !");
            return 1;
        }
        i++;

        ok = par_exchange_byte(0);
        if ((ok == 'O') || (ok == 'K'))
        {
            if (par_exchange_byte(0) == 'K')
            {
                scl_log(SCLT_DLLPG, 5, "[par_abort_download]Cancel OK :] [%d times, instead of 8]", i-1);
                return 0;
            }
        }
    }
    return 0;
}






/**
 * Receive data block
 * Returns
 *     0= No error
 *     1= Checksum mismatch
**/
int par_receive_datablock(unsigned long address, unsigned char* buffer, unsigned long length)
{
    unsigned char checksum[2];
    int i;

    /* Send source address and length to read */
    scl_log(SCLT_DLLPG, 5, "[par_receive_datablock]*** par_exchange_long(address=0x%08X)", address);
    par_exchange_long(address);
    scl_log(SCLT_DLLPG, 5, "[par_receive_datablock]*** par_exchange_long(length=%d)", length);
    par_exchange_long(length);
    checksum[0] = 0;


    /* Read data and generate checksum */
    for (i = 0; i < length; i++)
    {
        buffer[i] = par_exchange_byte(0);
        checksum[0] += buffer[i];
    }

    /* See if the checksum matchs. */
    checksum[1] = par_exchange_byte(0);
    scl_log(SCLT_DLLPG, 9, "[par_receive_datablock] CHKS=0x%02X, 0x%02X", checksum[0], checksum[1]);

    if(checksum[0] != checksum[1])
    {
        scl_log(SCLT_DLLPG, 3, "[par_receive_datablock]*** CHKSM: CALC=%02X, PAR=%02X", checksum[0], checksum[1]);
        return 1;
    }

    return 0;
}



/**
 * Receive data from Saturn.
 * Returns :
 *    SC_NOERROR            = No error.
 *    SC_ERROR_PNOTOPEN     = Port is not opened.
 *    SC_ERROR_PKTCHECKSUM  = Checksum mismatch.
 *    SC_ERROR_PKTRCV0      = Could not abort after a corrupted download.
 *    SC_ERROR_PKTHANDSHAKE = Couldn't terminate download request successfully.
**/
char SC_parlink::sc_receive(unsigned long address, unsigned char* buffer, unsigned long length)
{
    /* If specified, use extended function instead */
    if(_use_xcomms)
    {
        return parx_receive(address, buffer, length);
    }


    if(!_port_init) return SC_ERROR_PNOTOPEN;

    int i, error = SC_NOERROR;
    /* Transfer-related internal variables. */
    unsigned long p   = 0;
    unsigned long psz = _rcv_packet_size;
    unsigned long l   = _rcv_packet_size;


    /* Indicates that the process started. */
    STATUS_CALLBACK(CLBK_START, 0, buffer, 0, length);

    /* Synchronize with PAR */
    if(par_sync() != 0)
    {
        scl_log(SCLT_DLLPG, 5, "[sc_receive]Synchronize error ! Transfer aborted");
        error = SC_ERROR_PKTSYNC;
        goto receive_end;
    }


    /* Function $01 - Download memory */
    scl_log(SCLT_DLLPG, 5, "[sc_receive]*** par_exchange_byte(1)");
    par_exchange_byte(1);

    /* Read and discard contents of master SH-2 register R9 */
    scl_log(SCLT_DLLPG, 5, "[sc_receive]*** par_exchange_long(0)");
    par_exchange_long(0);


    /* 
     * Receiving all data in one pass is not recommended, since a transmission
     * error implies to re-download all the data.
     * And that we don't know what may happen when a transmission error happens.
     */
    int err;
    for(p=0; (p<length) && (error == SC_NOERROR); p+=psz)
    {
        STATUS_CALLBACK(CLBK_RECEIVE, address, buffer, p, length);

        l = psz;
        if((length-p) < psz) {l=length-p;}

        scl_log(SCLT_DLLPG, 5, "[sc_receive]receive loop: length=%d, psz=%d, p=%d, l=%d", length, psz, p, l);
        for(i=0; i<_checksum_retry; i++)
        {
            err = par_receive_datablock(address+p, buffer+p, l);
            if(err == 0) break;

            scl_log(SCLT_DLLPG, 5, "[sc_receive][ofs=%d, len=%d] Checksum error ! (Trycount = %d)", p, l, i+1);

            { // Resync with PAR

                /* Attempt to abort download.
                 * Example: Let's suppose we forgot to receive a byte : in 
                 *          order to cancel the download, send 7 `\0' bytes.
                 */
                scl_log(SCLT_DLLPG, 5, "[sc_receive]Try to cancel download ...");
                if(par_abort_download() != 0)
                {
                    scl_log(SCLT_DLLPG, 5, "[sc_receive]Download abort error ! Transfer aborted");
                    error = SC_ERROR_PKTRCV0;
                    goto receive_end;
                }
                scl_log(SCLT_DLLPG, 5, "[sc_receive]Try to synchronize ...");
                if(par_sync() != 0)
                {
                    scl_log(SCLT_DLLPG, 5, "[sc_receive]Synchronize error ! Transfer aborted");
                    error = SC_ERROR_PKTSYNC;
                    goto receive_end;
                }


                /* Function $01 - Download memory */
                scl_log(SCLT_DLLPG, 5, "[sc_receive]*** par_exchange_byte(1)");
                par_exchange_byte(1);

                /* Read and discard contents of master SH-2 register R9 */
                scl_log(SCLT_DLLPG, 5, "[sc_receive]*** par_exchange_long(0)");
                par_exchange_long(0);
            }
        }
        /* In the case we looped _checksum_retry time, there are chances that the
         * checksum/transmission/anything else might be bad, so in this case write
         * an error message to the log.
         */
        if(err != 0)
        {
            scl_log(SCLT_DLLPG, 5, "[sc_receive]Checksum error ! Transfer aborted");
            error = SC_ERROR_PKTCHECKSUM;
            goto receive_end;
        }
    }

    if(error == SC_NOERROR)
    {
        /* Abort download - dummy address with zero length */
        scl_log(SCLT_DLLPG, 5, "[sc_receive]*** par_exchange_long(0)");
        par_exchange_long(0);
        scl_log(SCLT_DLLPG, 5, "[sc_receive]*** par_exchange_long(0)");
        par_exchange_long(0);

        /* See if the PAR acknowledge request to stop download */
        scl_log(SCLT_DLLPG, 5, "[sc_receive]*** par_exchange_byte(0) != 'O' ?, error = %d", error);
        if(par_exchange_byte(0) != 'O') error = SC_ERROR_PKTHANDSHAKE;
        scl_log(SCLT_DLLPG, 5, "[sc_receive]*** par_exchange_byte(0) != 'K' ?, error = %d", error);
        if(par_exchange_byte(0) != 'K') error = SC_ERROR_PKTHANDSHAKE;
        scl_log(SCLT_DLLPG, 5, "[sc_receive]***                            error = %d", error);
    }


receive_end:
    /* Indicates that the process ended */
    STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);

    return error;
}






// /**
//  * Send data block
//  * Returns
//  *     0= No error
//  *     1= Checksum mismatch
// **/
// int par_send_datablock(unsigned long address, unsigned char* buffer, unsigned long length, int exec_flag)
// {
//     unsigned char data;
//     int i, err = 0;
// 
//     /* Send destination address and data length */
//     scl_log(SCLT_DLLPG, 5, "[par_send_datablock]*** par_exchange_long(address=0x%08X)", address);
//     par_exchange_long(address);
//     scl_log(SCLT_DLLPG, 5, "[par_send_datablock]*** par_exchange_long(length=%d)", length);
//     par_exchange_long(length);
// 
//     /* Send load-only or load-execute flag */
//     scl_log(SCLT_DLLPG, 5, "[par_send_datablock]*** par_exchange_long(exec_flag=%d)", exec_flag);
//     par_exchange_byte(exec_flag);
// 
//     /* Send data and generate checksum */
//     for (i = 0; i < length; i++)
//     {
//         data = par_exchange_byte(buffer[i]);
//         scl_log(SCLT_DLLPG, 5, "[par_send_datablock]--- par_exchange_long(buffer[%4d]=%02X)=%02X", i, buffer[i], data);
//         if(data != buffer[i])
//         {
//             scl_log(SCLT_DLLPG, 3, "[par_send_datablock]Transmission error !");
//             err = 1;
//         }
//     }
// 
//     return err;
// }
// 
// 
// 
// /**
//  * Send data to Saturn.
//  * Returns :
//  *    SC_NOERROR            = No error.
//  *    SC_ERROR_PNOTOPEN     = Port is not opened.
//  *    SC_ERROR_PKTCHECKSUM  = Checksum mismatch.
// **/
// char SC_parlink::sc_send(unsigned long address, unsigned char* buffer, unsigned long length, char exec_flag)
// {
//     /* If specified, use extended function instead */
//     if(_use_xcomms)
//     {
//         return parx_send(address, buffer, length, exec_flag);
//     }
// 
// 
//     if(!_port_init) return SC_ERROR_PNOTOPEN;
// 
//     int i, error = SC_NOERROR;
// 
//     /* Indicates that the process started. */
//     STATUS_CALLBACK(CLBK_START, 0, buffer, 0, length);
// 
//     /* Synchronize with PAR */
//     if(par_sync() != 0)
//     {
//         scl_log(SCLT_DLLPG, 5, "[sc_send]Synchronize error ! Transfer aborted");
//         error = SC_ERROR_PKTSYNC;
//         goto send_end;
//     }
// 
// 
//     /* Function $09 - Upload data */
//     scl_log(SCLT_DLLPG, 5, "[sc_send]*** par_exchange_byte(9)");
//     par_exchange_byte(9);
// 
// 
//     /* 
//      * Sending all data in one pass is not recommended, since a transmission
//      * error implies to re-download all the data.
//      * And that we don't know what may happen when a transmission error happens.
//      */
//     unsigned long p   = 0;
//     unsigned long psz = _snd_packet_size;
//     unsigned long l   = _snd_packet_size;
//     int err;
//     for(p=0; (p<length) && (error == SC_NOERROR); p+=psz)
//     {
//         STATUS_CALLBACK(CLBK_SEND, address, buffer, p, length);
// 
//         l = psz;
//         if((length-p) < psz) {l=length-p;}
// 
//         scl_log(SCLT_DLLPG, 5, "[sc_send]send loop: length=%d, psz=%d, p=%d, l=%d", length, psz, p, l);
//         for(i=0; i<_checksum_retry; i++)
//         {
//             err = par_send_datablock(address+p, buffer+p, l, ((p+l) >= length ? exec_flag : 0));
//             if(err == 0) break;
// 
//             scl_log(SCLT_DLLPG, 5, "[sc_send][ofs=%d, len=%d] Checksum error ! (Trycount = %d)", p, l, i+1);
// 
//             { // Resync with PAR
// 
//                 scl_log(SCLT_DLLPG, 5, "[sc_send]Try to synchronize ...");
//                 if(par_sync() != 0)
//                 {
//                     scl_log(SCLT_DLLPG, 5, "[sc_send]Synchronize error ! Transfer aborted");
//                     error = SC_ERROR_PKTSYNC;
//                     goto send_end;
//                 }
// 
//                 /* Function $09 - Upload data */
//                 scl_log(SCLT_DLLPG, 5, "[sc_send]*** par_exchange_byte(9)");
//                 par_exchange_byte(9);
//             }
//         }
//         /* In the case we looped _checksum_retry time, there are chances that the
//          * checksum/transmission/anything else might be bad, so in this case write
//          * an error message to the log.
//          */
//         if(err != 0)
//         {
//             scl_log(SCLT_DLLPG, 5, "[sc_send]Checksum error ! Transfer aborted");
//             error = SC_ERROR_PKTCHECKSUM;
//             goto send_end;
//         }
//     }
// 
//     if(error == SC_NOERROR)
//     {
//         scl_log(SCLT_DLLPG, 5, "[sc_send]Send OK !");
//     }
// 
// 
// send_end:
//     /* Indicates that the process ended */
//     STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);
// 
//     return error;
// }


/**
 * Send data to Saturn.
 * Returns :
 *    SC_NOERROR            = No error.
 *    SC_ERROR_PNOTOPEN     = Port is not opened.
**/
char SC_parlink::sc_send(unsigned long address, unsigned char* buffer, unsigned long length, char exec_flag)
{
    /* If specified, use extended function instead */
    if(_use_xcomms)
    {
        return parx_send(address, buffer, length, exec_flag);
    }


    if(!_port_init) return SC_ERROR_PNOTOPEN;

    int i, error = SC_NOERROR;
    /* Transfer-related internal variables. */
    unsigned char data = 0;

    /* Indicates that the process started. */
    STATUS_CALLBACK(CLBK_START, 0, buffer, 0, length);

    /* Synchronize with PAR */
    if(par_sync() != 0)
    {
        scl_log(SCLT_DLLPG, 5, "[sc_send]Synchronize error ! Transfer aborted");
        error = SC_ERROR_PKTSYNC;
        goto send_end;
    }

    /* Function $09 - Upload data */
    scl_log(SCLT_DLLPG, 5, "[sc_send]*** par_exchange_byte(9)");
    par_exchange_byte(9);


    /* Send destination address and data length */
    scl_log(SCLT_DLLPG, 5, "[sc_send]*** par_exchange_long(address=0x%08X)", address);
    par_exchange_long(address);
    scl_log(SCLT_DLLPG, 5, "[sc_send]*** par_exchange_long(length=%d)", length);
    par_exchange_long(length);

    /* Send load-only or load-execute flag */
    scl_log(SCLT_DLLPG, 5, "[sc_send]*** par_exchange_long(exec_flag=%d)", exec_flag);
    par_exchange_byte(exec_flag);

    /* Send data */
    for (i = 0; i < length; i++)
    {
        STATUS_CALLBACK(CLBK_SEND, address, buffer, i, length);

        //if(data != buffer[i])
        //{
        //    scl_log(SCLT_DLLPG, 5, "[sc_send] Transmission error !");
        //}
        data = par_exchange_byte(buffer[i]);
    }

send_end:
    /* Indicates that the process ended */
    STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);

    return error;
}




