/*
 * This file has been modified as part of the MH-MiNT project. See
 * the file Changes.MH for details and dates.
 */

/*
Copyright 1990,1991,1992 Eric R. Smith.
Copyright 1992,1993,1994 Atari Corporation.
All rights reserved.
*/

#include "mint.h"

/*
 * We initialize proc_clock to a very large value so that we don't have
 * to worry about unexpected process switches while starting up
 */

short proc_clock = 0x7fff;

/* used by filesystems for time/date stamps; updated once per second */
short timestamp, datestamp;

extern short in_kernel;	/* in main.c */

static void unnapme P_((PROC *));
static TIMEOUT	*newtimeout P_((short));
static void	disposetimeout P_((TIMEOUT *));
static void	inserttimeout P_ ((TIMEOUT *, long));
static void     dispose_old_timeouts P_ ((void));

#define TIMEOUTS	20	/* # of static timeout structs */
#define TIMEOUT_USED	0x01	/* timeout struct is in use */
#define TIMEOUT_STATIC	0x02	/* this is a static timeout */

/* This gets implizitly initialized to zero, thus the flags are
 * set up correctly.
 */
static TIMEOUT timeouts[TIMEOUTS] = { { 0, }, };
TIMEOUT *tlist = NULL;
TIMEOUT *expire_list = NULL;

/* Number of ticks after that an expired timeout is considered to be old
   and disposed automatically.  */
#define TIMEOUT_EXPIRE_LIMIT 400 /* 2 secs */

static TIMEOUT *
newtimeout(fromlist)
	short fromlist;
{
	TIMEOUT *t;
	short i, sr;

	if (!fromlist) {
		t = kmalloc(SIZEOF(TIMEOUT));
		if (t) {
			t->flags = 0;
			t->arg = 0;
			return t;
		}
	}
	sr = spl7();
	for (i = 0; i < TIMEOUTS; ++i) {
		if (!(timeouts[i].flags & TIMEOUT_USED)) {
			timeouts[i].flags |= (TIMEOUT_STATIC|TIMEOUT_USED);
			spl(sr);
			timeouts[i].arg = 0;
			return &timeouts[i];
		}
	}
	spl(sr);
	return 0;
}

static void
disposetimeout(t)
	TIMEOUT *t;
{
	if (t->flags & TIMEOUT_STATIC) t->flags &= ~TIMEOUT_USED;
	else kfree(t);
}

static void
dispose_old_timeouts ()
{
  TIMEOUT *t, **prev, *old;
  long now = *(long *) 0x4ba;
  short sr = spl7 ();

  for (prev = &expire_list, t = *prev; t; prev = &t->next, t = *prev)
    {
      if (t->when < now)
	{
	  /* This and the following timeouts are too old. Throw them away. */
	  *prev = 0;
	  spl (sr);
	  while (t)
	    {
	      old = t;
	      t = t->next;
	      disposetimeout (old);
	    }
	  return;
	}
    }
  spl (sr);
}

static void
inserttimeout(t, delta)
	TIMEOUT *t;
	long delta;
{
	TIMEOUT **prev, *cur;
	short sr = spl7();

	cur = tlist;
	prev = &tlist;
	while (cur) {
		if (cur->when >= delta) {
			cur->when -= delta;
			t->next = cur;
			t->when = delta;
			*prev = t;
			spl(sr);
			return;
		}
		delta -= cur->when;
		prev = &cur->next;
		cur = cur->next;
	}
	assert(delta >= 0);
	t->when = delta;
	t->next = cur;
	*prev = t;
	spl(sr);
}
	
/*
 * addtimeout(long delta, void (*func)()): schedule a timeout for the current
 * process, to take place in "delta" milliseconds. "func" specifies a
 * function to be called at that time; the function is passed as a parameter
 * the process for which the timeout was specified (i.e. the value of
 * curproc at the time addtimeout() was called; note that this is probably
 * *not* the current process when the timeout occurs).
 *
 * NOTE: if kernel memory is low, newtimeout() will try to get a statically
 * allocated timeout struct (fallback method).
 */

