/*-=-=-=-=-=-=-=-=-=-=-=- 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:	mutex
 *
 * DESCRIPTION:	Routines to enforce mutual exclusion in the face of
 *		access attempts from multiple CPUs.
 * 
 * CAVEATS:	To implement this right, we need a bus lock, but the
 *		uVAX Q-bus interface won't give that to us. SO ...
 *		we have to do something else instead. Needless to say,
 *		these routines could be streamlined on a more civilized 
 *		system.
 *
 * AUTHOR/DATE:	John Lloyd, Fall 1987
 *
 * MODIFICATION HISTORY:
 *
 *--------------------------------------------------------------------------*/

/* The mutual exclusion process here is derived from Decker's
   algorithm (as described by Dijkstra, in ``Co-operating Sequential
   Prcoesses'', Programming Languages, F. Genuys, 1968.).

   The pure version of this algorithm involves having evry process
   compete for a `turn' token by anouncing their desire and assumed
   state regarding possesion of the token through processor specific
   flags.

   Our version of the algorithm makes the simplifying assumption that
   there are at most 4 processes (1 per CPU). This is justifying since
   the uVAX q-bus system can only hold 4 processors. A different processor
   system would probably have mutex done in hardware, and we could dispense
   with this crazy file altogether.

   If we are willing to intriduce priority into the scheme, based on
   processor number, then we can simplify the algorithm even further (this
   assumption is OK as long as usage of the mutual exclusion flags is
   sparse. This is the actual version of mutex that we implement. The
   more complex version (without priorities) is given after in a non-active
   portion of the file.
*/

#include "../h/mutex.h"

int me;
unsigned long meOnly;
unsigned long higherPriority;

/* the cpu with the highest number has the highest priority */

mutexInit (cpu)
int cpu;
{
	MUTEX m;
	int i;

	me = cpu;
	m.need.l = 0;
	m.need.p[cpu] = 1;
	meOnly = m.need.l;
	m.need.l = 0;
	for (i=3; i>cpu; i--)
	 { m.need.p[i] = 1;
	 }
	higherPriority = m.need.l;
}

mutexClear (m)
MUTEX *m;
{
	m->need.l = 0;
	m->check = MUTEX_MAGIC;
}

#define SPINOUT		1000000		/* about 1 sec on sun4 */

mutexBegin (m)
MUTEX *m;
{
	register int spinout = SPINOUT;

	m->need.p[me] = 1;
	while (m->need.l != meOnly && --spinout > 0)
	 { if (m->need.l & higherPriority)
	    { m->need.p[me] = 0;
	      while (m->need.l && --spinout > 0)
		 ;
	      m->need.p[me] = 1;
	    }
	 }
	if (spinout <= 0)
	 { rciKPrintf ("mutexBegin() -- timeout on mutex 0x%x\n", m);
	   return (-1);
	 }
	else
	 { return(1);
	 }
}

mutexEnd (m)
MUTEX *m;
{
	m->need.p[me] = 0;
}

#if 0

/*---------------------------------------------------------------------------

	Older, more complex mutex method which avoids priority problems,
	but is overly slow and elborate.

  -------------------------------------------------------------------------*/

static long int me;
static long int meOnly;

mutexsetup (processor)
int processor;
{
	me = processor;
	meOnly = 1 << (processor*8);
}

mutexinit (m)
{
 	m->turn = 0;
}

mutexbegin (m)
MUTEX *m;
{
	register int i;
	union {
	  char p[4];
	  unsigned long l; } need;

	m->need.p[me] = 1;
	while (m->need.l != meOnly)
	 { need.l = m->need.l;
	   for (i=m->turn; i!=me; i=(++i >= 4 ? 0 : i)
	    { if (need.p[i])
	       { break;
	       }
	    }
	   if (i!=me)
	    { m->need.p[me] = 0;
	      while (m->need.l != 0)
	         ;
	      m->need.p[me] = 1;
	    }
	 }
}

mutexend (m)
MUTEX *m;
{
	m->turn = (m->turn+1) % 4;
	m->need.p[me] = 0;
}

#endif

#if TEST

#define	COMMAND(s)	(strcmp (key_buf, s) == 0)
#define	COMMANDS(s)	(strncmp (key_buf, s, strlen(s)) == 0)
char key_buf [256];

main()
{
	/* WARNING: This test progem assumes that the MUTEX type contains
	   ONLY the 4 per-cpu need bytes. */

	char *keytree, *tree_match_parse(), *tree_match_err_at();

	MUTEX mutex;
	int cpu;

	keytree = tree_match_parse("					\
(	quit								\
	init								\
	 (%d", &cpu, "			\"cpu number \")		\
	begin								\
	 (%x", &mutex, ",,,		\"initial mutex value (hex) \")	\
	end								\
)",0);
	if (keytree == 0)
	 { printf ("ERROR/Parse error at %.30s\n", tree_match_err_at());
	   exit (-1);
	 }
	do
 	 { tree_match (keytree, "MXTEST> ", key_buf);

	   if (COMMAND("init"))
	    { mutexInit (cpu);
	      printf ("me = %d higherPriority = 0x%x meOnly = 0x%x\n",
		       me, higherPriority, meOnly);
	    }
	   else if (COMMAND ("begin"))
	    { mutexBegin (&mutex);
	      printf ("mutex now = 0x%x\n", mutex);
	    }
	   else if (COMMAND ("end"))
	    { mutexEnd (&mutex);
	      printf ("mutex now = 0x%x\n", mutex);
	    }
	 }
	while (!(COMMAND("quit")));
}

#endif
