/*
    LWThreads.c, Copyright  (c) by George Fankhauser, Claudio Jeker
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public Licence.
 
    A full copy of the GNU GPL is provided in the file COPYING found in the 
    development root of Topsy.
*/
/*
	
	File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/ThreadsLight/LWThreads.c,v $
	Author(s):             George Fankhauser
	Affiliation:           ETH Zuerich, TIK
	Version:               $Revision: 2922 $
	Creation Date:         
	Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $      by: $Author: baderma $
	
*/
/*
    Topsy LightWeight Threads
    
    This package offers (more or less) the same as Topsy messages and
    threads do. The main diffs are:
    - they do not work across address spaces (user/kernel in Topsy)
    - they are not preemptable
    - they are much faster
    - quite small (about 1500 bytes machine code)
    - messages are not queued
    - allocation of structures is static, i.e. one has to know in advance
      how many threads will be used
    - uses only vmAlloc/vmFree (could be replaced with malloc/free on unix)
    - uses only a minimum HAL: save/restore (similar to setjmp/longjmp)
*/

#include "LWThreads.h"
#include "Syscall.h"
#include "Support.h"
#include "kalloc_api.h"

/* ptr to main struct (no longer global)
static LWThreadArrayDesc* lw;
*/

// implemented in asm (no longer needed)
//extern void lwSave(void*, void*, void*);
//extern void lwRestore(long, long, long);
//extern void lwRestoreParam(long, long, long, ThreadArg);

extern void lwSwitchTo(LWPool lw, ThreadId to);
/* this pseudo function has to be implemented in assembler */
/* static void lwSwitchTo(LWPool lw, ThreadId to)
{
	LWThread from = &lw->a[lw->current];
	// save
	lwSave(	&from->context.stackPointer,
			&from->context.framePointer,
			&from->context.programCounter);
	// restore
	lw->current = to;
	from = &lw->a[lw->current]; // from points on new lwThread
	lwRestore(	from->context.stackPointer,
				from->context.framePointer,
				from->context.programCounter);
	// lwRestore does not return		    
}
*/

extern void lwJumpToParam(LWPool lw, ThreadId to, ThreadArg parameter);
/* this pseudo function has to be implemented in assembler */
/* NOTE: This function is only used to jump into a new Lightweight Thread
         "lwRestoreParams does not need to restore the save registers */
/*static void lwJumpToParam(LWPool lw, ThreadId to, ThreadArg parameter)
{
	LWThread from = &lw->a[lw->current];
	// save
	// we don't want to trash ra, sp and fp ...
	// ... this is why it has to be in assembler
	lwSave(	&from->context.stackPointer,
			&from->context.framePointer,
			&from->context.programCounter);
	// restore
	lw->current = to;
	from = &lw->a[lw->current]; // from points on new LWThread
	lwRestoreParam(	from->context.stackPointer,
					from->context.framePointer,
					from->context.programCounter,
					parameter);
	// lwRestoreParam does not return		    
}
*/

LWError lwInit(LWPool *lw, unsigned int noOfThreads)
{
  int size = sizeof(LWThreadArrayDesc) + noOfThreads*sizeof(LWThreadDesc);
  int stacksize = (noOfThreads-1)*LW_STACKSIZE; /* lwThread 0 uses main stack */
  long i;

  if ((*lw = km_alloc(size, KM_THREAD)) != NULL)
  {
    if (((*lw)->stack = km_alloc(stacksize, KM_THREAD)) == NULL)
    {
      km_free_type(*lw, KM_THREAD);
      printf("lwInit: not enough Memory (stack size = %i)\n", stacksize);
      return LW_FAILED;
    }
    (*lw)->N = noOfThreads;
    (*lw)->a = (LWThread)((*lw) + sizeof(LWThreadArrayDesc));
    /* initalize all threads to dummy values ... */
    for (i = 1; i < (*lw)->N; i++)
    {
      (*lw)->a[i].next = LW_NO_THREAD;
      (*lw)->a[i].prev = LW_NO_THREAD;
      (*lw)->a[i].state = LW_THREAD_DOES_NOT_EXIST;
      (*lw)->a[i].msg = NULL;
    }
    /* except 0 this is the main thread is is allready running */
    (*lw)->a[0].next = 0; /* next c'est moi */
    (*lw)->a[0].prev = 0; /* prev again moi */
    (*lw)->a[0].state = LW_STATE_READY; /* hey I'm allready running */
    (*lw)->a[0].msg = NULL; /* no message */
    (*lw)->current = 0;   /* make the current thread the current thread :-) */
    (*lw)->n = 1;         /* there is now one lwThread running */

    return LW_OK;
  }
  else
  {
    printf("lwInit: not enough Memory (pool)\n");
    return LW_FAILED;
  }
}

