/*
 * Copyright (c) 2002-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: dagclock.c 13453 2010-12-02 23:30:08Z jomi.gregory $
 */

/* Endace headers. */
#include "dagapi.h"
#include "dag_platform.h"
#include "dagutil.h"
#include "dagclarg.h"

/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagclock.c 13453 2010-12-02 23:30:08Z jomi.gregory $";
static const char* const kRevisionString = "$Revision: 13453 $";

static char dagname_buf[DAGNAME_BUFSIZE] = "dag0";
static char dagname[DAGNAME_BUFSIZE];
static uint32_t threshold;
static int32_t timeout;
static int64_t phase;
static int64_t phase_correction;

static int dagstream;
static int dagfd;
static duckinf_t duckinf;

static int do_sync = 0, sync_timeout = 60, clear_stats = 0;
static uint32_t health_threshold=0;
static int do_monitor=0;

static volatile uint8_t *iom;
static unsigned duck_base;

static void dagduck(void) __attribute__((unused));
static void duckstatus(void);
static void duckconfig(int argc, char *argv[]);
static int duckmonitor(void);
static int ducksync(void);
static void duckioctl(int dagfd, duckinf_t *duckinf);

/* Commandline argument codes. */
enum
{
	CLA_HELP,
	CLA_VERBOSE,
	CLA_VERSION,
	CLA_CLRSTATS,
	CLA_SYNC,
	CLA_TIMEOUT,
	CLA_THRESHOLD,
	CLA_DEVICE,
	CLA_PHASE,
	CLA_MONITOR
};

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


static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("dagclock - Endace DAG clock utility.\n");
	printf("Usage: dagclock [-hvVxkm] [-d dag] [-K <timeout>] [-l <threshold>] [-p <phase correction>] [options]\n");
	dagclarg_display_usage(clarg, stdout);
	printf("\nThe following duck configuration options are available:\n");
	printf("        default    RS422 in, none out\n");
	printf("        none       None in, none out\n");
	printf("        rs422in    RS422 input\n");
	printf("        hostin     Host input (unused)\n");
	printf("        overin     Internal input (synchronise to host clock)\n");
	printf("        auxin      Aux input (unused)\n");
	printf("        rs422out   Output the rs422 input signal\n");
	printf("        loop       Output the selected input\n");
	printf("        hostout    Output from host (unused)\n");
	printf("        overout    Internal output (master card)\n");
	printf("        set        Set DAG Clock to PC clock\n");
	printf("        reset      Full clock reset: Clock set from PC, RS422 in, none out\n");
	printf("        sync       Wait for duck to sync before exiting\n");
}

#if defined(_WIN32)
static void
duckioctl(int dagfd, duckinf_t *duckinf)
{
	DWORD BytesTransfered = 0;

	if(DeviceIoControl(dag_gethandle(dagfd),
		IOCTL_GET_DUCKINFO,
		duckinf,
		sizeof(duckinf_t),
		duckinf,
		sizeof(duckinf_t),
		&BytesTransfered,
		NULL) == FALSE)
		panic("DeviceIoControl IOCTL_GET_DUCKINFO: %s\n",strerror(errno));
}
#elif defined (__linux__) || defined (__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__))
static void
duckioctl(int dagfd, duckinf_t *duckinf)
{
	int error;

	if((error = ioctl(dagfd, DAGIOCDUCK, duckinf)))
		dagutil_panic("DAGIOCDUCK failed with %d\n", error);
}
#endif


