/*
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $RCSfile: scout_thread.c,v $
 *
 * $Log: scout_thread.c,v $
 * Revision 1.3  1997/04/29 23:16:52  rrp
 * chanced from ifndef NDEBUG to ifdef XK_DEBUG
 *
 * Revision 1.2  1997/04/29 22:52:26  rrp
 * minor changes from linux port
 *
 * Revision 1.1  1997/01/28 22:50:41  rrp
 * Initial revision
 *
 * Revision 1.6  1996/05/28  23:18:33  davidm
 * Move fixed-priority scheduler to thread_prio.c.
 * idle_thread, idler: Remove.
 * timeslice: New variable.
 * threadMarkUnblocked: Rename from mark_unblock and globalize.
 * threadRegisterScheduler: New function.
 * threadInit: Initialize EDF and fixed-prio schedulers.
 *
 * Revision 1.5  1996/04/23 00:05:37  davidm
 * sched: Assign address of dummyThread instead of 0 to dead_man.
 *
 * Revision 1.4  1996/04/20 21:25:01  davidm
 * threadIdleCount: New variable.  Counts # of times idle thread gets called.
 *
 * Revision 1.3  1996/04/20  21:22:17  davidm
 * Fixed up floating point support some more.
 *
 * Revision 1.2  1996/03/30 02:18:46  davidm
 * Various fixes.
 *
 * Revision 1.1  1995/10/31  21:31:19  davidm
 * Initial revision
 *
 * Revision 1.1  1995/10/31  21:28:01  davidm
 */
/*
 * Threads package.
 */
#include <memory.h>
#include <stdio.h>
#include <strings.h>
#include <signal.h>
#include "xk_assert.h"
#include "xk_debug.h"
#include "x_util.h"
#include "platform.h"
#include "scout_thread.h"
#include "scout_thread_arch.h"
#include "machine.h"

#define NPRIOS  (THREAD_PRIO_MIN - THREAD_PRIO_MAX + 1)

int tracethread = 0;

struct ThreadStack {
    union {
	void *		sp;	/* top of stack */
	ThreadStack	next;	/* next free stack */
    } u;
    void *	stack_limit;
    long	magic;		/* magic number */
#ifdef OPTION_FPU_SUPPORT
    double	fpu_state[FPU_STATE_SIZE / sizeof(double)];
#endif OPTION_FPU_SUPPORT
};


static void start (void);		/* forward declaration */
static void retire (void);		/* never returns */
static void sched (void);

static struct ThreadOptions def_opts =
{
    "noname",				/* name */
    0,					/* arg */
    THREAD_PRIO_STD			/* prio */
};

const ThreadOptions	threadDefaultOptions = &def_opts;

/*
 * Invariant: the currently running thread is "threadSelf" (it is
 * *not* in any ready_q!).
 */
static struct Thread	dummyThread;	/* just so we have tSelf->errno */
static struct Queue     ready_q[NPRIOS];
static unsigned int     high_prio = THREAD_PRIO_MAX;


Thread			threadSelf = &dummyThread;
bool			threadInitialized = FALSE;

static Thread		dead_man = &dummyThread;

#ifdef OPTION_FPU_SUPPORT
static struct {
    bool		enabled;
    Thread		owner;
} fpu = {TRUE, &dummyThread};
#endif

static ThreadStack	first_stack = 0;
static sigset_t emptyset;

void threadShowBlocked (void);


#define ATTACH_STACK(s)				\
{						\
    (s) = first_stack;				\
    if (s) {					\
	first_stack = first_stack->u.next;	\
    } else {					\
	(s) = mkstack();			\
    }						\
    (s)->u.sp = (s)->stack_limit;		\
}

#define DETACH_STACK(s)				\
{						\
    (s)->u.next = first_stack;			\
    first_stack = (s);				\
}


#ifdef XK_DEBUG
#  define STACK_CHECK()							\
{									\
    if (tSelf != &dummyThread && 					\
       (threadFreeStack() <= 0 || tSelf->stack->magic != MAGIC_COOKIE))	\
    {									\
	xError("stack overflow!");					\
	retire();							\
    }									\
}
#else
# define STACK_CHECK()
#endif /* XK_DEBUG */


#ifdef XK_DEBUG

#define MAX_THREADS	256

static Thread	blocked[MAX_THREADS];

#define HASH(a)	(((unsigned long)(a) >> 3) % MAX_THREADS)


static void
mark_blocked (Thread t)
{
    int i, i0;

    i = i0 = HASH(t);
    while (blocked[i]) {
	i = (i + 1) % MAX_THREADS;
	if (i == i0) {
	    Kabort("thread.mark_blocked: too many threads!");
	}
    }
    blocked[i] = t;
}


