/*
 * process.c
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1996,1993,1991,1990  Arizona Board of Regents
 *
 * $Revision: 1.4 $
 * $Date: 1996/06/14 21:34:14 $
 * 
 * HISTORY
 * $Log: process.c,v $
 * Revision 1.4  1996/06/14 21:34:14  slm
 * Merged in brakmo's changes.
 *
 * Revision 1.3  1996/02/01  15:23:16  slm
 * Updated copyright and version.
 *
 * Revision 1.2  1995/08/28  16:16:40  acb
 * Initial revision for x3.3
 *
 * Revision 1.1  1994/10/26  20:48:59  hkaram
 * Initial revision
 */

#include "process.h"
#include "platform.h"
#include "event.h"
#include "event_i.h"
#include "xk_debug.h"
#include <stdarg.h>

int SignalsPossible = 0;

int traceprocesscreation, traceprocessswitch;

#ifdef __STDC__

static void	wake_sem( Event, VOID * );

#else

static void	wake_sem();

#endif

#define MAXARGS 6

typedef void (*CreateProcFunc) ( void *, ... );

extern int 	CurHost;
extern void	xsimWaitUntilHostNotBusy();
extern int	xsimHostNotBusy();

pthread_mutex_t master_monitor;

#define ARGSLISTSIZE 20

struct arglist {
  long arg[MAXARGS+2];
  struct argslist *next;
} *argslist = NULL;

static struct argslist *
getMoreArgs()
{
  struct arglist *list=NULL, *elem;
  int i;

  for (i=0; i < ARGSLISTSIZE; i++) {
    elem = (struct arglist *)xMalloc(sizeof(struct arglist));
    elem->next = list;
    list = elem;
  }
  return list;
}

void threadInit()
{
  pthread_init();

  pthread_mutex_init(&master_monitor, NULL);
}

static void enter_master_sledgehammer_monitor(struct arglist *);


/*
 *  xkernel_activate_thread
 *
 *     startup a thread, given the arguments
 *
 */
static void xkernel_activate_thread(struct arglist *args, int priority)
{
  pthread_t child;
  int thr_status=0;
  int detach_state = 1;
  pthread_attr_t thr_attr;

  xTrace0(processcreation, TR_FULL_TRACE, "xkernel_activate_thread enter");

  if (priority > THREAD_MAXPRIO) {
    priority=THREAD_MAXPRIO;
    xTrace0(processcreation, TR_SOFT_ERRORS, "requested priority was too high");
  }
  else if (priority < THREAD_MINPRIO) {
    priority=THREAD_MINPRIO;
    xTrace0(processcreation, TR_SOFT_ERRORS, "requested priority was too low");
  }

  pthread_attr_init(&thr_attr);
  detach_state = 1;
  pthread_attr_setdetachstate(&thr_attr, &detach_state);
  pthread_attr_setprio(&thr_attr, priority);

  /* start the thread suspended so we can set its priority */
  thr_status=pthread_create(&child, &thr_attr, (pthread_func_t)enter_master_sledgehammer_monitor, (void *)args);
  if (thr_status < 0) {
    xTrace1(processswitch, TR_ERRORS, "xkernel_activate_thread: thr_create failed (%d)",thr_status);
  }
  else {
    xTrace1(processcreation, TR_MAJOR_EVENTS, "Created thread_id: %d", child);
    xTrace1(processswitch, TR_GROSS_EVENTS, "Created thread_id: %d", child);
  }
}

/*
 *
 * CreateProcess(function_ptr, priority, argcount, arg1, arg2, ...)
 *
 */

bool
CreateProcess(void *r, int pri, int nargs, ... )
{
  va_list ap;
  int i;
  struct arglist *argelem;

  xTrace0(processcreation, TR_EVENTS, "Enter CreateProcess");

  va_start(ap, nargs);

  if (!argslist) {
    argslist = getMoreArgs();
  }
  argelem = argslist;
  argslist = argslist->next;

  argelem->arg[0] = (long)r;
  argelem->arg[1] = nargs;
  if (nargs > MAXARGS)
    Kabort("CreateProcess: cannot handle more than 6 args.");

  for (i=0; i<nargs; i++)
    argelem->arg[i+2] = va_arg(ap, long);

  xTrace2(processcreation, TR_MAJOR_EVENTS, "Solaris CreateProcess: func_addr %x nargs %d",
         r, nargs);

  xkernel_activate_thread(argelem, pri);
  return(TRUE);
}

/*
 *  CreateKernelProcess
 *
 *
 */
void
CreateKernelProcess(CreateProcFunc r, int prio, int arg1, int arg2, int arg3)
{
    CreateProcess((void *)r, prio, 3, arg1, arg2, arg3);
}


Yield()
{
  int	  host=CurHost;

  xk_master_unlock();
  pthread_yield(0);
  xk_master_lock();
  xsimWaitUntilHostNotBusy(host);
  CurHost = host;
}

