/**********************************************************************
 * Copyright (c) 2005, 2009 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * $Id: osslatch.h,v 1.13 2009/08/07 03:27:55 jwest Exp $
 *
 * Contributors:
 * IBM - Initial API and implementation
 * Spundun Bhatt (spundun@gmail.com), with the support and encouragement of the University of Southern California Information Sciences Institute Distributed Scalable Systems Division.
 **********************************************************************/

/*******************************************************************************


   Change Activity:
   defect Date        Who Description
   ====== =========== === ==============================================
   161881 Nov/30/2000 pj  Various changes.
   162293 Jan/29/2001 hdp IA64 Windows port
   165795 Feb/02/2001 jpk Forward fitted v7.2 EXS implementation
   166622 Feb/12/2001 jpk Fix SUN build
   170450 Mar/20/2001 ssu AS400 port changes
   173377 Apr/24/2001 jpk Removed SQLSIN
   179937 Jun/16/2001 jpk Added ossExsTestEnter() and changed 32-bit Windows
                          to use InterlockedExchanged()
   191482 Oct/19/2001 jpk Added some comments
   192160 Oct/23/2001 jpk Added more comments
   192091 Nov/12/2001 yka Code cleanup Phase V
   195526 Nov/13/2001 yka Code cleanup Phase VI
   189356 Nov/15/2001 yka Added OSSEXS_STATIC_INITIALIZER
   195525 Nov/20/2001 yka Rename OSSExs to OSSLatch
   186134 Mar/01/2002 dns changes so this file can be compiled as C code
                          on some platforms

*******************************************************************************/

/**
   \file osslatch.h
   \brief OSSe short-duration exclusive spin latch (lock)
 */

#ifndef OSSLATCH_HEADER_INCLUDED
#define OSSLATCH_HEADER_INCLUDED

/* System includes */
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

/* OSSe includes */
#include "oss.h"
#include "ossdebug.h"
#ifndef ENW
#include "ossfeat.h"
#endif
#include "osstime.h"

#if defined SQL_OS400
  #include <mih/locksl.h>
  #include <mih/unlocksl.h>
  #include <mih/matobjlk.h>
  #include <except.h>
#endif

#if defined SQL_OS400
  #define OSS_LATCH_LOCK    _LENR_LOCK
  #define OSS_LATCH_UNLOCK  0
#else
  #if !defined HPUX
    #define OSS_LATCH_LOCK    1
    #define OSS_LATCH_UNLOCK  0
  #else
    #define OSS_LATCH_LOCK    0
    #define OSS_LATCH_UNLOCK  1
    #define OSS_DEBUG_CHAR 0xCC      /* 186134 - used by HP code */
  #endif
#endif

/*******************************************************************************

   IMPORTANT: Keep the size and layout of the lock type and OSSLatch structure
   the consistent between 32- and 64-bit architectures.

   The size/layout does NOT have to be the same cross-platforms.  For example,
   AIX and NT don't require a common lock type.  BUT, the size/layout must be
   common between 32- and 64-bit AIX.

   Why?  It is not uncommon for these structures to be used in shared
   memory that is accessed by both 32- and 64-bit processes.

*******************************************************************************/

/*
 * LOCK TYPE
 */
#if defined SQLSUN
typedef volatile unsigned char OSSLock ;
#elif defined SQLWINT
/* Note: LONG is the same on 32- and 64-bit Windows platforms */
#include <windows.h>   /* 186134 */
#include <winbase.h>   /* 186134 - required for InterlockedExchange */
typedef volatile LONG OSSLock ;
#elif defined HPUX
typedef volatile struct { int x[5] ; }  OSSLock ;
#elif defined SQL_OS400
typedef volatile unsigned char OSSLock ;
#elif defined SQLSGI
/* The lock word size in abilock_t is the same for both 32- and 64-bit */
#include <abi_mutex.h>
typedef volatile abilock_t OSSLock ;
#elif defined __APPLE__
	#include <libkern/OSAtomic.h>
	#include <pthread.h>
	typedef OSSpinLock OSSLock ;
