/*-=-=-=-=-=-=-=-=-=-=-=- RCCL/RCI Notice of Copyright -=-=-=-=-=-=-=-=-=-=-*
 *									    *
 *	Copyright (C) 1987, 1991 by John E. Lloyd.			    *
 *									    *
 *	Information on copyright permissions, as well as a complete	    *
 *	disclaimer of warranty, is given in the file COPYRIGHT.		    *
 *									    *
 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/

/*---------------------------------------------------------------------------
 *		
 * MODULE NAME:	message
 *
 * DESCRIPTION:	The RCI message passing primitives.
 *
 * CAVEATS:	An implicit message passing channel is assumed to exist
 *		between every RCI task. That design descison was made
 *		so that the message passing primitives would be easy
 *		to use. However, people who want to do things "quick
 *		an dirty" can (and do) used shared memory and don't
 *		use the message passing primitives at all. Requiring
 *		explicit channel creation would make this code both
 *		cleaner and more flexible.
 *
 *		On the other hand ... no one uses the primitives anyway!
 *
 * AUTHOR/DATE:	John Lloyd, Fall 1987
 *
 * MODIFICATION HISTORY:
 *
 *--------------------------------------------------------------------------*/

#include "../h/cdefs.h"
#include "../h/buddyAlloc.h"
#include "../h/rciTask.h"
#include "../h/errorCodes.h"

#include "../rci/messageDefs.h"

extern int rciNumTasks;
extern int rciCpu;
extern int rciTaskId;
extern RCI_DESC rciTaskTable[];

extern char *rciMessagePool[];

static NOTICE_PACKET noticePool[MAX_NOTICE_PACKETS];
static NOTICE_PACKET *freeNoticePackets;
static BINFO binfo;
static char *messagePool;

char *buddyAlloc();

/* Diagnostic messages */

static char *tooManyMsg		= "%s -- too many outstanding messages\n";
static char *noMemMsg		= "%s -- out of message memory\n";
static char *badDescMsg		= "%s -- bad task mask\n"; 

monitorMessageInit ()
{
	/* Called once by monitorInit() when a monitor is set up.

	   The message pool is used to store outgoing messages before
	   they are picked up by the target tasks. Each cpu has one
	   message pool. It is assumed by this function that memory for
	   the message pool for the current cpu has been set up by
	   the monitor before this function is called.
	*/

	int i;

	messagePool = rciMessagePool[rciCpu];
	buddyAllocInit (&binfo, messagePool, MSG_POOL_SIZE, MIN_MSG, MAX_MSG);
	
	/* put all notice packets on the free list */

	freeNoticePackets = noticePool;
	for (i=0; i<MAX_NOTICE_PACKETS-1; i++)
	 { noticePool[i].next = &noticePool[i+1];
	 }
 	noticePool[MAX_NOTICE_PACKETS-1].next = NULL;
}

taskMessageInit (mr)
MSG_ROOT *mr;
{
	/* Called once by taskSetup() every time a task is created
	   on a CPU (is not called if taskSetup() is simply importing
	   that task to a paricular CPU. Sets up the message passing
	   data structures for that task.
	*/

	register NOTICE_BUF *nb;
	register int i;
	register NOTICE_PACKET *msgs;

	mr->outstanding = 0;
	nb = mr->recv;
	mutexClear (&nb->mutex);
	nb->first = 0;
 	nb->last = 0;
	nb = mr->free;
	mutexClear (&nb->mutex);
	nb->first = 0;
 	nb->last = 0;
	msgs = mr->msgs;
	for (i=0; i<MAX_RCI_TASKS; i++)
	 { msgs[i].next = msgs[i].prev = &msgs[i];
	 }
}

rciSend (taskMask, buffer, size)
register unsigned long taskMask;
const char *buffer;
int size;
{
	/* Message sending primitive. See 'man rciSend' for a
	   description of the functionality.
	*/

	char *fxnName = "rciSend()";

	MSG_NOTICE n;
	register MSG_ROOT *mr;
	char *msgArea;
	register int tid;

	/* go through and free all returned messages */

	mr = (MSG_ROOT *)rciTaskTable[rciTaskId].msg;
	while (getNotice (&n, mr->free))
  	 { msgArea = n.offset + messagePool;
	   buddyFree (&binfo, msgArea);
	   mr->outstanding--;
	 }

	if (mr->outstanding >= NOTICE_BUF_SIZE-1)
	 { errorMsg (ETooMany, tooManyMsg, fxnName);
	   return (-1);
	 }
	if ((msgArea = buddyAlloc(&binfo, size)) == NULL)
	 { errorMsg (EMemory, noMemMsg, fxnName);
	   return (-1);
	 }
	if (taskMask == 0)
	 { return (0);		/* well, you asked for nothing ... */
	 }
	tid = 0;		/* compute target task ID from taskMask */
 	while (!(taskMask & 1))
  	 { tid++;
	   taskMask >>= 1;
	 }
	if (tid > rciNumTasks)
	 { errorMsg (EDescriptor, badDescMsg, fxnName);
	   return(-1);
	 }
	n.offset = (unsigned int)msgArea - (unsigned int)messagePool;
 	n.size = size;
 	n.senderID = rciTaskId;

	bcopy (buffer, msgArea, size);

	if (putNotice (&n, ((MSG_ROOT*)rciTaskTable[tid].msg)->recv) == 0)
  	 { buddyFree (&binfo, msgArea);
	   errorMsg (ETooMany, tooManyMsg, fxnName);
	   return (-1);
	 }
 	else
  	 { mr->outstanding++;
	   return (size);
	 }
}

