#include "watchdog.h"
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/watchdog.h>

using namespace MityDSP;

/**
 *  Constructor for the watchdog abstraction
 *
 *  \param apDevice filename of device (defaults to /dev/watchdog)
 */
tcWatchdog::tcWatchdog(const char* apDevice)
: mnMask(0)
, mnSet(0)
, mnFd(-1)
{
	sem_init(&mhSem, 0, 1);
	mpFileName = new char[strlen(apDevice)+1];
	strcpy(mpFileName, apDevice);
}

/**
 *  Destructor.  Shuts down running watchdog.  cleans up resources.
 */
tcWatchdog::~tcWatchdog(void)
{
	const char *no_reboot = "V\n";
	if (mnFd >= 0)
	{
		printf("shutting down watchdog\n");
		write(mnFd,no_reboot,strlen(no_reboot));
		close(mnFd);
	}
	delete [] mpFileName;
	sem_destroy(&mhSem);
}

/**
 *   Enable the watchdog.  This should typically be
 *   called after at least 1 checkpoint has been registered
 *   and the application is ready for the watchdog operation
 *   to begin.
 *
 *   \param TimeoutSecs number of seconds before watchdog timeout
 *   \return non-zero on error.
 */
int tcWatchdog::StartWatchDog(int TimeoutSecs)
{
	mnFd = open(mpFileName, O_RDWR);
	if (mnFd < 0)
	{
		perror("open watchdog:");
		return mnFd;
	}
	// NOTE: L138 watchdog driver doesn't support WDIOC_SETTIMEOUT.
	//  Timeout defaults to 60 seconds
	ioctl(mnFd,WDIOC_SETTIMEOUT,&TimeoutSecs);
	return 0;
}

/**
 *  Register a checkpoint.  The tcWatchdog class supports multiple
 *  checkpoints (or threads, etc.) that must Checkin() prior to the
 *  watchdog being tagged.  This routine generates a handle for a 
 *  newly registered checkpoint.
 *
 *  \return handle to use for Checkin() or -1 on failure.
 */
int tcWatchdog::RegisterCheckpoint(void)
{
	int i, mask;
	int rv = -1;
	sem_wait(&mhSem);
	/* find next available slot in mask */
	for (i = 0, mask=1; i < 31; i++, mask<<=1)
	{
		if (!(mask & mnMask))
		{
			mnMask |= mask;
			rv = mask;
			break;
		}
	}
	sem_post(&mhSem);
	return rv;
}

/**
 *  Remove a registered checkpoint from the monitor list.
 *  \param handle the checkpoint previously from RegisterCheckpoint
 *  \return non-zero on error.
 */
int tcWatchdog::UnRegisterCheckpoint(int handle)
{
	int rv = -1;
	sem_wait(&mhSem);
	if (handle & mnMask)
	{
		mnMask &= ~handle;
		rv = 0;
	}
	sem_post(&mhSem);
	return rv;
}

/**
 *  Check in for the watchdog update.  When all registered 
 *  methods have checked in, the watchdog timer will be
 *  tagged/reset.
 *
 *  \param Checkpoint checkpoint handle from RegisterCheckpoint
 *
 *  \return non-zero on error.
 */
int tcWatchdog::Checkin(int Checkpoint)
{
	if (mnFd < 0)
		return -1;

	sem_wait(&mhSem);
	mnSet |= Checkpoint;
	if (mnSet == mnMask)
	{
		/* tag watchdog */
		mnSet = 0;
		ioctl(mnFd, WDIOC_KEEPALIVE, 0);
	}
	sem_post(&mhSem);

	return 0;
}

#ifdef WATCHDOG_MAIN
int main()
{
	tcWatchdog watch;
	int watchHandle;

	printf("Starting watchdog! Timeout interval 60s.\n");
	watch.StartWatchDog(60000);
	watchHandle = watch.RegisterCheckpoint();

	while (1 == 1)
	{
		printf(".");
		fflush(stdout);

		// Tickle watchdog
		watch.Checkin(watchHandle);
		// Sleep 1 second
		sleep(1);
	}

	return 0;
}
#endif
