/*
 * Copyright (c) 2007, 2008 University of Tsukuba
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the University of Tsukuba nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright (c) 2010-2012 Yuichi Watanabe
 */

#ifndef __CORE_SPINLOCK_H
#define __CORE_SPINLOCK_H

#include <core/cpu.h>
#include <core/types.h>
#ifdef SPINLOCK_DEBUG
#include <core/panic.h>
#endif

typedef u8 spinlock_t;
typedef u32 rw_spinlock_t;

#define SPINLOCK_INITIALIZER ((spinlock_t)0)

#ifdef SPINLOCK_DEBUG
#define spinlock_debug1(a) spinlock_debug2(a)
#define spinlock_debug2(a) #a
#define spinlock_lock(l) spinlock_lock_debug (l, \
	"spinlock_lock failed." \
	" file " __FILE__ \
	" line " spinlock_debug1 (__LINE__))
#endif /* SPINLOCK_DEBUG */

#ifdef SPINLOCK_DEBUG
static inline void
spinlock_lock_debug(spinlock_t *lock, char *msg)
#else
static inline void
spinlock_lock(spinlock_t *lock)
#endif
{
	spinlock_t old;

#ifdef SPINLOCK_DEBUG
	u32 count = 0;
#endif

	for (;;) {
		
		disable_interrupt();
		__asm__ volatile (
			" xchg  %1, %0 \n"	/* exchange old and *lock */
#ifdef __x86_64__
			: "=r" (old),		/* output 0 */
#else
			: "=abcd" (old),	/* output 0 */
#endif
			  "=m" (*lock)		/* output 1 */
			: "0" ((u8)1)		/* input 0 */
			);
		if (old == 0) {
			return;
		}
		enable_interrupt();

		do {
#ifdef SPINLOCK_DEBUG
			if (count++ == 0xffffffff) {
				panic("%s", msg);
			}
#endif
			__asm__ volatile (
				" pause \n"
				);
		} while (*(volatile spinlock_t *)lock == old);
	}
}

static inline void
spinlock_unlock(spinlock_t *l)
{
	u8 dummy;

	__asm__ volatile ("xchg %1, %0 \n"
#ifdef __x86_64__
		      : "=r" (dummy)
#else
		      : "=abcd" (dummy)
#endif
		      , "=m" (*l)
		      : "0" ((u8)0));
	enable_interrupt();
}

static inline void
spinlock_init (spinlock_t *l)
{
	*l = SPINLOCK_INITIALIZER;
}

static inline void
rw_spinlock_lock_sh (rw_spinlock_t *l)
{
	__asm__ volatile (" lock addl %2, %0 \n" /* increment *l */
		      "      jns  1f \n" /* if positive, succeeded */
		      "2: \n"
		      "      pause \n" /* spin loop hint */
		      "      cmpl %1, %0 \n" /* test a sign */
		      "      js   2b \n" /* if negative, do spin loop */
		      "1: \n"
		      : "=m" (*l)
		      : "ri" ((rw_spinlock_t)0)
		      , "ri" ((rw_spinlock_t)1)
		      : "cc");
}

static inline void
rw_spinlock_unlock_sh (rw_spinlock_t *l)
{
	__asm__ volatile ("lock subl %1, %0"
		      : "=m" (*l)
		      : "ri" ((rw_spinlock_t)1)
		      : "cc");
}

static inline void
rw_spinlock_lock_ex (rw_spinlock_t *l)
{
	__asm__ volatile (" lock cmpxchgl %2, %0 \n" /* if *l==0, *l=0x80000000 */
		      "      je       1f \n" /* and succeeded */
		      "2: \n"
		      "      xor      %1, %1 \n" /* clear %eax */
		      "      pause \n" /* spin loop hint */
		      " lock cmpxchgl %2, %0 \n" /* if *l==0, *l=0x80000000 */
		      "      jne      2b \n" /* else do spin loop */
		      "1: \n"
		      : "=m" (*l)
		      : "a" (0)
		      , "r" (0x80000000)
		      : "cc");
}

static inline void
rw_spinlock_unlock_ex (rw_spinlock_t *l)
{
	__asm__ volatile ("lock andl %1,%0"
		      : "=m" (*l)
		      : "ri" ((rw_spinlock_t)0x7FFFFFFF)
		      : "cc");
}

static inline void
rw_spinlock_init (rw_spinlock_t *l)
{
	*l = 0;
}

#endif
