/*
 * $RCSfile: process.c,v $
 *
 * x-kernel v3.3
 *
 * Original Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 * Modifications Copyright (c) 1993 Massachusetts Board of Regents
 *
 * $Log: process.c,v $
 * Revision 1.2  1996/01/30 20:52:53  slm
 * Updated copyright and version.
 *
 * Revision 1.1  1995/07/29  02:42:45  slm
 * Initial revision
 *
 * Revision 1.4.1.2.1.2  1994/11/16  23:44:55  hkaram
 * Changed assert.h to xk_assert.h
 *
 * Revision 1.4.1.2.1.1  1994/11/12  19:13:40  hkaram
 * New branch
 *
 * Revision 1.4.1.2  1994/06/03  18:05:16  menze
 * Added casts to CreateProcess1 args
 *
 * Revision 1.4.1.1  1994/03/14  22:43:01  umass
 * significant restructuring
 *
 * Revision 1.4  1993/12/16  02:16:18  menze
 * Pf* definitions are no longer global ... this led to some
 * CreateProcess type changes
 *
 * Revision 1.3  1993/11/13  00:45:46  menze
 * Original version from UMass
 */

#include <x_stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <ulocks.h>
#include <task.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>

#include "platform.h"
#include "event.h"
#include "event_i.h"
#include "xk_debug.h"
#include "process.h"



#define MAX_XK_THREADS 20
#define XKERNEL_STACK_SIZE     65536
#define XKERNEL_STACK_SIZE_OLD 32768
#define MAXARGS 6


static void process_schedule(Process *);
static void process_stub(void *);
static void process_enter_monitor(Process *);

Process *	 Active = (Process *) 0;
usptr_t	*	 xkernel_arena;

static char      arena_file_name[64];
static Process * processTable[MAX_XK_THREADS];
static usema_t * master_monitor;
static usema_t * warm_pool_semaphore;
static Queue     process_queue;

int		 tracequeueing;

#ifdef XK_DEBUG
#define traceProcess0(l, f) 		\
    if (traceprocesscreation >= l) { 	\
        printf(f); 			\
        putchar('\n');			\
    }

#define traceProcess1(l, f, arg1)	\
    if (traceprocesscreation >= l) { 	\
        printf(f, arg1);		\
        putchar('\n');			\
    }

#define traceProcess2(l, f, arg1, arg2)		\
    if (traceprocesscreation >= l) { 		\
        printf(f, arg1, arg2);			\
        putchar('\n');				\
    }

#else
#define traceProcess0(l, f) 		
#define traceProcess1(l, f, arg1) 		
#define traceProcess2(l, f, arg1, arg2) 		
#endif

