/*
** ppc.library emulation
** (c)1998-2000 Frank Wille <frank@phoenix.owl.de>
**
** PowerUp kernel message system
**
** V0.8a (25.07.2000) phx
**       PPCMsgPort.pln is used for port list ports only, to allow
**       public ports to be in a port list too.
** V0.8  (28.09.2000) phx
**       Function waitPortList() for PortLists support.
** 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);
}


struct PPCMsgPort *waitPortList(struct PPCLibBase *ppcbase,
                                struct PPCPortList *plist)
{
  struct Node *n;
  struct PPCMsgPort *mp,*mp_rdy;
  struct PPCMessage *m;
  ULONG signals = 0;

  plist->received_signals = 0;
  for (;;) {

    /* check which ports in this list already hold a message */
    for (n = plist->portlist.lh_Head, mp_rdy = NULL;
         n->ln_Succ; n = n->ln_Succ) {

      mp = (struct PPCMsgPort *)n->ln_Name; /* port base is stored in Name */
      obtainMPSemaphorePPC(ppcbase,&mp->mpSema);
      m = (struct PPCMessage *)_head(&mp->msgList);
      releaseMPSemaphorePPC(ppcbase,&mp->mpSema);
      if (m) {  /* a message is ready */
        mp_rdy = mp;
        plist->received_signals |= (1L<<(ULONG)mp->sigBit);
      }
    }

    /* check for received extended signals first */
    if (signals & plist->extended_signals) {
      KDB(DPrintf(ppcbase,"\text. signal 0x%08lx received, "
                  "ready port 0x%08lx\n",signals,(ULONG)mp_rdy));
      break;
    }
    if (mp_rdy) {
      KDB(DPrintf(ppcbase,"\tport 0x%08lx holds a message\n",(ULONG)mp_rdy));
      break;
    }

    /* wait until any port receives a new message */
    signals = __WaitPPC(ppcbase->PowerPCBase,plist->signal_mask);
    plist->received_signals |= signals;
  }

  return (mp_rdy);  /* return a MsgPort with a waiting message */
}
