/* $Id: rlink_cext.c 1190 2019-07-13 17:05:39Z mueller $
 * SPDX-License-Identifier: GPL-3.0-or-later
 * Copyright 2007-2016 by Walter F.J. Mueller <W.F.J.Mueller@gsi.de>
 *
 *  Revision History: 
 * Date         Rev  Vers    Comment
 * 2016-02-07   728   2.1    emit only one 'schedule clock stop' message
 * 2014-11-02   602   2.0    sideband handling for rlink v4; count EAGAINs
 * 2014-07-27   575   1.3.2  add ssize_t -> int casts to avoid warnings
 *                           add fflush(stdout) after standart open/close msgs
 * 2011-03-05   366   1.3.1  add RLINK_CEXT_TRACE=2 trace level
 * 2010-12-29   351   1.3    rename cext_rriext -> rlink_cext; rename functions
 *                           cext_* -> rlink_cext_* and fifo file names
 *                           tb_cext_* -> rlink_cext_*
 * 2007-11-18    96   1.2    add 'read before write' logic to avoid deadlocks
 *                           under cygwin broken fifo (size=1 !) implementation
 * 2007-10-19    90   1.1    add trace option, controlled by setting an
 *                           the environment variable CEXT_RRIEXT_TRACE=1
 * 2007-09-23    84   1.0    Initial version 
 */ 

#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sched.h>
#include <stdlib.h>
#include <string.h>

/* kSymEsc = 0xCA = 1100 1010 (bin) */
#define CESC  (0xCA)
#define QRBUFSIZE 1024

static int fd_rx = -1;
static int fd_tx = -1;

static int io_trace = 0;

static char qr_buf[QRBUFSIZE];
static int  qr_pr  = 0;
static int  qr_pw  = 0;
static int  qr_nb  = 0;
static int  qr_eof = 0;
static int  qr_err = EAGAIN;

static void rlink_cext_dotrace(const char *text, int dat)
{
  int i;
  int mask = 0x80;
  printf("rlink_cext-I: %s   ", text);
  for (i=0; i<8; i++) {
    printf("%c", (dat&mask)?'1':'0' );
    mask >>= 1;
  }
  printf("\n");
}

static void rlink_cext_doread()
{
  static int  neagain = 0;
  
  char buf[1];
  ssize_t nbyte;
  nbyte = read(fd_rx, buf, 1);
  if (io_trace > 1) {
    if (nbyte < 0 && errno == EAGAIN) {
      neagain += 1;
    } else {
      if (neagain) {
        printf("rlink_cext-I: reads with EAGAIN: %d seen\n", neagain);
        neagain = 0;
      }
      printf("rlink_cext-I: read   rc=%d", (int)nbyte);
      if (nbyte < 0) printf(" errno=%d %s", errno, strerror(errno));
      printf("\n");
    }
  }

  if (nbyte < 0) {
    qr_err = errno;
  } else if (nbyte == 0) {
    qr_err = EAGAIN;
    qr_eof = 1;
  } else {
    qr_err = EAGAIN;
    if (qr_nb < QRBUFSIZE) {
      if (io_trace) rlink_cext_dotrace("rcv8", (unsigned char) buf[0]);
      qr_buf[qr_pw++] = buf[0];
      if (qr_pw >= QRBUFSIZE) qr_pw = 0;
      qr_nb += 1;
    } else {
      printf("Buffer overflow\n"); /* FIXME: better error handling */
    }
  }
}

/* returns:
 *   <0          if error
 *   >=0 <=0xff  normal data
 *   == 0x100    idle
 *   0x01aahhll   if side band message seen
 *
 */