static void
mark_unblocked (Thread t)
{
    int i, i0;

    i = i0 = HASH(t);
    while (blocked[i] != t) {
	i = (i + 1) % MAX_THREADS;
	if (i == i0) {
	    Kabort("thread.mark_unblocked: couldn't find thread!");
	}
    }
    blocked[i] = 0;
}


void
threadShowBlocked (void)
{
    Thread t;
    int i;

    printf("%-16s %-16s %-16s name:\n", "tcb address:", "sp:", "pc:");
    for (i = 0; i < MAX_THREADS; ++i) {
	t = blocked[i];
	if (t) {
	    printf("%16p %16p %16p %s\n",
		   t, t->stack,
		   t->running ? GET_SAVED_PC(t->stack->u.sp) : t->func,
		   threadName(t));
	}
    }
}

#endif /* !XK_DEBUG */


/*
 * Print string using minimal amount of stack space:
 */
static void
error (const char *msg)
{
    int ch;

    while ((ch = *msg++) != '\0') {
	putchar(ch);
    }
    putchar('\n');
}


static ThreadStack
mkstack (void)
{
    ThreadStack s;

    /*
     * For now, we have only one size of stacks (i.e., we ignore stack
     * size option).
     */
    s = (ThreadStack) xMalloc(DEFAULT_STACK_SIZE);
    if (!s) {
	Kabort("thread.mkstack: out of memory");
    }
    s->stack_limit = (char *) s + DEFAULT_STACK_SIZE;
    s->magic = MAGIC_COOKIE;
    return s;
}


/*
 * Switch context from tSelf to t.  This is tricky code.  You
 * should verify the generated assembly code.
 */
static void
cswtch (Thread t)
{
    static Thread old;

#ifdef OPTION_FPU_SUPPORT
    if (fpu.enabled && t != fpu.owner) {
	fpu.enabled = FALSE;
	FPU_OFF;
    }
#endif

    old = threadSelf;
    threadSelf = t;
    if (old != &dummyThread) {
	SAVE_STATE(old->stack->u.sp);
    }
    if (tSelf->running) {
	LOAD_STATE(tSelf->stack->u.sp);
    } else {
	tSelf->running = TRUE;
	/*
	 * Switch to new stack and invoke start(); the second argument
	 * is just there to tell GCC that start() is really used.
	 */
        enable_signals(1);
	SWITCH_STACK_AND_START(tSelf->stack->u.sp, start);
    }
}


static void
sched (void)
{
    Thread next = 0;


    /* dispatch to highest priority thread: */
    while (1) {
        for (; high_prio <= THREAD_PRIO_MIN; ++high_prio) {
            next = queueRemove(&ready_q[high_prio - THREAD_PRIO_MAX]);
            if (next) {
                break;
            }
        }
	if (next) {
	    if (!next->stack) {
		ATTACH_STACK(next->stack);
	    }
	    cswtch(next);
	    if (dead_man != &dummyThread) {
		xTrace1(thread, TR_EVENTS,
			 "thread.sched: freeing space of dead thread "
			 "`at %p", dead_man);
		DETACH_STACK(dead_man->stack);
		xFree((char *)dead_man);
		dead_man = &dummyThread;
	    }
            enable_signals(1);
	    return;
	}
        /* Nothing to do, so take a nap */
        enable_signals(1);
        sigsuspend(&emptyset);
        enable_signals(0);
    }
}


static Thread
create (ThreadFunc func, const ThreadOptions options)
{
    Thread t;
    char * mem;
    int name_len;

    name_len = strlen(options->name) + 1;

    /*
     * Allocate thread descriptor, and name string in one chunk:
     */
    mem = xMalloc(sizeof(struct Thread) + name_len);
    if (!mem) {
	xTrace0(thread, TR_EVENTS,
		 "thread.create: not enough memory for new thread");
	return 0;
    }

    /* initialize memory: */
    t = (Thread) mem;
    t->option		= *options;
    t->option.name	= (char *) t + sizeof(struct Thread);
    t->running		= FALSE;
    t->func		= func;
    t->stack		= 0;
    strcpy(t->option.name, options->name);

#ifdef XK_DEBUG
    mark_blocked(t);
#endif
    return t;
}


/*
 * Retire the currently executing thread and invoke the scheduler.
 * All memory associated with the current thread is freed.
 */
static void
retire (void)
{
    dead_man = tSelf;	/* let next thread deallocate space */
#ifdef OPTION_FPU_SUPPORT
    if (tSelf == fpu.owner) {
	fpu.owner = &dummyThread;
    }
#endif
    threadSelf = &dummyThread;		/* avoid state saving */
    sched();
}


/*
 * Invoke the initial function of thread tSelf.  If it ever
 * returns, we simply retire the thread.
 */
