/*
** ppc.library emulation
** (c)1998-99 Frank Wille <frank@phoenix.owl.de>
**
** PowerUp kernel message system
**
** V0.5d (05.04.1999) phx
**       Created.
*/

#include "ppclibemu.h"
#include "msgsystem.h"
#include <powerup/ppclib/message.h>
#include <powerpc/memoryPPC.h>
#include "mpsema_protos.h"
#include "warpos_protos.h"


#ifdef KERNEL_DEBUG
#define KDB(x) x
#else
#define KDB(x)
#endif

/* flushMsg() */
#define FM_THIS 1
#define FM_PRED 2
#define FM_SUCC 4
#define FM_DATA 8


extern void DPrintf(struct PPCLibBase *,char *,...);



static void flushMsg(struct PPCBase *wosbase,struct PPCMessage *m,int flags)
{
  if (flags & FM_PRED)
    __SetCache(wosbase,CACHE_DCACHEFLUSH,m->n.ln_Pred,8);
  if (flags & FM_SUCC)
    __SetCache(wosbase,CACHE_DCACHEFLUSH,m->n.ln_Succ,8);
  if (flags & FM_THIS) {
    if (m->datasize!=NULL && (flags & FM_DATA))  /* flush message data? */
      __SetCache(wosbase,CACHE_DCACHEFLUSH,m->data,m->datasize);
    /* flush message structure */
    __SetCache(wosbase,CACHE_DCACHEFLUSH,m,
               (sizeof(struct PPCMessage)+31)&~31);
  }
}


static void signal(struct PPCLibBase *ppcbase,struct PPCMsgPort *mp)
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  struct PPCArgs pa;
  struct Task *tsk;

  if (tsk = (struct Task *)mp->sigTask) {
    if (tsk->tc_Node.ln_Type != NT_PPCTASK) {
      _memset(&pa,sizeof(struct PPCArgs),0);
      pa.PP_Code = (APTR)ppcbase->SysBase;
      pa.PP_Offset = -324;  /* _LVOSignal */
      pa.PP_Regs[PPREG_A1] = (ULONG)tsk;
      pa.PP_Regs[PPREG_D0] = 1L<<(ULONG)mp->sigBit;
      pa.PP_Regs[PPREG_A6] = (ULONG)ppcbase->SysBase;
      __Run68K(wosbase,&pa);
    }
    else
      __SignalPPC(wosbase,(struct TaskPPC *)tsk,1L<<(ULONG)mp->sigBit);
  }
}


struct PPCMsgPort *createPPCMsgPort(struct PPCLibBase *ppcbase,
                                    char *name,ULONG *errptr)
/* Allocate and initialize a PPCMsgPort, used to communicate between */
/* M68k and PPC, as well as PPC-internal. A special multi-processor */
/* semaphore will be set up to arbitrate CPU access. */
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  struct PPCMsgPort *mp;
  ULONG err = PPCPORTERROR_MEMORY;

  if (mp = __AllocVecPPC(wosbase,(sizeof(struct PPCMsgPort)+31)&~31,
                         MEMF_CLEAR|MEMF_CHIP|MEMF_PUBLIC,32)) {

    if ((mp->sigBit = (BYTE)__AllocSignalPPC(wosbase,-1)) >= 0) {
      if (initMPSemaphorePPC(ppcbase,&mp->mpSema,mp,
                             (sizeof(struct PPCMsgPort)+31)&~31)) {
        err = PPCPORTERROR_OK;
        _newlist(&mp->msgList);
        mp->sigTask = __FindTaskPPC(wosbase,NULL);
        mp->n.ln_Type = NT_MSGPORT;

        if (mp->n.ln_Name = name) {
          BOOL ret;

          obtainMPSemaphorePPC(ppcbase,&ppcbase->LibMPS);
          if (ret = (__FindNamePPC(wosbase,&ppcbase->PortList,
                                   mp->n.ln_Name) == NULL)) {
            _addtail(&ppcbase->PortList,&mp->n);
            __SetCache(wosbase,CACHE_DCACHEFLUSH,mp->n.ln_Pred,8);
            __SetCache(wosbase,CACHE_DCACHEFLUSH,mp,
                       (sizeof(struct PPCMsgPort)+31)&~31);
          }
          releaseMPSemaphorePPC(ppcbase,&ppcbase->LibMPS);
          if (!ret) {
            freeMPSemaphorePPC(ppcbase,&mp->mpSema);
            __FreeSignalPPC(wosbase,mp->sigBit);
            err = PPCPORTERROR_PORTEXISTS;
          }
        }
        else
          /* make MsgPort data visible for M68k */
          __SetCache(wosbase,CACHE_DCACHEFLUSH,mp,
                     (sizeof(struct PPCMsgPort)+31)&~31);
      }
      else
        __FreeSignalPPC(wosbase,mp->sigBit);
    }
    else 
      err = PPCPORTERROR_SIGNAL;
  }

  if (err!=PPCPORTERROR_OK && mp!=NULL) {
    __FreeVecPPC(wosbase,mp);
    mp = NULL;
  }
  if (errptr)
    *errptr = err;
  return (mp);
}


