/*
** ppc.library emulation
** (c)1998-99 Frank Wille <frank@phoenix.owl.de>
**
** PowerUp kernel TimerObject support
**
** V0.5d (28.03.1999) phx
**       Moved kernel_init/exit() to kernelinit.c
**       Renamed from kerntasks.c to kerntimerobj.c
** V0.4b (05.01.1999) phx
**       created
*/

#include "ppclibemu.h"
#include "timerobject.h"
#include <exec/memory.h>
#include "warpos_protos.h"


const char *TimerObjSrvName = "ppcemu TimerObjSrv";


extern void DPrintf(struct PPCLibBase *,char *,...);
static void enqTimObj(struct MinList *,struct PPCTimerObject *);



/**********************/
/* TimerObject Server */
/**********************/


struct MsgPortPPC *InitTOMsg(struct PPCBase *wosbase,struct TimObjSrvMsg *msg,
                             struct PPCTimerObject *to,UWORD cmd)
/* Initialize a TimObjSrvMsg, ready for PutMsgPPC(). */
/* If to==0, then allocate a temporary MsgPort and return a pointer to it. */
{
  struct MsgPortPPC *rp;

  _memset(msg,sizeof(struct TimObjSrvMsg),0);
  msg->to_Msg.mn_Length = sizeof(struct TimObjSrvMsg);
  msg->to_Cmd = cmd;
  if (!(msg->to_Ptr = to))
    rp = __CreateMsgPortPPC(wosbase);  /* create temporary reply port */
  else
    rp = to->replyport;
  __SetReplyPortPPC(wosbase,(struct Message *)msg,rp);
  return (rp);
}


static void enqTimObj(struct MinList *list,struct PPCTimerObject *newto)
/* enqueue new timer object in list, sorted by time */
{
  struct PPCTimerObject *to = (struct PPCTimerObject *)list->mlh_Head;
  struct PPCTimerObject *nextto;

  while (nextto = (struct PPCTimerObject *)to->to_Node.mln_Succ) {
    if (_cmp64(newto->StopTime,to->StopTime) < 0)
      break;
    to = nextto;
  }
  _insertbefore(newto,to);
  newto->to_Flags |= TOF_Enqueued;
}


void GetPPCTime(struct PPCBase *wosbase,ULONG *time)
/* get current PowerPC time in microseconds as 64bit-int */
{
  struct timeval tv;

  __GetSysTimePPC(wosbase,&tv);
  _mulu64(time,TO_1S,tv.tv_secs);
  tv.tv_secs = 0;
  _add64(time,&tv.tv_secs); /* add micros */
}


void GetDiffTime(struct PPCTimerObject *to,ULONG *dt)
/* get StopTime-StartTime difference */
{
  dt[0] = to->StopTime[0];
  dt[1] = to->StopTime[1];
  _sub64(dt,to->StartTime);
}


BOOL SendTOMsg(struct PPCBase *wosbase,struct PPCTimerObject *to,UWORD cmd)
/* send an ADD or REM command to the server */
{
  struct MsgPortPPC *to_mp;

  if (to_mp = __FindPortPPC(wosbase,(STRPTR)TimerObjSrvName)) {
    struct TimObjSrvMsg to_msg;

    InitTOMsg(wosbase,&to_msg,to,cmd);
    __PutMsgPPC(wosbase,to_mp,(struct Message *)&to_msg);
    while (to_msg.to_Msg.mn_Node.ln_Type != NT_REPLYMSG)
      __WaitPortPPC(wosbase,to->replyport);
    _remove(&to_msg);
    return (TRUE);
  }

  return (FALSE);
}


struct PPCTimerObject *CreateTimObj(struct PPCBase *wosbase,ULONG ticks,
                                    ULONG *err)
/* Allocate and initialize TimerObject. */
/* If ticks!=0, then it's a async. 50Hz timer */
{
  struct PPCTimerObject *to;
  ULONG rc = 1;  /* out of memory */

  if (to = __AllocVecPPC(wosbase,sizeof(struct PPCTimerObject),
                         MEMF_CLEAR|MEMF_FAST|MEMF_PUBLIC,0)) {
    if (ticks) {
      if (to->replyport = __CreateMsgPortPPC(wosbase)) {
        /* it's a 50Hz notification timer */
        to->to_Flags = TOF_50Hz;
        _mulu64(to->WaitPeriod,TO_TICK,ticks);
        to->NotifTask = __FindTaskPPC(wosbase,NULL);
        rc = 0;
      }
      else
        __FreeVecPPC(wosbase,to);
    }
    else
      rc = 0;
  }
  if (err)
    *err = rc;
  return (to);
}