#else
typedef volatile int OSSLock ;
#endif


#if defined(__linux__) && defined(USE_PTHREAD_ATOMICS)

	#ifdef _WIN32
		#define pthr_atom_critsec_t CRITICAL_SECTION
	#else
		#define pthr_atom_critsec_t pthread_mutex_t
	#endif


	/** CRITICAL_SECTION_CREATE  ***************************************************
	  * Create a critical section.
	  *
	  * The mutex is non-reentrant: a thread will block if it has already
	  * entered the mutex and tries to enter it again.
	  *
	  * A thread must enter the mutex once in order to own it.
	  * 
	  * When ra_mutexCreate returns, no thread owns the mutex.
	  */
	extern void pthr_atom_mutexCreate(pthr_atom_critsec_t *lock);

	/** CRITICAL_SECTION_ENTER  ***********************************************************
	  * Enter a previously created critical section on this thread.
	  */
	extern void pthr_atom_mutexEnter(pthr_atom_critsec_t *lock);

	/** CRITICAL_SECTION_EXIT  ************************************************************
	  * Exit a previously entered critical section on this thread.
	  */
	extern void pthr_atom_mutexExit(pthr_atom_critsec_t *lock);

	/** CRITICAL_SECTION_DESTROY  *********************************************************
	  * Destroy a critical section.
	  */
	extern void pthr_atom_mutexDestroy(pthr_atom_critsec_t *lock);


#endif
/** \brief Exclusive section handle */
typedef struct OSSLatch
{
   OSSLock lock ;
#ifdef ENW
   Uint32  init ;    /* Used to help identify uninitialized EXS - changed to Uint32 because this file may be included by C code */
#else
   bool    init ;    /* Used to help identify uninitialized EXS */
#endif
} OSSLatch ;


/*
 * Special purpose internal initializer macro for OSSLatch types.
 * OSS and OSSe use only.
 */
#if defined HPUX
#define OSSLATCH_STATIC_INITIALIZER \
{ {0x00010001, 0x00010001, 0x00010001, 0x00010001, 0x00010001}, 1 }
#elif defined SQLSGI
#define OSSLATCH_STATIC_INITIALIZER { {0}, 1 }
#else
#define OSSLATCH_STATIC_INITIALIZER { 0, 1 }
#endif


/*******************************************************************************

   Lock Macros

   oss_lockaddr
      Returns the address of the lock in the OSSLatch structure.  This
      macro provides the proper cast and/or alignment adjustment
      for the lock.

   oss_trylock
      Tries to acquire a lock.  If successful, the return value is non-zero.
      If the lock could not be acquired the return value is zero.

   oss_checklock
      Returns non-zero if the lock is free.  Otherwise, the return value
      is 0 if the lock is held.

   oss_unlock
      Frees a lock.  Void return.

   oss_initlock
      Initializes the lock.  Void return.

*******************************************************************************/
#if defined _AIX
#include <sys/atomic_op.h>

#if defined SQLO_USING_VACPP50
/*
 * vacpp5 accidentally removed __iospace_sync() as one of the magic known
 * compiler builtins, so we work around it by explictly including their
 * internal header.
 */
#include "builtins.h"
#endif

/*
 * If we acquire the lock, _check_lock will return FALSE because the
 * compare and swap was successful.
 */
#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
#define oss_trylock(pLatch)   (FALSE==_check_lock(((atomic_p)oss_lockaddr(pLatch)),OSS_LATCH_UNLOCK,OSS_LATCH_LOCK))
#define oss_checklock(pLatch) (OSS_LATCH_UNLOCK==(*oss_lockaddr(pLatch)))
#define oss_unlock(pLatch)    (__iospace_sync(), (void)(*oss_lockaddr(pLatch)=OSS_LATCH_UNLOCK))
#define oss_initlock(pLatch)  ((void)(oss_unlock((pLatch))))


