#ifndef	lint
static char rcs_id[] = "$Header: procs.c,v 1.4 88/06/01 16:19:19 ecc Exp $";
#endif	not lint

/*
 * threads/procs.c
 *
 * $Source: /usr0/ecc/nectar/src/cab/threads/RCS/procs.c,v $
 *
 * Implementation of procs (concurrent processes)
 * that underlie threads.
 */


#include <nectar_sys.h>
#include <threads.h>
#include "thread_internals.h"

/*
 * Queue of runnable procs.
 * Currently executing proc is at the head.
 */
PRIVATE struct Queue ready = QUEUE_INITIALIZER;
PRIVATE int n_blocked;		/* number of blocked procs */


PRIVATE void
Print_Proc(p)
	proc_t p;
    BEGIN(Print_Proc)
	char *s;

	switch (p->state) {
	    case PROC_RUNNING:
		s = "";
		break;
	    case PROC_SPINNING:
		s = "+";
		break;
	    case PROC_BLOCKED:
		s = "*";
		break;
	    default:
		ASSERT(SHOULDNT_HAPPEN);
	}
	printf(" %x(%s)%s",
		p, Thread_Name(p->incarnation), s);
	RET;
    END(Print_Proc)

PRIVATE void
Print_Queue(s, queue)
	string_t s;
	Queue_t queue;
    BEGIN(Print_Queue)
	PRINT_MSG((msg, "%s", s));
	QUEUE_MAP(queue, proc_t, Print_Proc);
	printf("\n");
	RET;
    END(Print_Queue)


PRIVATE proc_t
Proc_Alloc()
    BEGIN(Proc_Alloc)
	proc_t p = NEW(struct proc);

	p->incarnation = NO_THREAD;
	p->level = 0;
	p->state = PROC_RUNNING;
	RETURN(p);
    END(Proc_Alloc)

PUBLIC void
thread_Proc_Init()
    BEGIN(thread_Proc_Init)
	PRIVATE boolean_t procs_started = FALSE;
	proc_t p = Proc_Alloc();

	ASSERT(!procs_started);
	QUEUE_ENQ(&ready, p);
	n_blocked = 0;
	thread_Stack_Init(p);
	procs_started = TRUE;
	RET;
    END(thread_Proc_Init)

PUBLIC void
thread_Proc_Create()
    BEGIN(thread_Proc_Create)
	proc_t parent = thread_Proc_Self();
	proc_t child;

	Intr_Disable();
	child = Proc_Alloc();
	thread_Stack_Alloc(child);
	QUEUE_PREQ(&ready, child);	/* run child first */
	IF_DEBUG(Thread_debug, Print_Queue("thread_Proc_Create", &ready));
	/*
	 * Start executing child with interrupts disabled.
	 * Interrupts will be enabled again in thread_Body().
	 */
	thread_Proc_Start(parent->context, child, child->stack_base + child->stack_size);
	Intr_Enable();	/* wakeup point */
	RET;
    END(thread_Proc_Create)


EXPORT void
Condition_Wait(c, m)
	Condition_t c;
	Mutex_t m;
    BEGIN(Condition_Wait)
	proc_t p, q;
	boolean_t enabled = (Intr_Level() == 0);

	ASSERT(Intr_Level() <= 1);
	if (enabled)
		Intr_Disable();
	QUEUE_DEQ(&ready, proc_t, p);
	QUEUE_ENQ(&c->queue, p);
	p->state = PROC_BLOCKED;
	n_blocked += 1;

	IF_DEBUG(Thread_debug, Print_Queue("Condition_Wait", &c->queue));

	/*
	 * Release the mutex while we wait for the condition.
	 */
	Mutex_Unlock(m);
	QUEUE_HEAD(&ready, proc_t, q);
	if (q == NO_PROC)
		ERROR_HALT((msg, "*** All C threads are blocked. ***\n"));

	/*
	 * Switch contexts with interrupts disabled.
	 * All other wakeup points re-enable interrupts.
	 */
	thread_Proc_Switch(p->context, q->context);

	ASSERT(Intr_Level() == 1);
	if (enabled)
		Intr_Enable();	/* wakeup point */

	/*
	 * Control reaches here only after someone switches back to us.
	 * Re-acquire the mutex and return.
	 */
	ASSERT(p->state == PROC_RUNNING);
	Mutex_Lock(m);
	RET;
    END(Condition_Wait)