void process_init( void )
{
  int i, rv;
  struct rlimit limits;
  Process * my_process;

  /* initialize our data structures first */
  for( i = 0; i < MAX_XK_THREADS; i++ )
  {
    processTable[i] = NULL;
  }

  /* see how many we got */
  i = m_get_numprocs();
  traceProcess1(TR_EVENTS, "process_init sees %d processors", i);

  /* check stack size */
  if ((rv = getrlimit(RLIMIT_STACK, &limits)) != 0)
  {
    printf("process_init: got %d with %d errno from getrlimit\n", rv, errno);
    perror("process_init: ");
    Kabort("process_init");
  }
  traceProcess1(TR_EVENTS, "process_init got %d stacksize", limits.rlim_cur);

  /* set new stack size */
  limits.rlim_cur = XKERNEL_STACK_SIZE;

  if ((rv = setrlimit(RLIMIT_STACK, &limits)) != 0)
  {
    printf("process_init: got %d with %d errno from setrlimit\n", rv, errno);
    perror("process_init: ");
    Kabort("process_init");
  }
  traceProcess0(TR_EVENTS, "process_init set new stacksize");

  /* set number of threads we need */
  rv = usconfig(CONF_INITUSERS, MAX_XK_THREADS);
  if (errno)
  {
    printf("process_init: got %d errno from usconfig \n", errno);
    perror("process_init: ");
    Kabort("process_init");
  }
  traceProcess1(TR_EVENTS, "process_init set %d threads", MAX_XK_THREADS);

  /* initialize xkernel arena */
  sprintf(arena_file_name, "%s", "xkernel_arena");
  xkernel_arena = usinit(arena_file_name);
  if (xkernel_arena == (usptr_t *) 0)
  {
    printf("process_init: got %d errno from usinit \n", errno);
    perror("process_init: ");
    Kabort("process_init");
  }

  /* initialize tracing locks */
  traceProcess0(TR_EVENTS, "process_init calling xTraceInit");
  xTraceInit();

  master_monitor = usnewsema(xkernel_arena, 0);
  xAssert(master_monitor != NULL);
  warm_pool_semaphore = usnewsema(xkernel_arena, 0);
  xAssert(warm_pool_semaphore != NULL);

  xTrace0(processcreation, TR_EVENTS, "process_init calling queue_init");
  queue_init(&process_queue);

  xTrace0(processcreation, TR_EVENTS, "process_init calling sproc");
  for (i = 1; i < MAX_XK_THREADS; i++)
  {
    rv = sproc(process_stub, (PR_SADDR | PR_SFDS), (void *) i);
    if (rv == -1)
    {
      printf("process_init: got %d with %d errno from sproc iteration %d\n", 
             rv, errno, i);
      perror("process_init: ");
      Kabort("process_init");
    }
  }
  if ((my_process = (Process *)xMalloc(sizeof(Process))) == NULL)
    Kabort("process_init: no memory");
  my_process->thread_id = 0;
  my_process->pid = getpid();
  my_process->ppid = getppid();
  my_process->group_id = getpgrp();

  processTable[0] = my_process;
  
  /* wait for kids to warm up */
  for (i = 1; i < MAX_XK_THREADS; i++)
  {
    rv = uspsema(warm_pool_semaphore);
    if (rv < 0)
    {
      printf("process_init: got %d with %d errno from uspsema \n", rv, errno);
      perror("process_init: ");
      Kabort("process_init");
    }
  }

  xTrace0(processcreation, TR_EVENTS, "process_init FINISHED");
}

void
process_kill_all_others(void)
{
   int my_pid = getpid();
   int i;

   for( i = 0; i < MAX_XK_THREADS; i++ )
       if( (processTable[i] != NULL) && (processTable[i]->pid != my_pid) )
           kill(processTable[i]->pid, 9);
}

void
process_unblock_children(void)
{
   int i;

   for (i = 0; i < MAX_XK_THREADS; i++)
       if (processTable[i] != NULL)
           unblockproc(processTable[i]->pid);
}

static void 
child_signal_handler(void)
{
  fprintf(stderr, "child signal handler %d\n", getpid());
  exit(0);
}

static void 
process_stub(thread_id)
  void * thread_id;
{
  Process * my_process;

  signal(SIGUSR1, child_signal_handler);

  if ((my_process = (Process *)xMalloc(sizeof(Process))) == NULL)
    Kabort("process_stub: no memory");
  my_process->thread_id = (int) thread_id;
  my_process->pid = getpid();
  my_process->ppid = getppid();
  my_process->group_id = getpgrp();

  processTable[(int) thread_id] = my_process;

  xTrace3(processswitch, TR_EVENTS, 
    "process_stub: I am pid %d, thread %d, in group %d",
      my_process->pid, my_process->thread_id, my_process->group_id);
  process_schedule(my_process);
}

Process *
process_self()
{
	int i;

	for (i = 0; i < MAX_XK_THREADS; i++)
	{
		if (processTable[i])
			if (processTable[i]->pid == getpid())
				return processTable[i];
	}

	return (Process *) 0;
}

