/*
 * 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: dagwatchdog.c 11428 2009-06-23 06:55:26Z vladimir $
 */

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

/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagwatchdog.c 11428 2009-06-23 06:55:26Z vladimir $";
static const char* const kRevisionString = "$Revision: 11428 $";


#define DEFAULT_DAG_DEVICE "dag0"
#define DEFAULT_FAIL_MILLISECONDS 10000
#define WATCHDOG_PAT_MILLISECONDS 200


/* Unit variables. */
static char uDagnameBuffer[DAGNAME_BUFSIZE] = DEFAULT_DAG_DEVICE;
static char uDagname[DAGNAME_BUFSIZE];
static int uDagFd = 0;
static int uDagStream = 0;
static volatile uint8_t* uDagIOMemory;
static dag_reg_t* uDagRegisters;
static volatile struct timeval uLastDataSeen = {0,0};
static uint32_t uFailsafeBase = 0;
static int uFailMilliseconds = DEFAULT_FAIL_MILLISECONDS;
static int uExpectTraffic = 1;
static int uMonitorThreadRunning = 0;
static int uPatOnly = 0;
static volatile int uRun = 1;

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
static pthread_t uMonitorThread;
#endif /* Platform-specific code. */


/* Internal routines. */
static uint32_t dagiomread(uint32_t addr);
static void dagiomwrite(uint32_t addr, uint32_t val);
static void initialize_failsafe(void);
static void pat_watchdog(void);
static void do_simple_watchdog_patting(void) __attribute__((noreturn));
static void print_version(void);
static void print_usage(ClArgPtr clarg);
static void signal_handler(int signal);

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

static void* monitor_thread(void* context);

#elif defined(_WIN32)

DWORD WINAPI monitor_thread(LPVOID lpParameter);

#endif /* _WIN32 */

/* Implementation of internal routines. */
static uint32_t
dagiomread(uint32_t addr)
{
	return *(volatile uint32_t*)(uDagIOMemory + addr);
}


static void
dagiomwrite(uint32_t addr, uint32_t val)
{
	*(volatile uint32_t*)(uDagIOMemory + addr) = val;
}


