/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/

/* Notes:
 * - by default, implemented several entries - single exis semantics,
 *   NOT recursive! Define RECURSIVE to get recursive mutex.
 * - priority inheritance NOT implemented
 */

#include "common.h"

#define RECURSIVE

/* Internal OSAL mutex representation */
struct osal_mutex {
	struct task_struct *owner;
#ifdef RECURSIVE
	int recursion;
#endif
	wait_queue_head_t wait;
	int dead;			/* if non-zero, mutex was destroyed */
};


static struct handle_table mutex_handle_table;
#define allocate_osal_mutex_handle(handle) \
	ALLOCATE_HANDLE_MACRO(&mutex_handle_table, handle, TMOSAL_ERR_MUTEX_FULL)
#define allocate_osal_mutex(om) \
	ALLOCATE_ZEROED_MACRO(om)
#define bind_osal_mutex_to_handle(handle, om) \
	BIND_OBJECT_MACRO(&mutex_handle_table, handle, om)
#define cancel_osal_mutex_handle(handle) \
	CANCEL_HANDLE_MACRO(&mutex_handle_table, handle)
#define get_osal_mutex(handle, om) \
	GET_OBJECT_MACRO(&mutex_handle_table, handle, om, TMOSAL_ERR_HANDLE)
#define put_osal_mutex(handle) \
	PUT_OBJECT_MACRO(&mutex_handle_table, handle)
#define mark_dead_osal_mutex(handle) \
	MARK_DEAD_OBJECT_MACRO(&mutex_handle_table, handle)


/* This will be used for owner field if mutex was acuired in interrupt
 * handler */
#define INTERRUPT_OWNER ((struct task_struct *)0x100)

/* Mutex API */

tmErrorCode_t tmosalMutexCreate(ptmosalMutexHandle_t pioMutexHandle,
		const tmosalMutexCreateFlags_t kFlags)
{
	u32 handle;
	struct osal_mutex *om;
	
	BUG_IF_INTERRUPT();
	BUG_IF_NOT_PREEMPTIBLE();
	
	if (unlikely(!pioMutexHandle))
		return TMOSAL_ERR_BAD_PARAM;

	allocate_osal_mutex_handle(handle);
	allocate_osal_mutex(om);
	if (unlikely(!om)) {
		cancel_osal_mutex_handle(handle);
		return TMOSAL_ERR_OUT_OF_MEMORY;
	}

	init_waitqueue_head(&om->wait);

	STAT_HANDLE_CREATE(curMutexNr, maxMutexNr);
	bind_osal_mutex_to_handle(handle, om);
	*pioMutexHandle = handle;
	return TM_OK;
}
EXPORT_SYMBOL(tmosalMutexCreate);


tmErrorCode_t tmosalMutexDestroy(const tmosalMutexHandle_t kMutexHandle)
{
	struct osal_mutex *om;
	unsigned long flags;
	int empty;
	tmErrorCode_t ret;
	
	get_osal_mutex(kMutexHandle, om);
	spin_lock_irqsave(&om->wait.lock, flags);
	om->dead = 1;
	empty = list_empty(&om->wait.task_list);
	spin_unlock_irqrestore(&om->wait.lock, flags);

	if (likely(empty))
		ret = TM_OK;
	else {
		wake_up(&om->wait);
		if (!in_interrupt() && preemptible())
			schedule();
		/* mutex will be deallocated by waiting task */
		ret = TMOSAL_ERR_MUTEX_TASK;
	}

	STAT_HANDLE_DESTROY(curMutexNr);
	mark_dead_osal_mutex(kMutexHandle);
	put_osal_mutex(kMutexHandle);
	return ret;
}
EXPORT_SYMBOL(tmosalMutexDestroy);