TIMEOUT * ARGS_ON_STACK
addtimeout(delta, func)
	long delta;
	void (*func) P_((PROC *));
{
	TIMEOUT *t;
	TIMEOUT **prev;
	short sr;

	/* Try to reuse an already expired timeout that had the
	   same function attached */
	sr = spl7();
	prev = &expire_list;
	for (t = *prev; t != NULL; prev = &t->next, t = *prev)
	  if (t->proc == curproc && t->func == func)
	    {
	      *prev = t->next;
	      break;
	    }

	spl(sr);
	if (t == NULL)
	  t = newtimeout(0);

/* BUG: we should have some fallback mechanism for timeouts when the
   kernel memory is exhausted
 */
	assert(t);

	t->proc = curproc;
	t->func = func;
	inserttimeout(t, delta);
	return t;
}

/*
 * addroottimeout(long delta, void (*)(PROC *), short flags);
 * Same as addtimeout(), except that the timeout is attached to Pid 0 (MiNT).
 * This means the timeout won't be cancelled if the process which was
 * running at the time addroottimeout() was called exits.
 *
 * Currently only bit 0 of `flags' is used. Meaning:
 * Bit 0 set: Call from interrupt (cannot use kmalloc, use statically
 *	allocated `struct timeout' instead).
 * Bit 0 clear: Not called from interrupt, can use kmalloc.
 *
 * Thus addroottimeout() can be called from interrupts (bit 0 of flags set),
 * which makes it *extremly* useful for device drivers.
 * A serial device driver would make an addroottimeout(0, check_keys, 1)
 * if some bytes have arrived.
 * check_keys() is then called at the next context switch, can use all
 * the kernel functions and can do time cosuming jobs.
 */

TIMEOUT * ARGS_ON_STACK
addroottimeout(delta, func, flags)
	long delta;
	void (*func) P_((PROC *));
	short flags;
{
	TIMEOUT *t;
	TIMEOUT **prev;
	short sr;

	/* Try to reuse an already expired timeout that had the
	   same function attached */
	sr = spl7();
	prev = &expire_list;
	for (t = *prev; t != NULL; t = *prev)
	  {
	    if (t->proc == rootproc && t->func == func)
	      {
		*prev = t->next;
		break;
	      }
	    prev = &t->next;
	  }
	spl(sr);

	if (!t)
	  t = newtimeout(flags & 1);

	if (!t) return NULL;
	t->proc = rootproc;
	t->func = func;
	inserttimeout(t, delta);
	return t;
}

/*
 * cancelalltimeouts(): cancels all pending timeouts for the current
 * process
 */

void ARGS_ON_STACK
cancelalltimeouts()
{
	TIMEOUT *cur, **prev, *old;
	long delta;
	short sr = spl7 ();

	cur = tlist;
	prev = &tlist;
	while (cur) {
		if (cur->proc == curproc) {
			delta = cur->when;
			old = cur;
			*prev = cur = cur->next;
			if (cur) cur->when += delta;
			spl(sr);
			disposetimeout(old);
			sr = spl7();
		/* ++kay: just in case an interrupt handler installed a
		 * timeout right after `prev' and before `cur' */
			cur = *prev;
		}
		else {
			prev = &cur->next;
			cur = cur->next;
		}
	}
	prev = &expire_list;
	for (cur = *prev; cur; cur = *prev)
	  {
	    if (cur->proc == curproc)
	      {
		*prev = cur->next;
		spl (sr);
		disposetimeout (cur);
		sr = spl7 ();
	      }
	    else
	      prev = &cur->next;
	  }
	spl (sr);
}

/*
 * Cancel a specific timeout. If the timeout isn't on the list, or isn't
 * for this process, we do nothing; otherwise, we cancel the time out
 * and then free the memory it used. *NOTE*: it's very possible (indeed
 * likely) that "this" was already removed from the list and disposed of
 * by the timeout processing routines, so it's important that we check
 * for it's presence in the list and do absolutely nothing if we don't
 * find it there!
 */

void ARGS_ON_STACK
canceltimeout(this)
	TIMEOUT *this;
{
	TIMEOUT *cur, **prev;
	short sr = spl7();

	/* First look at the list of expired timeouts */
	prev = &expire_list;
	for (cur = *prev; cur; cur = *prev)
	  {
	    if (cur == this && cur->proc == curproc)
	      {
		*prev = cur->next;
		spl (sr);
		disposetimeout (this);
		return;
	      }
	    prev = &cur->next;
	  }

	prev = &tlist;
	for (cur = tlist; cur; cur = cur->next) {
		if (cur == this && cur->proc == curproc) {
			*prev = cur->next;
			if (cur->next) {
				cur->next->when += this->when;
			}
			spl (sr);
			disposetimeout(this);
			return;
		}
		prev = &cur->next;
	}
	spl(sr);
}