int rlink_cext_getbyte(int clk)
{
  char buf[1];
  ssize_t nbyte;
  int irc;
  int tdat;
  char* env_val;

  static int odat;
  static int nidle = 0;
  static int ncesc = 0;
  static int nside = -1;
  static int exitdone = 0;

  if (fd_rx < 0) {		/* fifo's not yet opened */
    fd_rx = open("rlink_cext_fifo_rx", O_RDONLY|O_NONBLOCK);
    if (fd_rx <= 0) {
      perror("rlink_cext-E: failed to open rlink_cext_fifo_rx");
      return -2;
    }
    printf("rlink_cext-I: connected to rlink_cext_fifo_rx\n");
    fflush(stdout);
    
    fd_tx = open("rlink_cext_fifo_tx", O_WRONLY);
    if (fd_tx <= 0) {
      perror("rlink_cext-E: failed to open rlink_cext_fifo_tx");
      return -2;
    }
    printf("rlink_cext-I: connected to rlink_cext_fifo_tx\n");
    fflush(stdout);

    nidle = 0;
    ncesc = 0;
    nside = -1;

    /* determine trace level from RLINK_CEXT_TRACE: */
    /*  "1"  trace bytes read and written */
    /*  "2"  trace also read() and write() calls */
    io_trace = 0;
    env_val = getenv("RLINK_CEXT_TRACE");
    if (env_val) {
      printf("rlink_cext-I: seen RLINK_CEXT_TRACE=%s\n", env_val);
      if (strcmp(env_val, "0") == 0) {
        printf("rlink_cext-I: set trace level to 0 (off)\n");
      } else if (strcmp(env_val, "1") == 0) {
        printf("rlink_cext-I: set trace level to 1 (bytes)\n");
        io_trace = 1;
      } else if (strcmp(env_val, "2") == 0) {
        printf("rlink_cext-I: set trace level to 2 (bytes + calls)\n");
        io_trace = 2;
      } else {
        printf("rlink_cext-E: invalid RLINK_CEXT_TRACE value; ignored\n");
      }
    }
    
  }

  rlink_cext_doread();

  if (qr_nb == 0) {		            /* no character to be processed */
    if (qr_eof != 0) {		            /* EOF seen */
      if (ncesc >= 2) {			    /*  two+ CESC seen  ? */
	printf("rlink_cext-I: seen EOF, wait for reconnect\n");
        fflush(stdout);
	close(fd_rx);
	close(fd_tx);
	fd_rx = -1;
	fd_tx = -1;
	usleep(500000);			    /* wait 0.5 sec */
	return 0x100;			    /* return idle, will reconnect */
      }
    
      if (exitdone == 0) {
        printf("rlink_cext-I: seen EOF, schedule clock stop and exit\n");
        exitdone = 1;
      }
      fflush(stdout);
      return -1;			    /* signal EOF seen */

    } else if (qr_err == EAGAIN) {          /* nothing read, return idle */
      if (nidle < 8 || (nidle%1024)==0) {
	irc = sched_yield();
	if (irc < 0) perror("rlink_cext-W: sched_yield failed");
      }
      nidle += 1;
      return 0x100;
    } else {			            /* must be a read error */
      errno = qr_err;
      perror("rlink_cext-E: read error on rlink_cext_fifo_rx");
      return -3;
    }
  }

  nidle = 0;
  tdat = (unsigned char) qr_buf[qr_pr++];
  if (qr_pr >= QRBUFSIZE) qr_pr = 0;
  qr_nb -= 1;
  
  if (tdat == CESC) {
    ncesc += 1;
    if (ncesc == 2) nside = 0;
  } else {
    ncesc = 0;
  }

  switch (nside) {
  case -1:				    /* normal data */
    return tdat;
  case 0:				    /* 2nd CESC, return it */
    nside += 1;
    odat = 0x01000000;                      /* init odat */
    return tdat;

  /* decode oob data as formated by RlinkPacketBufSnd::SndOob() */
  /* odat format:  0x01aadddd */
  case 1:				    /* get ADDR(3:0) */
    nside += 1;
    odat  |= (tdat&0x0f)<<16;
    return 0x100;
  case 2:				    /* get ADDR(7:4) */
    nside += 1;
    odat  |= (tdat&0x0f)<<20;
    return 0x100;
  case 3:				    /* get data( 3: 0) */
    nside += 1;
    odat  |= (tdat&0x0f);
    return 0x100;
  case 4:				    /* get data( 7: 4) */
    nside += 1;
    odat  |= (tdat&0x0f)<<4;
    return 0x100;
  case 5:				    /* get data(11: 8) */
    nside += 1;
    odat  |= (tdat&0x0f)<<8;
    return 0x100;
  case 6:				    /* get data(15:12) */
    nside  = -1;
    odat  |= (tdat&0x0f)<<12;
    return odat;
  }  
}

int rlink_cext_putbyte(int dat) 
{
  char buf[1];
  ssize_t nbyte;

  rlink_cext_doread();

  if (io_trace) rlink_cext_dotrace("snd8", dat);

  buf[0] = (unsigned char) dat;
  nbyte = write(fd_tx, buf, 1);
  if (io_trace > 1) {
    printf("rlink_cext-I: write  rc=%d", (int)nbyte);
    if (nbyte < 0) printf(" errno=%d %s", errno, strerror(errno));
    printf("\n");
  }

  if (nbyte < 0) {
    perror("rlink_cext-E: write error on rlink_cext_fifo_tx");
    return -3;
  }

  return 0;
}
