/*-------------------------------------------------------------------------
 *
 * s_lock.c--
 *	  buffer manager interface routines
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/storage/buffer/Attic/s_lock.c,v 1.7 1998/05/04 23:49:09 scrappy Exp $
 *
 *-------------------------------------------------------------------------
 */

#include <stdio.h>

#include "config.h"
#include "c.h"
#include "storage/s_lock.h"


/*
 * Each time we busy spin we select the next element of this array as the
 * number of microseconds to wait. This accomplishes pseudo random back-off.
 * Values are not critical and are weighted to the low end of the range. They
 * were chosen to work even with different select() timer resolutions on
 * different platforms.
 * note: total time to cycle through all 16 entries might be about .1 second.
 */
int			s_spincycle[S_NSPINCYCLE] =
{0, 0, 0, 1000, 5000, 0, 10000, 3000,
 0, 10000, 0, 15000, 9000, 21000, 6000, 30000
};


#if defined(S_LOCK_DEBUG)
/*
 * s_lock(lock) - take a spinlock
 * add intrumentation code to this and define S_LOCK_DEBUG
 * instead of hacking up the macro in s_lock.h
 */
void
s_lock(slock_t *lock, char *file, int line)
{
	int			spins = 0;

	while (TAS(lock))
	{
		struct timeval delay;

		delay.tv_sec = 0;
		delay.tv_usec = s_spincycle[spins++ % S_NSPINCYCLE];
		(void) select(0, NULL, NULL, NULL, &delay);
		if (spins > S_MAX_BUSY)
		{
			/* It's been well over a minute...  */
			s_lock_stuck(lock, file, line);
		}
	}
}
#endif /* S_LOCK_DEBUG */


/*
 * s_lock_stuck(lock) - deal with stuck spinlock
 */
void
s_lock_stuck(slock_t *lock, char *file, int line)
{
	fprintf(stderr,
			"\nFATAL: s_lock(%08x) at %s:%d, stuck spinlock. Aborting.\n",
			(unsigned int) lock, file, line);
	fprintf(stdout,
			"\nFATAL: s_lock(%08x) at %s:%d, stuck spinlock. Aborting.\n",
			(unsigned int) lock, file, line);
	abort();
}



/*
 * Various TAS implementations moved from s_lock.h to avoid redundant
 * definitions of the same routine.
 * RESOLVE: move this to tas.c. Alternatively get rid of tas.[cso] and fold
 * all that into this file.
 */


#if defined(linux)
/*************************************************************************
 * All the Linux flavors
 */


#if defined(__alpha)
int
tas(slock_t *lock)
{
	slock_t		_res;

  __asm__("      ldq   $0, %0              \n\
                 bne   $0, already_set     \n\
                 ldq_l $0, %0	           \n\
                 bne   $0, already_set     \n\
                 or    $31, 1, $0          \n\
                 stq_c $0, %0	           \n\
                 beq   $0, stqc_fail       \n\
        success: bis   $31, $31, %1        \n\
                 mb		                   \n\
                 jmp   $31, end	           \n\
      stqc_fail: or    $31, 1, $0	       \n\
    already_set: bis   $0, $0, %1	       \n\
            end: nop      ": "=m"(*lock), "=r"(_res): :"0");

	return (_res != 0);
}
#endif /* __alpha */



#if defined(i386)
int
tas(slock_t *lock)
{
	slock_t		_res = 1;

  __asm__("lock; xchgb %0,%1": "=q"(_res), "=m"(*lock):"0"(0x1));
	return (_res != 0);
}
#endif /* i386 */



#if defined(sparc)

int
tas(slock_t *lock)
{
	slock_t		_res;
	slock_t    *tmplock = lock;

  __asm__("ldstub [%1], %0" \
  :			"=&r"(_res), "=r"(tmplock) \
  :			"1"(tmplock));
	return (_res != 0);
}

#endif /* sparc */



#if defined(PPC)