int
dagclock_main(int argc, char *argv[])
{
	int argindex;
	ClArgPtr clarg = NULL;
	int code;
	FILE* errorfile = NULL;
	int clarg_result;
	int retval = EXIT_SUCCESS;

	dag_reg_t result[DAG_REG_MAX_ENTRIES];
	unsigned regn;
	dag_reg_t *regs;

	duckinf.Set_Duck_Field = 0;
	duckinf.Last_TSC = 0;

	dagutil_set_progname("dagclock");

	/* Set up default DAG device. */
	if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
	{
		dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
	}

	/* 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(clarg, "clear clock statistics", "--clearstats", 'x', CLA_CLRSTATS);
	dagclarg_add(clarg, "wait for duck to sync before exiting", "--sync", 'k', CLA_SYNC);
	dagclarg_add_int(clarg, "sync timeout in seconds, default 60", "--timeout", 'K', "timeout", &timeout, CLA_TIMEOUT);
	dagclarg_add_uint(clarg, "health threshold in ns, default 596", "--threshold", 'l', "threshold", &threshold, CLA_THRESHOLD);
	dagclarg_add(clarg, "monitor clock health and display each second", "--monitor", 'm', CLA_MONITOR);
	dagclarg_add_int64(clarg, "phase correction in ns, default 0", "--phase", 'p', "phase", &phase, CLA_PHASE);
	dagclarg_add_string(clarg, "DAG device to use.", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);


	/* Parse the command line options. */
	clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == clarg_result)
	{
		switch (code)
		{
			case CLA_HELP:
				print_usage(clarg);
				return EXIT_SUCCESS;
				break;

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

			case CLA_VERSION:
				print_version();
				return EXIT_SUCCESS;
				break;

			case CLA_SYNC:
				do_sync = 1;
				/* fall through */

			case CLA_CLRSTATS:
				clear_stats |= Set_Duck_Clear_Stats;
				break;

			case CLA_TIMEOUT:
				sync_timeout = timeout;
				break;

			case CLA_THRESHOLD:

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
				health_threshold = threshold * (0x100000000ll/1000000000.0);
#elif defined(_WIN32)
				health_threshold = threshold * (0x100000000/1000000000.0);
#endif
				break;

			case CLA_PHASE:

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
				phase_correction = phase * (0x100000000ll/1000000000.0);
#elif defined(_WIN32)
				phase_correction = phase * (0x100000000/1000000000.0);
#endif
				phase = 1; /* set phase */
				break;

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

			case CLA_MONITOR:
				do_monitor = 1;
				break;

			default:
				if (argv[argindex][0] == '-')
				{
					/* Unknown option. */
					dagutil_error("unknown option %s\n", argv[argindex]); 
					print_usage(clarg);
					return EXIT_FAILURE;
				}
				break;
		}
		clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

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

	argv = (char**) dagclarg_get_unprocessed_args(clarg, &argc);


	dagfd = dag_open(dagname);
	if (dagfd < 0)
		dagutil_panic("dag_open %s: %s\n", dagname, strerror(errno));

	iom = dag_iom(dagfd);
	
	/* Find DUCK */
	regs = dag_regs(dagfd);	
	regn=0;
	if ((dag_reg_table_find(regs, 0, DAG_REG_DUCK, result, &regn)) || (!regn))
		dagutil_panic("Dag does not support DUCK functions\n");
	else
		duck_base = DAG_REG_ADDR(*result);

# if 0
	dagduck();
# endif

	if(argc > 0)
		duckconfig(argc, argv);

	duckinf.Set_Duck_Field |= clear_stats;
	if(health_threshold) {
		duckinf.Set_Duck_Field |= Set_Duck_Health_Thresh;
		duckinf.Health_Thresh = health_threshold;
	}

	if(phase) {
		duckinf.Set_Duck_Field |= Set_Duck_Phase_Corr;
		duckinf.Phase_Correction = phase_correction;
	}

	duckioctl(dagfd, &duckinf);

	if(do_monitor) {
		retval = duckmonitor();
	} else {
		if(do_sync)
			retval = ducksync();
		duckstatus();
	}

	if(dag_close(dagfd))
		dagutil_panic("dag_close %s: %s\n", dagname, strerror(errno));

	return retval;
}