static void 
process_schedule(my_process)
Process * my_process;
{
  int rv;

  xTrace2(processswitch, TR_EVENTS, "process_schedule: pid %d, thread %d",
      my_process->pid, my_process->thread_id);

  rv = usvsema(warm_pool_semaphore);
  if( rv < 0 )
  {
    printf("process_schedule: got %d with %d errno from usvsema\n", rv, errno);
    perror("process_schedule: ");
    Kabort("process_schedule");
  }

  while (1)
  {
    queue_insert_first(&process_queue, my_process);
    blockproc(my_process->pid);
    process_enter_monitor(my_process);
  }
}

static void 
process_enter_monitor(proc)
  Process * proc;
{
  xk_master_lock();

  xTrace4(processswitch, TR_EVENTS, 
     "process_enter_monitor: pid %d, thread %d, func %X, numargs %d",
      proc->pid, proc->thread_id, proc->func, proc->num_args);
  Active = proc;
  switch(proc->num_args) {
    case 0: 
      (proc->func)();
      break;
    case 1: 
      (proc->func)(proc->args[0]);
      break;
    case 2: 
      (proc->func)(proc->args[0], proc->args[1]);
      break;
    case 3: 
      (proc->func)(proc->args[0], proc->args[1], proc->args[2]);
      break;
    case 4: 
      (proc->func)(proc->args[0], proc->args[1], proc->args[2], 
                   proc->args[3]);
      break;
    case 5: 
      (proc->func)(proc->args[0], proc->args[1], proc->args[2], 
                   proc->args[3], proc->args[4]);
      break;
    case 6: 
      (proc->func)(proc->args[0], proc->args[1], proc->args[2], 
                   proc->args[3], proc->args[4], proc->args[5]);
      break;
    default:
      Kabort("process_enter_monitor: BAD num_args!");
  }
  Active = NULL;
  xTrace2(processswitch, TR_EVENTS, "process_exit_monitor: pid %d, thread %d",
      proc->pid, proc->thread_id);
  xk_master_unlock();
}

void
CreateProcess0( r, prio )
     Pfi r;
     int prio;
{
  Process * new_process;

  xTrace1(processcreation, TR_MAJOR_EVENTS,
	  "IRIX CreateProcess0: r %x", r);
  new_process = queue_remove_first(&process_queue);
  if (new_process == (Process *) NULL)
    Kabort("CreateProcess0: out of processes!");
  
  new_process->func = r;
  new_process->num_args = 0;

  unblockproc(new_process->pid);
  xTrace1(processcreation, TR_MAJOR_EVENTS,
	  "IRIX CreateProcess0: r %X finished", r);
}

void
CreateProcess1( r, prio , arg0)
     CreateProcFunc r;
     int prio;
     void *arg0;
{
  Process * new_process;

  xTrace1(processcreation, TR_MAJOR_EVENTS,
	  "IRIX CreateProcess1: r %x", r);
  new_process = queue_remove_first(&process_queue);
  if (new_process == (Process *) NULL)
    Kabort("CreateProcess1: out of processes!");
  
  new_process->func = (Pfi)r;
  new_process->num_args = 1;
  new_process->args[0] = (int)arg0;

  unblockproc(new_process->pid);
}

void
CreateProcess2( r, prio , arg0, arg1)
     Pfi r;
     int prio;
     int arg0;
     int arg1;
{
  Process * new_process;

  xTrace1(processcreation, TR_MAJOR_EVENTS,
	  "IRIX CreateProcess2: r %x", r);
  new_process = queue_remove_first(&process_queue);
  if (new_process == (Process *) NULL)
    Kabort("CreateProcess2: out of processes!");
  
  new_process->func = r;
  new_process->num_args = 2;
  new_process->args[0] = arg0;
  new_process->args[1] = arg1;

  unblockproc(new_process->pid);
}

