/**
 * @file atomic.h
 * @brief Header file for implementation of atomic operation
 * 
 * Implements some atomic operations
 *  
 * This file is based on Kalisto, Development Kernel copyrighted (c) to 
 * Distributed Systems Research Group MFF UK, Czech republic.
 */

#ifndef _ATOMIC_H_
#define _ATOMIC_H_


/**
 * Atomic variable.
 *
 * The atomic_t data type is opaque for the users and small enough (and not
 * going to grow) for passing around by value.
 */
struct atomic {
	/** value of atomic structure */
	volatile int	value;
};

/** standart notation .._t */
typedef struct atomic	atomic_t;


/**
 * @brief  Atomic variable initializer.
 * @param val value of the atomic variable.
 */
#define AV_INIT(val)	{ .value = (val) }


/**
 * @brief Gets the value of an atomic variable.
 * 
 *
 * Note: While this is often done using a macro, there is no variability
 * in the code that would warrant that. Moreover, in case of type mismatch
 * this produces a more understandable error message.
 * 
 * @param av pointer to atomic variable 
 */
static inline int
av_get (atomic_t * av)
{
	return av->value;
}


/**
 * @ brief Sets an atomic variable to a specific value.
 * 
 * @param av atomic variable to be set.
 * @param val valut to be set to
 */
static inline void
av_set (atomic_t * av, int val)
{
	av->value = val;
}


/**
 * @brief Atomically adds a number to the value of an atomic variable. 
 * 
 * Does NOT return the result.
 * @param av Atomic value to be set
 * @param num value to be added
 */
static inline void
av_add (atomic_t * av, int num)
{
	int temp;

	__asm__ __volatile__ (

	"	.set	push				\n"
	"	.set	noreorder			\n"
	"						\n"
	"1:	ll	%[temp], %[value]		\n"
	"	addu	%[temp], %[num]			\n"
	"	sc	%[temp], %[value]		\n"
	"	beqz	%[temp], 1b			\n"
	"						\n"
	"	.set	pop				\n"

	:	[temp] "=&r" (temp),
		[value] "+m" ((av)->value)
	: 	[num] "Ir" (num)
	);
}


/**
 * @brief Atomically subtracts a number from the value of an atomic variable. 
 * Does NOT return the result.
 * @param av Atomic value to be set
 * @param num value how much substract variable 
 */
static inline void
av_sub (atomic_t * av, int num)
{
	int temp;

	__asm__ __volatile__ (

	"	.set	push				\n"
	"	.set	noreorder			\n"
	"						\n"
	"1:	ll	%[temp], %[value]		\n"
	"	subu	%[temp], %[num]			\n"
	"	sc	%[temp], %[value]		\n"
	"	beqz	%[temp], 1b			\n"
	"						\n"
	"	.set	pop				\n"

	: 	[temp] "=&r" (temp),
		[value] "+m" ((av)->value)
	: 	[num] "Ir" (num)
	);
}


/**
 * @brief Atomically adds a number to the value of an atomic variable and returns
 * the result.
 */
static inline int
av_add_result (atomic_t * av, int num)
{
	int temp, result;

	__asm__ __volatile__ (

	"	.set	push				\n"
	"	.set	noreorder			\n"
	"						\n"
	"1:	ll	%[temp], %[value]		\n"
	"	addu	%[result], %[temp], %[num]	\n"
	"	sc	%[result], %[value]		\n"
	"	beqz	%[result], 1b			\n"
	"						\n"
	"	addu	%[result], %[temp], %[num]	\n"
	"	sync					\n"
	"						\n"
	"	.set	pop				\n"

	: 	[temp] "=&r" (temp),
		[result] "=&r" (result),
		[value] "+m" ((av)->value)
	: 	[num] "Ir" (num)
	: 	"memory"
	);

	return result;
}


/**
 * @brief Atomically subtracts a number from the value of an atomic variable and
 * returns the result.
 * @param av Atomic value to be set
 * @param num value how much substract variable
 */
static inline int
av_sub_result (atomic_t * av, int num)
{
	int temp, result;

	__asm__ __volatile__ (

	"	.set	push				\n"
	"	.set	noreorder			\n"
	"						\n"
	"1:	ll	%[temp], %[value]		\n"
	"	subu	%[result], %[temp], %[num]	\n"
	"	sc	%[result], %[value]		\n"
	"	beqz	%[result], 1b			\n"
	"						\n"
	"	subu	%[result], %[temp], %[num]	\n"
	"	sync					\n"
	"						\n"
	"	.set	pop				\n"

	:	[temp] "=&r" (temp),
		[result] "=&r" (result),
		[value] "+m" ((av)->value)
	: 	[num] "Ir" (num)
	: 	"memory"
	);

	return result;
}


#endif /* _ATOMIC_H_ */
