/*
    Lock.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/Topsy/Lock.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 $
  */

#include "Lock.h"
#include "Support.h"
#include "Assert.h"

/*
 * a simple spin-lock that assumes a bus-interlocked testAndSet function
 * that takes a pointer to a Boolean and returns TRUE if the lock was taken 
 * FALSE if it could not be locked.
 */

/* the lock is in unlocked state at the beginning */
void lockInit(Lock lock)
{
  lock->lockVariable = FALSE;
}

/* busy waits until the lock is available */
void lock(Lock lock)
{
  while (!testAndSet(&(lock->lockVariable)))
    ;
}

void unlock(Lock lock)
{
  lock->lockVariable = FALSE;
}

/* try once and give up if it can't be locked */
Boolean lockTry(Lock lock)
{
  return (testAndSet(&(lock->lockVariable)));
}

/*
 * a more complex lock that can handel shared and exclusive
 * locks. There is no limit on shared "readers" but only one exclusive
 * "writer" is allowed.
 */
void bigLockInit(BigLock big)
{
  lockInit(&big->lock);
  big->shared = 0;
  big->exclusive = 0;
  big->wait = 0;
}

/* increase shared counter but only if both exclusive and wait are equal
	 zero. At the moment we do a spin lock, this is not optimal. */
void bigLockShared(BigLock big)
{
  while (1)
  {
    lock(&big->lock);
    if ( big->exclusive == 0 && big->wait == 0)
    {
      big->shared++;
      unlock(&big->lock);
      return;
    }
    unlock(&big->lock);
  }
}

/* first increase wait and then try to get the exclusive lock */
void bigLockUpgrade(BigLock big)
{
  /* make this thread waiting */
  lock(&big->lock);
  big->shared--;
  big->wait++;
  ASSERT(big->shared >= 0 );
  unlock(&big->lock);

  /* get exclusive */
  while (1)
  {
    lock(&big->lock);
    if ( big->exclusive == 0 && big->shared == 0)
    {
      big->exclusive++;
      big->wait--;
      unlock(&big->lock);
      return;
    }
    unlock(&big->lock);
  }
}

/* release exclusive lock and go back to shared lock */
void bigLockDowngrade(BigLock big)
{
  lock(&big->lock);
  big->exclusive--;
  ASSERT(big->exclusive >= 0 );
  unlock(&big->lock);

  /* get shared back */
  bigLockShared(big);
}

/* release a shared lock */
void bigLockUnlock(BigLock big)
{
  lock(&big->lock);
  big->shared--;
  ASSERT(big->shared >= 0 );
  unlock(&big->lock);
}

Boolean bigLockSharedTry(BigLock big)
{
  if ( lockTry(&big->lock) != TRUE )
  {
    return FALSE;
  }
  if ( big->exclusive == 0 && big->wait == 0)
  {
    big->shared++;
    unlock(&big->lock);
    return TRUE;
  }
  return FALSE;
}

Boolean bigLockUpgradeTry(BigLock big)
{
  if ( lockTry(&big->lock) != TRUE )
  {
    return FALSE;
  }
  if ( big->exclusive == 0 )
  {
    big->shared--;
    big->exclusive++;
    unlock(&big->lock);
    return TRUE;
  }
  return FALSE;
}

/* it is not possible to try downgrade an exclusive lock because there is the
	 possibility that we have to wait for other exclusive locks to run first. */
Boolean bigLockUnexclusiveTry(BigLock big)
{
  if ( lockTry(&big->lock) != TRUE )
  {
    return FALSE;
  }
  big->exclusive--;
  ASSERT(big->exclusive >= 0 );
  unlock(&big->lock);

  return FALSE;
}

Boolean bigLockUnlockTry(BigLock big)
{
  if ( lockTry(&big->lock) != TRUE )
  {
    return FALSE;
  }
  big->shared--;
  ASSERT(big->shared >= 0 );
  unlock(&big->lock);

  return FALSE;
}