/******************************************************************************/
#elif defined SQLSUN
#include <synch.h>

OSS_EXTERNC volatile int ldstub( volatile unsigned char * ) ;

/*
 * ldstub returns the previous value of the lock.  If the previous
 * value of the lock is OSS_LATCH_UNLOCK then we have obtained the lock.
 */
#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
#define oss_trylock(pLatch)   (OSS_LATCH_UNLOCK==ldstub(oss_lockaddr(pLatch)))
#define oss_checklock(pLatch) (OSS_LATCH_UNLOCK==(*oss_lockaddr(pLatch)))
#define oss_unlock(pLatch)    ((void)(*oss_lockaddr(pLatch)=OSS_LATCH_UNLOCK))
#define oss_initlock(pLatch)  ((void)(oss_unlock((pLatch))))


/******************************************************************************/
#elif defined HPUX

OSS_EXTERNC int  LDCWS(  volatile void * ) ;
OSS_EXTERNC int  LDWSMA( volatile void * ) ;
OSS_EXTERNC void STWSMA( volatile void *, int ) ;

/*
 * HPUX lock implementation is the reverse of the other platforms.
 * To obtain a lock, we clear (zero) the lock word.  When the lock is
 * free, the lock word value is non-zero.  Because of this, the
 * ossLatchInit funtion must be used to initialize an EXS.  You cannot
 * just memset the EXS to zero and assume you are safe.
 *
 * If a lock is acquired, LDCWS will return the previous lock word
 * value (OSS_LATCH_UNLOCK).  If the lock is free, LDWSMA will return
 * OSS_LATCH_UNLOCK.
 */
#define oss_lockaddr(pLatch)  ((volatile void *)(((uintptr_t)(&((pLatch)->lock))+15)&~15UL))
#define oss_trylock(pLatch)   (OSS_LATCH_UNLOCK==LDCWS(oss_lockaddr(pLatch)))
#define oss_checklock(pLatch) (OSS_LATCH_UNLOCK==LDWSMA(oss_lockaddr(pLatch)))
#define oss_unlock(pLatch)    ((void)(STWSMA(oss_lockaddr(pLatch),OSS_LATCH_UNLOCK)))
#define oss_initlock(pLatch)  ((void)(oss_unlock((pLatch))))


/******************************************************************************/
#elif defined SQLSGI
#include <abi_mutex.h>

/*
 * If acquire_lock gets the lock, the return value is zero.
 * stat_lock returns UNLOCKED if the lock is free.
 * release_lock returns 0 if the lock was successfully freed.
 * init_lock returns 0 if the lock was successfully initialized.
 */
#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
#define oss_trylock(pLatch)   (0==acquire_lock((abilock_t*) oss_lockaddr(pLatch)))
#define oss_checklock(pLatch) (UNLOCKED==stat_lock((abilock_t*) oss_lockaddr(pLatch)))
#define oss_unlock(pLatch)    ((void)(release_lock((abilock_t*) oss_lockaddr(pLatch))))
#define oss_initlock(pLatch)  ((void)(init_lock((abilock_t*) oss_lockaddr(pLatch))))

/******************************************************************************/
#elif defined SQLWINT

/*
 * Windows
 */
#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
#define oss_trylock(pLatch)   (OSS_LATCH_UNLOCK==InterlockedExchange((LONG *)oss_lockaddr(pLatch),OSS_LATCH_LOCK))
#define oss_checklock(pLatch) (OSS_LATCH_UNLOCK==(*oss_lockaddr(pLatch)))
#define oss_unlock(pLatch)    ((void)(InterlockedExchange((LONG *)(oss_lockaddr(pLatch)),OSS_LATCH_UNLOCK)))
#define oss_initlock(pLatch)  ((void)(oss_unlock((pLatch))))


/******************************************************************************/
#elif defined SQLO_INTEL_IA32 || defined SQLLinux

