/*
 * process.c
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1996,1993,1991,1990  Arizona Board of Regents
 *
 * $Revision: 1.3 $
 * $Date: 1996/06/03 19:35:03 $
 */

#include "xtype.h"
#include <thread.h>
#include <errno.h>
#include <synch.h>
#include <stdarg.h>
#include <sys/time.h>

#include "xkernel.h"	
#include "event_i.h"

/* externs */
void bufferPoolInit(void);
void xkBufferPoolInit(void);
void CreateKernelProcess(CreateProcFunc, int, int, int, int);
void Yield(void);
void inputThreadInit(void);

static void enter_master_sledgehammer_monitor(int *);

#define MAXARGS 6
#define MAX_KERNEL_THREADS_FOR_THREADS 5
#define XK_MAX_RUNNING_THREADS 24	/* try to keep running threads less
					   than this */

static int current_xk_threads = 0;
int         max_xk_threads_inuse = 0;
int         xk_thread_limit = MAX_KERNEL_THREADS_FOR_THREADS;

mutex_t sledgehammer_concurrency_control;
#define XTHREAD_SELF() thr_self()
#define XTHREAD_TYPE   thread_t
Process *Active;

#ifdef XKLOCKDEBUG
int xklockdepthreq = 0;
int xklockdepth = 0;
int tracexklock = 0;
#endif XKLOCKDEBUG

/*
 * xk_master_lock, xk_master_unlock
 *    user callable master concurrency control
 */
void
xk_master_lock(void)
{
    MASTER_LOCK;
}

void
xk_master_unlock(void)
{
    MASTER_UNLOCK;
}

void semWait(Semaphore *s)
{
    xk_master_unlock();
    xTrace1(processswitch, TR_GROSS_EVENTS, "Wait on sema: %x", s);
    sema_wait(s);
    xk_master_lock();
}

void semSignal(Semaphore *s)
{
    xTrace1(processswitch, TR_GROSS_EVENTS, "Signal on sema: %x", s);
    sema_post(s);
}

void semInit(Semaphore *s, unsigned n) 
{
    sema_init(s, n, USYNC_THREAD, s);
}

/*
 * threadInit
 */
void threadInit(void)
{
    xTrace0(processcreation, TR_FULL_TRACE, "Enter event function threadInit");
    mutex_init(&sledgehammer_concurrency_control, USYNC_THREAD, 0);

    /*
     * Since we don't have any real concurrency under the xkernel master
     * lock, we can run with just a single mach thread.
     */
    thr_setconcurrency(xk_thread_limit);
}

void inputThreadInit(void)
{
    /* defined in input_proc.c */
    /* xkFillThreadPool(); */
    xkBufferPoolInit();
}

/*
 * xkernel_activate_thread
 *    startup a thread, given the arguments
 */
static void xkernel_activate_thread(int *args, int priority)
{
    XTHREAD_TYPE child;
    int thr_status = 0;

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

    /* this is redundant since we start threads on new lwps anyway */
    if (current_xk_threads+4 >= xk_thread_limit) {
        xk_thread_limit += 2;
        thr_setconcurrency(xk_thread_limit);
        xTrace1(processcreation, TR_ERRORS, "xkernel thread limit now %d",
	        xk_thread_limit);
    }

    /* start the thread suspended so we can set its priority */
    thr_status = thr_create(0, 0,
			   (void *(*)(void *))enter_master_sledgehammer_monitor,
			   (void *)args, 
			   THR_DETACHED /* | THR_NEW_LWP */ | THR_SUSPENDED,
			   &child);
    if (!thr_status) {
        if (priority > THREAD_MAXPRIO) {
            priority = THREAD_MAXPRIO;
            xTrace1(processcreation, TR_SOFT_ERRORS,
		    "thread %d: requested priority was too high", child);
        }
        else if (priority < THREAD_MINPRIO) {
            priority = THREAD_MINPRIO;
            xTrace1(processcreation, TR_SOFT_ERRORS,
		    "thread %d: requested priority was too low", child);
        }
        thr_setprio(child, priority);
        thr_continue(child);

        xTrace1(processcreation, TR_MAJOR_EVENTS,
		"Created thread_id: %d", child);
        xTrace1(processswitch, TR_GROSS_EVENTS, "Created thread_id: %d", child);
    }
    else {
        xTrace1(processswitch, TR_ERRORS,
		"xkernel_aktivate_thread: thr_create failed (%d)", thr_status);
    }
}

