/****************************************************************************/
/*	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"

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

/** @addtogroup bx_time */
/** @{ */

/**
*	Arms a timer.
*
*	The function arms a timer. When the timer expires, it will invoke a
*	delayed ISR function. If a reload value is specified, then upon expiry
*	the timer will not be deactivated, but restarted with the specified
*	reload value.
*
*	@param	timer	Pointer to an initialised timer object
*	@param	expire	The time to wait before the timer expires,
*					in microseconds. Note that the longest period
*					the timer can wait is 1h 11m 34.9s. Also note
*					that although the time is expressed in microsecs,
*					the resolution of the timers depends on the frequency
*					with which the BX_TimerTick() function is called.
*	@param	period	If this value is not 0, then the timer will be restarted
*					with this value when it expires. If it is 0, then upon
*					expiry the timer becomes inactive.
*
*	@retval	>0				The timer was activated
*	@retval	BX_ERR_OBJECT	The timer object is not initialised
*	@retval	BX_ERR_INUSE	The timer object is already running
*	@retval	BX_ERR_PARAM	The \p expire argument was 0
*/

int		BX_TimerStart( BX_TIME *timer, unsigned expire, unsigned period )
{
int		cpsr;

	if ( timer->type != BX_TYPE_TIME ) return( BX_ERR_OBJECT );
	if ( ! expire ) return( BX_ERR_PARAM );

	cpsr = _bx_IrqDis();

	if ( timer->stat == TIMER_RELOAD || timer->stat == TIMER_ACTIVE ) {

		_bx_IrqRes( cpsr );
		return( BX_ERR_INUSE );
	}

	_bx_TimerStart( timer, expire, period, cpsr );
	return( 1 );
}

/** @} */

/****************************************************************************/
/*					Library internal functions								*/
/****************************************************************************/

/*
*	Starts a timer
*	~~~~~~~~~~~~~~
*	This function is used when a process goes to wait for something with
*	a timeout.
*/

void	_bx_TimerStart( BX_TIME *timer, unsigned expire,
					    unsigned period, int cpsr )
{
	timer->load = expire;
	timer->peri = period;
	timer->stat = TIMER_RELOAD;
	_bx_QueueEvent( cpsr, _bx_TimerProcess, timer, (void *) 1 );
}

/*
*	Adds a timer to the timer queue
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	This function is always executed as an event queue function.
*	The first parameter is a pointer to a timer. The second is
*	a flag that is '1' under normal circumstances and '0' when the
*	function is called from the timer expired handler; it is used to
*	avoid infinite recursion.
*/

void	_bx_TimerProcess( void *par1, void *par2 )
{
BX_STAT		*stat;
BX_TIME		*tptr;
BX_TIME		*prev, *next;
unsigned	cpsr, load, test;

	stat = &_bx_state;
	tptr = par1;

	// Lock the timer queue so that the timer tick
	// interrupt doesn't affect us.

	stat->tlk2++;

	// Make sure that the timer is left alone

	cpsr = _bx_IrqDis();

	for ( ;; ) {

		if ( tptr->stat == TIMER_ACTIVE || tptr->stat == TIMER_UNUSED ) {

			// The timer has no pending processing, ignore it

			break;
		}

		if ( tptr->runs ) {

			// The timer is currently on the timer queue, we have to remove it

			if ( tptr->prev )

				tptr->prev->next = tptr->next;
			else
				stat->tqhd = tptr->next;

			if ( tptr->next ) {

				tptr->next->wait += tptr->wait;
				tptr->next->prev  = tptr->prev;
			}
			else
				stat->tqtl = tptr->prev;

			tptr->runs = 0;
		}

		if ( tptr->stat == TIMER_CANCEL ) {

			// We have to cancel this timer. Since we've
			// removed it from the queue, it's very simple.

			tptr->stat = TIMER_UNUSED;
			break;
		}

		// We have to load this timer. Get a copy of the required time.

		load = test = tptr->load;

		// Now we can enable interrupts while we're scanning the queue
		// We know that the queue is not changing while we're working.

		_bx_IrqEnaSys( cpsr );

		// Find the element

		for ( prev = NULL, next = stat->tqhd ;
			  next && next->wait < test ;
			  prev = next, next = next->next )

				test -= next->wait;

		// Now we know where to insert the timer. Disable the interrupt
		// and check if after the search the timer is still as it was before

		cpsr = _bx_IrqDis();

		if ( tptr->stat != TIMER_RELOAD ) {

			// The timer state was changed, process it

			continue;
		}

		if ( tptr->load != load ) {

			// The load time has been changed. Start all over again.

			continue;
		}

		// Everything is fine. Insert the timer to the queue

		if ( prev )

			prev->next = tptr;
		else
			stat->tqhd = tptr;

		if ( next ) {

			next->wait -= test;
			next->prev  = tptr;
		}
		else
			stat->tqtl = tptr;

		tptr->wait = test;
		tptr->next = next;
		tptr->prev = prev;
		tptr->stat = TIMER_ACTIVE;
		tptr->runs = 1;

		// We're done.

		break;
	}

	if ( par2 && ( load = stat->tacc ) ) {

		// Yes, there's accumulated time. Take it and pass it to the
		// tick handler. We can enable interrupts but we have to lock
		// the queue on the handler's behalf.

		stat->tlk1 = 1;
		stat->tacc = 0;
		_bx_IrqEnaSys( cpsr );
		_bx_TimerExpired( stat, (void *) load );
	}

	// Unlock the timer queue. We don't need to enable the
	// interrupt, the next thing any of our callers do is just that.

	stat->tlk2--;
}