void DeleteTimObj(struct PPCBase *wosbase,struct PPCTimerObject *to)
/* Delete a TimerObject */
{
  if (to) {
    /* check if it's a waiting notification timer object */
    if ((to->to_Flags & (TOF_50Hz|TOF_Enqueued)) ==
        (TOF_50Hz|TOF_Enqueued))
      SendTOMsg(wosbase,to,TOCMD_REM);

    __DeleteMsgPortPPC(wosbase,to->replyport);
    __FreeVecPPC(wosbase,to);
  }
}


void TimerObjectServer(struct PPCLibBase *ppcbase,
                       struct TaskPPC *mother,ULONG sigmsk)
/* The PPC server task for TimerObjects. r2 is initialized. */
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  struct TaskPPC *mytask = __FindTaskPPC(wosbase,NULL);
  char *myname = mytask->tp_Task.tc_Node.ln_Name;
  struct MsgPortPPC *TimObjPort;

  DPrintf(ppcbase,"%s: launched\n",myname);

  if (TimObjPort = __CreateMsgPortPPC(wosbase)) {
    struct MinList TimObjList;
    struct TimObjSrvMsg *msg;
    ULONG portmask = 1L << (ULONG)TimObjPort->mp_Port.mp_SigBit;
    struct PPCTimerObject *to;
    BOOL exitflag = FALSE;
    ULONG waittime[2];

    _newlist(&TimObjList);
    TimObjPort->mp_Port.mp_Node.ln_Pri = 0;
    TimObjPort->mp_Port.mp_Node.ln_Name = (char *)TimerObjSrvName;
    __AddPortPPC(wosbase,TimObjPort);  /* make port globally visible */
    /* tell mother that we're ready */
    __SignalPPC(wosbase,mother,sigmsk);
    DPrintf(ppcbase,"%s: initialized\n",myname);

    /* main loop - wait for incoming messages */
    for (;;) {
      while (msg = (struct TimObjSrvMsg *)__GetMsgPPC(wosbase,TimObjPort)) {
        to = msg->to_Ptr;
        switch (msg->to_Cmd) {

          case TOCMD_EXIT:
            exitflag = TRUE;
            break;

          case TOCMD_ADD:
            if (to) {
              if (((to->to_Flags & (TOF_50Hz|TOF_Enqueued)) == TOF_50Hz)
                  && to->NotifTask) {
                GetPPCTime(wosbase,to->StartTime);
                to->StopTime[0] = to->StartTime[0];
                to->StopTime[1] = to->StartTime[1];
                _add64(to->StopTime,to->WaitPeriod);
                enqTimObj(&TimObjList,to);
              }
            }
            break;

          case TOCMD_REM:
            if (to) {
              if ((to->to_Flags & (TOF_50Hz|TOF_Enqueued))
                  == (TOF_50Hz|TOF_Enqueued)) {
                _remove(to);
                to->to_Flags &= ~TOF_Enqueued;
              }
            }
            break;
        }
        __ReplyMsgPPC(wosbase,(struct Message *)msg);
      }

      if (exitflag)  /* library was expunged - stop server task */
        break;

      /* check if the alarm-time for any TimerObject is reached */
      while (to = _head(&TimObjList)) {
        ULONG tim[2];

        GetPPCTime(wosbase,tim);
        if (_cmp64(to->StopTime,tim) > 0) {
          waittime[0] = to->StopTime[0];
          waittime[1] = to->StopTime[1];
          _sub64(waittime,tim);
          break;  /* no more tasks to notifiy for now */
        }

        /* notify owner-task of TimerObject */
        _remove(to);
        to->to_Flags &= ~TOF_Enqueued;
        __SignalPPC(wosbase,to->NotifTask,to->NotifMask);

        if (((to->to_Flags & (TOF_50Hz|TOF_Enqueued|TOF_AutoRemove))
             == TOF_50Hz) && to->NotifTask) {
          /* calculate next alarm time, reenqueue TimerObject */
          to->StartTime[0] = to->StopTime[0];
          to->StartTime[1] = to->StopTime[1];
          _add64(to->StopTime,to->WaitPeriod);
          enqTimObj(&TimObjList,to);
        }
      }

      /* If a TimerObject is waiting, then don't miss its alarm-time */
      if (to)
        __WaitTime(wosbase,portmask,waittime[1]);
      else
        __WaitPPC(wosbase,portmask); /* list empty: just wait for next msg */
    }

    /* task stops when library is expunged, there should be no msgs left! */
    __RemPortPPC(wosbase,TimObjPort);
    __DeleteMsgPortPPC(wosbase,TimObjPort);
  }

  DPrintf(ppcbase,"%s: exit\n",myname);
  __DeleteTaskPPC(wosbase,NULL);
}