void lwDestroy(LWPool lw)
{
  long i;
  char *stack = lw->stack;

  if ( lw->n != 1 )
  {
    for (i = 0; i < lw->N; i++)
    {
      lwKill(lw, i);
    }
  }
  km_free_type(lw, KM_THREAD);
  /* XXX does that work, running without a stack ??? */
  km_free_type(stack, KM_THREAD);
  /* It seems so --cjeker 20010315 */
  /* with the new MM this will no longer */
  tmExit(); /* nothing left to run, so kill also the thread */
}

LWError lwMsgSend(LWPool lw, ThreadId to, Message *msg)
{
  LWThread receiver;

  // redirect self
  if (to == LW_SELF)
    to = lw->current;
  // check existence
  if (to >= lw->N || to < 0)
  {
    return LW_FAILED;
  }

  receiver = &(lw->a[to]);
  if (receiver->state == LW_THREAD_DOES_NOT_EXIST)
  {
    return LW_FAILED;
  }
  /* regardless of state, pass message on */
  /* NOTE: the message is passed as pointer and not copied so do not modify
   *       or free the message until it was processed by the reciever */
  receiver->msg = msg;
  receiver->from = lw->current;
  receiver->state = LW_STATE_READY;

  //-CJ	printf("lwMsgSend: switch to: %i current %i\n", (int)to, (int)lw->current);
  //-CJ	printf("lwMsgSend: reciever->msg %p, from %li\n", receiver->msg, (long)receiver->from);
  //-CJ	printf("lwMsgSend: current context: SP %p, FP %p, PC %p\n",
  //-CJ			(void*)lw->a[lw->current].context.stackPointer,
  //-CJ			(void*)lw->a[lw->current].context.framePointer,
  //-CJ			(void*)lw->a[lw->current].context.programCounter);
  //-CJ	printf("lwMsgSend: next context: SP %p, FP %p, PC %p\n",
  //-CJ			(void*)lw->a[to].context.stackPointer,
  //-CJ			(void*)lw->a[to].context.framePointer,
  //-CJ			(void*)lw->a[to].context.programCounter);
  //-CJ	printf("lwMsgSend: %p %li\n", lw, (long)to);
  lwSwitchTo(lw, to); /* does not return */
  //-CJ	printf("lwMsgSend: after lwSwitchTo\n");
  return LW_OK;
}

//-CJextern int BC;

/* timeout and msgId is for signature compatibility only and ignored */
LWError lwMsgRecv(LWPool lw, ThreadId* from, MessageId msgId, Message* msg, int timeOut)
{
  LWThread current;

  //-CJ	printf("lwMsgRecv: entering\n");
  current = &(lw->a[lw->current]);
  if (current->msg == NULL)
  {
    current->state = LW_STATE_WAIT;
    //-CJ		printf("lwMsgRecv: doing yield\n");
    lwYield(lw);
  }
  // deliv msg
  //-CJ	printf("lwMsgRecv: current ctx %p, current->msg %p, msg %p\n", current, current->msg, msg);
  //-CJ	BC = 1;
  byteCopy(msg, current->msg, sizeof(Message) );
  *from = msg->from = current->from;
  current->msg = NULL;
  current->from = LW_NO_THREAD;
  // cont exec
  return LW_OK;
}

/* name is for signature compatibility only and silently ignored */
LWError lwCreate(LWPool lw, ThreadId id, LWThreadMainFunction mainFunction,
                 ThreadArg parameter, char *name)
{
  ThreadId t1, t2;
  LWThreadContext* ctxt;

  if (lw->n >= lw->N)
    return LW_FAILED;

  if (lw->a[id].state != LW_THREAD_DOES_NOT_EXIST)
  {
    return LW_FAILED;
  }

  lw->n++;
  lw->a[id].state = LW_STATE_READY;
  lw->a[id].msg = NULL;
  ctxt = &(lw->a[id].context);
  //-CJ	ctxt->stackPointer=(long)lw->stack + id * LW_STACKSIZE - 4;
  ctxt->stackPointer=(long)lw->stack + id * LW_STACKSIZE - 8;
  ctxt->framePointer = 0;
  ctxt->programCounter = (unsigned long)mainFunction;

  if (lw->current == LW_NO_THREAD)
  {
    printf("PANIC: lwCreate called with no running thread\n");
    return LW_FAILED;
  }
  else
  {
    t1 = lw->current;
    t2 = lw->a[lw->current].next;
    lw->a[id].next = t2;
    lw->a[id].prev = t1;
    lw->a[t1].next = id;
    lw->a[t2].prev = id;
  }

  /* does not return */
  lwJumpToParam(lw, id, parameter);
  return LW_OK;
}

