/****************************************************************************/
/*	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 "coop.h"

/** @addtogroup coop_event */
/** @{ */

/**
*	Wait for an event.
*
*	Waits until at least one event in the event mask becomes set for
*	the thread. The mask can not be empty; if it is, then BZ_EVENT_ALWAYS
*	is returned without anything else being done. Otherwise, this
*	call will cause a scheduling round even if the caller specifies the
*	BZ_EVENT_ALWAYS event in the mask. Thus it can be used as a courtesy
*	call to allow other threads to run even if the caller does not really
*	want to wait for anything. The returned value is a bitmask of events
*	that were both set in the mask and were pending at the time when the
*	call returns. These events are deleted from the caller's pending
*	event list.
*
*	@param	mask	The events to wait for
*
*	@retval	BZ_EVENT_ALWAYS	The mask was empty.
*
*	@retval	0				The caller specified BZ_EVENT_ALWAYS and there
*							were no pending events that matched the the mask.
*
*	@retval	>0				The bitmap of pending events that matched the mask.
*
*	@note
*		If you call this function with interrupts disabled, as a side
*		effect, it will enable them before returning.
*
*	@attention
*		This function must not be called from an interrupt. It you do
*		that, the system will crash.
*/

int		BZ_EventWait( int mask )
{
BZ_STAT		*base;
BZ_PROC		*self, *proc;
int			this, cntr;

	if ( ! mask ) return( BZ_EVENT_ALWAYS );

	base = &_bz_stat;
	self = base->proc + base->self;

	BZ_IrqDisable( 1 );

	// If the user called EventWait, we will always go around and
	// do a scheduling loop. Thus, at this point we just store the
	// mask.

	self->mask = mask;

	// Depending on whether we're priority based or round-robin,
	// we either start the search at the top or at the current
	// position.

	if ( base->mode )

		this = base->maxp - 1;
	else
		this = ( base->self ? base->self : base->maxp ) - 1;

	proc = base->proc + this;

	// For the scan we enable the interrupts, we have no reason to
	// keep them disabled and we want to minimise latency.

	for ( ;; ) {

		base->flag = 0;
		BZ_IrqDisable( 0 );

		for ( cntr = base->maxp ; cntr ; cntr-- ) {

			if ( ( mask = proc->sigs & proc->mask ) ) {

				// We can wake up this one

				BZ_IrqDisable( 1 );
				mask &= ~BZ_EVENT_ALWAYS;
				base->self  = this;
				proc->sigs ^= mask;
				proc->mask  = 0;

				// Due to the context switch the local vars of this
				// function, i.e. BZ_EventWait() will assume the
				// values for the process that just woke up.
				// To avoid any problem arising out of that (although
				// one could write very crufty code and omit this comment
				// to make it more interesting) we pass the return value
				// through the context switch and return immediately.
				// The context switch will also enable the interrupt.

				return( _bz_context_switch( mask, &self->cntx, &proc->cntx ) );
			}

			proc--;

			if ( --this < 0 ) {

				this = base->maxp - 1;
				proc = base->proc + this;
			}
		}

		// We did a round and found no runnable process.
		// Disable the interrupt for a moment and see if
		// any IT routine has delivered a signal at all.

		BZ_IrqDisable( 1 );

		if ( ! base->flag ) {

			// Nope. We have to go to sleep, waiting for an interrupt.

			(*base->idle)();
		}
	}
}

/** @} */