static int
tas_dummy()
{
	__asm__("				\n\
tas:						\n\
			lwarx	5,0,3	\n\
			cmpwi	5,0		\n\
			bne		fail	\n\
			addi	5,5,1	\n\
        	stwcx.  5,0,3	\n\
     		beq		success	\n\
fail:		li		3,1		\n\
			blr				\n\
success:					\n\
			li 3,0			\n\
        	blr				\n\
	");
}

#endif /* PPC */



#else /* defined(linux) */
/***************************************************************************
 * All Non-Linux
 */



#if defined(sun3)
static void
tas_dummy()						/* really means: extern int tas(slock_t *lock); */
{
	asm("LLA0:");
	asm("   .data");
	asm("   .text");
	asm("|#PROC# 04");
	asm("   .globl  _tas");
	asm("_tas:");
	asm("|#PROLOGUE# 1");
	asm("   movel   sp@(0x4),a0");
	asm("   tas a0@");
	asm("   beq LLA1");
	asm("   moveq   #-128,d0");
	asm("   rts");
	asm("LLA1:");
	asm("   moveq   #0,d0");
	asm("   rts");
	asm("   .data");
}
#endif /* sun3 */



#if defined(NEED_SPARC_TAS_ASM)
/*
 * bsd and bsdi sparc machines
 */

/* if we're using -ansi w/ gcc, use __asm__ instead of asm */
#if defined(__STRICT_ANSI__)
#define asm(x)	__asm__(x)
#endif /* __STRICT_ANSI__ */

static void
tas_dummy()						/* really means: extern int tas(slock_t *lock); */
{
	asm(".seg \"data\"");
	asm(".seg \"text\"");
	asm("_tas:");

	/*
	 * Sparc atomic test and set (sparc calls it "atomic load-store")
	 */
	asm("ldstub [%r8], %r8");

	asm("retl");
	asm("nop");
}

#endif /* NEED_SPARC_TAS_ASM */




#if defined(NEED_VAX_TAS_ASM)
/*
 * VAXen -- even multiprocessor ones
 * (thanks to Tom Ivar Helbekkmo)
 */
typedef unsigned char slock_t;

int
tas(slock_t *lock)
{
	register	ret;

	asm("	movl $1, r0
		bbssi $0, (%1), 1f
		clrl r0
  1:	movl r0, %0 "
  :		"=r"(ret)				/* return value, in register */
  :		"r"(lock)				/* argument, 'lock pointer', in register */
  :		"r0");					/* inline code uses this register */

	return ret;
}

#endif /* NEED_VAX_TAS_ASM */



#if defined(NEED_I386_TAS_ASM)
/*
 * i386 based things
 */

#if defined(USE_UNIVEL_CC)
asm int
tas(slock_t *s_lock)
{
	%lab locked;
/* Upon entry, %eax will contain the pointer to the lock byte */
	pushl % ebx
	xchgl % eax, %ebx
	xor % eax, %eax
	movb $255, %al
	lock
	xchgb % al, (%ebx)
	popl % ebx
}


#else /* USE_UNIVEL_CC */

int
tas(slock_t *lock)
{
	slock_t		_res = 1;

  __asm__("lock; xchgb %0,%1": "=q"(_res), "=m"(*lock):"0"(0x1));
	return (_res != 0);
}

#endif /* USE_UNIVEL_CC */

#endif /* NEED_I386_TAS_ASM */


#endif /* linux */


#if defined(S_LOCK_TEST)

slock_t		test_lock;

void
main()
{
	S_INIT_LOCK(&test_lock);

	if (!S_LOCK_FREE(&test_lock))
	{
		printf("S_LOCK_TEST: failed, lock not initialized.\n");
		exit(1);
	}

	S_LOCK(&test_lock);

	if (S_LOCK_FREE(&test_lock))
	{
		printf("S_LOCK_TEST: failed, lock not locked\n");
		exit(2);
	}

	printf("S_LOCK_TEST: this will hang for a few minutes and then abort\n");
	printf("             with a 'stuck spinlock' message if S_LOCK()\n");
	printf("             and TAS() are working.\n");
	S_LOCK(&test_lock);

	printf("S_LOCK_TEST: failed, lock not locked~\n");
	exit(3);

}

#endif /* S_LOCK_TEST */