/*
 * 32-bit Intel (Linux, Dynix, Unixware, OS/2, ...) or Linux 390 or Linux IA64.
 *
 * Note: this must be kept in sync with the way this is defined
 * in OSS (spinlock.h).  This prototype is different than the
 * spinlock.h proto for OS/2, but we don't really care about that
 * anymore.
 */
#ifdef ENW
/* 186134 - using the xchg linux function instead of the DB2 defined sqloxchg */
#if defined(__linux__) && defined(__i386__)
	/* 186134 - system.h file uses new as a function parameter name which causes errors
	 * compiling as C++ code - so we will redefine new because it is not used
	 * in the oss code */
	#define new newval
#elif defined(__linux__) && defined(__powerpc64__) /* SW: Need to revisit this  - why Linux/ppc64 is so different */
	#include "osslatch.linux_ppc64.h"
	#define panic(a) printf(a)
#else
	#include <asm/types.h>
	/* panic function is used to print error messages in asm/system.h */
	#define panic(a) printf(a)
#endif

#if defined(__linux__) && (defined SQLO_INTEL_EM64T || defined SQLO_INTEL_IA64)
	/* 186134 - system.h file uses new as a function parameter name which causes errors
	 * compiling as C++ code - so we will redefine new because it is not used
	 * in the oss code */
	#define new newval
#endif

#if defined(__linux__) && defined(USE_PTHREAD_ATOMICS)
		/* Empty */
#elif !(defined(__linux__) && defined(__s390__) && defined(USE_GCC_ATOMICS))
	#include <asm/bitops.h>
	#ifndef  LOCK_PREFIX
	   #ifdef CONFIG_SMP
	      #define LOCK_PREFIX "lock ; "
	   #else
	      #define LOCK_PREFIX ""
	   #endif
	#endif
#endif

#if defined(__linux__) && defined(USE_PTHREAD_ATOMICS) 
#elif defined(__linux__) && defined(__powerpc64__) /* SW: Need to revisit this  - why Linux/ppc64 is so different */
	/* Do not include system.h since it won't compile */
#elif defined(__linux__) && defined(__s390__) && defined(USE_GCC_ATOMICS)
	// Do not include system.h, instead define xchg here - sl
	#ifndef xchg
		#define xchg(ptr,value) __sync_lock_test_and_set(ptr,value)
	#endif
#else
	#include <asm/system.h>
#endif

#if defined(__linux__) && defined(USE_PTHREAD_ATOMICS)

	extern volatile int globalLatchLockInit;
	extern pthr_atom_critsec_t * globalLatchLock;

#else
	#define sqloxchg(a,b) xchg(a,b)
#endif

#else
OSS_EXTERNC int sqloxchg( int *, int ) ;
#endif

#if defined(__linux__) && defined(USE_PTHREAD_ATOMICS)

	OSS_EXTERNC int sqloxchg(OSSLatch *latch, int * addr, int value);

	#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
	#define oss_checklock(pLatch) (OSS_LATCH_UNLOCK==(*oss_lockaddr(pLatch)))

	#define oss_trylock(pLatch)   (OSS_LATCH_UNLOCK==sqloxchg(pLatch, ((int *)oss_lockaddr(pLatch)),OSS_LATCH_LOCK))
	#define oss_unlock(pLatch)    ((void)(sqloxchg(pLatch, ((int *)oss_lockaddr(pLatch)),OSS_LATCH_UNLOCK)))

	OSS_EXTERNC void initLatchLock(OSSLatch *latch);
	#define oss_initlock(pLatch)  ((void)(initLatchLock(pLatch)))

#else

	#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
	#define oss_trylock(pLatch)   (OSS_LATCH_UNLOCK==sqloxchg(((int *)oss_lockaddr(pLatch)),OSS_LATCH_LOCK))
	#define oss_checklock(pLatch) (OSS_LATCH_UNLOCK==(*oss_lockaddr(pLatch)))
	#define oss_unlock(pLatch)    ((void)(sqloxchg(((int *)oss_lockaddr(pLatch)),OSS_LATCH_UNLOCK)))
	#define oss_initlock(pLatch)  ((void)(oss_unlock((pLatch))))

