/*-=-=-=-=-=-=-=-=-=-=-=- 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:	atomicOps
 *
 * DESCRIPTION:	Routines to do atomic operations on data structures.
 *		At the moment, these include 'testAndSet' and
 *		copying of a structure.
 *
 * CAVEATS:	When executed from the planning level, the
 *		routines must presently be called
 *		in kernel mode to prevent interruption by the ARBITER
 *		control level. This requires executing through a 'cmkrnl()'
 *		("Change Mode to KeRNeL") syscall, which slows things down
 *		a lot. This should be cleaned up when we give the planning
 *		level the ability to mask out RCI control level interrupts.
 *
 * AUTHOR/DATE:	John Lloyd, Fall 1987
 *
 * MODIFICATION HISTORY:
 *
 *--------------------------------------------------------------------------*/

#if !defined(SGI_CFG) && !defined(VXWORKS_CFG) 

#include <signal.h>

#if !defined(VXWORKS_CFG) && !defined(LINUX_CFG)
#include <sys/types.h>	
#include <sys/buf.h>
#endif

#include "../h/cdefs.h"
#include "../h/mutex.h"
#include "../h/rciConfig.h"
#include "../rci/maskInterrupts.h"

extern MUTEX mutexTestAndSet;

extern int rciSignalRefCount;
extern int rciTaskId;
extern int rciCpu;
extern int _rciFd;
extern int rciSimOnly;

int (*_rciLockReadBuf)() = NULLF;
int (*_rciLockWriteBuf)() = NULLF;
int (*_rciUnlockBuf)() = NULLF;
int (*_rciKutCallFxn)() = NULLF;

static int pageSize = UNDEF;

/* IRIX: all we do is simply keep the RCI control task from running.
   One way to do this is to give us a priority one notch higher. */

static int initDone = 0;
atomicOpsInit()
{
	/* this routine MUST be called be RCI setup code */
	initDone = 1;
	pageSize = getpagesize();
	mutexInit(rciCpu);
}

/* Routines to implement testAndSet() ... */

doTestAndSet (flag, mutex)
int *flag;
MUTEX *mutex;
{
	int code;

	MASK_INTERRUPTS;
	mutexBegin (mutex);
	if (*flag == 0)
	 { *flag = 1;
	   code = 1;
	 }
	else
	 { code = 0;
	 }
	mutexEnd (mutex);
	UNMASK_INTERRUPTS;
	return (code);
}

testAndSet (flag, mutex)
int *flag;
MUTEX *mutex;
{
#ifndef LINUX_CFG
	char *fxnName = "testAndSet()";
	int code;

	if (!(MUTEX_VALID(mutex)))
	 { mutexAbort(fxnName, mutex);
	   return(-1);
	 }
	if (!_rciFd)
	 { /* then no other RCI tasks could have been set up yet */
	   code = (*flag ? 0 : (*flag = 1));
	 }
	else if (rciCpu == ARBITER_CPU && !rciInKernelMode())
	 { if (rciTaskId == 0 && rciSignalRefCount > 0)
	    { blockRciSignals();
	    }
	   code = rciKutExec (doTestAndSet, flag, mutex);
	   if (rciTaskId == 0 && rciSignalRefCount > 0)
	    { unblockRciSignals();
	    }
	 }
	else
	 { code = doTestAndSet (flag, mutex);
	 }
	return (code);
#endif
}

/* Routines to implement atomicCopy() ... */

touchBuf (addr, size)
char *addr;
int size;
{
	char *base0, *base1, *bp;
	char touch;

	base0 = (char*)((unsigned int)addr/pageSize*pageSize);
	base1 = (char*)(((unsigned int)addr+size-1)/pageSize*pageSize);
	for (bp=base0; bp<=base1; bp+=pageSize)
	 { touch = *bp;
	 }
}

atomicCopyAux (mutex, src, dest, size)
MUTEX *mutex;
char *src, *dest;
int size;
{
	char *rbase, *wbase;
	char *rtop, *wtop;
	extern int bcopy();

	/* paranoid: page align the source and dest regions, and clip
	   the read region if the write region overlaps it. */

	rbase = (char*)((unsigned int)src/pageSize*pageSize);
	wbase = (char*)((unsigned int)dest/pageSize*pageSize);
	rtop = rbase + (size-1+pageSize)/pageSize*pageSize - 1;
	wtop = wbase + (size-1+pageSize)/pageSize*pageSize - 1;

	if (rbase <= wbase && rtop >= wbase)
	 { rtop = wbase - 1;
	 }
	else if (rtop > wtop && rbase <= wtop)
	 { rbase = wtop + 1;
	 }

	if (rtop-rbase+1 != 0)
	 { if (!(*_rciLockReadBuf)(rbase, rtop-rbase+1))
	    { return (-1);
	    }
	 }
	if (!(*_rciLockWriteBuf)(wbase, wtop-wbase+1))
	 { return (-1);
	 }
	(*_rciKutCallFxn)(bcopy, src, dest, size);
	if (rtop-rbase != 0)
	 { (*_rciUnlockBuf)(rbase, rtop-rbase+1);
	 }
	(*_rciUnlockBuf)(wbase, wtop-wbase+1);
	return (1);
}