/*
 * Like Condition_Wait, but uses interrupt enable/disable
 * rather than mutex lock/unlock.
 */
EXPORT void
Condition_Wait_Intr(c)
	Condition_t c;
    BEGIN(Condition_Wait_Intr)
	proc_t p, q;

	ASSERT(Intr_Level() == 1);
	/* interrupts are already disabled */
	QUEUE_DEQ(&ready, proc_t, p);
	QUEUE_ENQ(&c->queue, p);
	p->state = PROC_BLOCKED;
	n_blocked += 1;

	IF_DEBUG(Thread_debug, Print_Queue("Condition_Wait", &c->queue));

	QUEUE_HEAD(&ready, proc_t, q);
	if (q == NO_PROC)
		ERROR_HALT((msg, "*** All C threads are blocked. ***\n"));

	/*
	 * Switch contexts with interrupts disabled.
	 * All wakeup points (except the one below) re-enable interrupts.
	 */
	thread_Proc_Switch(p->context, q->context);

	/*
	 * Control reaches here only after someone switches back to us.
	 * Leave interrupts disabled and return.
	 */
	ASSERT(p->state == PROC_RUNNING);
	ASSERT(Intr_Level() == 1);
	RET;
    END(Condition_Wait_Intr)


/*
 * Continue a waiting thread.
 * Called from signal or broadcast with the condition variable locked.
 */

#define	PROC_CONTINUE(p) \
    do { \
	(p)->state = PROC_RUNNING; \
	QUEUE_ENQ(&ready, (p)); \
	n_blocked -= 1; \
    } while (0)

/*
 * Called by Condition_Signal macro when someone is waiting.
 */
PUBLIC void
condition_Signal(c)
	Condition_t c;
    BEGIN(condition_Signal)
	proc_t p;

	IF_DEBUG(Thread_debug, Print_Queue("Condition_Signal", &c->queue));

	Intr_Disable();
	QUEUE_DEQ(&c->queue, proc_t, p);
	if (p != NO_PROC)
		PROC_CONTINUE(p);
	Intr_Enable();
	RET;
    END(condition_Signal)

/*
 * Called by Condition_Broadcast macro when someone is waiting.
 */
PUBLIC void
condition_Broadcast(c)
	Condition_t c;
    BEGIN(condition_Broadcast)
	proc_t p;

	IF_DEBUG(Thread_debug, Print_Queue("Condition_Broadcast", &c->queue));

	Intr_Disable();
	for (;;) {
		QUEUE_DEQ(&c->queue, proc_t, p);
		if (p == NO_PROC)
			break;
		PROC_CONTINUE(p);
	}
	Intr_Enable();
	RET;
    END(condition_Broadcast)


EXPORT void
Thread_Yield()
    BEGIN(Thread_Yield)
	ASSERT(Intr_Level() == 0);
	Intr_Disable();
	if (ready.head != ready.tail) {
		/*
		 * Place ourself at the end of the ready queue.
		 */
		proc_t p, q;
		QUEUE_DEQ(&ready, proc_t, p);
		QUEUE_ENQ(&ready, p);
		QUEUE_HEAD(&ready, proc_t, q);
		/*
		 * Switch contexts with interrupts disabled.
		 * All wakeup points re-enable interrupts.
		 */
		thread_Proc_Switch(p->context, q->context);
	}
	Intr_Enable();	/* wakeup point */
	ASSERT(Intr_Level() == 0);
	RET;
    END(Thread_Yield)


EXPORT void
Thread_Set_Level(n)
	int n;
    BEGIN(Thread_Set_Level)
    	thread_Proc_Self()->level = n;
	RET;
    END(Thread_Set_Level)

EXPORT int
Thread_Level()
    BEGIN(Thread_Level)
    	RETURN(thread_Proc_Self()->level);
    END(Thread_Level)


/*
 * Force linking with alternate version of blocking system calls.
 */
#ifndef	lint
IMPORT select();
PRIVATE int (*select_proc)() = select;
#endif	not lint
