/* General purpose software timer facilities
 * Copyright 1991 Phil Karn, KA9Q
 */
#include "global.h"
#include "timer.h"
#include "proc.h"
#include "daemon.h"
#include "hardware.h"
#include "commands.h"
#ifndef MSDOS
#include "session.h"
#include "socket.h"
#endif

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: timer.c,v 1.25 2001/05/06 16:32:58 brian Exp $";
#endif



/* Head of running timer chain.
 * The list of running timers is sorted in increasing order of expiration;
 * i.e., the first timer to expire is always at the head of the list.
 */
struct timer *Timers;
static int Timer_mutex = TNOS_MUTEX_UNLOCKED;

static void t_alarm (void *x);
static int valid_timer (struct timer *t, int exitOnErr);
void stopalltimers (struct proc *pp);
static void start_timer_internal (struct timer *t, struct proc *p);


struct timerstats {
	uint32 passes;
	uint32 started;
	uint32 stopped;
	uint32 expired;
	uint32 alarms;
};

static struct timerstats tstats = {0, 0, 0, 0, 0};



/* Process that handles clock ticks */
/* Fixed to solve some timing problems when multiple ticks
 * get handled at once... from Walt Corey, KZ1F
 */
void
timerproc (
int i OPTIONAL,
void *v1 OPTIONAL,
void *v2 OPTIONAL
) {
register struct timer *t;
char i_state;
int tmp;
void (**vf) (void);
int32 cur_clock;

	server_disconnect_io ();
	Timer_mutex = TNOS_MUTEX_UNLOCKED;
	for ( ; ; ) {
		kwait (NULL);	/* Let them run before handling more ticks */

		/* Atomic read and decrement of Tick */
		for ( ; ; ) {
			i_state = (char) disable ();
			tmp = Tick;
			if (tmp > 0) {
				Tick--;
				restore (i_state);
				break;
			}
			restore (i_state);
			kwait (&Tick);
		}
#ifndef TNOS_68K
		if (!istate ()) {
			restore (1);
#ifdef MSDOS
			tcmdprintf ("timer: ints were off!\n");
#endif
		}
#endif

		tstats.passes++;

		/* Call the functions listed in config.c */
		for (vf = Cfunc; *vf != NULL; vf++)
			(*vf) ();

		if (Timers == NULLTIMER)
			continue;	/* No active timers, all done */

		cur_clock = rdclock();
		kmutex_lock (&Timer_mutex);

		/* Now go through the list of expired timers, removing each
		 * one and kicking the notify function, if there is one
		 * Note use of subtraction and comparison to zero rather
		 * than the more obvious simple comparison; this avoids
		 * problems when the clock count wraps around.
		 */
		while (Timers != NULLTIMER && (cur_clock - Timers->expiration) >= 0) {
			if (Timers->next == Timers) {
				tcmdprintf ("PANIC: Timer loop at %lx\n", (long) Timers);
				iostop ();
				exit (1);
			}
			/* Save Timers since stop_timer will change it */
			t = Timers;
			Timers = t->next;

			t->state = TIMER_EXPIRE;
			t->next = NULLTIMER;
			tstats.expired++;
			if (valid_timer (t, 1) == 1)	{
				if (t->func)
					(*t->func) (t->arg);
			}
		}
		kmutex_unlock (&Timer_mutex);
	}
}



/* Start a timer - link to specific process */
static void
start_timer_internal (struct timer *t, struct proc *p)
{
register struct timer *tnext;
struct timer *tprev = NULLTIMER;

	if (t == NULLTIMER)
		return;

	if (t->next != NULLTIMER)
		stop_timer (t);

	if (t->duration == 0)
		return;		/* A duration value of 0 disables the timer */

	tstats.started++;
	t->next = NULLTIMER;	/* insure forward chain is NULL */
	t->expiration = rdclock() + t->duration;
	t->state = TIMER_RUN;

	t->magic1 = TIMER_MAGIC1;
	t->magic2 = TIMER_MAGIC2;
	t->theproc = (void *) p;

	/* Find right place on list for this guy. Once again, note use
	 * of subtraction and comparison with zero rather than direct
	 * comparison of expiration times.
	 */
	for (tnext = Timers; valid_timer(tnext, 1) == 1; tprev = tnext, tnext = tnext->next) {
		if ((tnext->expiration - t->expiration) >= 0)
			break;
	}

	if (tnext == t)
		tnext = NULLTIMER;

	/* At this point, tprev points to the entry that should go right
	 * before us, and tnext points to the entry just after us. Either or
	 * both may be null.
	 */
	if (tprev == NULLTIMER)
		Timers = t;	/* Put at beginning */
	else
		tprev->next = t;

	t->next = tnext;
}



/* Start a timer */
void
start_timer (struct timer *t)
{
	start_timer_internal (t, Curproc);
}


/* Start a timer, detached from the current process */
void
start_detached_timer (struct timer *timer)
{
	start_timer_internal (timer, Command->proc);
}



/* Stop a timer */
void
stop_timer (struct timer *timer)
{
register struct timer *t;
struct timer *tlast = NULLTIMER;

	if (!valid_timer(timer, 0) || !run_timer (timer))
		return;

	/* Verify that timer is really on list */
	for (t = Timers; valid_timer (t, 1) == 1; tlast = t, t = t->next)
		if (t == timer)
			break;

	if (t != NULLTIMER)	{
		tstats.stopped++;

		/* Delete from active timer list */
		if (tlast != NULLTIMER)
			tlast->next = t->next;
		else
			Timers = t->next;	/* Was first on list */

		t->state = TIMER_STOP;
		t->next = NULLTIMER;
	}
}