enum {
	Duck_Command	= 0x00,
	Duck_Config	= 0x04,
	Duck_High_Load	= 0x0c,
	Duck_DDS_Rate	= 0x14,
	Duck_High_Read	= 0x18,
	Duck_Low_Read	= 0x1c
};

# define	DUCK(OFF)	(*(volatile unsigned *)(iom+duck_base+(OFF)))

static void
dagduck(void)
{
# if 0
	printf("command \t0x%x\n", DUCK(Duck_Command));
	printf("config  \t0x%x\n", DUCK(Duck_Config));
	printf("highload\t0x%x\n", DUCK(Duck_High_Load));
	printf("ddsrate \t0x%x\n", DUCK(Duck_DDS_Rate));
	printf("highread\t0x%x\n", DUCK(Duck_High_Read));
	printf("lowread \t0x%x\n", DUCK(Duck_Low_Read));
# endif
}

static void
duckstatus(void)
{
	unsigned	val, mask, none;
	time_t		last;
	char		*last_tick;

	val = DUCK(Duck_Config);

	none = 1;
	printf("muxin\t");
	for( mask = 1 ; mask < 0x10; mask <<= 1)
		switch(val&mask) {
		case 0x00:
			continue;
		case 0x01:
			printf("rs422 ");
			none = 0;
			break;
		case 0x02:
			printf("host ");
			none = 0;
			break;
		case 0x04:
			printf("over ");
			none = 0;
			break;
		case 0x08:
			printf("aux ");
			none = 0;
			break;
		default:
			dagutil_panic("internal error at %s %u\n",
						  __FILE__, __LINE__);
		}
	if(none)
		printf("none ");
	printf("\n");

	none = 1;
	printf("muxout\t");
	for( mask = 0x100 ; mask < 0x1000 ; mask <<=1 )
		switch(val&mask) {
		case 0x000:
			continue;
		case 0x100:
			printf("rs422 ");
			none = 0;
			break;
		case 0x200:
			printf("loop ");
			none = 0;
			break;
		case 0x400:
			printf("host ");
			none = 0;
			break;
		case 0x800:
			printf("over ");
			none = 0;
			break;
		default:
			dagutil_panic("internal error at %s line %u\n",
						  __FILE__, __LINE__);
		}
	if(none)
		printf("none ");
	printf("\n");

	printf("status\t");
	printf("%sSynchronised ", duckinf.Health?"":"Not ");
	printf("Threshold %.0fns ", duckinf.Health_Thresh / (0x100000000ll/1000000000.0));
	printf("Phase correction %.0fns ", duckinf.Phase_Correction / (0x100000000ll/1000000000.0));
	printf("Failures %d ", duckinf.Sickness);
	printf("Resyncs %d\n", duckinf.Resyncs);

	printf("error\t");
	printf("Freq %.0fppb ", duckinf.Freq_Err / (0x100000000ll/1000000000.0));
	printf("Phase %.0fns ", duckinf.Phase_Err / (0x100000000ll/1000000000.0));
	printf("Worst Freq %.0fppb ", duckinf.Worst_Freq_Err / (0x100000000ll/1000000000.0));
	printf("Worst Phase %.0fns\n", duckinf.Worst_Phase_Err / (0x100000000ll/1000000000.0));

	printf("crystal\t");
	printf("Actual %dHz ", duckinf.Crystal_Freq);
	printf("Synthesized %dHz\n", duckinf.Synth_Freq);

	printf("input\t");
	printf("Total %d ", duckinf.Pulses);
	printf("Bad %d ", duckinf.Bad_Pulses);
	printf("Singles Missed %d ", duckinf.Single_Pulses_Missing);
	printf("Longest Sequence Missed %d\n", duckinf.Longest_Pulse_Missing);

	last_tick = ctime(&duckinf.Stat_Start);
	printf("start\t%s", last_tick);

	last_tick = ctime(&duckinf.Stat_End);
	printf("host\t%s", last_tick);

	if(duckinf.Last_Ticks) {
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
		last = (time_t)((duckinf.Last_Ticks >> 32) + (int)(((duckinf.Last_Ticks&0xffffffff) + (double)0x80000000)/0x100000000ll));
#elif defined(_WIN32)
		last = (time_t)((duckinf.Last_Ticks >> 32) + (int)(((duckinf.Last_Ticks&0xffffffff) + (double)0x80000000)/0x100000000));
#endif
		last_tick = ctime(&last);
		printf("dag\t%s", last_tick);
	} else
		printf("dag\tNo active input - Free running\n");
	
	if (0 != duckinf.Last_TSC)
	{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
		printf("TSC\t%"PRIu64"\n", duckinf.Last_TSC);
#elif defined(_WIN32)
		printf("TSC\t%I64u\n", duckinf.Last_TSC);
#endif
	}
}