#endif

/******************************************************************************/
#elif defined SQLOS390

/*
 * OS390
 */
#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
#define oss_checklock(pLatch) (OSS_LATCH_UNLOCK==(*oss_lockaddr(pLatch)))
#define oss_unlock(pLatch)    ((void)(*oss_lockaddr(pLatch)=OSS_LATCH_UNLOCK))
#define oss_initlock(pLatch)  ((void)(oss_unlock((pLatch))))

#ifdef __cplusplus
inline int oss_trylock(OSSLatch * pLatch)
#else
#pragma inline(oss_trylock)
int oss_trylock(OSSLatch * pLatch)
#endif
{
  cs_t oldval = OSS_LATCH_UNLOCK;
  return (0==cs(&oldval,((cs_t*)oss_lockaddr(pLatch)),OSS_LATCH_LOCK));
}

/******************************************************************************/
#elif defined __APPLE__

#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
#define oss_trylock(pLatch)   ( OSSpinLockTry(oss_lockaddr(pLatch)) )
#define oss_checklock(pLatch) ( *oss_lockaddr(pLatch) )
#define oss_unlock(pLatch)    ((void)OSSpinLockUnlock(oss_lockaddr(pLatch)))
#define oss_initlock(pLatch)  ((void)(oss_unlock((pLatch))))

/******************************************************************************/
#elif defined SQL_OS400       /*@d170450ssu*/

/*
 * AS/400
 */

/*The lock is implemented using the locksl and unlocksl mi instructions.
 *locksl will lock the byte and unlocksl will unlock it. Note that this
 *instruction does not actually change the value of the byte. The
 *matobjlk instruction can be used to get the status of the lock on the
 *byte.
 */
#define oss_lockaddr(pLatch)  (&((pLatch)->lock))
#define oss_unlock(pLatch)    unlocksl((_SPCPTR)oss_lockaddr(pLatch), OSS_LATCH_LOCK )
#define oss_initlock(pLatch)

inline int oss_checklock(OSSLatch * pLatch) {
    /* The receiver template used by 'matobjlk'. */
    _MOBJL_Template_T    allocated_locks;

    allocated_locks.Template_Size = sizeof( _MOBJL_Template_T );

    //Look at the bit pattern returned by
    //'matobjlk' in the 'Lock_Alloc' field of the template. The fifth
    //bit (bit 4) of this field represents the Lock-Exclusive-No-Read
    //(LENR) lock.
    matobjlk(&allocated_locks, (_ANYPTR)oss_lockaddr(pLatch));

    if ( allocated_locks.Lock_Alloc & OSS_LATCH_LOCK ) {
       return 0;
    }
    return 1;
}

inline int oss_trylock(OSSLatch * pLatch) {
    //See if already locked
    if(!oss_checklock(pLatch)) {
       return 0;
    }

    //Exclusive lock the byte. Catch MCH5804 = locked time out exception
    #pragma exception_handler(isLocked, 0, 0,  \
            _C2_MH_ESCAPE, _CTLA_HANDLE, "MCH5804")
    locksl((_SPCPTR)oss_lockaddr(pLatch), OSS_LATCH_LOCK );
    #pragma disable_handler

    //Make sure that it really is locked
    return !oss_checklock(pLatch);

    isLocked:
       return 0;
}


/******************************************************************************/
#else
#error Unsupported platform
#endif

/* bugzilla 64229 - adding #include "RASharedMemory.h" to RAServerTypes2.h
   causes this file to be included for all of the RAServer c files.  On
	Solaris this causes a link problem because the following functions are
   mutiply defined.  So for Solaris we won't define these functions for
	C files
*/
#if (!defined(SQLSUN) && !defined(__powerpc64__)) || defined(__cplusplus)

