#include <sys/fcntl.h>
#include <sys/open.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <sys/signal.h>
#include <sys/user.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/edt.h>
#include <sys/sysmacros.h>

#include "rci.h"
#include "rciioctl.h"
#include "genrcireg.h"

extern int genrci_internal_major;
extern GenrciSoftc genrcii[];

static onTimeout();
static resetTimerQueue();
static queueTimerEvent();
static getTimerEvent();

static onTimeout(arg)
caddr_t arg;
{
	/* Timeout routine. Implements a private 'hardclock' routine
	   that keeps track of the time ('tick') and queues events
	   (alarms and beat pulses) when they come due. */

	register GenrciSoftc *info = (GenrciSoftc*)arg;
	int req = 0;

	info->tid = timeout (onTimeout, arg, 1); /* reset timeout right away */
	info->tidset = 1; /* isn't this unnecessary? */
	info->tick++;

	if (info->alarmTick >= 0 && info->alarmTick <= info->tick)
	 { req |= RCI_ALARM_READY;
	   info->alarmTick = -1;
	 }
	if (info->beatTick >= 0 && info->beatTick <= info->tick)
	 { req |= RCI_BEAT_READY;
	   info->beatTick += info->beatPeriod;
	 }
	if (req)
	 { if (queueTimerEvent(&info->tq, req, info->tick))
	    { pollwakeup (&info->inpq);
	    }
	 }
}

genrciinit()
{
}

genrciopen(dev,flag,otyp)
dev_t dev;
int flag;
int otyp;
{
	/* This device can only be openned by one process at a time;
	   this mainly makes the software a bit easier to implement. */
	
      	GenrciSoftc *info;
	int unit;
	int s;

	info = &genrcii[minor(dev)];

	s = splhi();
	if (info->flags & GENRCI_OPEN)
	 { u.u_error = EBUSY;
	   splx(s);
	   return;
	 }
	else
	 { info->flags |= GENRCI_OPEN;
	 }
	splx(s);

	info->beatTick = -1;
	info->beatPeriod = 0;
	info->alarmTick = -1;
	info->tick = 0;
	info->tidset = 0;
	resetTimerQueue(&info->tq,TQ_DEFAULT_SIZE);

	printf("genrciopen\n");
}

genrciclose(dev,flag,otyp)
dev_t dev;
int flag;
int otyp;
{
	/* When closing the device, be sure to clear any pending 
	   timeouts. */

	GenrciSoftc *info;
	int s;

	info = &genrcii[minor(dev)];
	if (info->tidset)
	 { untimeout (info->tid);
	   info->tidset = 0;
	 }
	s = splhi();
	info->flags &= ~(GENRCI_OPEN);
	splx(s);
}

genrciread(dev)
dev_t dev;
{
	/* If there are any timer events pending, then return
	   the first one in the queue. Otherwise, return nothing. */

	GenrciSoftc *info;
	RciTimerEvent tevent;

	info = &genrcii[minor(dev)];
	if (getTimerEvent(&info->tq, &tevent))
	 { if (copyout (&tevent, u.u_base, sizeof(RciTimerEvent)))
	    { u.u_error = EFAULT;
	      return;
	    }
	   u.u_count -= sizeof(RciTimerEvent);
	 }
}

genrciwrite(dev)
dev_t dev;
{
	u.u_error = ENXIO;
}

genrcipoll(dev, events)
dev_t dev;
int *events;
{
	/* If there are no timer events pending, the queue the process
	   for a possible select wakeup. */

	GenrciSoftc *info;
	int ready, s;

	info = &genrcii[minor(dev)];

	ready = *events;
	if (ready & POLLIN)
	 { s = splhi();
	   if (TIMER_QUEUE_EMPTY(&info->tq))
	    { pollqueue (&info->inpq, u.u_procp);
	      *events &= ~POLLIN;
	    }
	   splx(s);
	 }
}

