/*
 * Copyright (C) 2005-2013 Junjiro R. Okajima
 *
 * This program, aufs is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * simple read-write semaphore wrappers
 */

#ifndef __AUFS_RWSEM_H__
#define __AUFS_RWSEM_H__

#ifdef __KERNEL__

#include "debug.h"

struct au_rwsem {
	struct rw_semaphore	rwsem;
#ifdef CONFIG_AUFS_DEBUG
	/* just for debugging, not almighty counter */
	atomic_t		rcnt, wcnt;
#endif
};

#ifdef CONFIG_AUFS_DEBUG
#define AuDbgCntInit(rw) do { \
	atomic_set(&(rw)->rcnt, 0); \
	atomic_set(&(rw)->wcnt, 0); \
	smp_mb(); /* atomic set */ \
} while (0)

#define AuDbgRcntInc(rw)	atomic_inc(&(rw)->rcnt)
#define AuDbgRcntDec(rw)	WARN_ON(atomic_dec_return(&(rw)->rcnt) < 0)
#define AuDbgWcntInc(rw)	atomic_inc(&(rw)->wcnt)
#define AuDbgWcntDec(rw)	WARN_ON(atomic_dec_return(&(rw)->wcnt) < 0)
#else
#define AuDbgCntInit(rw)	do {} while (0)
#define AuDbgRcntInc(rw)	do {} while (0)
#define AuDbgRcntDec(rw)	do {} while (0)
#define AuDbgWcntInc(rw)	do {} while (0)
#define AuDbgWcntDec(rw)	do {} while (0)
#endif /* CONFIG_AUFS_DEBUG */

/* to debug easier, do not make them inlined functions */
#define AuRwMustNoWaiters(rw)	AuDebugOn(!list_empty(&(rw)->rwsem.wait_list))
/* rwsem_is_locked() is unusable */
#define AuRwMustReadLock(rw)	AuDebugOn(atomic_read(&(rw)->rcnt) <= 0)
#define AuRwMustWriteLock(rw)	AuDebugOn(atomic_read(&(rw)->wcnt) <= 0)
#define AuRwMustAnyLock(rw)	AuDebugOn(atomic_read(&(rw)->rcnt) <= 0 \
					&& atomic_read(&(rw)->wcnt) <= 0)
#define AuRwDestroy(rw)		AuDebugOn(atomic_read(&(rw)->rcnt) \
					|| atomic_read(&(rw)->wcnt))

#define au_rw_class(rw, key)	lockdep_set_class(&(rw)->rwsem, key)

static inline void au_rw_init(struct au_rwsem *rw)
{
	AuDbgCntInit(rw);
	init_rwsem(&rw->rwsem);
}

static inline void au_rw_init_wlock(struct au_rwsem *rw)
{
	au_rw_init(rw);
	down_write(&rw->rwsem);
	AuDbgWcntInc(rw);
}

static inline void au_rw_init_wlock_nested(struct au_rwsem *rw,
					   unsigned int lsc)
{
	au_rw_init(rw);
	down_write_nested(&rw->rwsem, lsc);
	AuDbgWcntInc(rw);
}

static inline void au_rw_read_lock(struct au_rwsem *rw)
{
	down_read(&rw->rwsem);
	AuDbgRcntInc(rw);
}

static inline void au_rw_read_lock_nested(struct au_rwsem *rw, unsigned int lsc)
{
	down_read_nested(&rw->rwsem, lsc);
	AuDbgRcntInc(rw);
}

static inline void au_rw_read_unlock(struct au_rwsem *rw)
{
	AuRwMustReadLock(rw);
	AuDbgRcntDec(rw);
	up_read(&rw->rwsem);
}

static inline void au_rw_dgrade_lock(struct au_rwsem *rw)
{
	AuRwMustWriteLock(rw);
	AuDbgRcntInc(rw);
	AuDbgWcntDec(rw);
	downgrade_write(&rw->rwsem);
}

static inline void au_rw_write_lock(struct au_rwsem *rw)
{
	down_write(&rw->rwsem);
	AuDbgWcntInc(rw);
}

static inline void au_rw_write_lock_nested(struct au_rwsem *rw,
					   unsigned int lsc)
{
	down_write_nested(&rw->rwsem, lsc);
	AuDbgWcntInc(rw);
}

static inline void au_rw_write_unlock(struct au_rwsem *rw)
{
	AuRwMustWriteLock(rw);
	AuDbgWcntDec(rw);
	up_write(&rw->rwsem);
}

/* why is not _nested version defined */
static inline int au_rw_read_trylock(struct au_rwsem *rw)
{
	int ret = down_read_trylock(&rw->rwsem);
	if (ret)
		AuDbgRcntInc(rw);
	return ret;
}

static inline int au_rw_write_trylock(struct au_rwsem *rw)
{
	int ret = down_write_trylock(&rw->rwsem);
	if (ret)
		AuDbgWcntInc(rw);
	return ret;
}

#undef AuDbgCntInit
#undef AuDbgRcntInc
#undef AuDbgRcntDec
#undef AuDbgWcntInc
#undef AuDbgWcntDec

#define AuSimpleLockRwsemFuncs(prefix, param, rwsem) \
static inline void prefix##_read_lock(param) \
{ au_rw_read_lock(rwsem); } \
static inline void prefix##_write_lock(param) \
{ au_rw_write_lock(rwsem); } \
static inline int prefix##_read_trylock(param) \
{ return au_rw_read_trylock(rwsem); } \
static inline int prefix##_write_trylock(param) \
{ return au_rw_write_trylock(rwsem); }
/* why is not _nested version defined */
/* static inline void prefix##_read_trylock_nested(param, lsc)
{ au_rw_read_trylock_nested(rwsem, lsc)); }
static inline void prefix##_write_trylock_nestd(param, lsc)
{ au_rw_write_trylock_nested(rwsem, lsc); } */

#define AuSimpleUnlockRwsemFuncs(prefix, param, rwsem) \
static inline void prefix##_read_unlock(param) \
{ au_rw_read_unlock(rwsem); } \
static inline void prefix##_write_unlock(param) \
{ au_rw_write_unlock(rwsem); } \
static inline void prefix##_downgrade_lock(param) \
{ au_rw_dgrade_lock(rwsem); }

#define AuSimpleRwsemFuncs(prefix, param, rwsem) \
	AuSimpleLockRwsemFuncs(prefix, param, rwsem) \
	AuSimpleUnlockRwsemFuncs(prefix, param, rwsem)

#endif /* __KERNEL__ */
#endif /* __AUFS_RWSEM_H__ */
