/*
   TMIPC.c, Copyright  (c) by ,
   Swiss Federal Institute of Technology,
   Computer Engineering and Networks Laboratory.

   TOPSY -- A Teachable Operating System.
   Implementation of a tiny and simple
   micro kernel for teaching purposes.

   For further information, please visit http://www.tik.ee.ethz.ch/~topsy

   This software is provided under the terms of the GNU General Public Licence.

   A full copy of the GNU GPL is provided in the file COPYING found in the 
   development root of Topsy.
   */
/*
File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Threads/TMIPC.c,v $
Author(s):             
Affiliation:           ETH Zuerich, TIK
Version:               $Revision: 3048 $
Creation Date:         
Last Date of Change:   $Date: 2007-07-20 18:11:54 +0200 (Fre, 20 Jul 2007) $      by: $Author: sryffel $
*/

#include "TMThread.h"
#include "TMScheduler.h"
#include "TMHal.h"
#include "TMProtectionDomain.h"
#include "Support.h"
#include "HashList.h"
#include "List.h"
#include "Assert.h"
#include "MemoryLayout.h"

#include "vm.h"
#include "vm_calls.h"

#include "TMIPC.h"

//#define IPC_DEBUG_ON
#define IPC_DEBUG_OFF

#ifdef IPC_DEBUG_ON
#       define IPC_DEBUG(...)                                                                                                              \
                do {                                                                                                                            \
                        printf(__VA_ARGS__);                                                                                                               \
                } while (0)
#else
#       define IPC_DEBUG(...)                                                                                                              \
                while (0) {                                                                                                                     \
                        printf(__VA_ARGS__);                                                                                                               \
                }
#endif

/* Global variables */
extern List threadList;                 /* Linear list of all threads */


/*
 * When a thread exits (either exited or killed), a linear search is performed
 * among all receive-blocked threads to make them ready again if they expected
 * anything from the no longer existing thread (no infinite waiting for msg!).
 */
void ipcFreeBlockedThreads( ThreadId exitThreadId)
{
  Thread* threadPtr;
  int flags;

  if (listGetFirst( threadList, (void**)&threadPtr) != LIST_OK)
  {
    PANIC("listGetFirst() did not work");
  }
  while (threadPtr != NULL)
  {
    LOCK_THREAD(threadPtr, flags);
    {
      // the status could not possibly change, do not acquire lock
      if (threadPtr->schedInfo.status == BLOCKED)
      {
        if (threadPtr->msgQueue.threadIdPending == exitThreadId)
        {
          /* threadPtr must be re-activated with a failed return value */
          tmSetReturnValue(threadPtr->contextPtr, TM_MSGRECVFAILED);
          tmHalThreadSetReady(threadPtr, MSG_RECV);
        }
      }
    }
    UNLOCK_THREAD(threadPtr, flags);
    /*	else if (threadPtr->schedInfo.status == READY) {
     *	} else {
     *	}
     */ /* not used at the moment, make it easier for the compiler ;-) */
    listGetNext( threadList, (void**)&threadPtr);
  }
}


/*
 * Initialisation of the message queue
 */
void initMsgQueue( MessageQueue* queuePtr)
{
  int i = 0;

  queuePtr->freeList = 0;
  queuePtr->busyList = -1;
  for (i=0;i<MAXNBMSGINQUEUE;i++)
    queuePtr->ctrl[i] = (i+1) % MAXNBMSGINQUEUE;
  queuePtr->ctrl[MAXNBMSGINQUEUE-1] = -1;
  queuePtr->msgPendingStatus = NOT_WAITING;
}


/*
 * A new message is inserted in the queue of the receiving thread
 */
