/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include "kernel.h"

/****************************************************************************/
/*					Local prototypes										*/
/****************************************************************************/

static void		SemaWait( void *par1, void *par2 );
static void		SemaTimo( void *par1, void *par2 );
static int		SemaTest( BX_SEMA *sema, unsigned value, BX_PROC *self );

/****************************************************************************/
/*					Public API functions									*/
/****************************************************************************/

/** @addtogroup bx_sema */
/** @{ */

/**
*	Waits for a number of resources on a semaphore.
*
*	@param	sema			Pointer to a semaphore
*	@param	value			The number of resources to be taken
*	@param	timeout			The maximum time to wait, in microsecs. If the
*							value is -1 (cast to <tt>unsigned</tt>), then
*							there is no timeout, the function will wait
*							indefinitely. If the timeout value is 0, then the
*							function will not wait, it will return to the
*							caller immediately.
*	@retval	>0				Success, the given number of resources were taken
*	@retval =0				Timeout, no resources were taken
*	@retval BX_ERR_OBJECT	The semaphore was not initialised.
*	@retval BX_ERR_CONTEXT	The function was called from an ISR or delayed ISR.
*	@retval	BX_ERR_SUSPEND	The caller would have to wait but it is currently
*							running with IRQ or FIQ disabled and thus can not
*							be suspended.
*	@note
*		Although the timeout value is specified in microseconds, the actual
*		resolution depends on the frequency of calls to the BX_TimerTick()
*		function.
*
*	@attention
*		This function should not be called from an ISR or delayed ISR.
*		This function can suspend the caller.
*/

int		BX_SemaGet( BX_SEMA *sema, unsigned int value, unsigned int timeout )
{
BX_PROC	*self;
int		cpsr, retv;

	if ( ! ( self = BX_ProcSelf() ) ) {

		// Only user land calls are accepted

		return( BX_ERR_CONTEXT );
	}

	// Do as much of the timer stuff as we can without disabling the
	// interrupt

	if ( timeout && timeout != (unsigned) -1 ) {

		BX_TimerAction( &self->timo, SemaTimo, sema, self );
	}

	// Now we have to disable the interrupt and see if we can ge the
	// semaphore right now.

	cpsr = _bx_IrqDis();
	retv = SemaTest( sema, value, self );

	if ( ! retv ) {

		// We can't get what we want, we have to wait

		if ( timeout ) {

			// The user is willing to wait

			if ( self->prot > BX_PROT_COOP ) {

				// We can not suspend the caller, report error

				retv = BX_ERR_SUSPEND;
			}
			else {

				// Make note of the requested amount in the
				// process descriptor and add ourselves to the semaphore.

				if ( timeout != (unsigned) -1 ) {

					_bx_TimerStart( &self->timo, timeout, 0, 0 );
				}

				self->ramt = value;
				_bx_QueueEvent( cpsr, SemaWait, sema, self );
				retv = self->ramt;
			}
		}
	}

	_bx_IrqEnaUsr( cpsr );
	return( retv );
}

/** @} */

/****************************************************************************/
/*					Local functions											*/
/****************************************************************************/

/*
*	Suspends the calling process and queues it on the semaphore
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static void	SemaWait( void *par1, void *par2 )
{
BX_SEMA	*sema;
BX_PROC	*proc;
int		cpsr;
int		retv;

	sema = par1;
	proc = par2;
	cpsr = _bx_IrqDis();

	retv = SemaTest( sema, proc->ramt, proc );

	if ( retv > 0 ) {

		// We got the semaphore immediately

		if ( proc->timo.stat != TIMER_CANCEL &&
			 proc->timo.stat != TIMER_UNUSED ) {

			proc->timo.stat = TIMER_CANCEL;
			_bx_TimerProcess( &proc->timo, (void *) 1 );
		}
	}
	else if ( retv == 0 ) {

		_bx_ProcSuspend( proc, NULL );
		(*sema->base.ftab->ins)( &sema->base, proc );

	}
	else if ( retv < 0 ) {

		// If we implemented internal consistency checks,
		// then at this point we would panic() because
		// being here means that someone destroyed an
		// active semaphore.
	}

	// We assume success

	proc->ramt = 1;
	_bx_IrqEnaSys( cpsr );
}

/*
*	Timeout waiting on the semaphore
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	This function is always called by the timer subsystem, thus we know
*	that it executes in system context.
*/

static void SemaTimo( void *par1, void *par2 )
{
BX_SEMA	*sema;
BX_PROC	*proc;
int		cpsr;

	sema = par1;
	proc = par2;
	cpsr = _bx_IrqDis();

	// Remove the process from the semaphore's queue

	(*sema->base.ftab->del)( &sema->base, proc );

	// Wake up the process

	_bx_ProcRestart( proc, NULL );

	// Set the return value to indicate failure

	proc->ramt = 0;
	_bx_IrqEnaSys( cpsr );
}

/*
*	Tests if the given amount can be taken from the given semaphore
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns +1 if yes (and takes it), 0 if not and a negative error
*	code in case of error.
*/

static int	SemaTest( BX_SEMA *sema, unsigned value, BX_PROC *self )
{
	if ( sema->base.type != BX_TYPE_SEMA ) return( BX_ERR_OBJECT );
	if ( ! value ) return( +1 );

	// Check if we can get the semaphore without suspending.
	// We can only do that if the semaphore has enough resources.

	if ( sema->cntr >= value ) {

		// Now, if the semaphore queue is empty, we're in.
		// Otherwise, if the semaphore has smallest-first policy,
		// then we're winning again, because the first guy in the
		// queue could not get the sema, that is, it was asking for
		// more than what the sema got. Since we're asking for not
		// more, then obviously we are the smallest requester.
		// If the sema has a by-priority policy and our effective
		// priority is higher than the first waiting, then we're
		// winning again.

		if ( sema->base.head == NULL ||
			 sema->base.ftab == &_bx_poli_srqf ||
			 ( sema->base.ftab == &_bx_poli_prio &&
			   self->epri > sema->base.head->epri ) ) {

			// We can get what we want immediately

			sema->cntr -= value;
			return( +1 );
		}
	}

	// We can't satisfy the request immediatelly

	return( 0 );
}