void
CreateProcess3( r, prio , arg0, arg1, arg2 )
     Pfi r;
     int prio;
     int arg0;
     int arg1;
     int arg2;
{
  Process * new_process;

  xTrace1(processcreation, TR_MAJOR_EVENTS,
	  "IRIX CreateProcess3: r %x", r);
  new_process = queue_remove_first(&process_queue);
  if (new_process == (Process *) NULL)
    Kabort("CreateProcess3: out of processes!");
  
  new_process->func = r;
  new_process->num_args = 3;
  new_process->args[0] = arg0;
  new_process->args[1] = arg1;
  new_process->args[2] = arg2;

  unblockproc(new_process->pid);
}

void
CreateProcess4( r, prio , arg0, arg1, arg2, arg3)
     Pfi r;
     int prio;
     int arg0;
     int arg1;
     int arg2;
     int arg3;
{
  Process * new_process;

  xTrace1(processcreation, TR_MAJOR_EVENTS,
	  "IRIX CreateProcess4: r %x", r);
  new_process = queue_remove_first(&process_queue);
  if (new_process == (Process *) NULL)
    Kabort("CreateProcess4: out of processes!");
  
  new_process->func = r;
  new_process->num_args = 4;
  new_process->args[0] = arg0;
  new_process->args[1] = arg1;
  new_process->args[2] = arg2;
  new_process->args[3] = arg3;

  unblockproc(new_process->pid);
}

void
CreateProcess5( r, prio , arg0, arg1, arg2, arg3, arg4)
     Pfi r;
     int prio;
     int arg0;
     int arg1;
     int arg2;
     int arg3;
     int arg4;
{
  Process * new_process;

  xTrace1(processcreation, TR_MAJOR_EVENTS,
	  "IRIX CreateProcess5: r %x", r);
  new_process = queue_remove_first(&process_queue);
  if (new_process == (Process *) NULL)
    Kabort("CreateProcess5: out of processes!");
  
  new_process->func = r;
  new_process->num_args = 5;
  new_process->args[0] = arg0;
  new_process->args[1] = arg1;
  new_process->args[2] = arg2;
  new_process->args[3] = arg3;
  new_process->args[4] = arg4;

  unblockproc(new_process->pid);
}

void
CreateProcess6( r, prio , arg0, arg1, arg2, arg3, arg4, arg5)
     Pfi r;
     int prio;
     int arg0;
     int arg1;
     int arg2;
     int arg3;
     int arg4;
     int arg5;
{
  Process * new_process;

  xTrace1(processcreation, TR_MAJOR_EVENTS,
	  "IRIX CreateProcess6: r %x", r);
  new_process = queue_remove_first(&process_queue);
  if (new_process == (Process *) NULL)
    Kabort("CreateProcess6: out of processes!");
  
  new_process->func = r;
  new_process->num_args = 6;
  new_process->args[0] = arg0;
  new_process->args[1] = arg1;
  new_process->args[2] = arg2;
  new_process->args[3] = arg3;
  new_process->args[4] = arg4;
  new_process->args[5] = arg5;

  unblockproc(new_process->pid);
}

void
CreateKernelProcess(r, prio, nargs, a1, a2)
  Pfi r;
  int prio;
  int nargs;
  int a1, a2;
{
  switch( nargs )
  {
    case 0:
      CreateProcess0(r, prio);
      break;
    case 1:
      CreateProcess1((CreateProcFunc)r, prio, (void *)a1);
      break;
    default:
      /* always do something */
      CreateProcess2(r, prio, a1, a2);
  }
}


void
Yield()
{
  xk_master_unlock();

  sginap(0);

  xk_master_lock();
}

void
Delay(n)
  int n;  /* delay in milliseconds */
{
  long delay_in_ticks;

  delay_in_ticks = ((long)n * (long)CLICKS_PER_SEC) / 1000;
  /*       ticks = (ms * ticks/second) / ms/second */

  xk_master_unlock();
  sginap( delay_in_ticks );
  xk_master_lock();
}