static int
duckmonitor(void)
{
	int count = 0;
	int timeout = 0;

	duckinf.Set_Duck_Field = 0;

	duckstatus();
	printf("\n");

	while (1) {

		duckioctl(dagfd, &duckinf);

		if((count++ % 20) == 0) {
			printf("Sync  Inputs     Bad Failures Resyncs  Freq(ppb) Phase(ns) Crystal(Hz)\n");

		}
		
		printf("%4d ", duckinf.Health);
		printf("%7d ", duckinf.Pulses);
		printf("%7d ", duckinf.Bad_Pulses);
		
		printf("%8d ", duckinf.Sickness);
		printf("%7d ", duckinf.Resyncs);
		printf("%10.0f ", duckinf.Freq_Err / (0x100000000ll/1000000000.0));
		printf("%9.0f ", duckinf.Phase_Err / (0x100000000ll/1000000000.0));
		printf("%11d ", duckinf.Crystal_Freq);
		
		printf("\n");
		fflush(stdout);

		if(do_sync) {
			if ((!duckinf.Health) && (timeout < sync_timeout)) {
				timeout++;
			} else {
				if (!duckinf.Health)
					dagutil_warning("Failed to synchronise after %ds\n", sync_timeout);
				else { /* healthy, clear stats */
					duckinf.Set_Duck_Field |= clear_stats;
					duckioctl(dagfd, &duckinf);
				}
				return !duckinf.Health;
			}
		}
		sleep(1);	
	}
	return EXIT_SUCCESS;
}


typedef struct configtab {
	char	*key;		/* the command */
	int	mlen;		/* minimum match length */
	int	inst;		/* instruction to be executed */
} configtab_t;

enum {
	INST_DEFAULT,
	INST_NONE,
	INST_RS422IN,
	INST_HOSTIN,
	INST_OVERIN,
	INST_AUXIN,
	INST_RS422OUT,
	INST_LOOP,
	INST_HOSTOUT,
	INST_OVEROUT,
	INST_SET,
	INST_RESET,
	INST_SYNC,
    INST_MASTER
};

static configtab_t configtab[] = {
	{   "default",  7,  INST_DEFAULT        },
	{   "none",     4,  INST_NONE       },
	{   "rs422in",  6,  INST_RS422IN        },
	{   "hostin",   5,  INST_HOSTIN     },
	{   "overin",   5,  INST_OVERIN     },
	{   "auxin",    4,  INST_AUXIN      },
	{   "rs422out", 6,  INST_RS422OUT       },
	{   "loop",     4,  INST_LOOP       },
	{   "hostout",  5,  INST_HOSTOUT        },
	{   "overout",  5,  INST_OVEROUT        },
	{   "set",      3,  INST_SET        },
	{   "reset",    5,  INST_RESET      },
	{   "sync",     4,  INST_SYNC       },
	{   "master",   6,  INST_MASTER     },
	{   NULL,       0,  0           },
};

