/*
** ppc.library emulation
** (c)1998-99 Frank Wille <frank@phoenix.owl.de>
**
** PowerUp Message System, 68k-side
**
** V0.5d (05.04.1999) phx
**       Created. Implemented functions:
**        PPCCreatePort(), PPCDeletePort(), PPCObtainPort(), PPCReleasePort(),
**        PPCWaitPort(), PPCCreateMessage(), PPCDeleteMessage(),
**        PPCGetMessage(), PPCGetMessageAttr(), PPCReplyMessage(),
**        PPCSendMessage().
*/

#include "ppclibemu.h"
#include "errors.h"
#include "supp.h"
#include "msgsystem.h"
#include "mpsema_protos.h"
#include <exec/execbase.h>
#include <exec/memory.h>
#include <powerup/ppclib/message.h>
#include <proto/exec.h>
#include <proto/utility.h>
#include <proto/powerpc.h>


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


static char *noname = "";


#if 1
extern void PPCCacheClearE(__reg("a0")APTR,__reg("d0")ULONG,
                           __reg("d1")ULONG,__reg("a6")void *);
extern void PPCCacheInvalidE(__reg("a0")APTR,__reg("d0")ULONG,
                             __reg("d1")ULONG,__reg("a6")void *);
#else
#define PPCCacheClearE(p,l,f,b) __SetCache68K(CACHE_DCACHEFLUSH,p,l,(b)->PowerPCBase)
#define PPCCacheInvalidE(p,l,f,b) __SetCache68K(CACHE_DCACHEINV,p,l,(b)->PowerPCBase)
#endif

APTR getSignalPPC(__reg("a0")struct PPCBase *) =
  "\tmove.l\t-484(a0),d0";



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


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 = getSignalPPC(wosbase);  /* SignalPPC() func. ptr. */
      pa.PP_Regs[PPREG_D0] = (ULONG)wosbase;
      pa.PP_Regs[PPREG_D1] = (ULONG)tsk;
      pa.PP_Regs[PPREG_A0] = 1L<<(ULONG)mp->sigBit;
      __RunPPC(&pa,wosbase);
    }
    else
      __Signal(tsk,1L<<(ULONG)mp->sigBit,ppcbase->SysBase);
  }
}


struct PPCMsgPort *PPCCreatePort(__reg("a0")struct TagItem *ti,
                                 __reg("a6")struct PPCLibBase *ppcbase)
{
  static const char *FN = "PPCCreatePort(): ";
  struct PPCBase *wosbase = ppcbase->PowerPCBase;
  struct PPCMsgPort *mp;
  ULONG *errptr,err=PPCPORTERROR_MEMORY;

  if (mp = (struct PPCMsgPort *)
            __AllocVec32((sizeof(struct PPCMsgPort)+31)&~31,
                         MEMF_CLEAR|MEMF_CHIP|MEMF_PUBLIC,
                         ppcbase->PowerPCBase)) {

    if ((mp->sigBit = (BYTE)__AllocSignal(-1,ppcbase->SysBase)) >= 0) {
      if (initMPSemaphore68k(ppcbase,&mp->mpSema,mp,
                             (sizeof(struct PPCMsgPort)+31)&~31)) {
        Dprintf(ppcbase,"%sSemaphores created\n",FN);

        err = PPCPORTERROR_OK;
        _newlist(&mp->msgList);
        mp->sigTask = __FindTask(NULL,ppcbase->SysBase);
        mp->n.ln_Type = NT_MSGPORT;

        if (mp->n.ln_Name = (char *)__GetTagData(PPCPORTTAG_NAME,NULL,ti,
                                                 ppcbase->UtilityBase)) {
          /* add global MsgPort */
          BOOL ret;

          obtainMPSemaphore68k(ppcbase,&ppcbase->LibMPS);
          if (ret = (__FindName(&ppcbase->PortList,mp->n.ln_Name,
                                ppcbase->SysBase) == NULL)) {
            _addtail(&ppcbase->PortList,&mp->n);
            PPCCacheClearE((APTR)mp->n.ln_Pred,8,CACRF_ClearD,ppcbase);
            PPCCacheClearE((APTR)mp,(sizeof(struct PPCMsgPort)+31)&~31,
                           CACRF_ClearD,ppcbase);
          }
          releaseMPSemaphore68k(ppcbase,&ppcbase->LibMPS);
          if (!ret) {
            freeMPSemaphore68k(ppcbase,&mp->mpSema);
            __FreeSignal(mp->sigBit,ppcbase->SysBase);
            err = PPCPORTERROR_PORTEXISTS;
            Dprintf(ppcbase,"%sPort exists!\n",FN);
          }
        }
        else
          /* make MsgPort data visible for PowerPC */
          PPCCacheClearE((APTR)mp,(sizeof(struct PPCMsgPort)+31)&~31,
                         CACRF_ClearD,ppcbase);
      }
      else
        __FreeSignal(mp->sigBit,ppcbase->SysBase);
    }
    else 
      err = PPCPORTERROR_SIGNAL;
  }

  if (err!=PPCPORTERROR_OK && mp!=NULL) {
    free32(mp,ppcbase);
    mp = NULL;
  }
  if (errptr = (ULONG *)__GetTagData(PPCPORTTAG_ERROR,NULL,ti,
                                     ppcbase->UtilityBase))
    *errptr = err;

  Dprintf(ppcbase,"%s0x%08lx (%s)\n",FN,(unsigned long)mp,
          mp ? (mp->n.ln_Name?mp->n.ln_Name:noname) : noname);
  return (mp);
}


