/*
 * RTLinux mutex and condvar implementation
 *
 * Written by Michael Barabanov
 * Rewritten by Victor Yodaiken after a long delay.
 * Copyright (C) Finite State Machine Labs Inc., 1999,2000
 * Released under the terms of the GPL Version 2
 * 
 * StandAlone RTLinux integration 
 * written by Vicente Esteve LLoret <viesllo@inf.upv.es> 
 *
 *
 * 
 */

#include <rtl_conf.h>
#include <rtl_sched.h>
#include <rtl_mutex.h>
#include <rtl_sync.h>

#if _RTL_POSIX_MUTEXS

int pthread_mutexattr_init(pthread_mutexattr_t *attr)
{
  attr->type = PTHREAD_MUTEX_DEFAULT;
  attr->pshared = PTHREAD_PROCESS_SHARED;
  attr->protocol = PTHREAD_PRIO_NONE;
  attr->prioceiling = sched_get_priority_max(SCHED_FIFO);

}

int pthread_mutex_destroy(pthread_mutex_t *mutex)
{
  mutex->valid = 0;
  return 0;
}

int pthread_mutex_init(pthread_mutex_t *mutex,
		    const pthread_mutexattr_t *attr)
{
  pthread_mutexattr_t defattr;
  mutex->valid = 1;
  mutex->busy = 0;
  mutex->flags = 0;
  rtl_wait_init(&mutex->wait);
  rtl_spin_lock_init (&mutex->lock);
  
  if (!attr) 
  {
    pthread_mutexattr_init(&defattr);
    attr = &defattr;
  }
  
  mutex->type = attr->type;
  mutex->protocol = attr->protocol;
  mutex->prioceiling = attr->prioceiling;
  return 0;
}


static inline int __pthread_mutex_trylock(pthread_mutex_t *mutex)
{
#ifdef _RTL_POSIX_THREAD_PRIO_PROTECT
/* if _RTL_POSIX_THREAD_PRIO_PROTECT, this code is protected by a spinlock */
  if (mutex->protocol == PTHREAD_PRIO_PROTECT) 
  {
    if (RTL_PRIO(RTL_CURRENT) > mutex->prioceiling) 
    {
      return EINVAL;
    }
  mutex->oldprio = RTL_PRIO (RTL_CURRENT);
  RTL_PRIO (RTL_CURRENT) = mutex->prioceiling;
  }
#endif
  if (test_and_set_bit(0, &mutex->busy)) 
  {
    return EBUSY;
  }
  return 0;
}

int pthread_mutex_trylock(pthread_mutex_t *mutex)
{
  rtl_irqstate_t flags;
  int ret;
  switch (mutex->type) 
  {
    case PTHREAD_MUTEX_SPINLOCK_NP:
      rtl_no_interrupts (flags);
      if (rtl_spin_trylock(&mutex->lock)) 
      {
        rtl_restore_interrupts (flags);
	return EBUSY;
      } 
      else 
      {
	mutex->flags = flags;
	return 0;
      }
      break;
    case PTHREAD_MUTEX_NORMAL:
    default:
#ifdef _RTL_POSIX_THREAD_PRIO_PROTECT
      rtl_spin_lock_irqsave (&mutex->lock, flags);
#endif
      ret = __pthread_mutex_trylock(mutex);
#ifdef _RTL_POSIX_THREAD_PRIO_PROTECT
      rtl_spin_unlock_irqrestore(&mutex->lock, flags);
#endif
      return ret;
  }
}


int pthread_mutex_lock(pthread_mutex_t *mutex)
{
  rtl_irqstate_t flags;
  int ret;

  switch (mutex->type) 
  {
    case PTHREAD_MUTEX_SPINLOCK_NP:
      rtl_no_interrupts (flags);
      rtl_spin_lock (&mutex->lock);
      mutex->flags = flags;
      return 0;
    case PTHREAD_MUTEX_NORMAL:
    default:
      if (!mutex->valid) 
      {
        return EINVAL;
      }

      rtl_spin_lock_irqsave (&mutex->lock, flags);
      while ((ret = __pthread_mutex_trylock(mutex))) 
      {
        if (ret == EINVAL) 
        {
	  rtl_spin_unlock_irqrestore(&mutex->lock, flags);
	  return ret;
	}
        ret = rtl_wait_sleep (&mutex->wait, &mutex->lock);
        rtl_spin_lock(&mutex->lock);

      }
      rtl_spin_unlock_irqrestore(&mutex->lock, flags);
      return 0;
  }
}


int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
  rtl_irqstate_t flags;

  switch (mutex->type) 
  {
    case PTHREAD_MUTEX_SPINLOCK_NP:
      flags = mutex->flags;
      rtl_spin_unlock(&mutex->lock);
      rtl_restore_interrupts (flags);
      return 0;
    case PTHREAD_MUTEX_NORMAL:
    default:
      if (!mutex->valid) 
      {
        return EINVAL;
      }

      rtl_spin_lock_irqsave (&mutex->lock, flags);
#ifdef _RTL_POSIX_THREAD_PRIO_PROTECT
      if (mutex->protocol == PTHREAD_PRIO_PROTECT) 
      {
        RTL_PRIO(RTL_CURRENT) = mutex->oldprio;
      }
#endif
      clear_bit (0, &mutex->busy);
      rtl_wait_wakeup(&mutex->wait);
      rtl_spin_unlock_irqrestore (&mutex->lock, flags);
			/* XXX we do not call the scheduler here by design;
			 * for fast wakeups, use semaphores & pthread_wakeup_np */
      return 0;
  }
}

#ifdef _RTL_POSIX_THREAD_PRIO_PROTECT
int pthread_mutex_setprioceiling(pthread_mutex_t *mutex, int prioceiling, int *old_ceiling)

{
  int ret;
  ret = pthread_mutex_lock (mutex);
  if (ret) 
  {
    return ret;
  }
  *old_ceiling = mutex->prioceiling;
  mutex->prioceiling = prioceiling;
  pthread_mutex_unlock (mutex);
  return 0;
}
#endif


#endif // _RTL_POSIX_MUTEXS