genrciioctl(dev,cmd,arg)
dev_t dev;
int cmd, arg;
{
	GenrciSoftc *info;

	info = &genrcii[minor(dev)];
	switch (cmd)
	 { default:
	    { u.u_error = ENOTTY;
	      break;
	    }
	   case RCIIOC_CONFIRM:
	    { break;
	    }
	   case RCIIOC_TIMER_ENABLE:
	    { int enable = arg;

	      if (enable && !info->tidset)
	       { info->tid = timeout (onTimeout, (caddr_t)info, 1);
		 info->tidset = 1;
	       }
	      else if (!enable && info->tidset)
	       { untimeout (info->tid);
		 info->tidset = 0;
	       }
	      break;
	    }
	   case RCIIOC_TIMER_GET:
	    { int tickval;

	      tickval = info->tick; /* hope this is atomic */
	      if (copyout((char*)&tickval, (char*)arg, sizeof(int)))
	       { u.u_error = EFAULT;
	       }
	      break;
	    }
	   case RCIIOC_TIMER_SET:
	    { int tick = arg;

	      if (tick < 0)
	       { u.u_error = EINVAL;
		 break;
	       }
	      info->tick = tick; /* hope this is atomic */
	      break;
	    }
	   case RCIIOC_BEAT_REQ:
	    { int period = arg;
	      int s;

	      if (period < 0)
	       { u.u_error = EINVAL;
		 break;
	       }
	      s = splhi();
	      if (period)
	       { info->beatPeriod = period;
		 info->beatTick = info->tick + period;
	       }
	      else
	       { info->beatTick = -1;
		 info->beatPeriod = 0;
	       }
	      splx(s);
	      break;
	    }
	   case RCIIOC_ALARM_REQ:
	    { int ticks = arg;
	      int s;

	      if (ticks < 0)
	       { u.u_error = EINVAL;
		 break;
	       }
	      s = splhi();
	      if (ticks <= info->tick)
	       { if (queueTimerEvent(&info->tq, RCI_ALARM_READY, info->tick))
		  { pollwakeup (&info->inpq);
		  }
	       }
	      else
	       { info->alarmTick = ticks;
	       }
	      splx(s);

	      break;
	    }
	   case RCIIOC_SET_TIMERQ:
	    { int size = arg;

	      if ((size < 1) || (size > TQ_MAX_SIZE))
	       { u.u_error = EINVAL;
		 break;
	       }
	      resetTimerQueue (&info->tq, size);
	      break;
	    }
	   case RCIIOC_GET_TIMERQ:
	    { int size;

	      size = info->tq.size;
	      if (copyout ((char*)&size, (char*)arg, sizeof(int)))
	       { u.u_error = EFAULT;
		 break;
	       }
	      break;
	    }
	 }
}

static resetTimerQueue(tq,size)
RciTimerQueue *tq;
int size;
{
	/* Clear a timer event queue and set its size to the
	   specified value. */

	int s;

	s = splhi();
	tq->size = size;
	tq->head = 0;
	tq->tail = 0;
	splx(s);
}

static queueTimerEvent(tq,req,tick)
register RciTimerQueue *tq;
int req;
int tick;
{
	/* Queue a timer event, if there is room on the queue, by
	   advancing the tail and adding it there. */

	int newtail;

	newtail = tq->tail + 1;
	if (newtail > tq->size) newtail = 0;
	if (newtail != tq->head)
	 { tq->buf[tq->tail].req = req;
	   tq->buf[tq->tail].tick = tick;
	   tq->tail = newtail;
	   return (1);
	 }
	else
	 { return (0);
	 }
}

static getTimerEvent(tq, event)
register RciTimerQueue *tq;
RciTimerEvent *event;
{
	/* Remove a timer event from the queue, 
	   if there are any there. */

	if (tq->head != tq->tail)
	 { int newhead;
	   event->req = tq->buf[tq->head].req;
	   event->tick = tq->buf[tq->head].tick;
	   newhead = tq->head + 1;
	   if (newhead > tq->size) newhead = 0;
	   tq->head = newhead;
	   return (1);
	 }
	else
	 { return (0);
	 }
}
