/**********************************************************************
*
* Copyright (c) 2005 Endace Technology Ltd, Hamilton, New Zealand.
* All rights reserved.
*
* This source code is proprietary to Endace Technology Limited and no part
* of it may be redistributed, published or disclosed except as outlined in
* the written contract supplied with this product.
*
* $Id: dagconsole.c 6031 2007-01-15 02:04:38Z cassandra $
*
**********************************************************************/

/**********************************************************************
* FILE:         dagconsole.c
* DESCRIPTION:  This module implements an simple console for 
*               communication with Linux on the DAG3.7T.
*
* HISTORY:
*       16-03-05 DNH 1.0  Initial version derived from dagxs.c
*
**********************************************************************/

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
#include <inttypes.h>
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/poll.h>
#include <termios.h>
#elif defined (_WIN32)
#include <signal.h>
#include <winsock2.h>

#include <getopt.h>
#include <timeofday.h>
#include <regex.h>
extern char *optarg;
extern int optind;
extern int opterr;

#endif /* _WIN32 */

#include "dagapi.h"
#include "dagutil.h"
#include "dagclarg.h"
#include "dagema.h"
#include "dagema/ema_types.h"


/* CVS Header. */
static const char *const kDagconsoleCvsHeader =
    "$Id: dagconsole.c 6031 2007-01-15 02:04:38Z cassandra $";
static const char *const kRevisionString = "$Revision: 6031 $";




#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	#define DEFAULT_DEVICE	"/dev/dag0"     /* default device                    */

	char *exit_str = "bye\n";

#elif defined(_WIN32)

	#define DEFAULT_DEVICE	"dag0"  /* default device                    */

	char *exit_str = "bye\r";

#else /* _WIN32 */
#error	Unknown operating system
#endif


typedef struct poll_set_t
{
    int max_sd;
    fd_set read;
    fd_set write;
    fd_set exception;
	
} poll_set_t;




int send_sigint = 0;
int run=1;

FILE *debug_log = NULL;

char g_instr[128], *str=NULL;
int ch_count=0;

volatile int g_idle=0;	/* Tracks idle time on the comms, used to sleep the process to reduce load on host */


enum
{
	CLA_DEVICE,
	CLA_HELP,
	CLA_VERBOSE,
	CLA_VERSION,
	CLA_RST,
	CLA_RDONLY,
	CLA_MEMTEST
};




/*****************************************************************************/
/*** Functions                                                             ***/
/*****************************************************************************/

