#ifndef __LINUX_FRLOCK_H
#define __LINUX_FRLOCK_H

/*
 * Fast read-write spinlocks.
 *
 * Fast reader/writer locks without starving writers. This type of
 * lock for data where the reader wants a consitent set of information
 * and is willing to retry if the information changes.  Readers never
 * block but they may have to retry if a writer is in
 * progress. Writers do not wait for readers. 
 *
 * Generalization on sequence variables used for gettimeofday on x86-64 
 * by Andrea Arcangeli
 *
 * This is not as cache friendly as brlock. Also, this will not work
 * for data that contains pointers, because any writer could
 * invalidate a pointer that a reader was following.
 *
 * 
 * Expected reader usage:
 * 	do {
 *	    seq = fr_read_begin();
 * 	...
 *      } while (seq != fr_read_end());
 *
 * On non-SMP the spin locks disappear but the writer still needs
 * to increment the sequence variables because an interrupt routine could
 * change the state of the data.
 */

#include <linux/config.h>
#include <linux/spinlock.h>
#include <asm/system.h>

typedef struct {
	spinlock_t lock;
	unsigned pre_sequence;
	unsigned post_sequence;
} frlock_t;

#define FR_LOCK_UNLOCKED	{ SPIN_LOCK_UNLOCKED, 0, 0 }
#define frlock_init(x)		do { *(x) = (frlock_t) FR_LOCK_UNLOCKED; } while (0)

static inline void fr_write_lock(frlock_t *rw)
{
	spin_lock(&rw->lock);
	rw->pre_sequence++;
	wmb();
}	

static inline void fr_write_unlock(frlock_t *rw) 
{
	wmb();
	rw->post_sequence++;
	spin_unlock(&rw->lock);
}

static inline int fr_write_trylock(frlock_t *rw)
{
	int ret  = spin_trylock(&rw->lock);

	if (ret) {
		++rw->pre_sequence;
		wmb();
	}
	return ret;
}

static inline unsigned fr_read_begin(frlock_t *rw) 
{
	unsigned ret = rw->post_sequence;
	rmb();
	return ret;
	
}

static inline unsigned fr_read_end(frlock_t *rw)
{
	rmb();
	return rw->pre_sequence;
}

/*
 * Possible sw/hw IRQ protected versions of the interfaces.
 */
#define fr_write_lock_irqsave(lock, flags) \
	do { local_irq_save(flags);	 fr_write_lock(lock); } while (0)
#define fr_write_lock_irq(lock) \
	do { local_irq_disable();	 fr_write_lock(lock); } while (0)
#define fr_write_lock_bh(lock) \
        do { local_bh_disable();	 fr_write_lock(lock); } while (0)

#define fr_write_unlock_irqrestore(lock, flags)	\
	do { fr_write_unlock(lock); local_irq_restore(flags); } while(0)
#define fr_write_unlock_irq(lock) \
	do { fr_write_unlock(lock); local_irq_enable(); } while(0)
#define fr_write_unlock_bh(lock) \
	do { fr_write_unlock(lock); local_bh_enable(); } while(0)

#endif /* __LINUX_FRLOCK_H */