#define EMPTY(r)	((r)->next == (r))

rciFlush (taskMask)
unsigned long taskMask;
{
	/* Flushes incoming messages from specified tasks. See 
	   'man rciFlush' for a description of the functionality.
	*/

	int tid;
	MSG_NOTICE n;
	register MSG_ROOT *mr = (MSG_ROOT*)rciTaskTable[rciTaskId].msg;
	unsigned long mask;

	taskMask &= (1<<MAX_RCI_TASKS)-1;	/* safety check */

 	/* first, flush the receive buffer */

	while (getNotice (&n, mr->recv))
 	 { tid = n.senderID;
	   if ((1<<tid) & taskMask)
	    { n.senderID = rciTaskId;
	      if (putNotice (&n, ((MSG_ROOT*)rciTaskTable[tid].msg)->free) == 0)
	       { rciAbort (EInternal,
   "rciFlush() -- free notice buffer for task %d is full\n", tid);
		 return (-1);
	       }
	    }
	   else
	    { if (enqueueNotice (&n, &(mr->msgs[n.senderID])) == 0)
	       { rciAbort (EInternal,
			   "rciFlush() -- out of message notices\n");
		 return (-1);
	       }
	    }
	 }

	/* now flush all the notice queues */

	tid = 0;
	for (mask = taskMask; mask; mask >>= 1)
	 { if (mask & 1)
	    { while ( dequeueNotice (&n, &(mr->msgs[tid])) )
	       { n.senderID = rciTaskId;
		 if (putNotice (&n, ((MSG_ROOT*)rciTaskTable[tid].msg)->free) == 0)
		  { rciAbort (EInternal,
   "rciFlush() -- free notice buffer for task %d is full\n", tid);
		    return (-1);
		  }
	       }
	    }
	   tid++;
	 }

	return (1);
}

rciReceive (taskMask, buffer, size, sender)
unsigned long taskMask;
char *buffer;
int size;
RCI_DESC **sender;
{
	/* Returns first message received from the indicated task.
	   'man rciReceive' for a description of the functionality.
	*/

	MSG_NOTICE n;
	MSG_NOTICE f;
	register MSG_ROOT *mr = (MSG_ROOT*)rciTaskTable[rciTaskId].msg;
	register int tid;
	char *msgArea;
	int bytesReceived;

 	/* first, flush the receive buffer */

	while (getNotice (&n, mr->recv))
 	 { if (enqueueNotice (&n, &(mr->msgs[n.senderID])) == 0)
	    { rciAbort (EInternal,
			"rciReceive() -- out of message notices\n");
	      return (-1);
	    }
	 }

	/* Now look to find a message from one of the tasks */

	tid = 0;
	taskMask &= (1<<MAX_RCI_TASKS)-1;	/* safety check */
	while (!(taskMask & 1) || EMPTY(&(mr->msgs[tid])) )
	 { if (taskMask == 0)
	    { return (0);
	    }
	   else
	    { tid++;
	      taskMask >>= 1;
	    }
	 }

	dequeueNotice (&n, &(mr->msgs[tid]));
	msgArea = n.offset + rciMessagePool[rciTaskTable[tid].cpu];
	bytesReceived = MIN(n.size, size);
	bcopy (msgArea, buffer, bytesReceived);

	/* build a free message and send it back */

	f.offset = n.offset;
	f.size = n.size;
	f.senderID = rciTaskId;

	if (putNotice (&f, ((MSG_ROOT*)rciTaskTable[tid].msg)->free) == 0)
	 { rciAbort (EInternal,
"rciReceive() -- free notice buffer for task %d is full\n", tid);
	   return (-1);
	 }

	if (sender)
	 { *sender = &rciTaskTable[tid];
	 }
	return (bytesReceived);	
}

enqueueNotice (n, root)
MSG_NOTICE *n;
NOTICE_PACKET *root;
{
	/* Puts a message notice in a queue where it will wait to
	   be picked up an 'rciRecieve()' call for that queue's
	   task.
	*/

	NOTICE_PACKET *np;

	if (np = freeNoticePackets)
	 { freeNoticePackets = np->next;
	   np->n = *n;
	   root->prev->next = np;
	   root->prev = np;
	   return (1);
	 }
 	else
  	 { return (0);
	 }
}

dequeueNotice (n, root)
MSG_NOTICE *n;
NOTICE_PACKET *root;
{
	/* Removes a message notice from a queue and frees the notice
	   packet.
	*/

	NOTICE_PACKET *np;

	if ((np = root->next) == root)
	 { return (0);
	 }
 	else if (np == root->prev)
  	 { root->next = root;
	   root->prev = root;
	 }
 	else
  	 { root->next = np->next;
	 }
	*n = np->n;
	np->next = freeNoticePackets;
	freeNoticePackets = np;
	return (1);
}