/* name is for signature compatibility only and silently ignored */
LWError lwStart(LWPool lw, ThreadId* id, LWThreadMainFunction mainFunction,
                ThreadArg parameter, char *name)
{
  long i;
  ThreadId t1, t2;
  LWThreadContext* ctxt;

  if (lw->n >= lw->N)
    return LW_FAILED;

  for (i = 1; i < lw->N; i++)
  { /* don't use the first slot. Used by main thread */
    if (lw->a[i].state == LW_THREAD_DOES_NOT_EXIST)
    {
      break;
    }
  }

  //-CJ	printf("lwStart: free Thread No. %i\n", i);
  // set info
  lw->n++;
  *id = i;
  lw->a[i].state = LW_STATE_READY;
  lw->a[i].msg = NULL;
  ctxt = &(lw->a[i].context);
  //-CJ	ctxt->stackPointer=(long)lw->stack + i * LW_STACKSIZE - 4;
  ctxt->stackPointer=(long)lw->stack + i * LW_STACKSIZE - 8;
  ctxt->framePointer = 0;
  ctxt->programCounter = (unsigned long)mainFunction;
  /*-CJ	printf("lwStart: Thread No. %i inited: SP %p, FP %p, PC %p\n", i,
  			ctxt->stackPointer, ctxt->framePointer, ctxt->programCounter); */
  if (lw->current == LW_NO_THREAD)
  {
    printf("PANIC: lwStart called with no running thread\n");
    return LW_FAILED;
  }
  else
  {
    t1 = lw->current;
    t2 = lw->a[lw->current].next;
    lw->a[i].next = t2;
    lw->a[i].prev = t1;
    lw->a[t1].next = i;
    lw->a[t2].prev = i;
  }
  //-CJ	printf("lwStart: Thread %i will be started (current Thread %i)\n", i, lw->current);
  /* does not return */
  lwJumpToParam(lw, i, parameter);
  return LW_OK;
}

LWError lwKill(LWPool lw, ThreadId id)
{
  ThreadId t1, t2;

  if (lw->n <= 0)
    return LW_FAILED;
  if (id >= lw->N || id < 0)
    return LW_FAILED;
  if (lw->a[id].state == LW_THREAD_DOES_NOT_EXIST)
    return LW_FAILED;

  lw->a[id].state = LW_THREAD_DOES_NOT_EXIST;

  if (lw->n == 1)
  {  /* last lwThread dies */
    lwDestroy(lw); /* does not return (kills thread) */
  }
  else
  {
    t2 = lw->a[id].next;
    t1 = lw->a[id].prev;
    lw->a[t1].next = t2;
    lw->a[t2].prev = t1;
  }
  lw->n--;
  return LW_OK;
}

void lwExit(LWPool lw)
{
  lwKill(lw, lw->current);
  lwYield(lw);
}

void lwYield(LWPool lw)
{
  ThreadId next = lw->a[lw->current].next;

  while (lw->a[next].state != LW_STATE_READY)
  {
    next = lw->a[next].next;
    if (next == lw->current)
    {
      printf("lwYield: DEADLOCK DETECTED, AIIIEEEEEE\n");
      lwDestroy(lw); // !deadlock! ; XXX Fix me!
    }
  }
  //-CJ	printf("lwYield: switch to: %i current %i\n", next, lw->current);
  /*-CJ	printf("lwYield: current context: SP %p, FP %p, PC %p\n",
  			lw->a[lw->current].context.stackPointer,
  			lw->a[lw->current].context.framePointer,
  			lw->a[lw->current].context.programCounter);
  	printf("lwYield: next context: SP %p, FP %p, PC %p\n",
  			lw->a[next].context.stackPointer,
  			lw->a[next].context.framePointer,
  			lw->a[next].context.programCounter); */
  lwSwitchTo(lw, next);
}