/*
 * CreateProcess(function_ptr, priority, argcount, arg1, arg2, ...)
 */
bool
CreateProcess(void *r, int pri, int nargs, ...)
{
    va_list ap;
    short   prio;
    int     i;
    int     *arg_vec;
    int     *argp;

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

    va_start(ap, nargs);
    prio = pri;
 
    arg_vec = (int *)xMalloc((MAXARGS+2)*sizeof(int));
    argp = arg_vec;
    *argp++ = (int)r;
    *argp++ = nargs;
    for (i = 0; i < nargs; i++)
	*argp++ = va_arg(ap, int);

    xTrace2(processcreation, TR_MAJOR_EVENTS,
	    "Solaris CreateProcess: func_addr %x nargs %d", r, nargs);
    xkernel_activate_thread(arg_vec, prio);
    return TRUE;
}

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

/*
 * Yield
 */
void
Yield(void)
{
    Process *active = Active;

    xTrace0(processswitch, TR_FULL_TRACE, "Enter event function Yield");
    MASTER_UNLOCK;
    thr_yield();
    MASTER_LOCK;
    Active = active;
}

/*
 * 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(int *args)
{
    CreateProcFunc user_fn;
    int            nargs;
    int            threadcount;
    Process        *pd;
    int            *args_ptr = args;

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

    pd = (Process *)xMalloc(sizeof(Process));
    pd->link = NULL;  pd->thread = XTHREAD_SELF();

    user_fn = (CreateProcFunc)*(args++);
    nargs = *(args++);

    threadcount = current_xk_threads++;
    if (threadcount > max_xk_threads_inuse) {
        max_xk_threads_inuse = threadcount;
        xTrace1(processcreation, TR_EVENTS,
		"processcreate: new thread high water mark %d", threadcount);
    }

    if (nargs > MAXARGS)
        Kabort("master_sledgehammer_monitor: cannot handle more than 6 args.");
    {
        int argv[MAXARGS];
        int i;

        for (i = 0; i < nargs; i++)
            argv[i] = *(args++);
        Active = pd;
        xFree((char *)args_ptr);
        xTrace1(processcreation, TR_EVENTS,
		"enter_master_monitor: starting thread %d", XTHREAD_SELF());

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

    xFree((char *)pd);

    current_xk_threads--;
    MASTER_UNLOCK;
    xTrace1(processcreation, TR_MAJOR_EVENTS,
	    "master_monitor: thread %d unlocks", XTHREAD_SELF());
}

/* where are these used ??
 *
 * int delay_state;
 * 
 * struct timeval delay_time;
 */

/*
 * Delay
 *    n in ms 
 */
void
Delay(int n) 
{
    timestruc_t delay;

    xTrace0(processswitch, TR_FULL_TRACE, "Enter event function Delay");

    /* break ms into sec and nsec */
    delay.tv_sec = n/1000;
    delay.tv_nsec = (n%1000)*1e6;

    MASTER_UNLOCK;
    nanosleep(&delay, NULL);
    MASTER_LOCK;
}

void
xkIncreaseCthreadLimit(int n)
{
    if (thr_getconcurrency()) {
        xk_thread_limit += n;
        thr_setconcurrency(xk_thread_limit);
        xTrace1(processcreation, TR_MAJOR_EVENTS,
                "increased xkernel cthread limit to %d",
                thr_getconcurrency());
    }
    else {
        xTrace0(processcreation, TR_MAJOR_EVENTS,
          "increaseCthreadLimit -- xkernel is not limiting number of cthreads");
    }
}
