/*
	 TMScheduler.c, Copyright  (c) by Christian Conrad & George,
	 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/Threads/TMScheduler.c,v $
		Author(s):             Christian Conrad & George Fankhauser
		Affiliation:           ETH Zuerich, TIK
		Version:               $Revision: 3042 $
		Creation Date:         
		Last Date of Change:   $Date: 2007-06-28 01:28:34 +0200 (Don, 28 Jun 2007) $      by: $Author: sryffel $
 */

#include "TMScheduler.h"

#include "TMIPC.h"		/* ipcCheckPendingFlag */
#include "TMThread.h"	/* threadLock          */
#include "TMHal.h"
#include "TMClock.h"
#include "TMTime.h"
#include "Lock.h"
#include "Support.h"
#include "System.h"
#include "Assert.h"

/* LR 010131 */
#ifdef WITH_TIMER_QUEUE
#include "TMTimerNG.h"
#endif

/* globals (need to be accessed in exception handler to get current context) */
Scheduler scheduler;

/* local variables */
static LockDesc sLockDesc;
static Lock schedLock = &sLockDesc;


/* Clock interrupt handler
 * this function is just for readability. we would have called schedule
 * directly.
 * it's also the function where we would place clock updates.
 */
void tmClockHandler()
{
  /* Stop the clock. If not done the interrupt is generated
  	 once again and again ... 
   */
  tmResetClockInterrupt(CLOCK0);
  tmTimeUpdate();	/* update time by one tick */
  /* LR 010131  play with the new timer queue */
#ifdef WITH_TIMER_QUEUE

  tmTimerFire();
#endif

}


/* idle thread's main function, quite boring but nevertheless important
 */
void tmIdleMain()
{
  while (TRUE)
  {
    powerSaver();
  }
}


/*
 * Initialisation of the scheduler with queues and lock.
 * this function is only used at startup to initialize the scheduler with
 * a thread (which saves us the continuous testing on running == NULL) in
 * schedule().
 */
void schedulerInit(Thread* threadPtr)
{
  int i								;

  lockInit(schedLock);

  //-CJ	scheduler.running = threadPtr;
  for (i = 0; i < NBPRIORITYLEVELS; i++)
  {
    //-LR     CHECK(scheduler.prioList[i].ready   = listNew(), "listNew failed");
    //-LR     CHECK(scheduler.prioList[i].blocked = listNew(), "listNew failed");
    scheduler.prioList[i].ready   = listNew();
    scheduler.prioList[i].blocked = listNew();
  }
}


/* return the current thread
 */
Thread* schedulerRunning()
{
  return scheduler.running;
#if 0 //-CJ the caller needs to check if we have a valid thread

  if (scheduler.running)
    return scheduler.running;
  else
  {
    printf("Unfortunately, there is no running scheduled..\n");
    while (1)
      ; /* just waiting for user to help me */
  }
#endif
}


/*
 * A new thread structure is registered in the scheduler at its corresponding
 * priority level and at the head of the blocked queue.
 */
void schedulerInsert( Thread* threadPtr, ThreadPriority priority)
{

  //-LR   printf("schedulerInsert: threadPtr = 0x%8x, priority = %i\n",
  //-LR         (int)threadPtr, (int)priority);

  threadPtr->schedInfo.status = BLOCKED;
  threadPtr->schedInfo.priority = priority;

  lock(schedLock);
  {
    listAddInFront( scheduler.prioList[priority].blocked, threadPtr,
                    &(threadPtr->schedInfo.hint));
  }
  unlock(schedLock);
  //-LR   printf("schedulerInsert: return\n");
}


/*
 * The thread structure passed as argument is removed from any queue
 */