tmErrorCode_t tmosalMutexEnter(const tmosalMutexHandle_t kMutexHandle,
		const pInt32 pkTimeout)
{
	struct osal_mutex *om;
	unsigned long flags;
	int jif;
	tmErrorCode_t ret;
	struct task_struct *this_owner;
	DECLARE_WAITQUEUE(wait, current);
			
	BUG_IF_SLEEP_IN_ATOMIC(pkTimeout);

	get_osal_mutex(kMutexHandle, om);

	jif = (pkTimeout && *pkTimeout > 0) ? msecs_to_jiffies(*pkTimeout) : 0;
	
	this_owner = in_interrupt() ? INTERRUPT_OWNER : current;

	spin_lock_irqsave(&om->wait.lock, flags);

	/* return immidiatly if already own */
	if (om->owner == this_owner) {
		ret = TM_OK;
		if (unlikely(om->dead))
			ret = TMOSAL_ERR_MUTEX_DEL;
		else {
#ifdef RECURSIVE
			om->recursion++;
#endif
			ret = TM_OK;
		}
		spin_unlock_irqrestore(&om->wait.lock, flags);
		put_osal_mutex(kMutexHandle);
		return ret;
	}
		
	
	wait.flags = 0;		/* non-exclusive */
	__add_wait_queue(&om->wait, &wait);

	while (1) {

		/* check for dead */
		if (unlikely(om->dead)) {
			ret = TMOSAL_ERR_MUTEX_DEL;
			break;
		}
	
		/* check for acquire possibility */
		if (likely(!om->owner)) {
			om->owner = in_interrupt() ? INTERRUPT_OWNER : current;
			ret = TM_OK;
			break;
		}

		/* check for timeout condition */
		if (pkTimeout && !jif) {
			ret = TMOSAL_ERR_MUTEX_TIMEOUT;
			break;
		}
	
		/* will sleep */
		set_current_state(TASK_INTERRUPTIBLE);
		spin_unlock_irqrestore(&om->wait.lock, flags);
		if (pkTimeout)
			jif = schedule_timeout(jif);
		else
			schedule();
		spin_lock_irqsave(&om->wait.lock, flags);

		/* will check the wakeup reason on new iteration */
	}

	__remove_wait_queue(&om->wait, &wait);
	spin_unlock_irqrestore(&om->wait.lock, flags);
	put_osal_mutex(kMutexHandle);
	return ret;
}
EXPORT_SYMBOL(tmosalMutexEnter);


tmErrorCode_t tmosalMutexExit(const tmosalMutexHandle_t kMutexHandle)
{
	struct osal_mutex *om;
	struct task_struct *this_owner;
	unsigned long flags;
	int waiter_exists;
	
	this_owner = in_interrupt() ? INTERRUPT_OWNER : current;
	get_osal_mutex(kMutexHandle, om);
	spin_lock_irqsave(&om->wait.lock, flags);

	/* check the owner */
	if (unlikely(om->owner != this_owner)) {
		spin_unlock_irqrestore(&om->wait.lock, flags);
		put_osal_mutex(kMutexHandle);
		return TMOSAL_ERR_MUTEX_NOTOWNED;
	}

#ifdef RECURSIVE
	if (om->recursion) {
		om->recursion--;
		spin_unlock_irqrestore(&om->wait.lock, flags);
		put_osal_mutex(kMutexHandle);
		return TM_OK;
	}
#endif
	
	om->owner = 0;
	waiter_exists = !list_empty(&om->wait.task_list);
	spin_unlock_irqrestore(&om->wait.lock, flags);

	if (unlikely(waiter_exists)) {
		/* it could be better to use wake_up_locked() here, to avoid
		   races. Unfortunately, it is not exported. But there is no
		   much harm anyway, because extra call to wake_up() will just
		   wakeup threads, which will check situation and go sleep
		   again if needed */
		wake_up(&om->wait);
		if (preemptible() && !in_interrupt())
			schedule();
	}

	put_osal_mutex(kMutexHandle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalMutexExit);


int osal_mutex_submodule_init(void)
{
	return init_handle_table(&mutex_handle_table, MUTEX_HANDLE_TABLE_INITIAL_SIZE, "mutex");
}

void osal_mutex_submodule_uninit(void)
{
	uninit_handle_table(&mutex_handle_table);
}