/*
 *
 * enter_master_sledgehammer_monitor
 *
 * Concurrency control is enforced in the xkernel by means of a monitor,
 *  in which all execution takes place. Here it is!
 *
 * A new thread begins here.
 * Inside the kernel:
 *      Dequeue a newThreadInfo block and do what it says
 * Outside the kernel:
 *      We are passed the argBlock.
 *
 * All the process block stuff looks totally defunct
 *
 */

static void
enter_master_sledgehammer_monitor(struct arglist *argelem)
{
  CreateProcFunc user_fn;
  int nargs;
  int threadcount;
  int host=CurHost;
  long *args;

  xTrace0(processcreation, TR_MAJOR_EVENTS, "enter_master_monitor: entering\n");
  xk_master_lock();
  xTrace0(processcreation, TR_MAJOR_EVENTS, "enter_master_monitor: locked\n");

  user_fn = (CreateProcFunc)argelem->arg[0];
  nargs = argelem->arg[1];
  args = &argelem->arg[2];

  CurHost = host;
  xTrace1(processcreation,TR_EVENTS,"enter_master_monitor: starting thread %d",
	  pthread_self());

  switch(nargs) {
  case 0: (user_fn)(0);
      break;
  case 1: (user_fn)(args[0]);
      break;
  case 2: (user_fn)(args[0], args[1]);
      break;
  case 3: (user_fn)(args[0], args[1], args[2]);
      break;
  case 4: (user_fn)(args[0], args[1], args[2], args[3]);
      break;
  case 5: (user_fn)(args[0], args[1], args[2], args[3], args[4]);
      break;
  case 6: (user_fn)(args[0], args[1], args[2], args[3], args[4], args[5]);
      break;
  }

  argelem->next = argslist;
  argslist = argelem;

  xk_master_unlock();
  xTrace1(processcreation, TR_MAJOR_EVENTS, "master_monitor: thread %d unlocks",
           pthread_self() );

}


static void
wake_sem( ev, arg )
    Event	ev;
    VOID 	*arg;
{
  semSignal((Semaphore *)arg);
}


void
Delay(n)
     int n;
{
  Semaphore s;

  semInit(&s, 0);
  evDetach( evSchedule(wake_sem, &s, n * 1000) );
  semWait(&s);
}


  

semInit(s, n)
register Semaphore *s;
unsigned n;
{
  s->count = n;
  if (pthread_cond_init(&s->cv, NULL) < 0) {
    Kabort("realP: cv_create failed");
  }
}

void
semWait(Semaphore *s)
{
  if (--(s)->count < 0)
    realP(s);
}

void
semSignal(Semaphore *s)
{
  if (++(s)->count <= 0)
    realV(s);
}



realPforHost(s)
register Semaphore *s;
{
  int	   host=CurHost;

  xTrace2(processswitch, TR_MAJOR_EVENTS, "P on %#x", s, NULL);
  
#ifdef XK_THREAD_TRACE
  evMarkBlocked(s);
#endif XK_THREAD_TRACE

  while (1) {
    if (pthread_cond_wait(&s->cv, &master_monitor) < 0) {
      Kabort("realP: cv_wait failed");
    }
    if (xsimHostNotBusy(host))
      break;
  }

#ifdef XK_THREAD_TRACE
  evMarkRunning();
#endif XK_THREAD_TRACE

  CurHost = host;
}

realP(s)
register Semaphore *s;
{
  int	   host=CurHost;

  xTrace2(processswitch, TR_MAJOR_EVENTS, "P on %#x", s, NULL);
  
  if (s->count < 0) {
#ifdef XK_THREAD_TRACE
    evMarkBlocked(s);
#endif XK_THREAD_TRACE

    if (pthread_cond_wait(&s->cv, &master_monitor) < 0) {
      Kabort("realP: cv_wait failed");
    }
    xsimWaitUntilHostNotBusy(host);

#ifdef XK_THREAD_TRACE
    evMarkRunning();
#endif XK_THREAD_TRACE

    CurHost = host;
  }
}

realV(s)
register Semaphore *s;
{
  xTrace2(processswitch, TR_MAJOR_EVENTS, "V on %#x by %s", s, NULL);

  if (s->count <= 0) {
    if (pthread_cond_signal(&s->cv) < 0) {
      Kabort("realV: cv_signal failed");
    }
  }
}

VAll(s)
register Semaphore *s;
{
  xTrace2(processswitch, TR_MAJOR_EVENTS, "VAll on %#x by %s", s, NULL);

  if (pthread_cond_broadcast(&s->cv) < 0) {
    Kabort("VAll: cv_broadcast failed");
  }
  s->count = 0;
}

/* InitSema may, and in this case, does allocate resources.  There
 * has to be a routine to release semaphores.  This is it.
 * -- Robbert van Renesse
 */
FreeSema(s)
register Semaphore *s;
{
  if (pthread_cond_destroy(&s->cv) < 0) {
    Kabort("realP: cv_destroy failed");
  }
  return 0;
}