void schedulerRemove( Thread* threadPtr)
{
  ThreadPriority priority = threadPtr->schedInfo.priority;

  lock(schedLock);
  {
    if (threadPtr->schedInfo.status == BLOCKED)
    {
      listRemove( scheduler.prioList[priority].blocked, threadPtr,
                  threadPtr->schedInfo.hint);
    }
    else if (threadPtr->schedInfo.status == READY)
    {
      listRemove( scheduler.prioList[priority].ready, threadPtr,
                  threadPtr->schedInfo.hint);
    }
    else
    {
      PANIC("Not possible to remove a RUNNING thread from scheduler");
    }
  }
  unlock(schedLock);
}


/*
 * The thread passed as argument is appended at the end of the ready
 * list of same priority level. It is assumed the thread is already registered
 * in the scheduler queues.
 * ! This function may run in an exception handler.
 */
Error schedulerSetReady( Thread* threadPtr)
{
  ThreadPriority priority = threadPtr->schedInfo.priority;
  ASSERT(!lockTry(&threadPtr->lock));

  if ((threadPtr->schedInfo.status == READY) ||
      (threadPtr->schedInfo.status == RUNNING))
    return TM_OK;

  lock(schedLock);
  {
    /* swap thread from blocked-list to ready-list */
    listSwap(scheduler.prioList[priority].blocked,
             scheduler.prioList[priority].ready,
             threadPtr, threadPtr->schedInfo.hint);
    threadPtr->schedInfo.status = READY;
  }
  unlock(schedLock);
  return TM_OK;
}


/*
 * The thread passed as argument is added at the beginning of the blocked
 * list of the same priority level. It is assumed the thread is already  
 * registered in the scheduler queues.
 * ! This function may run in an exception handler.
 */
Error schedulerSetBlocked( Thread* threadPtr)
{
  ThreadPriority priority = threadPtr->schedInfo.priority;
  ASSERT(!lockTry(&threadPtr->lock));

  if (threadPtr->schedInfo.status == BLOCKED)
    return TM_OK;

  lock(schedLock);
  {
    /* swap thread from ready-list to blocked-list */
    listSwap(scheduler.prioList[priority].ready,
             scheduler.prioList[priority].blocked,
             threadPtr, threadPtr->schedInfo.hint);

    threadPtr->schedInfo.status = BLOCKED;
    /* remark: at this point, scheduler.running may point to a blocked
     * thread. so after setBlocked we should call schedule (see msgDispatcher).
     */
  }
  unlock(schedLock);
  return TM_OK;
}

/*
 * The thread is removed from its initial priority list and added to  
 * the new priority level.
 * ! This function may run in an exception handler.
 */
Error schedulerChangePriority(Thread* threadPtr, ThreadPriority new_priority)
{
  ThreadPriority old_priority = threadPtr->schedInfo.priority;
  ASSERT(!lockTry(&threadPtr->lock));

  if (new_priority == old_priority)
    return TM_OK;
  
  threadPtr->schedInfo.priority = new_priority;

  if (threadPtr->schedInfo.status == BLOCKED)
  {
    // thread is in blocked list
    lock(schedLock);
    {
      /* swap thread from the old to the new priority list */
      listSwap(scheduler.prioList[old_priority].blocked,
               scheduler.prioList[new_priority].blocked,
               threadPtr, threadPtr->schedInfo.hint);
    }
    unlock(schedLock);
  }
  else
  {
    // thread is in ready list
    lock(schedLock);
    {
      /* swap thread from the old to the new priority list */
      listSwap(scheduler.prioList[old_priority].ready,
               scheduler.prioList[new_priority].ready,
               threadPtr, threadPtr->schedInfo.hint);
    }
    unlock(schedLock);
  }

  return TM_OK;
}

/*
 * The next thread with the highest priority is set to running.
 * ! This function may run in an interrupt handler.
 */