#ifdef ENW
#ifndef __cplusplus
void OSS_API ossLatchInit ( OSSLatch * pLatch );
void OSS_API ossLatchGet( OSSLatch * pLatch );
int OSS_API ossLatchTestGet( OSSLatch * pLatch );
void OSS_API ossLatchRelease ( OSSLatch * pLatch );
#endif
#endif

/**
   \brief Initializes a latch.

   \param pLatch [in]
      Address of a latch handle.
 */
#ifdef __cplusplus
inline void OSS_API ossLatchInit ( OSSLatch * pLatch )
#else
#pragma inline(ossLatchInit)
void OSS_API ossLatchInit ( OSSLatch * pLatch )
#endif
{
   OSS_ASSERT( !ossIsBadWritePtr( pLatch, sizeof( *pLatch ) ) ) ;

#if defined HPUX
   /*
    * Fill the OSSLatch structure with a unique eye-catching value then
    * oss_unlock the particular lock word within the structure.  This way
    * it will be easier to identify the lock word in a dump of an HPUX
    * OSSLatch structure.
    */
   memset( (void *)&(pLatch->lock), OSS_DEBUG_CHAR, sizeof( pLatch->lock ) ) ;
#endif
   oss_initlock( pLatch ) ;
   pLatch->init = 1 ;
   return ;
}


/**
   \brief Gets a latch.

   \note The latch handle must be initialized prior to using this function!

   \param pLatch [in]
      Address of a latch handle.
 */
#ifdef __cplusplus
inline void OSS_API ossLatchGet( OSSLatch * pLatch )
#else
#pragma inline(ossLatchGet)
void OSS_API ossLatchGet( OSSLatch * pLatch )
#endif
{
   volatile int i, j, k ;

   OSS_ASSERT( !ossIsBadWritePtr( pLatch, sizeof( *pLatch ) ) ) ;
   OSS_ASSERT( pLatch->init ) ;

   while ( !oss_trylock( pLatch ) )
   {
      /* Spin */
      for ( k = 0, i = 1; !oss_checklock( pLatch ); i++ )
      {
         if ( ( i & 0x1 ) && ( k & 0xFFFF ) )
            ossYield() ;
         else
            for ( j = 0 ; j < 20 ; j++ )
               k += i * j + 5 ;
      }
   }
   return ;
}


/**
   \brief Tests and gets a latch if available.

   \note The latch handle must be initialized prior to using this function!

   \param pLatch [in]
      Address of a latch handle.

   \returns If the latch is available, the function returns true and
      gets the latch.  Otherwise, the return value is false.
 */
/* 186134 - changed return type to int from bool because this could be included from C code */
#ifdef __cplusplus
inline int OSS_API ossLatchTestGet( OSSLatch * pLatch )
#else
#pragma inline(ossLatchTestGet)
int OSS_API ossLatchTestGet( OSSLatch * pLatch )
#endif
{
   OSS_ASSERT( !ossIsBadWritePtr( pLatch, sizeof( *pLatch ) ) ) ;
   OSS_ASSERT( pLatch->init ) ;

   if ( oss_trylock( pLatch ) )
   {
      return 1 ;
   }
   else
   {
      return 0 ;
   }
}


/**
   \brief Release the latch.

   \note The latch handle must be initialized prior to using this function!

   \param pLatch [in]
      Address of a latch handle.
 */
#ifdef __cplusplus
inline void OSS_API ossLatchRelease ( OSSLatch * pLatch )
#else
#pragma inline(ossLatchRelease)
void OSS_API ossLatchRelease ( OSSLatch * pLatch )
#endif
{
   OSS_ASSERT( !ossIsBadWritePtr( pLatch, sizeof( *pLatch ) ) ) ;
   OSS_ASSERT( pLatch->init ) ;

   oss_unlock( pLatch ) ;
   return ;
}

#endif  /* 64229 */

#endif