static void
initialize_failsafe(void)
{
	dag_reg_t result[DAG_REG_MAX_ENTRIES];
	uint32_t reg_count = 0;
	uint32_t val;
	int counter = 100;

	/* Find failsafe module. */
	if ((0 != dag_reg_table_find(uDagRegisters, 0, DAG_REG_FAILSAFE, result, &reg_count)) || (0 == reg_count))
	{
		dagutil_panic("failsafe module not found\n");
	}
	uFailsafeBase = DAG_REG_ADDR(*result);

	/* Verify failsafe bit. */
	val = dagiomread(uFailsafeBase);
	if (0 == (val & BIT31))
	{
		dagutil_panic("failsafe logic is not present on this card.\n");
	}

	/* Activate failsafe (bring card in-circuit). */
	printf("initializing failsafe logic...");
	dagiomwrite(uFailsafeBase, 0x01);

	/* Verify failsafe status. */
	val = dagiomread(uFailsafeBase);
    while (0 == (val & BIT16))
	{
		usleep(100 * 1000);
		counter--;
		if (0 == counter)
		{
			printf("\n");
			dagutil_panic("card did not activate after ten seconds.\n");
		}

		val = dagiomread(uFailsafeBase);
		/* Improve robustness */
		dagiomwrite(uFailsafeBase, 0x01);
	}
	printf("done\n");
}

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
static void*
monitor_thread(void* context)
#else /* _WIN32 */
DWORD WINAPI
monitor_thread(LPVOID lpParameter)
#endif /* _WIN32 */
{
	struct timeval time_now;
	struct timeval stop_patting_time;
	struct timeval previous_data_seen;
	struct timeval delay;

	uMonitorThreadRunning = 1;
	dagutil_verbose("[MONITOR] Starting (pid = %d).\n", (int)getpid());

	/* Ensure that traffic is present. */
	if (1 == uExpectTraffic)
	{
		sleep(1); /* This doesn't give much scope to miss since the failsafe timeout is 1.6s. */
		if ((0 == uLastDataSeen.tv_sec) && (0 == uLastDataSeen.tv_usec))
		{
			dagutil_warning("[MONITOR] main thread is not receiving data.\n");
		}
	}

	/* Start timer. */
	gettimeofday(&time_now, NULL);
	stop_patting_time = time_now;
	stop_patting_time.tv_usec += 1000 * uFailMilliseconds;
	while (stop_patting_time.tv_usec >= 1000000)
	{
		stop_patting_time.tv_sec++;
		stop_patting_time.tv_usec -= 1000000;

		if (0 == uRun)
		{
			/* Abort - probably due to a signal. */
			break;
		}
	}

	dagutil_verbose("[MONITOR] about to start patting watchdog.\n");

	/* Pat watchdog at WATCHDOG_PAT_MILLISECONDS intervals. */
	while ((time_now.tv_sec < stop_patting_time.tv_sec) || 
			((time_now.tv_sec == stop_patting_time.tv_sec) && (time_now.tv_usec < stop_patting_time.tv_usec)))
	{
		/* Sleep for WATCHDOG_PAT_MILLISECONDS. */
		usleep(WATCHDOG_PAT_MILLISECONDS * 1000);

		pat_watchdog();

		gettimeofday(&time_now, NULL);

		if (0 == uRun)
		{
			/* Abort - probably due to a signal. */
			break;
		}
	}

	dagutil_verbose("[MONITOR] stopped patting watchdog.\n");

	/* We've stopped patting the watchdog -- time how long until traffic stops. */
	gettimeofday(&stop_patting_time, NULL);

	/* Wait to see when traffic stops being received. */
	while (1 == uRun)
	{
		previous_data_seen = uLastDataSeen;
		sleep(1);

		if ((previous_data_seen.tv_sec == uLastDataSeen.tv_sec)
			&& (previous_data_seen.tv_usec == uLastDataSeen.tv_usec))
		{
			break;
		}

		/* If traffic has not stopped after ten seconds, there's a problem. */
		if (uLastDataSeen.tv_sec - stop_patting_time.tv_sec > 10)
		{
			dagutil_panic("[MONITOR] data is still arriving 10 seconds after the watchdog patting stopped.\n");
		}
	}
	dagutil_verbose("[MONITOR] traffic stopped.\n");

	/* Calculate the difference between the end of the patting and the end of the data. */
	delay.tv_sec = uLastDataSeen.tv_sec - stop_patting_time.tv_sec;
	delay.tv_usec = uLastDataSeen.tv_usec - stop_patting_time.tv_usec;
	if (delay.tv_usec < 0)
	{
		delay.tv_usec += 1000000;
		delay.tv_sec--;
	}

	printf("[MONITOR] Data stopped arriving %d.%06d seconds after the watchdog patting stopped.\n",
		   (int) delay.tv_sec, (int) delay.tv_usec);

	dagutil_verbose("[MONITOR] finished.\n");
	uMonitorThreadRunning = 0;

	return NULL;
}


static void
pat_watchdog(void)
{
	dagutil_verbose("[MONITOR] patting watchdog\n");
	dagiomwrite(uFailsafeBase, 0x01);
}


static void
do_simple_watchdog_patting(void)
{
	int result;

	/* Pat watchdog until stopped. */
	uDagFd = dag_open(uDagname);
	if (uDagFd < 0)
	{
		dagutil_panic("dag_open(\"%s\"): %s\n", uDagname, strerror(errno));
	}

	uDagIOMemory = dag_iom(uDagFd);
	uDagRegisters = dag_regs(uDagFd);
	initialize_failsafe();

	/* Don't configure the card for data capture when patting the watchdog is the only task. */

	while (1 == uRun)
	{
		pat_watchdog();
		usleep(WATCHDOG_PAT_MILLISECONDS * 1000);
	}

	result = dag_close(uDagFd);
	if (-1 == result)
	{
		dagutil_panic("dag_close(%d): %s\n", uDagFd, strerror(errno));
	}

	exit(EXIT_SUCCESS);
}


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