void schedule()
{
  ThreadPriority priority;
  Thread* newRunning = NULL;
  Thread* oldRunning = scheduler.running;
  int flags;

  /* here we may be in the clock interrupt and possible are in race
   * condition with the thread manager (e.g. start/exit). this can be 
   * solved by testing schedLock. if it's locked we just give up because
   * we must give the thread manager a chance to release this lock.
   */
  if (!lockTry(schedLock))
    return;
  else
    unlock(schedLock);

  /* next we must avoid picking a thread that makes a syscall while the
   * thread manager is in the process of updating the global list/hashlist
   * of threads.
   */
  if (!lockTry(threadLock))
    return;
  else
    unlock(threadLock);

  /* Current running thread is put at the end of its ready queue,
   * unless it was previously put to sleep (by doing a msgRecv)
   */
  if (oldRunning->schedInfo.status != BLOCKED)
  {
    //-LR     CHECK(listMoveToEnd(scheduler.prioList[oldRunning->schedInfo.priority].ready,
    //-LR         oldRunning, oldRunning->schedInfo.hint) == LIST_OK,
    //-LR         "listMoveToEnd failed");
    LOCK_THREAD(oldRunning, flags);
    {
      listMoveToEnd(scheduler.prioList[oldRunning->schedInfo.priority].ready,
                    oldRunning, oldRunning->schedInfo.hint);
      oldRunning->schedInfo.status = READY;
    }
    UNLOCK_THREAD(oldRunning, flags);
  }

  /* Loop over all ready queues from higher to lower priority to find
   * next thread that is allowed to run
   */
  for (priority = KERNEL_PRIORITY_HIGH; priority < NBPRIORITYLEVELS; priority++)
  {
    listGetFirst(scheduler.prioList[priority].ready, (void**)&newRunning);
    if (newRunning != NULL)
    {
      /* yup, we found a non-empty ready queue and we always pick
       * the first one. we already put the old running thread to its
       * queue end which results in a fine priority round-robin
       * scheduling.
       */
      LOCK_THREAD(newRunning, flags);
      {
        newRunning->schedInfo.status = RUNNING;
        tmHalIpcResetPendingFlag(newRunning);
      }
      UNLOCK_THREAD(newRunning, flags);
      break;
    }
    /* the idle thread(s) guarantee that we always find a ready thread */
  }

  SysScheduled();

  ASSERT(newRunning != NULL);

  scheduler.running = newRunning;
  //printf("scheduled %i:%s @0x%08x  eip=0x%x\n", (int)newRunning->id, newRunning->name, (unsigned int)newRunning, newRunning->contextPtr->tf_eip);
  //DisplayContext("oldRunning:", oldRunning->contextPtr);
  //DisplayContext("newRunning:", newRunning->contextPtr);
  //-LR   BRK();
}

int threadDisplay(Thread *thread)
{
  if (thread)
  {
    printf("%i:%s \n", (int)thread->id, thread->name);
  }
  else
  {
    return 1;
  }
  return 0;
}

int parseThreadsList()
{
  int     counter = 0;
#if 0

  ThreadPriority priority;
  Thread* newRunning = NULL;

  printf("Ready   threads:\n");
  for (priority = KERNEL_PRIORITY_HIGH; priority < NBPRIORITYLEVELS; priority++)
  {
    listGetFirst(scheduler.prioList[priority].ready, (void**)&newRunning);
    while (newRunning != NULL)
    {
      counter++;
      threadDisplay(newRunning);
      listGetNext(scheduler.prioList[priority].ready, (void**)&newRunning);
    }
  }
  printf("\n");
  printf("Blocked threads:\n");
  for (priority = KERNEL_PRIORITY_HIGH; priority < NBPRIORITYLEVELS; priority++)
  {
    listGetFirst(scheduler.prioList[priority].blocked, (void**)&newRunning);
    while (newRunning != NULL)
    {
      counter++;
      threadDisplay(newRunning);
      listGetNext(scheduler.prioList[priority].blocked, (void**)&newRunning);
    }
  }
  printf("\n");
#endif

  return counter;
}