atomicCopy (mutex, src, dest, size)
MUTEX *mutex;
void *src;
void *dest;
int size;
{
#ifndef LINUX_CFG
	char *fxnName = "atomicCopy()";

	if (!(MUTEX_VALID(mutex)))
	 { mutexAbort(fxnName, mutex);
	   return(-1);
	 }
	if (!_rciFd) /* then no other RCI tasks have been set up yet */
	 { bcopy (src, dest, size);	      
	 }
	else if (rciCpu == ARBITER_CPU && !rciInKernelMode())
	 { if (rciTaskId == 0 && rciSignalRefCount > 0)
	    { blockRciSignals();
	    }
#if !defined(vax)			/* paranoid */
	   touchBuf (src, size);
	   touchBuf (dest, size);
#endif
	   if (rciSimOnly)
	    { bcopy (src, dest, size);
	    }
	   else
	    { if (rciKrnlExec (atomicCopyAux, mutex, src, dest, size) < 0)
	       { kill (getpid(), SIGSEGV);
	       }
	    }
	   if (rciTaskId == 0 && rciSignalRefCount > 0)
	    { unblockRciSignals();
	    }
	 }
	else
	 { MASK_INTERRUPTS;
	   mutexBegin (mutex);
	   bcopy (src, dest, size);
	   mutexEnd (mutex);
	   UNMASK_INTERRUPTS;
	 }
	return (1);
#endif
}

/* Routines to implement atomicSet(), atomicClear(), and atomicPut() ... */

#define SET_CMD		0x1
#define CLEAR_CMD	0x2
#define PUT_CMD		0x3

unsigned long setBitMask (addr, mask, cmd)
unsigned long *addr;
unsigned long mask;
int cmd;
{
	/* sets the bits as described by cmd and returns the entire
	   setting of the previous bit mask */

	unsigned long oldValue;
	
	oldValue = *addr;
	if (cmd == SET_CMD)
	 { *addr |= mask;
	 }
	else if (cmd == CLEAR_CMD)
	 { *addr &= ~(mask);
	 }
	else if (cmd == PUT_CMD)
	 { *addr = mask;
	 }
	return (oldValue);
}


unsigned long doAtomicSet (mutex, addr, mask, cmd)
MUTEX *mutex;
unsigned long *addr;
unsigned long mask;
int cmd;
{
	unsigned long oldValue;

	MASK_INTERRUPTS;
	mutexBegin (mutex);
	oldValue = setBitMask (addr, mask, cmd);
	mutexEnd (mutex);
	UNMASK_INTERRUPTS;
	return (oldValue);
}

unsigned long atomicSetCmd (mutex, addr, mask, cmd)
MUTEX *mutex;
unsigned long *addr;
unsigned long mask;
int cmd;
{
#ifndef LINUX_CFG
	unsigned long oldValue;

	if (!_rciFd)	/* then no RCI tasks could have been set up yet */
	 { oldValue = setBitMask (addr, mask, cmd);
	 }
	else if (rciCpu == ARBITER_CPU && !rciInKernelMode())
	 { if (rciTaskId == 0 && rciSignalRefCount > 0)
	    { blockRciSignals();
	    }
	   oldValue = (unsigned long) rciKutExec(doAtomicSet, 
						 mutex, addr, mask, cmd);
	   if (rciTaskId == 0 && rciSignalRefCount > 0)
	    { unblockRciSignals();
	    }
	 }
	else
	 { oldValue = doAtomicSet (mutex, addr, mask, cmd);
	 }
	return (oldValue);
#endif
}

unsigned long atomicSet (mutex, addr, mask)
MUTEX *mutex;
unsigned long *addr;
unsigned long mask;
{
	/* do (*addr |= mask) atomically */

	if (!(MUTEX_VALID(mutex)))
	 { mutexAbort("atomicSet()", mutex);
	   return(-1);
	 }
	return (atomicSetCmd (mutex, addr, mask, SET_CMD));
}

unsigned long atomicClear (mutex, addr, mask)
MUTEX *mutex;
unsigned long *addr;
unsigned long mask;
{
	/* do (*addr &= ~mask) atomically */

	if (!(MUTEX_VALID(mutex)))
	 { mutexAbort("atomicClear()", mutex);
	   return(-1);
	 }
	return (atomicSetCmd (mutex, addr, mask, CLEAR_CMD));
}

unsigned long atomicPut (mutex, addr, mask)
MUTEX *mutex;
unsigned long *addr;
unsigned long mask;
{
	/* do (*addr |= mask) atomically */

	if (!(MUTEX_VALID(mutex)))
	 { mutexAbort("atomicPut()", mutex);
	   return(-1);
	 }
	return (atomicSetCmd (mutex, addr, mask, PUT_CMD));
}

#endif /* !defined(SGI_CFG) && !defined(VXWORKS_CFG) */