void ARGS_ON_STACK
cancelroottimeout(this)
	TIMEOUT *this;
{
	TIMEOUT *cur, **prev;
	short sr = spl7();

	/* First look at the list of expired timeouts */
	prev = &expire_list;
	for (cur = *prev; cur; cur = *prev)
	  {
	    if (cur == this && cur->proc == rootproc)
	      {
		*prev = cur->next;
		spl (sr);
		disposetimeout (this);
		return;
	      }
	    prev = &cur->next;
	  }
	
	prev = &tlist;
	for (cur = tlist; cur; cur = cur->next) {
		if (cur == this && (cur->proc == rootproc)) {
			*prev = cur->next;
			if (cur->next) {
				cur->next->when += this->when;
			}
			spl (sr);
			disposetimeout(this);
			return;
		}
		prev = &cur->next;
	}
	spl(sr);
}

/*
 * timeout: called every 20 ms or so by GEMDOS, this routine
 * is responsible for maintaining process times and such.
 * it should also decrement the "proc_clock" variable, but
 * should *not* take any action when it reaches 0 (the state of the
 * stack is too uncertain, and time is too critical). Instead,
 * a vbl routine checks periodically and if "proc_clock" is 0
 * suspends the current process
 */

volatile int our_clock = 1000;

/* variables for monitoring the keyboard */
extern IOREC_T	*keyrec;	/* keyboard i/o record pointer */
extern short	kintr;		/* keyboard interrupt pending (see intr.s) */

void ARGS_ON_STACK
timeout()
{
	int ms;		/* time between ticks */

	kintr = keyrec->head != keyrec->tail;

	ms = *((short *)0x442L);
	if (proc_clock > 0)
		proc_clock--;

	our_clock -= ms;
	if (tlist) {
		tlist->when -= ms;
	}
}

/*
 * sleep() calls this routine to check on alarms and other sorts
 * of time-outs on every context switch.
 */

void
checkalarms()
{
	extern long searchtime;		/* in dosdir.c */
	PROC *p;
	long delta;
	void (*evnt) P_((PROC *, long arg));
	TIMEOUT *old;
	short sr;
	long arg;

/* do the once per second things */
	while (our_clock < 0) {
		our_clock += 1000;
		timestamp = Tgettime();
		datestamp = Tgetdate();
		searchtime++;
		reset_priorities();
	}

	sr = spl7();
/* see if there are outstanding timeout requests to do */
	while (tlist && ((delta = tlist->when) <= 0)) {
		p = tlist->proc;
/* hack: pass an extra long as arg, those intrested in it will need
 * a cast and have to place it in t->arg themselves but that way
 * everything else still works without change	-nox */
		arg = tlist->arg;
		evnt = (void (*)P_((PROC *, long)))tlist->func;
		old = tlist;
		tlist = tlist->next;
/* if delta < 0, it's possible that the time has come for the next timeout
 * to occur.
 * ++kay: moved this before the timeout fuction is called, in case the
 * timeout function installes a new timeout. */
		if (tlist)
			tlist->when += delta;
		old->next = expire_list;
		old->when = *(long *) 0x4ba + TIMEOUT_EXPIRE_LIMIT;
		expire_list = old;
		spl(sr);
/* ++kay: debug output at spl7 hangs the system, so moved it here */
		TRACE(("doing timeout code for pid %d", p->pid));

	/* call the timeout function */
		(*evnt)(p, arg);
		sr = spl7();
	}
	spl(sr);
	/* Now look at the expired timeouts if some are getting old */
	dispose_old_timeouts ();
}

/*
 * nap(n): nap for n milliseconds. Used in loops where we're waiting for
 * an event. If we expect the event *very* soon, we should use yield
 * instead.
 * NOTE: we may not sleep for exactly n milliseconds; signals can wake
 * us earlier, and the vagaries of process scheduling may cause us to
 * oversleep...
 */

static void
unnapme(p)
	PROC *p;
{
	short sr = spl7();
	if (p->wait_q == SELECT_Q && p->wait_cond == (long)nap) {
		rm_q(SELECT_Q, p);
		add_q(READY_Q, p);
		p->wait_cond = 0;
	}
	spl(sr);
}

void ARGS_ON_STACK 
nap(n)
	unsigned n;
{
	TIMEOUT *t;

	t = addtimeout((long)n, unnapme);
	sleep(SELECT_Q, (long)nap);
	canceltimeout(t);
}