static int
ducksync(void)
{
	int timeout=0;

	while((!duckinf.Health)&&(timeout < sync_timeout)) {
		sleep(1);
		timeout++;
		duckinf.Set_Duck_Field |= clear_stats;
		duckioctl(dagfd, &duckinf);
	}
	if(!duckinf.Health)
		dagutil_warning("Failed to synchronise after %ds\n", sync_timeout);
	return !duckinf.Health;
}

static void
duckconfig(int argc, char *argv[])
{
	int	opt, len, i, reset = 0;
	char	*p;
	unsigned val, current;
	uint32_t	magic = DAGRESET_DUCK;
#if defined(_WIN32)
	DWORD BytesTransfered = 0;
#endif

	for( opt = 0 ; opt < argc ; opt++ ) {
		for( p = argv[opt]; *p ; p++ )
			*p = tolower(*p);
		p = argv[opt];
		for( i = 0 ; configtab[i].key != NULL ; i++ ) {
			len = strlen(p);
			if(len < configtab[i].mlen)
				continue;
			if(!strncmp(p, configtab[i].key, strlen(p)))
				break;
		}
		if(configtab[i].key == NULL)
			dagutil_panic("unsupported configuration option '%s'\n", argv[opt]);
		val = DUCK(Duck_Config);
		
		reset = 0;

		switch(configtab[i].inst) {
		case INST_DEFAULT:
			val = 1;
			break;
		case INST_NONE:
			val = 0;
			break;
		case INST_RS422IN:
			val |= 0x01;
			break;
		case INST_HOSTIN:
			val |= 0x02;
			break;
		case INST_OVERIN:
			val |= 0x04;
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
			/* Set sync threshold to 20*default value, ~12 microseconds */
			health_threshold = 12000 * (0x100000000ll/1000000000.0);
#elif defined(_WIN32)
			/* Set sync threshold to ~50 microseconds */
			health_threshold = 50000 * (0x100000000/1000000000.0);
#endif
			break;
		case INST_AUXIN:
			val |= 0x08;
			break;
		case INST_RS422OUT:
			val |= 0x100;
			break;
		case INST_LOOP:
			val |= 0x200;
			break;
		case INST_HOSTOUT:
			val |= 0x400;
			break;
		case INST_OVEROUT:
			val |= 0x800;
			break;
		case INST_SET:
			reset = 1;
			break;
		case INST_RESET:
			val = 1;
			reset = 1;
			break;
		case INST_SYNC:
			do_sync = 1;
			clear_stats |= Set_Duck_Clear_Stats;
			break;
       		 case INST_MASTER:
			/* reset none overout*/
			reset = 1;
			val = 0x800;
			break;
		default:
			dagutil_panic("internal error in %s line %u\n",
						  __FILE__, __LINE__);
		}
		if(reset) {

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

			if(ioctl(dagfd, DAGIOCRESET, &magic) < 0)
				dagutil_panic("ioctl DAGIOCRESET %s: %s\n", dagname, strerror(errno));

#elif defined(_WIN32)

			if(DeviceIoControl(dag_gethandle(dagfd),
					   IOCTL_DUCK_RESET,
					   NULL,
					   0,
					   NULL,
					   0,
					   &BytesTransfered,
					   NULL) == FALSE)
				panic("DeviceIoControl dag IOCTL_DUCK_RESET: %s\n", strerror(errno));

#endif /* Platform-specific code. */
		}
		/* Read Config (after reset) */
		current = DUCK(Duck_Config);
		/* Clear mux bits (preserve upper control bits) */
		current &= 0xffff0000;
		/* Set configured low mux bits (if any) */
		current |= (val & 0xffff);
		DUCK(Duck_Config) = current;
	}
}


#ifndef ENDACE_UNIT_TEST
int
main(int argc, const char* const * argv)
{
	return dagclock_main(argc, (char**) argv);
}
#endif /* ENDACE_UNIT_TEST */