static int addMessageInQueue(Thread* threadPtr, Message* msgPtr)
{
  MessageQueue* queue = &(threadPtr->msgQueue);
  int oldFreeList = queue->freeList;
  int tmpBusyList = queue->busyList;
  int flags;
  Message threadKillMsg = {TMTHREADID, 0, TM_KILL, NULL, {{NULL, NULL, NULL}}};

  ASSERT(!lockTry(&threadPtr->lock));

  if (queue->freeList == -1)
  {
    /* Message queue full */
    ERROR("addMessageInQueue(): msg queue is full");
    printf("  msg queue of thread %i\n", threadPtr->id);
    if (threadPtr->msgQueue.msgPendingStatus == WAITING)
    {
      printf("  expecting id %2li from thread %li\n",
             threadPtr->msgQueue.msgIdPending, threadPtr->msgQueue.threadIdPending);

    }

    /* If the thread is in user space, then it is killed */
    if ( ((threadPtr->permission) & KERNEL_PERM_MSK) == 0)
    {
      // is USER
      ioConsolePutString("(thread ");
      ioConsolePutString(threadPtr->name);
      ioConsolePutString(" will be killed)\n");

      threadKillMsg.msg.tmKill.id = threadPtr->id;
      kSend( TMTHREADID, &threadKillMsg);
    }
    return TM_MSGQUEUEOVERFLOW;
  }
  else
  {
    /* Copy message at position queue->freeList */
    byteCopy( &(queue->msg[queue->freeList]),
        msgPtr,
        sizeof(Message));
    /* Updating queue->freeList */
    queue->freeList = queue->ctrl[queue->freeList];
    /* Updating queue->busyList */
    if (queue->busyList == -1)
    {
      queue->busyList = oldFreeList;
      queue->ctrl[oldFreeList] = -1;
    }
    else
    {
      while (queue->ctrl[tmpBusyList] != -1)
      {
        tmpBusyList = queue->ctrl[tmpBusyList];
      }
      queue->ctrl[tmpBusyList] = oldFreeList;
      queue->ctrl[oldFreeList] = -1;
    }
  }
  return TM_OK;
}


/*
 * A single message is retrieved from the message queue.
 * Message must be of type msgPtr->id and sender msgPtr->from as
 * requested by the tmMsgRecv() call.
 */
static int getMessageFromQueue(
    Thread* threadPtr, /* thread about to receive a msg */
    Message* msgPtr)   /* message structure */
{
  MessageQueue* queue = &(threadPtr->msgQueue);
  int tmpBusyList = queue->busyList;
  int tmp = queue->busyList;
  MessageId expectedMsgId = msgPtr->id;
  ThreadId expectedThreadId = msgPtr->from;
  Boolean notFound = TRUE;
  Message msg;

  ASSERT(!lockTry(&threadPtr->lock));

  if (queue->busyList == -1)
  {
    return TM_NOSUCHMESSAGE;
  }
  while (tmpBusyList != -1 && notFound)
  {
    /* Check if message 'msg' at position 'tmpBusyList' matches request */
    msg = queue->msg[tmpBusyList];
    if ( expectedMsgId == ANYMSGTYPE && expectedThreadId == ANY)
    {
      notFound = FALSE;
    }
    else if (expectedMsgId==ANYMSGTYPE && expectedThreadId==msg.from)
    {
      notFound = FALSE;
    }
    else if ( expectedMsgId == msg.id && expectedThreadId == ANY)
    {
      notFound = FALSE;
    }
    else if ( expectedMsgId == msg.id && expectedThreadId == msg.from)
    {
      notFound = FALSE;
    }
    if (notFound == FALSE)
    {
      byteCopy( msgPtr, &msg, sizeof(Message));
      /* Update of queue->busyList */
      if (tmpBusyList == queue->busyList)
      {
        queue->busyList = queue->ctrl[tmpBusyList];
        queue->ctrl[tmpBusyList] = queue->freeList;
        queue->freeList = tmpBusyList;
      }
      else
      {
        while ( queue->ctrl[tmp] != tmpBusyList)
        {
          tmp = queue->ctrl[tmp];
        }
        queue->ctrl[tmp] = queue->ctrl[tmpBusyList];
        /* Update of queue->freeList */
        queue->ctrl[tmpBusyList] = queue->freeList;
        queue->freeList = tmpBusyList;
      }
    }
    tmpBusyList = queue->ctrl[tmpBusyList];
  }
  if (notFound)
  {
    return TM_NOSUCHMESSAGE;
  }
  else
  {
    return TM_OK;
  }
}