static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("DAG card failsafe utility.\n");
	printf("Usage: dagwatchdog [options]\n");

	dagclarg_display_usage(clarg, stdout);

	printf("\n");
	printf("  dagwatchdog tests the failsafe mechanism on a failsafe-equipped DAG card by 'patting' the\n");
	printf("  watchdog timer until the specified millisecond time interval has passed.  It then measures\n");
	printf("  how long it takes for the failsafe to trip and disconnect the DAG card.\n");
	printf("\n");
	printf("  A secondary mode of operation allows dagwatchdog to operate as a background watchdog patter\n");
	printf("  so that regular DAG utilities (which aren't failsafe aware) can use failsafe-equipped DAG cards.\n");
	printf("  In this mode (-p,--pat-only) dagwatchdog simply pats the watchdog to keep the DAG card active,\n");
	printf("  and options other than the device are ignored.\n");
	printf("\n");
}


static void
signal_handler(int signal)
{
	uRun = 0;
}


/* Commandline argument codes. */
enum
{
	CLA_DEVICE,
	CLA_HELP,
	CLA_PAT_ONLY,
	CLA_TIME,
	CLA_NO_TRAFFIC,
	CLA_VERBOSE,
	CLA_VERSION
};


int
dagwatchdog_main(int argc, const char * const * const argv)
{
	ClArgPtr clarg;
	FILE* errorfile = NULL;
	const char * const * unprocessed_argv = NULL;
	int index;
	int code;
	int result;
	int argindex;

	dagutil_set_progname("dagwatchdog");
	dagutil_set_signal_handler(signal_handler);

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

	/* Set up the command line options. */
	clarg = dagclarg_init(argc, argv);
	dagclarg_add(clarg, "This page.", "--help", 'h', CLA_HELP);
	dagclarg_add(clarg, "Increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add(clarg, "Display version information.", "--version", 'V', CLA_VERSION);
	dagclarg_add(clarg, "Pat watchdog only.", "--pat-only", 'p', CLA_PAT_ONLY);
	dagclarg_add_string(clarg, "DAG device to use (default is " DEFAULT_DAG_DEVICE ").", "--device", 'd', "device", uDagnameBuffer, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add_int(clarg, "Time in milliseconds before watchdog patting stops (default is 10s).", "--time", 't', "milliseconds", &uFailMilliseconds, CLA_TIME);
	dagclarg_add(clarg, "Don't expect any traffic on the link.", "--no-traffic", 'n', CLA_NO_TRAFFIC);

	/* Parse the command line options. */
	result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == result)
	{
		switch (code)
		{
		case CLA_DEVICE:
			if (-1 == dag_parse_name(uDagnameBuffer, uDagname, DAGNAME_BUFSIZE, &uDagStream))
			{
				dagutil_panic("dag_parse_name(%s): %s\n", uDagnameBuffer, strerror(errno));
			}
			dagutil_verbose("device=%s\n", uDagname);
			break;

		case CLA_HELP:
			print_usage(clarg);
			return EXIT_SUCCESS;
			break;

		case CLA_NO_TRAFFIC:
			uExpectTraffic = 0;
			break;

		case CLA_PAT_ONLY:
			uPatOnly = 1;
			break;

		case CLA_TIME:
			if (uFailMilliseconds <= 0)
			{
				uFailMilliseconds = DEFAULT_FAIL_MILLISECONDS;
			}
			dagutil_verbose("time=%d\n", uFailMilliseconds);
			break;

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

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

		default:
			if ((0 == strcmp(argv[argindex], "--usage")) || (0 == strcmp(argv[argindex], "-?")))
			{
				print_usage(clarg);
				return EXIT_SUCCESS;
			}
			else if (0 == strcmp(argv[argindex], "--verbose"))
			{
				dagutil_inc_verbosity();
				errorfile = stderr;
			}
			else if (argv[argindex][0] == '-')
			{
				/* Unknown option. */
				dagutil_error("unknown option %s\n", argv[argindex]); 
				dagclarg_display_usage(clarg, stdout);
				return EXIT_FAILURE;
			}
			break;
		}

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

	if (-1 == result)
	{
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]); 
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}
	
	/* Display unprocessed arguments if verbose flag was given. */
	unprocessed_argv = (const char* const*) dagclarg_get_unprocessed_args(clarg, (int*) &argc);
	if (unprocessed_argv && (0 < argc) && (0 < dagutil_get_verbosity()))
	{
		for (index = 0; index < argc; index++)
		{
			dagutil_verbose("unprocessed argument: '%s'\n", unprocessed_argv[index]);
		}
	}

	/* ClargPtr should no longer be necessary. */
	dagclarg_dispose(clarg);

	if (1 == uPatOnly)
	{
		/* Pat watchdog until stopped. */
		do_simple_watchdog_patting();
	}

	dagutil_verbose("[MAIN] Starting (pid = %d).\n", (int)getpid());

	uDagFd = dag_open(uDagname);
	if (uDagFd < 0)
	{
		dagutil_panic("dag_open(\"%s\"): %s\n", uDagname, strerror(errno));
	}

	uDagIOMemory = dag_iom(uDagFd);
	uDagRegisters = dag_regs(uDagFd);
	initialize_failsafe();

	result = dag_configure(uDagFd, "slen=64");
	if (-1 == result)
	{
		dagutil_panic("dag_configure(%d, \"slen=64\"): %s\n", uDagFd, strerror(errno));
	}

	result = dag_attach_stream(uDagFd, uDagStream, 0, 0);
	if ((intptr_t) MAP_FAILED == result)
	{
		dagutil_panic("dag_attach_stream(%d, %d, 0, 0): %s\n", uDagFd, uDagStream, strerror(errno));
	}

	result = dag_start_stream(uDagFd, uDagStream);
	if (-1 == result)
	{
		dagutil_panic("dag_start_stream(%d, %d): %s\n", uDagFd, uDagStream, strerror(errno));
	}

	/* Start monitor thread. */
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	result = pthread_create(&uMonitorThread, NULL, monitor_thread, NULL);
	if (0 != result)
	{
		dagutil_panic("could not create monitor thread: %s\n", strerror(result));
	}

	result = pthread_detach(uMonitorThread);
	if (0 != result)
	{
		dagutil_panic("could not detach monitor thread: %s\n", strerror(result));
	}

#else /* _WIN32 */

	if(CreateThread(NULL, 0, monitor_thread, NULL, 0, NULL) == NULL)
	{
		dagutil_panic("unable to start the monitor thread\n");
	}

#endif /* _WIN32 */

	if (1 == uExpectTraffic)
	{
		/* Capture and throw away data as fast as possible, 
		 * keeping a record of when the last data was received.
		 */
		uint8_t* top = NULL;
		uint8_t* bottom = NULL;
		
		while (1 == uRun)
		{
			top = dag_advance_stream(uDagFd, 0, &bottom);
	
			if (NULL == top)
			{
				dagutil_panic("dag_advance_stream %s:%u: %s\n", uDagname, uDagStream, strerror(dag_get_last_error()));
			}

			if (top != bottom)
			{
				/* Some data was received. */
				gettimeofday((struct timeval*) &uLastDataSeen, NULL);
				dagutil_verbose("[MAIN] seen data.\n");
			}

			bottom = top;
		}
	}
	else
	{
		/* No traffic.  All we can do is watch the relay energisation status. */
		/* Wait to see when relays become unenergized. */
		uint32_t val = dagiomread(uFailsafeBase);

		while (val & BIT16)
		{
			dagutil_verbose("[MAIN] relays energised.\n");
			gettimeofday((struct timeval*) &uLastDataSeen, NULL);
			usleep(WATCHDOG_PAT_MILLISECONDS * 1000);
			val = dagiomread(uFailsafeBase);

			if (0 == uRun)
			{
				/* Abort - probably due to a signal. */
				break;
			}
		}

		dagutil_verbose("[MAIN] relays NOT energised.\n");
	}

	while (1 == uMonitorThreadRunning)
	{
		/* Wait for monitor to exit. */
		sleep(1);
	}

	/* Clean up card. */
	result = dag_stop_stream(uDagFd, uDagStream);
	if (-1 == result)
	{
		dagutil_panic("dag_stop_stream(%d, %d): %s\n", uDagFd, uDagStream, strerror(errno));
	}

	result = dag_detach_stream(uDagFd, uDagStream);
	if (-1 == result)
	{
		dagutil_panic("dag_detach_stream(%d, %d): %s\n", uDagFd, uDagStream, strerror(errno));
	}

	result = dag_close(uDagFd);
	if (-1 == result)
	{
		dagutil_panic("dag_close(%d): %s\n", uDagFd, strerror(errno));
	}

	dagutil_verbose("[MAIN] finished.\n");

	return EXIT_SUCCESS;
}


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