/*****************************************************************************/
static void
print_version(void)
{
	printf("dagconsole (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}
/*****************************************************************************/

static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("Usage: dagconsole [options]\n");
	dagclarg_display_usage(clarg, stdout);
}



/**********************************************************************
 *
 * FUNCTION:     lock_ema
 *
 * DESCRIPTION:  To play nicely with the source for the Embedded Messaging
 *               API we have to provide these functions. The purpose of these
 *               functions in the EMA is to lock access to the DRB registers
 *               to only one process. In the case of dagconsole we want
 *               this application to open the server socket and monitor
 *               the DRB registers but not lock out any other application
 *               that wants to use the EMA, the EMA is then smart enough
 *               to direct messages to the server socket supplied by this 
 *               dagconsole application.
 *
 * PARAMETERS:   ctx          IN/OUT     Ignored.
 *
 * RETURNS:      0 if the lock was successiful otherwise -1
 *
 **********************************************************************/

#if defined (__sun) || defined (__linux__) || defined(__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__))

int
lock_ema (ema_context_t* ctx)
{
	int    fd;
	char   filename[128];
	char   str_pid[12];
	pid_t  lock_pid;
	int    result = 0;
	
	
	/* sanity check */
	if ( ctx == NULL )
		return -1;
		
	/* file name and path */
	snprintf (filename, 128, "/var/lock/LCK..dagema%d", ctx->dag_id);
	
	
	/* check if the lock file exists in which case another application has
	 * locked the drb so throw an error. */
	fd = open (filename, O_RDWR);
	if ( fd >= 0 )
	{
		result = -1;

		if ( read(fd, str_pid, 11) != 11 )
		{
			unlink (filename);
			result = 0;
		}
		else
		{		
			str_pid[11] = '\0';
			lock_pid = atoi(str_pid);
			
			if ( (kill(lock_pid, 0) == -1) && (errno == ESRCH) )
			{
				/* process has terminated, delete the lock file */
				unlink (filename);
				result = 0;
			}
		}
		
		close (fd);
	}
	
	
	/* */
	ctx->lock_file = -1;
	return result;
}

#else

int
lock_ema (ema_context_t* ctx)
{
	HANDLE	hMultex;
	CHAR    mut_name[128];

	/* sanity checking */
	if ( ctx == NULL )
		return -1;
	
	
	/* */
	snprintf (mut_name, 128, "dagema_%d", ctx->dag_id);
	
		
	/* create (or attach too) a mutex to control access to the shared memory */
	hMultex = CreateMutex (NULL, FALSE, mut_name);
	if ( hMultex == NULL )
	{
		dagutil_error ("CreateMutex");
		return -1;
	}
	
	/* determine if the mutex is in use or not, if not in use this function grabs it */
	if ( WaitForSingleObject(hMultex, 0) != WAIT_OBJECT_0 )
	{
		/* in use */
		return -1;
	}
	
	
	/* save the mutex handle for unlocking */
	ctx->hLockMutex = hMultex;
	
	/* Release the mutex so other libraries can run */
	ReleaseMutex (hMultex);
	CloseHandle (hMultex);

	return 0;
}

#endif


/**********************************************************************
 *
 * FUNCTION:     unlock_ema
 *
 * DESCRIPTION:  Because this application never locks down the connection
 *               there is no need to unlock it.
 *
 * PARAMETERS:   
 *
 * RETURNS:      
 *
 **********************************************************************/
int unlock_ema (ema_context_t* ctx)
{
	/* do nothing */
	return 0;	
}











/**********************************************************************
 *
 * FUNCTION:     console_char_handler
 *
 * DESCRIPTION:  
 *
 * PARAMETERS:   
 *
 * RETURNS:      
 *
 **********************************************************************/
int console_char_handler(int c)
{
	int res;
	res = putchar(c);

	/* Must flush the output so the console output is in sync with the card */
        fflush(stdout);

	/* Comms interface not idle */
	g_idle = 0;

	return res;
}


/**********************************************************************
 *
 * FUNCTION:     check_quit
 *
 * DESCRIPTION:  
 *
 * PARAMETERS:   
 *
 * RETURNS:      
 *
 **********************************************************************/
int check_quit(char **str_pp, int *ch_count)
{
	static int state = 0;
	static int rewind=0;
	static char *orig = NULL;

    if (rewind == 0)
	{
		if (**str_pp == exit_str[state])
		{
			/* Consume the character */
			(*str_pp)++;
			(*ch_count)--;
			state++;
			if (exit_str[state] == 0)
				return 1;
			return 0;
		}
		else if (state) 
		{
			rewind = state;
			state = 0;
			orig = *str_pp;	/* keep this until after the rewind is complete */
		}
		else
		{
			return -1;
		}
    }

    if (rewind)
	{
        if (state == rewind)
		{
	    	/* last character was sent, reset searcher */
	    	*str_pp = orig;
	    	rewind = 0;
	    	state = 0;
		}
		else
		{
			/* Feed back the original characters in order */
			*str_pp = &exit_str[state++];
			(*ch_count)++;
		}

		return -1;	/* Failed to match */
    }
    return 0;
}


/**********************************************************************
 *
 * FUNCTION:     next_char
 *
 * DESCRIPTION:  
 *
 * PARAMETERS:   
 *
 * RETURNS:      
 *
 **********************************************************************/
int next_char(int fd_stdin, int *quit, char* character)
{
    char ch = 0;
    int res = 0;
    int pll;
#if defined (_WIN32)
    int records_read = 0;
	int i = 0;
    INPUT_RECORD irec[128];
    INPUT_RECORD irec2[128];
  	HANDLE hStdIn;

	hStdIn = GetStdHandle(STD_INPUT_HANDLE); 
    if (hStdIn == INVALID_HANDLE_VALUE) 
		return 0;


#elif defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
    struct timeval tv;
    poll_set_t pollSet;
    poll_set_t pollSetTmp;

    FD_ZERO(&pollSet.read);
    FD_ZERO(&pollSet.write);
    FD_ZERO(&pollSet.exception);
    pollSet.max_sd = 0;
    
    FD_SET(fd_stdin, &pollSet.read);
    FD_SET(fd_stdin, &pollSet.exception);
    
    pollSet.max_sd = fd_stdin;
    
    pollSetTmp = pollSet;

    /* 1 sec delay, this should hurt anything,
     * poll with 0 delay will kill some smp machine. jeff 2006-05-05*/
    tv.tv_sec = 1;
    tv.tv_usec = 0;
    
#endif

	if (str)
	{
		/* 
		 * The following code implements a state machine to
		 * scan the input for the quit string (currently "bye<cr>").
		 * The next character is not returned until it is confirmed
		 * that the quit string sequence is not being entered.
		 * A succesful quit string is not returned as input.
		 */
		do
		{
			res = check_quit(&str, &ch_count);
			if (res == 1) {
				*quit = 1;
				return 0;
			}
			if (res != 0) {
				ch = *str;
				str++;
				ch_count--;
			}

			if ((!*str) || (ch_count == 0))  {
				str = NULL;
			}

			return 0;
		} while (str != NULL);
	
	} else {
		/* No data to send, so check for sigint and send if active */
		if (send_sigint) {
		    send_sigint = 0;
		    dagutil_verbose_level(2,"\n(sending sigint)\n");
		    /* Send a <ctrl-c> to the board */
		    str = "";
		} else {
		    /* Next check for user input ready (one line at a time) */        
		    /* poll file descriptors for entries */
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

			pll = select(pollSet.max_sd + 1, &pollSetTmp.read, &pollSetTmp.write,
		                &pollSetTmp.exception, &tv);
#elif defined (_WIN32)
			pll = PeekConsoleInput(hStdIn, irec2, 1, &records_read);

#endif 
 		    res = 0;

#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
		    if (pll > 0)
			{
				res = read(fd_stdin, g_instr, 128);
				//if (res == -1)
				//	perror ("read failed on stdin");
			}
#elif defined (_WIN32)
			if (pll > 0 && records_read >0)
			{
				ReadConsoleInput(hStdIn, irec, 128, &records_read);

				for(i = 0; i<records_read; i++)
				{
					if(irec[i].EventType == KEY_EVENT )
					{
						if(irec[i].Event.KeyEvent.bKeyDown == TRUE)
						{
							g_instr[res] = irec[i].Event.KeyEvent.uChar.AsciiChar;
							res++;
						}
					}
				}
			}
#endif
			
     
			if (res > 0) {
				ch_count = res;
				str = g_instr;
				*character = g_instr[0];
			}
			else
			{
			    ch_count = 0; 
			    str = NULL;
			}
		}
    }

    return res;
}


/**********************************************************************
 *
 * FUNCTION:     capture
 *
 * DESCRIPTION:  Main loop.  Echos all output characters from the board to stdout,
 *               and sends each line of input from stdin to the board.
 *               The sigint signal is trapped and sent to the board as a ctrl-c 
 *               character.
 *               The function returns if the user enters the word "bye".
 *
 * PARAMETERS:   
 *
 * RETURNS:      
 *
 **********************************************************************/
void capture(int dagfd) 
{
	char keyboard_char;
	char out_char=0;
	int quit=0;
	int fd_stdin = fileno(stdin);
	char buf[2049];
	int buf_ind = 0;
	int ret;

	while (run)
	{

	    /* Note: output from the board is received via the console_char_handler,
		 * a call-back handler that is invoked whenever tty data is ready to be
		 * read during a read or write on the drb
		 */

		buf_ind = 0;

		g_idle++;

		/* Check for keyboard input */
		if (out_char <= 0)
			out_char = next_char(fd_stdin, &quit, &keyboard_char);
		
		if (quit)	return;

		
		/* Sending keyboard input? */
		while (out_char > 0)
		{
			buf[buf_ind] = keyboard_char;
			buf_ind++;
			if (buf_ind >= 2048) 
				break;

			out_char = next_char(fd_stdin, &quit, &keyboard_char);

			if (quit) 	return;
				
			g_idle = 0;
		}

		if (buf_ind != 0)
		{
			ret = dagema_write_console_string(dagfd, buf, buf_ind);
			if(ret !=0 )
			{
				dagutil_error("string not accepted %d\n", ret);
			}
		}

		
		/* Sleep if the card doesn't respond quickly, otherwise
		   process the next char - this speeds up the comms interface without
		   over-burdening the host */
		if (g_idle > 100)
		{
			usleep(1000);
			g_idle = 0;
		}
	}
}



/**********************************************************************
 *
 * FUNCTION:     sighandler
 *
 * DESCRIPTION:  
 *
 * PARAMETERS:   
 *
 * RETURNS:      
 *
 **********************************************************************/
static void sighandler(int signal)
{
	fflush(stderr);
    if (signal == SIGINT) {
    	send_sigint++;
    }
	if(send_sigint > 1)
		run = 0;
}



/**********************************************************************
 *
 * FUNCTION:     main
 *
 * DESCRIPTION:  
 *
 * PARAMETERS:   
 *
 * RETURNS:      
 *
 **********************************************************************/
int main (int argc, char **argv)
{
	int      dagfd;
	int      fd_stdin = fileno(stdin);
	char     dagname_buf[DAGNAME_BUFSIZE] = DEFAULT_DEVICE;
	char     dagname[DAGNAME_BUFSIZE];
	int      dagstream=0;
	int      res = 0;
	ClArgPtr clarg = NULL;
	FILE     *errorfile = NULL;
	int      argindex;
	uint32_t reset_flags;
	int      code;
	int      reset_xscale = 0;
	int      run_memtest  = 0;
	int      readonly     = 0;
	
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
	struct sigaction sigact;    /* signal catching */
	struct termios term_settings, orig_settings;
#elif defined (_WIN32)
	WORD     wVersionRequested;
	WSADATA  wsaData;
	HANDLE   hStdIn;
	DWORD    term_settings, orig_settings;
	
	hStdIn = GetStdHandle(STD_INPUT_HANDLE); 
	if (hStdIn == INVALID_HANDLE_VALUE) 
		return;
#endif 

	printf("\nEndace XScale Debug (rev1.2)\n");
	printf("(c) 2005 Endace Technology Ltd.\n\n");

	dagutil_set_progname("dagconsole");


	/* Set up the command line options. */
	clarg = dagclarg_init(argc, (const char* const *) argv);

	dagclarg_add(clarg, "display help (this page).", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');
	dagclarg_add(clarg, "display version information.", "--version", 'V', CLA_VERSION);
	dagclarg_add(clarg, "increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add_string(clarg, "DAG device to use.  Default: dag0.", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add(clarg, "reset xscale", "--rst", 'x', CLA_RST);
	dagclarg_add(clarg, "read only (keyboard input not sent)", "--rdonly", 'r', CLA_RDONLY);
	dagclarg_add(clarg, "run memory test before booting", "--memtest", 'm', CLA_MEMTEST);

	/* Parse the command line options. */
	res = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == res)
	{
		switch (code)
		{
			case CLA_DEVICE:
				break;

			case CLA_HELP:
				print_usage(clarg);
				return EXIT_SUCCESS;

			case CLA_VERBOSE:
				dagutil_inc_verbosity();
				errorfile = stderr;
				break;

			case CLA_VERSION:
				print_version();
				return EXIT_SUCCESS;

			case CLA_RST:
				reset_xscale = 1;
				break;

			case CLA_RDONLY:
				readonly = 1;
				break;

			case CLA_MEMTEST:
				run_memtest = 1;
				break;

			default:
			/* Unknown option. */
				dagutil_error("unknown option %s\n", argv[argindex]); 
				print_usage(clarg);
				return EXIT_FAILURE;
		}

		res = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
	{
		dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
	}
	dagutil_verbose_level(2, "device=%s\n", dagname);

	if (-1 == res)
	{
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}			
	
	



	if ((debug_log = fopen ("debug.log", "w")) == NULL)
	{
		dagutil_error ("Failed to open debug.log: %s", strerror(errno));
		exit(0);
	}

	/* Catch signals. */
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
	sigact.sa_handler = sighandler;
	sigemptyset (&sigact.sa_mask);
	sigact.sa_flags = 0;
	if (sigaction (SIGINT, &sigact, NULL) < 0) {
		fprintf (stderr, "sigaction SIGINT: %s\n", strerror (errno));
		exit (EXIT_FAILURE);
	}
#elif defined(_WIN32)
	dagutil_set_signal_handler(sighandler);
#endif /* Platform-specific code. */

	if ((dagfd = dag_open(dagname)) < 0)
	{
		dagutil_error ("dag_open %s: %s\n", dagname, strerror(errno));
		exit(0);
	}


#if defined(_WIN32)
	wVersionRequested = MAKEWORD( 2, 2 );
	res = WSAStartup( wVersionRequested, &wsaData );
	if ( res != 0 )
	{
		dagutil_error ("WSAStartup failed with error code %d\n", WSAGetLastError());
		exit(0);
	}
#endif

	
	
	/* Reset the XScale if asked to */
	if (reset_xscale)
	{
		dagutil_verbose_level (1, "Restarting XScale %s ...\n", (run_memtest == 1) ? "(with memory test)" : "");

		reset_flags = (run_memtest == 1) ? EMA_RUN_DRAM_MEMORY_TEST : 0;
		if ( dagema_reset_processor(dagfd, reset_flags) != 0 )
		{
			dagutil_error ("XScale reset failed (errorcode: %d).\n", dagema_get_last_error());
			goto Exit;
		}
		
		dagutil_verbose_level (1, "... reset complete.\n");
	}
	

    /* Initialize board communication interface */
   	/* Socket to dagconsole mode (i.e. for Linux on Xscale) */
	res = dagema_open_conn(dagfd);
	if (res < 0)
	{
		dagutil_error ("Failed to connect to board (errorcode: %d)\n", dagema_get_last_error());
		goto Exit;
	}


	/* Set the console callback handler */
	res = dagema_set_console_handler (dagfd, console_char_handler);
	if (res < 0)
	{
		dagema_close_conn(dagfd, 0);
		dagutil_error ("Failed to install console handler (errorcode: %d)\n", dagema_get_last_error());
		goto Exit;
	}

	
	
	/*
	 * configure stdin and terminal to pass all keys through
	 * including control signals.
	 */

#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	/* unbuffered input, no echo, and ignore signals for stdin */
	tcgetattr(fd_stdin, &orig_settings);
	tcgetattr(fd_stdin, &term_settings);
	term_settings.c_lflag &= ~ICANON;        /* unbuffered input */
	term_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
	/* c_cc[] not parsed if set ~ICANON */
	term_settings.c_cc[VMIN] = 1;
	term_settings.c_cc[VTIME] = 0;
	tcsetattr(fd_stdin, TCSANOW, &term_settings);
#elif defined (_WIN32)
	GetConsoleMode(hStdIn, &orig_settings);

	term_settings = orig_settings;
	term_settings &= ~ENABLE_LINE_INPUT;
	term_settings &= ~ENABLE_ECHO_INPUT;
	term_settings &= ~ENABLE_PROCESSED_INPUT;
	term_settings &= ~ENABLE_WINDOW_INPUT;
	term_settings &= ~ENABLE_MOUSE_INPUT;

	if (SetConsoleMode(hStdIn, term_settings) == 0 )
		dagutil_error ("Set Console Mode error: %d\n", GetLastError());

#endif

	/* Main capture loop.  Exits when user types "bye" */
	capture(dagfd);

	/*
	 * Restore original terminal settings
	 */
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
	tcsetattr(fd_stdin, TCSANOW, &orig_settings);
#elif defined (_WIN32)
	SetConsoleMode(hStdIn, &orig_settings);
#endif /* _WIN32 */


	
	/*
	 * Cleanup and shutdown
	 */

	dagema_close_conn(dagfd, 0);

Exit:

#if defined(_WIN32)
	WSACleanup( );
#endif

	dag_close(dagfd);

	printf("\n");

	fclose(debug_log);

	return 0;
}