Error _kSend(Thread* destThreadPtr, Message* msgPtr)
{
  Thread* srcThreadPtr;
  struct vm_domain* vm_space;
  MessageQueue* queue;  /* used for simplification in below expressions */
  Error errorCode = TM_OK;
  Boolean copyInTmp = FALSE;        /* help flag */
  int status, flags;

  /* check validity of destThreadPtr */
  if ( destThreadPtr == NULL)
  {
    return TM_THREADNOTFOUND;
  }
  ASSERT(!lockTry(&destThreadPtr->lock));

  /* Lookup in hash table to find pointer onto destination thread */
  if ( (srcThreadPtr = getTHbyId(msgPtr->from)) == NULL)
  {
    return TM_THREADNOTFOUND;
  }

  msgPtr->to = destThreadPtr->id;

  IPC_DEBUG("kSend: %2li -> %2li  id %2li ... ",
            msgPtr->from, msgPtr->to, msgPtr->id);

  /* fast IPC and Inter-Protection-Domain IPC handling. */
  if ( msgPtr->enveloppe != NULL )
  {
    /* extended message, if not in the same PD do a loan-out */
    if ( srcThreadPtr->pdPtr != destThreadPtr->pdPtr )
    {
      /* the PD pointers do not point to the same PD */
      vm_space = &srcThreadPtr->pdPtr->vm_space;
      msgPtr->id |= MSG_NEEDS_LOANIN;
      //-BL printf("\n**** kSend before loan-out. msgPtr->enveloppe = %p\n", msgPtr->enveloppe);
      if ( vm_region_loan_out(&vm_space->vm_map, &vm_space->vm_pmap,
            msgPtr->enveloppe, (struct vm_page**)&msgPtr->enveloppe) != VM_OK )
      {
        WARNING("kSend: vm_region_loan_out failed");
        return TM_IPCFAILED;
      }
      //-BL printf("\n**** kSend after loan-out. msgPtr->enveloppe = %p\n", msgPtr->enveloppe);
      //-CJ printf("@@@@@ LOAN-OUT: from %li (to %li) page %p %#lx\n",
      //-CJ 		srcThreadPtr->id, destThreadPtr->id, msgPtr->enveloppe,
      //-CJ 		((struct vm_page*)msgPtr->enveloppe)->pagenr);
    }
  }

  /* tmHalThreadGetSchedStatus() returns the status of the topmost context,
   *  which is before any interrupt / (nested) exception
   */
  switch( tmHalThreadGetSchedStatus(destThreadPtr, MSG_RECV))
  {
    case RUNNING:
    case READY:
      /* Destination thread is not waiting for a message, copy in queue */
      //-CJ			printf("ksend: addMessageInQueue thread %p msgPtr %p\n",destThreadPtr, msgPtr);
      errorCode = addMessageInQueue( destThreadPtr, msgPtr);
      IPC_DEBUG("already ready %li\n", destThreadPtr->id);
      break;
    case BLOCKED:
      /* Thread is expecting a message, check if there is a match */
      queue = &(destThreadPtr->msgQueue);

      /* Check status of pending message */
      if (queue->msgPendingStatus==FILLED)
      {
        IPC_DEBUG("\n");
        printf("kSend: queue pending filled\n");
        copyInTmp = FALSE; /* do not overwrite previous unread message, 
                            * why is thread still it blocked? */
      }
      else if (queue->msgPendingStatus==NOT_WAITING)
      {
        IPC_DEBUG("\n");
        printf("kSend: thread %i not waiting\n", destThreadPtr->id);
        copyInTmp = FALSE; /* thread does not want message, 
                            * why is it blocked then? */
      }
      else if ( queue->threadIdPending == ANY &&
          queue->msgIdPending == ANYMSGTYPE)
      {
        //-CJ				printf("kSend: queue pending hit any/anymsgtype\n");
        copyInTmp = TRUE;
      }
      else if ( queue->threadIdPending == msgPtr->from &&
          queue->msgIdPending == ANYMSGTYPE)
      {
        //-CJ				printf("kSend: queue pending hit id/anymsgtype\n");
        copyInTmp = TRUE;
      }
      else if ( queue->threadIdPending == ANY &&
          queue->msgIdPending == (msgPtr->id & ~MSG_NEEDS_LOANIN))
      {
        //-CJ				printf("kSend: queue pending hit any/msgid\n");
        copyInTmp = TRUE;
      }
      else if ( queue->threadIdPending == msgPtr->from &&
          queue->msgIdPending == (msgPtr->id & ~MSG_NEEDS_LOANIN))
      {
        //-CJ				printf("kSend: queue pending hit id/msgid\n");
        copyInTmp = TRUE;
      }
      else
      {
        IPC_DEBUG("kSend: msg not expected\n");
      }
      if (copyInTmp)
      {
        /* fast IPC and Inter-Protection-Domain IPC handling. */
        if ( msgPtr->enveloppe != NULL && msgPtr->id & MSG_NEEDS_LOANIN )
        {
          msgPtr->id ^= MSG_NEEDS_LOANIN; /* remove MSG_NEEDS_LOANIN bit */
          //-CJ					printf("@@@@@ LOAN-IN: msg %p to %li id %i page %p %#lx\n",
          //-CJ							msgPtr, destThreadPtr->id, msgPtr->id, msgPtr->enveloppe,
          //-CJ							((struct vm_page*)msgPtr->enveloppe)->pagenr);

          vm_space = &(destThreadPtr->pdPtr->vm_space);
          if ( vm_region_loan_in(&vm_space->vm_map, &vm_space->vm_pmap,
                &msgPtr->enveloppe,
                (struct vm_page*)msgPtr->enveloppe) != VM_OK )
          {
            WARNING("kSend: vm_region_loan_in failed");
            errorCode = TM_IPCFAILED;
            goto kSend_return;
          }
        }

        queue->msgPendingStatus = FILLED;
        /* Message must be copied at the address stated in msgPendingPtr */
        //-CJ	printf("kSend: moveMessageToPD(destThread %p, msgPtr %p, queue %p)\n", destThreadPtr, msgPtr, queue->msgPendingPtr);
        moveMessageToPD(destThreadPtr, msgPtr, queue->msgPendingPtr);

        /* The destination thread is set to READY status (wake up) */
        tmHalThreadSetReady(destThreadPtr, MSG_RECV);
        IPC_DEBUG("unblocked %li\n", destThreadPtr->id);

        errorCode = TM_OK; /* let's do a new scheduling decision */
      }
      else
      {
        IPC_DEBUG("kSend: Message not expected by thread %li:\"%s\"\n       message   id %2li from thread %li\n       expecting id %2li from thread %li\n",
                  destThreadPtr->id, destThreadPtr->name, 
                  msgPtr->id, msgPtr->from,
                  queue->msgIdPending, queue->threadIdPending);
        /* Message has to be copied in message queue */
        errorCode = addMessageInQueue( destThreadPtr, msgPtr);

      }
      break;
  }
  if (errorCode == TM_OK)
  {
    /* A new scheduling decision is necessary if a higher priority
     * thread has to become active 
     */
    if (srcThreadPtr != destThreadPtr)
      LOCK_THREAD(srcThreadPtr, flags);
    {
      // resched the active, because _kSend() can be called within any Handler
      tmHalThreadSetResched(srcThreadPtr, ACTIVE);
    }
    if (srcThreadPtr != destThreadPtr)
      UNLOCK_THREAD(srcThreadPtr, flags);
  }


kSend_return:
  ipcMsgSent();

  return errorCode;
}