void 
xk_master_lock()
{
  int return_value;

  xTrace1(processswitch, TR_EVENTS, "%d blocking for master lock",
    getpid());

  return_value = uspsema(master_monitor);
  xIfTrace(processswitch, TR_ALWAYS) {
    if(return_value < 0) {
      printf("xk_master_lock: got %d return with %d errno from P()\n", 
             return_value, errno);
      perror("xk_master_lock: ");
    }
  }

  xTrace1(processswitch, TR_EVENTS, "%d acquired master lock",
    getpid());
}


void 
xk_master_unlock()
{
  int return_value;

  xTrace1(processswitch, TR_EVENTS, "%d releasing master lock",
    getpid());

  return_value = usvsema(master_monitor);
  xIfTrace(processswitch, TR_ALWAYS) {
    if(return_value < 0) {
      printf("xk_master_unlock: got %d return with %d errno from V()\n", 
             return_value, errno);
      perror("xk_master_unlock: ");
    }
  }
}


void 
queue_init(queue)
Queue * queue;
{
  xTrace1(queueing, TR_EVENTS, "queue_init on queue %X", queue);
  queue->count = 0;
  queue->head = queue->tail = (Process *) 0;
  queue->mutex = usnewlock(xkernel_arena);
  xTrace1(queueing, TR_EVENTS, "queue_init %X finished", queue);
}

#define CHAIN(first, second)  (first)->next = (second); \
			      (second)->prev = (first)

void 
queue_insert_first(queue, element)
Queue * 	queue;
Process *	element;
{
  xTrace2(queueing, TR_EVENTS, "queue_insert_first queue %X element %X", 
    queue, element);

  ussetlock(queue->mutex);
  if (queue->count == 0)
    queue->tail = element->prev = element->next = element;
  else 
  {
    CHAIN(element, queue->head);
    CHAIN(queue->tail, element);
  }
  queue->head = element;
  queue->count ++;
  usunsetlock(queue->mutex);

  xTrace1(queueing, TR_EVENTS, "queue_insert_first queue %X finished", 
    queue);
}


void 
queue_insert_last(queue, element)
Queue * 	queue;
Process *	element;
{
  xTrace2(queueing, TR_EVENTS, "queue_insert_last queue %X element %X", 
    queue, element);

  ussetlock(queue->mutex);
  if (queue->count == 0)
    queue->head = element->prev = element->next = element;
  else 
  {
    CHAIN(element, queue->head);
    CHAIN(queue->tail, element);
  }
  queue->tail = element;
  queue->count ++;
  usunsetlock(queue->mutex);

  xTrace1(queueing, TR_EVENTS, "queue_insert_last queue %X finished", 
    queue);
}


Process * 
queue_remove_first(queue)
Queue * 	queue;
{
  Process * element;

  xTrace1(queueing, TR_EVENTS, "queue_remove on queue %X", queue);
  ussetlock(queue->mutex);

  element = queue->head;
  if (queue->count == 1)
  {
    queue->head = queue->tail = (Process *) 0;
    queue->count -= 1;
  }
  else if (queue->count > 1)
  {
    queue->head = element->next;
    CHAIN(queue->tail, queue->head);
    queue->count -= 1;
  }
   
  usunsetlock(queue->mutex);
  xTrace1(queueing, TR_EVENTS, "queue_remove finished, found %X", 
    element);

  return element;
}


/********************************
 debug code
 ********************************/

void 
queue_print(queue)
Queue * queue;
{
  int 	i;
  Process * process;

  
  ussetlock(queue->mutex);
  printf("queue %X, %d elements:\n", queue, queue->count);
  for (i = 0, process = queue->head; i < queue->count; i++)
  {
    printf("  element %2d: process %X, pid %d, thread %d\n", 
        i, process, process->pid, process->thread_id);
    process = process->next;
  }
  usunsetlock(queue->mutex);
  printf("done printing queue\n");
}