/* called from killproc to cleanup pending timers */
void
stopalltimers (struct proc *pp)
{
struct timer *t, *tmp;

	for (t = Timers; valid_timer (t, 0) == 1; t = tmp)	{
		tmp = t->next;
		if (t->theproc == pp)
			stop_timer (t);
	}
}



/* Return milliseconds remaining on this timer */
int32
read_timer (struct timer *t)
{
int32 remaining;

	if (t == NULLTIMER || !run_timer (t))
		return 0;
	remaining = t->expiration - rdclock();
	if (remaining <= 0)
		return 0;	/* Already expired */
	else
		return remaining * MSPTICK;
}



void
set_timer (struct timer *t, int32 interval)
{
#ifdef MSDOS
#define FUDGE 1
#else
#define FUDGE 0
#endif

	if (t == NULLTIMER)
		return;

	/* Round the interval up to the next full tick, and then
	 * add another tick to guarantee that the timeout will not
	 * occur before the interval is up. This is necessary because
	 * we're asynchonous with the system clock.
	 */
	if (interval != 0)
		t->duration = FUDGE + (interval + MSPTICK - 1) / MSPTICK;
	else
		t->duration = 0;
}



/* Delay process for specified number of milliseconds.
 * Normally returns 0; returns -1 if aborted by alarm.
 */
int
kpause (int32 ms)
{
int val = 0;

	if (Curproc == NULLPROC || ms == 0)
		return 0;
	kalarm (ms);
	/* The actual event doesn't matter, since we'll be alerted */
	while (Curproc->alarm.state == TIMER_RUN) {
		if ((val = kwait (Curproc)) != 0)
			break;
	}
	kalarm (0L);		/* Make sure it's stopped, in case we were killed */
	return (val == EALARM) ? 0 : -1;
}



static void
t_alarm (void *x)
{
	tstats.alarms++;
	alert ((struct proc *) x, EALARM);
}



/* Send signal to current process after specified number of milliseconds */
void
kalarm (int32 ms)
{
	if (Curproc != NULLPROC) {
		set_timer (&Curproc->alarm, ms);
		Curproc->alarm.func = t_alarm;
		Curproc->alarm.arg = (char *) Curproc;
		start_timer (&Curproc->alarm);
	}
}



/* Convert time count in seconds to printable days:hr:min:sec format */
char *
tformat (int32 t)
{
static char buf[17];
int days, hrs, mins, secs;
int minus = 0;

	if (t < 0) {
		t = -t;
		minus = 1;
	}

	secs = t % 60;
	t /= 60;
	mins = t % 60;
	t /= 60;
	hrs = t % 24;
	days = t / 24;

	sprintf (buf, "%s%d:%02d:%02d:%02d", (minus) ? "-" : "", days, hrs, mins, secs);
	return buf;
}



/* Read the Clock global variable, with interrupts off to avoid possible
 * inconsistency on 16-bit machines
 */
int32
rdclock(void)
{
int i_state;
int32 rval;

	i_state = disable ();
	rval = Clock;
	restore (i_state);
	return rval;
}



static int
valid_timer (struct timer *t, int exitOnErr)
{
struct proc *theps;

	if (t == NULLTIMER)
		return 0;
	if (t->magic1 != TIMER_MAGIC1 || t->magic2 != TIMER_MAGIC2)	{
		if (exitOnErr && shall_we_crash())
			crash_it_already ("corrupted timer table 1");
		return -1;
	}

	theps = (struct proc *) t->theproc;
	if (theps == NULLPROC || theps->name == NULLCHAR)	{
		if (exitOnErr && shall_we_crash())
			crash_it_already ("corrupted timer table 2");
		return -1;
	}		
	return 1;
}



/* Display the contents of the timer queue in human-readable format */

int
dotimers (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
struct timer *t;
int sentheader = 0;
struct proc *theps;
int32 exp;

	tprintf ("\nTimer Stats:\n    passes %lu started %lu stopped %lu\n    expired %lu alarms %lu\n",
		tstats.passes, tstats.started, tstats.stopped, tstats.expired, tstats.alarms);


	/* in order to make sure that this routine and timerproc are NOT
	   fighting for the timer queue, we put a mutex around it. */
	kmutex_lock (&Timer_mutex);
	for (t = Timers; t != NULLTIMER; t = t->next)	{
		theps = (struct proc *) t->theproc;
		if (!valid_timer (t, 1))	{
			stop_timer (t);
			break;
		}
		if (!sentheader)	{
			sentheader++;
			tprintf ("\n%-42s    %-10s    %s\n", "Process", "  Duration", "  Expires At");
			tprintf (  "%-42s    %-10s    %s\n", "=======", "  ========", "==============");
		}
		exp = t->expiration * MSPTICK;
		tprintf ("%-42.42s    %10ld    %s.%03ld\n", theps->name, dur_timer(t), 
			tformat (exp / 1000), exp % 1000);
	}
	kmutex_unlock (&Timer_mutex);
	tputc ('\n');
	return 0;
}