Error _kRecv( Message* msgPtr,   /* message structure (contains exp.
                                 * sender and type)  (in PD of tmThread) 
                                 */
    Thread* threadPtr) /* thread wishing to receive a message */
{
  struct vm_domain* vm_space;
  MessageQueue* queuePtr = &(threadPtr->msgQueue);
  Error errorCode, ret;

  ASSERT(!lockTry(&threadPtr->lock));

  IPC_DEBUG("kRecv: %2li <- %2li  id %2li ... ",
            threadPtr->id, msgPtr->from, msgPtr->id);

  /* Check to see if the expected message is already in the queue */
  if ((errorCode=getMessageFromQueue( threadPtr, msgPtr)) == TM_OK)
  {
    /* Expected message was copied in msgPtr */

    /* fast IPC and Inter-Protection-Domain IPC handling. */
    if ( msgPtr->enveloppe != NULL && msgPtr->id & MSG_NEEDS_LOANIN )
    {
      /* extended message, if not in the same PD do a loan-in */
      msgPtr->id ^= MSG_NEEDS_LOANIN; /* remove MSG_NEEDS_LOANIN bit */
      //-CJ			printf("@@@@@ LOAN-IN: to %li page %p %#lx\n",
      //-CJ					threadPtr->id, msgPtr->enveloppe,
      //-CJ					((struct vm_page*)msgPtr->enveloppe)->pagenr);
      vm_space = &threadPtr->pdPtr->vm_space;
      //-BL printf("\n**** kRecv before loan-in. msgPtr->enveloppe = %p\n", msgPtr->enveloppe);
      if ( vm_region_loan_in(&vm_space->vm_map, &vm_space->vm_pmap,
            &msgPtr->enveloppe,
            (struct vm_page*)msgPtr->enveloppe) != VM_OK )
      {
        WARNING("kRecv: vm_region_loan_in failed");
        ret = TM_IPCFAILED;
        goto kRecv_return;
      }
      //-BL printf("\n**** kRecv after loan-in. msgPtr->enveloppe = %p\n", msgPtr->enveloppe);
    }

    ret = TM_OK;
    goto kRecv_return;
  }
  else
  {
    /* The expected message has not yet arrived in the message queue */
    queuePtr->msgPendingStatus = WAITING;     /* thread waiting for a
                                               * special message */
    queuePtr->msgPendingPtr = kRECV_GET_MSGPTR(threadPtr);	/* address where the message
                                                             * is expected in PD of requesting thread */
    queuePtr->threadIdPending = msgPtr->from; /* expected message sender */
    queuePtr->msgIdPending = msgPtr->id;      /* expected message type */
    //-CJ		printf("kRecv: Thread % li waiting for msg from % li type %i\n",
    //-CJ				threadPtr->id, msgPtr->from, msgPtr->id);
    ret = TM_NOSUCHMESSAGE;
    goto kRecv_return;
  }


kRecv_return:

  return ret;
}