static void
start (void)
{
    if (dead_man != &dummyThread) {
	xTrace1(thread, TR_EVENTS,
		 "thread.start: freeing space of dead thread at %p", dead_man);
	DETACH_STACK(dead_man->stack);
	xFree((char *)dead_man);
	dead_man = &dummyThread;
    }

    xTrace2(thread, TR_EVENTS,
	     "thread.start: starting thread at %p free stack %ld bytes",
	     tSelf, threadFreeStack());

    tSelf->func(tSelf->option.arg);

    xTrace1(thread, TR_EVENTS, "thread.start: thread %p terminating.", tSelf);
    retire();
}


void
threadInit (ThreadFunc init, void * arg)
{
    struct ThreadOptions to;
    unsigned int prio;
    Thread t;

    sigemptyset(&emptyset);
    for (prio = THREAD_PRIO_MAX; prio <= THREAD_PRIO_MIN; ++prio) {
        queueInit(&ready_q[prio - THREAD_PRIO_MAX]);
    }

#ifdef XK_DEBUG
    memset(blocked, 0, sizeof(blocked));
#endif

    /* create initial thread: */
    to = def_opts;
    to.name = "initializer";
    to.arg  = arg;
    t = create(init, &to);
    if (!t) {
	Kabort("threadInit: failed to create initializer thread");
    }
    threadWakeup(t);
    threadInitialized = TRUE;
    retire();	/* terminate boot stack and get things going: */
}


Thread
threadCreate (ThreadFunc fun, const ThreadOptions options)
{
    Thread p;

    STACK_CHECK();
    enable_signals(0);
    p = create(fun, options);
    enable_signals(1);
    return p;
}


void
threadStop (void)
{
    enable_signals(0);
    xTrace1(thread, TR_EVENTS, "threadStop: thread %p terminating.", tSelf);
    retire();
}


void
threadSuspend (void)
{
    /*
     * The thread is already off the ready queue it came from, so
     * there isn't much left to do...
     */
    STACK_CHECK();
    enable_signals(0);
#ifdef XK_DEBUG
    mark_blocked(tSelf);
#endif    
    sched();
}


void
threadSuspendWithContinuation (void)
{
    STACK_CHECK();
    enable_signals(0);
#ifdef XK_DEBUG
    mark_blocked(tSelf);
#endif    
    tSelf->running = FALSE;
    DETACH_STACK(tSelf->stack);
    tSelf->stack = 0;
    threadSelf = &dummyThread;		/* no need to save any state */
    sched();
}


void
threadYield (void)
{
    STACK_CHECK();
    enable_signals(0);
    xAssert((unsigned int) (tSelf->option.priority - THREAD_PRIO_MAX)
             <= THREAD_PRIO_MIN);
    queueAppend(&ready_q[tSelf->option.priority - THREAD_PRIO_MAX], tSelf);
    if (tSelf->option.priority < high_prio) {
        high_prio = tSelf->option.priority;
    }
    sched();
}


void
threadFPUFault (void)
{
#ifdef OPTION_FPU_SUPPORT
    FPU_ON;
    fpu.enabled = TRUE;
    if (tSelf != fpu.owner) {
	if (fpu.owner->stack) {
	    SAVE_FPU_STATE(fpu.owner->stack->fpu_state);
	}
	fpu.owner = tSelf;

	/*
	 * fpu.owner or fpu.owner->stack can be 0 if somebody tries
	 * to access the fpu while in the scheduler.  This does not
	 * happen normally, but is quite common when interrupting
	 * via kernel gdb.  It's better to play it safe here.
	 */
	if (fpu.owner->stack) {
	    LOAD_FPU_STATE(fpu.owner->stack->fpu_state);
	}
    }
#else
    error("threadFPUFault: floating-point fault while FPU is disabled "
	  "(may need to enable OPTION_FPU_SUPPORT)");
#endif /* OPTION_FPU_SUPPORT */
}


size_t
threadFreeStack (void)
{
    char sp;
    size_t free_stack;

    free_stack = (&sp - (char *) tSelf->stack) - MIN_STACK_SIZE;
    return free_stack;
}


const char *
threadName (Thread t)
{
    return t->option.name;
}


void
threadWakeup (Thread t)
{
    enable_signals(0);
#ifdef XK_DEBUG
    mark_unblocked(t);
#endif
    xAssert((unsigned int) (t->option.priority - THREAD_PRIO_MAX)
             <= THREAD_PRIO_MIN);
    queueAppend(&ready_q[t->option.priority - THREAD_PRIO_MAX], t);
    if (t->option.priority < high_prio) {
        high_prio = t->option.priority;
    }
    enable_signals(1);
}