BOOL deletePPCMsgPort(struct PPCLibBase *ppcbase,struct PPCMsgPort *mp)
/* delete the MsgPort if it's no longer obtained by somebody else */
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;

  if (mp) {
    obtainMPSemaphorePPC(ppcbase,&mp->mpSema);
    if (mp->obtaincnt == 0) {
      if (mp->n.ln_Name) {
        obtainMPSemaphorePPC(ppcbase,&ppcbase->LibMPS);
        _remove(&mp->n);
        __SetCache(wosbase,CACHE_DCACHEFLUSH,mp->n.ln_Pred,8);
        __SetCache(wosbase,CACHE_DCACHEFLUSH,mp->n.ln_Succ,8);
        releaseMPSemaphorePPC(ppcbase,&ppcbase->LibMPS);
      }
      mp->n.ln_Type = (UBYTE)-1;
      mp->msgList.lh_Head = (struct Node *)-1;
      mp->sigTask = (void *)-1;
      releaseMPSemaphorePPC(ppcbase,&mp->mpSema);

      freeMPSemaphorePPC(ppcbase,&mp->mpSema);
      __FreeSignalPPC(wosbase,mp->sigBit);
      __FreeVecPPC(wosbase,mp);
      return (TRUE);
    }

    releaseMPSemaphorePPC(ppcbase,&mp->mpSema);
    return (FALSE);
  }
  return (TRUE);
}


struct PPCMsgPort *obtainPPCMsgPort(struct PPCLibBase *ppcbase,char *name)
/* find and obtain a global PPC-MsgPort by name */
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  struct PPCMsgPort *mp = NULL;

  if (name) {
    obtainMPSemaphorePPC(ppcbase,&ppcbase->LibMPS);
    mp = (struct PPCMsgPort *)__FindNamePPC(wosbase,&ppcbase->PortList,name);
    releaseMPSemaphorePPC(ppcbase,&ppcbase->LibMPS);
    if (mp) {
      /* increment obtain-counter */
      obtainMPSemaphorePPC(ppcbase,&mp->mpSema);
      mp->obtaincnt++;
      releaseMPSemaphorePPC(ppcbase,&mp->mpSema);
    }
  }
  return (mp);
}


BOOL releasePPCMsgPort(struct PPCLibBase *ppcbase,struct PPCMsgPort *mp)
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  BOOL rc = FALSE;

  if (mp) {
    obtainMPSemaphorePPC(ppcbase,&mp->mpSema);
    if (--mp->obtaincnt == 0)
      rc = TRUE;
    releaseMPSemaphorePPC(ppcbase,&mp->mpSema);
  }
  return (rc);
}


struct PPCMessage *waitPPCMessage(struct PPCLibBase *ppcbase,
                                  struct PPCMsgPort *mp)
/* return first message, if none available wait for next one */
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  struct PPCMessage *m = NULL;

  for (;;) {
    obtainMPSemaphorePPC(ppcbase,&mp->mpSema);
    m = (struct PPCMessage *)_head(&mp->msgList);
    releaseMPSemaphorePPC(ppcbase,&mp->mpSema);
    if (m)
      break;

    /* wait for arrival of next message */
    __WaitPPC(wosbase,1L<<(ULONG)mp->sigBit);
  }
  return (m);
}


struct PPCMessage *getPPCMessage(struct PPCLibBase *ppcbase,
                                 struct PPCMsgPort *mp)
/* remove first message */
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  struct PPCMessage *m;

  obtainMPSemaphorePPC(ppcbase,&mp->mpSema);
  if (m = (struct PPCMessage *)_remhead(&mp->msgList))
    flushMsg(wosbase,m,FM_SUCC);
  releaseMPSemaphorePPC(ppcbase,&mp->mpSema);
  return (m);
}


BOOL putPPCMessage(struct PPCLibBase *ppcbase,struct PPCMsgPort *mp,
                   struct PPCMessage *m)
/* send a message to a PPCMsgPort controlled by the M68k or the PowerPC */
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;

  obtainMPSemaphorePPC(ppcbase,&mp->mpSema);
  m->n.ln_Type = NT_MESSAGE;
  _addtail(&mp->msgList,&m->n);  /* add to list of messages */
  flushMsg(wosbase,m,FM_THIS|FM_DATA|FM_PRED);
  releaseMPSemaphorePPC(ppcbase,&mp->mpSema);

  /* notify MsgPort owner */
#if 0  /* WarpOS bug */
  if (mp->sigTask)
    __SignalPPC(wosbase,(struct TaskPPC *)mp->sigTask,1L<<(ULONG)mp->sigBit);
#else
  signal(ppcbase,mp);
#endif
  return (TRUE);
}


BOOL replyPPCMessage(struct PPCLibBase *ppcbase,struct PPCMessage *m)
{
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  struct PPCMsgPort *reply;

  if (reply = m->replyPort) {
    obtainMPSemaphorePPC(ppcbase,&reply->mpSema);
    m->n.ln_Type = NT_REPLYMSG;
    _addtail(&reply->msgList,&m->n);  /* add to list of messages */
    flushMsg(wosbase,m,FM_THIS|FM_DATA|FM_PRED);
    releaseMPSemaphorePPC(ppcbase,&reply->mpSema);

    /* notify MsgPort owner */
#if 0  /* WarpOS bug */
    if (reply->sigTask)
      __SignalPPC(wosbase,(struct TaskPPC *)reply->sigTask,
                  1L<<(ULONG)reply->sigBit);
#else
      signal(ppcbase,reply);
#endif
  }
  else {
    m->n.ln_Type = NT_FREEMSG;
    flushMsg(wosbase,m,FM_THIS|FM_DATA);
  }
  return (TRUE);
}