/* A SYSCALL exception occurred, thus either tmMsgSend() or tmMsgRecv() was
 * invoked with a message.
 */
int msgDispatcher( ThreadId fromId,                  /* thread that caused exception */
    Message* msgPtr,                  /* message reference (in PD of tmThread) */
    unsigned long int timeout,        /* timeout for receiving */
    MsgOpCode code)                   /* SYSCALL_SEND_OP/SYSCALL_RECV_OP */
{
  Thread* threadPtr;
  Thread* destThreadPtr;
  Thread* fromThreadPtr;
  ThreadId to;
  int flags;

  // printf("msgDispatcher: from=%i, &msg=%p\n", fromId, msgPtr);
  /* Lookup in hash table to find pointer onto threadId */
  if ( (threadPtr = getTHbyId(fromId)) == NULL)
  {
    PANIC("msgDispatcher(): required thread id does not exist");
    tmSetReturnValue(threadPtr->contextPtr, TM_MSGSENDFAILED);
    return TM_FAILED;
  }

  //-LR  Debug("In msgDispatcher SYSCALL=%i\n",(int)code);

  switch (code)
  {
    case SYSCALL_SEND_OP:
      /* Filtering TM_YIELD:
       * No message is sent to the thread manager, as it would be obliged
       * to perform a new scheduling decision and a restoreContext() outside
       * of an exception context, which leads to tricky problems.
       * Instead, threadYield() is directly invoked from this point in an
       * exception context.
       */
      //-LR			Debug("Trying to run SYSCALL_SEND_OP\n");
      if (msgPtr->id==TM_YIELD && msgPtr->from==TMTHREADID)
      {
        tmSetReturnValue(threadPtr->contextPtr, TM_MSGSENDOK);
        tmHalThreadYield(threadPtr, MSG_SEND);
      }
      else
      {
        to = msgPtr->from;
        msgPtr->from = fromId;		/* copy sender id to msg */

        if ( (destThreadPtr = getTHbyId(to)) == NULL)
        {
          PANIC("msgDispatcher(): required destination thread id does not exist");
        }

        LOCK_THREAD(destThreadPtr, flags);
        {
          if (_kSend( destThreadPtr, msgPtr) != TM_OK )
          {
            tmSetReturnValue(threadPtr->contextPtr, TM_MSGSENDFAILED);
          }
          else
          {
            /* message was successfully copied in messageQueue */
            tmSetReturnValue(threadPtr->contextPtr, TM_MSGSENDOK);
          }
          /* A new scheduling decision was taken in kSend(), as kSend() may
           * be called from other modules, independently of msgDispatcher() 
           */
        }
        UNLOCK_THREAD(destThreadPtr, flags);

      }
      break;

    case SYSCALL_RECV_OP:
      //-LR			Debug("Trying to run SYSCALL_RECV_OP\n");
      /* Check if the expected threadId exists */
      if ( ((fromThreadPtr = getTHbyId(msgPtr->from)) == NULL)
          && (msgPtr->from != ANY))
      {
        tmSetReturnValue(threadPtr->contextPtr, TM_MSGRECVFAILED);
      }
      else
      {
        LOCK_THREAD(threadPtr, flags);
        {

          if (_kRecv( msgPtr, threadPtr) != TM_OK )
          {
            /* No corresponding message found, put receiver to sleep,
             * and make a new scheduling decision 
             */

            //-LR					Debug("To be blocked: id = %i\n", threadPtr->id);
            //-LR           BRK();

            //-CJ					if (threadPtr->id > 0) {
            //-CJ						ioConsolePutString("thread id ");
            //-CJ						ioConsolePutInt(threadPtr->id);
            //-CJ						ioConsolePutString(" blocked, waiting\n");
            //-CJ					}
            tmHalThreadSetResched(threadPtr, MSG_RECV);
            tmHalThreadSetBlocked(threadPtr, MSG_RECV);
            IPC_DEBUG("blocked %li\n", threadPtr->id);
          }
          else
          {
            tmHalThreadSetReady(threadPtr, MSG_RECV); 
            IPC_DEBUG("unblocked %li\n", threadPtr->id);
          }
          /* The expected message was copied in msgPtr */
          tmSetReturnValue(threadPtr->contextPtr, TM_MSGRECVOK);

        }
        UNLOCK_THREAD(threadPtr, flags);

      }
      break;
    default:
      printf("What the hell is going on ???\n");
      printf("We do not understand this SYSCALL=%i\n",(int)code);
      return TM_FAILED;
      //			while (1); /* until we have a halt on all architectures we halt with this */
  }
  return TM_OK;
}