BOOL PPCDeletePort(__reg("a0")struct PPCMsgPort *mp,
                   __reg("a6")struct PPCLibBase *ppcbase)
{
  static const char *FN = "PPCDeletePort(): ";

  if (mp) {
    obtainMPSemaphore68k(ppcbase,&mp->mpSema);
    Dprintf(ppcbase,"%sMsgPort 0x%08lx locked\n",FN,(unsigned long)mp);

    if (mp->obtaincnt == 0) {
      if (mp->n.ln_Name) {
        Dprintf(ppcbase,"%sremoving from global ports list\n",FN);
        obtainMPSemaphore68k(ppcbase,&ppcbase->LibMPS);
        _remove(&mp->n);
        PPCCacheClearE((APTR)mp->n.ln_Pred,8,CACRF_ClearD,ppcbase);
        PPCCacheClearE((APTR)mp->n.ln_Succ,8,CACRF_ClearD,ppcbase);
        releaseMPSemaphore68k(ppcbase,&ppcbase->LibMPS);
      }
      mp->n.ln_Type = (UBYTE)-1;
      mp->msgList.lh_Head = (struct Node *)-1;
      mp->sigTask = (void *)-1;
      releaseMPSemaphore68k(ppcbase,&mp->mpSema);

      freeMPSemaphore68k(ppcbase,&mp->mpSema);
      __FreeSignal(mp->sigBit,ppcbase->SysBase);
      free32(mp,ppcbase);
      Dprintf(ppcbase,"%ssuccessful\n",FN);
      return (TRUE);
    }

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


struct PPCMsgPort *PPCObtainPort(__reg("a0")struct TagItem *ti,
                                 __reg("a6")struct PPCLibBase *ppcbase)
{
  static const char *FN = "PPCObtainPort(): ";
  struct PPCMsgPort *mp;
  char *name = (char *)__GetTagData(PPCPORTTAG_NAME,NULL,ti,
                                    ppcbase->UtilityBase);
  ULONG *errptr = (ULONG *)__GetTagData(PPCPORTTAG_ERROR,NULL,ti,
                                        ppcbase->UtilityBase);

  Dprintf(ppcbase,"%s\"%s\"\n",FN,name);
  if (name) {
    obtainMPSemaphore68k(ppcbase,&ppcbase->LibMPS);
    mp = (struct PPCMsgPort *)__FindName(&ppcbase->PortList,name,
                                         ppcbase->SysBase);
    releaseMPSemaphore68k(ppcbase,&ppcbase->LibMPS);
    if (mp) {
      /* increment obtain-counter */
      obtainMPSemaphore68k(ppcbase,&mp->mpSema);
      mp->obtaincnt++;
      releaseMPSemaphore68k(ppcbase,&mp->mpSema);
    }
  }
  if (errptr)
    *errptr = mp ? PPCPORTERROR_OK : PPCPORTERROR_NOTFOUND;
  Dprintf(ppcbase,"%sport=0x%08lx\n",FN,(unsigned long)mp);
  return (mp);
}


BOOL PPCReleasePort(__reg("a0")struct PPCMsgPort *mp,
                    __reg("a6")struct PPCLibBase *ppcbase)
{
  BOOL rc = FALSE;

  Dprintf(ppcbase,"PPCReleasePort(): 0x%08lx\n",(unsigned long)mp); 
  if (mp) {
    obtainMPSemaphore68k(ppcbase,&mp->mpSema);
    if (--mp->obtaincnt == 0)
      rc = TRUE;
    releaseMPSemaphore68k(ppcbase,&mp->mpSema);
  }
  return (rc);
}


struct PPCMessage *PPCWaitPort(__reg("a0")struct PPCMsgPort *mp,
                               __reg("a6")struct PPCLibBase *ppcbase)
{
  static const char *FN = "PPCWaitPort(): ";
  struct PPCMessage *m = NULL;

  if (mp) {
    Dprintf(ppcbase,"%swaiting for port 0x%08lx (%s)...\n",FN,
            (unsigned long)mp,mp->n.ln_Name?mp->n.ln_Name:noname);
    for (;;) {
      obtainMPSemaphore68k(ppcbase,&mp->mpSema);
      m = (struct PPCMessage *)_head(&mp->msgList);
      releaseMPSemaphore68k(ppcbase,&mp->mpSema);
      if (m)
        break;

      /* wait for arrival of next message */
      __Wait(1L<<(ULONG)mp->sigBit,ppcbase->SysBase);
    }
  }
  else
    DPrintf(ppcbase,"%sMsgPort to wait for = NULL!\n");
  return (m);
}


struct PPCMessage *PPCCreateMessage(__reg("a0")struct PPCMsgPort *reply,
                                    __reg("d0")ULONG len,
                                    __reg("a6")struct PPCLibBase *ppcbase)
{
  struct PPCMessage *m;

  if (m = (struct PPCMessage *)
           __AllocVec32((sizeof(struct PPCMessage)+31)&~31,
                        MEMF_CLEAR|MEMF_CHIP|MEMF_PUBLIC,
                        ppcbase->PowerPCBase)) {
    m->n.ln_Type = NT_FREEMSG;
    m->replyPort = reply;
    m->maxdatasize = len;
  }
  Dprintf(ppcbase,"PPCCreateMessage(): 0x%08lx\n",(unsigned long)m);
  return (m);
}


void PPCDeleteMessage(__reg("a0")struct PPCMessage *m,
                      __reg("a6")struct PPCLibBase *ppcbase)
{
  /* only delete if valid pointer and message is replied or free */
  if (m) {
    if (m->n.ln_Type != NT_MESSAGE) {
      m->n.ln_Type = (UBYTE)-1;
      m->replyPort = (struct PPCMsgPort *)-1;
      Dprintf(ppcbase,"PPCDeleteMessage(): 0x%08lx\n",(unsigned long)m);
      free32(m,ppcbase);
    }
  }
}


ULONG PPCGetMessageAttr(__reg("a0")struct PPCMessage *m,__reg("d0")ULONG tag,
                        __reg("a6")struct PPCLibBase *ppcbase)
{
  static const char *FN = "PPCGetMessageAttr(): ";
  ULONG attr;

  switch (tag) {
    case PPCMSGTAG_DATA:
      attr = (ULONG)m->data;
      break;
    case PPCMSGTAG_DATALENGTH:
      attr = m->datasize;
      break;
    case PPCMSGTAG_MSGID:
      attr = m->msgid;
      break;
    default:
      Dprintf(ppcbase,"%sillegal tag: 0x%08lx\n",FN,tag);
      attr = 0;
      break;
  }
  return (attr);
}


struct PPCMessage *PPCGetMessage(__reg("a0")struct PPCMsgPort *mp
                                 __reg("a6")struct PPCLibBase *ppcbase)
{
  static const char *FN = "PPCGetMessage(): ";
  struct PPCMessage *m;

  if (mp) {
    obtainMPSemaphore68k(ppcbase,&mp->mpSema);
    if (m = (struct PPCMessage *)_remhead(&mp->msgList))
      flushMsg(ppcbase,m,FM_SUCC);
    releaseMPSemaphore68k(ppcbase,&mp->mpSema);
    Dprintf(ppcbase,"%s0x%08lx\n",FN,(unsigned long)m);
  }
  else {
    m = NULL;
    Dprintf(ppcbase,"%sCan't get! MsgPort = NULL!\n",FN);
  }
  return (m);
}


BOOL PPCSendMessage(__reg("a0")struct PPCMsgPort *mp,
                    __reg("a1")struct PPCMessage *m,
                    __reg("a2")void *data,__reg("d0")ULONG datasize,
                    __reg("d1")ULONG msgid,
                    __reg("a6")struct PPCLibBase *ppcbase)
{
  BOOL rc = FALSE;

  if (mp!=NULL && m!=NULL) {
    Dprintf(ppcbase,"PPCSendMessage(): msg=0x%08lx (data=0x%08lx, len=%lu "
                    "id=0x%08lx) to MsgPort=0x%08lx (%s)\n",
            (unsigned long)m,data,datasize,msgid,(unsigned long)mp,
            mp->n.ln_Name?mp->n.ln_Name:noname);

    obtainMPSemaphore68k(ppcbase,&mp->mpSema);
    m->data = data;
    m->datasize = datasize;
    m->msgid = msgid;
    m->n.ln_Type = NT_MESSAGE;
    _addtail(&mp->msgList,&m->n);  /* add to list of messages */
    flushMsg(ppcbase,m,FM_THIS|FM_DATA|FM_PRED);
    releaseMPSemaphore68k(ppcbase,&mp->mpSema);

    /* notify MsgPort owner */
#if 0  /* WarpOS bug */
    if (mp->sigTask)
      __Signal((struct Task *)mp->sigTask,1L<<(ULONG)mp->sigBit,
               ppcbase->SysBase);
#else
      signal(ppcbase,mp);
#endif
    rc = TRUE;
  }
  return (rc);
}


BOOL PPCReplyMessage(__reg("a0")struct PPCMessage *m,
                     __reg("a6")struct PPCLibBase *ppcbase)
{
  struct PPCMsgPort *reply;
  BOOL rc = FALSE;

  if (m) {
    Dprintf(ppcbase,"PPCReplyMessage(): msg=0x%08lx (reply port=0x%08lx)\n",
            (unsigned long)m,(unsigned long)m->replyPort);

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

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