    /***********************************************************************
                   Copyright (c) 2007-2008 SKY Computers, Inc.
    
     Redistribution and use in source and binary forms are permitted
     provided that this notice is preserved and that due credit is given to
     SKY Computers, Inc. The name of SKY Computers, Inc. may not be used to
     endorse or promote products derived from this software without
     specific prior written permission. This software is provided ``as is''
     without express or implied warranty.
     ************************************************************************/
  
/*
 *--------------------------------------------------------------------------
 *
 >++
 *
 *   File: lk_lock.c
 *
 *   Function: To provide ssome basic lock functions via pthread mutexes.
 *
 *   Calling Sequence: Several, see below.
 *
 *   Implementation Details:
 *       1. Lock functions are called within this module but may also be
 *          called from outside this module.
 *       2. The simulated failure is called from lk_compute.c
 *
 *   Restrictions:
 *       1. Error checking, reporting could be better.
 *
 >**
 *   Revision Information:
 *    Date       By   Rev    Changes Made....
 *    08/04/08   bwj  ----   New module, in process.
 >--
 *--------------------------------------------------------------------------
 */

#ifndef LK_LOCK_C_
#define LK_LOCK_C_

static char const lk_lock_h_rcsid[] = 
  "$Id: lk_lock.c,v 0.0 2008/08/04 12:15:33 bjackson Exp $";

#endif

/* System includes */
#include "sky_ex_inc.h"
#include "lk_tcb.h"
#include <pthread.h>

#define SYNC_THREADS  (NUM_CO_THREADS + NUM_SUM_THREADS)
#define SYNC_NSET     (0x1)
#define SYNC_SET      (0x2)
#define TT_LOG        (NUM_THREADS)

static int lk_current_data_block;
static int lk_lock_done_once = NO;
static int lk_thread_step[SYNC_THREADS];

static pthread_mutex_t Mlock_1;// = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t Mlock_2;// = PTHREAD_MUTEX_INITIALIZER;

/*
 *  TimeTrac Externals -- 
 *      Do the following if you want the TimeTrac variables to disappear 
 *      when the TimeTrac function calls disappear (see the Makefile).
 */
#ifdef TIME_TRAC
extern TTHandle Trace_Log[];
extern TTEventHandle tt_sync_bgn[], tt_sync_end[];
TTEventHandle tt_lk_bgn[3], tt_lk_end[3];
#endif

/* ---------------------------------------------------------------------- */ 

/*
 >++
 *
 *   int lk_lock_init (int rank): Initialize all of the lock parameters.
 *   
 *       Returns: 0 on successful initialization.
 >--
 */

int lk_lock_init (int rank)
{
  int   i;
  int   ret;
  char  trace_name[128];
    
  /* Only allowed for the master thread. */
  if (rank != 0)
      return (-1);
  
  /* Initialize the globals. */
  lk_lock_done_once = YES;
  lk_current_data_block = 0;
  for (i = 0; i < SYNC_THREADS; i++)
      lk_thread_step[i] = SYNC_NSET;
  
  /* Initialize the locks (we have only 2). */
  ret = pthread_mutex_init (&Mlock_1, NULL);
  if (ret != 0)
      perror ("ERROR -- pthread_mutex_init 1");

  ret = pthread_mutex_init (&Mlock_2, NULL);
  if (ret != 0)
      perror ("ERROR -- pthread_mutex_init 2");

  /* Create a name for this TimeTrac file. */
  sprintf (trace_name, "%02d_Locks", TT_LOG);

  /*
   *  Timetrac logging to this trace will occur from different threads; you must 
   *  set the thread safe argument (2nd arg, below) to allow multiple threads to 
   *  log to the same file (or the log file may contain bad entries).
   */
  ret = time_trac_open (trace_name, 2, TT_MAX_EVENTS*6, 0xffffffff, TT_NO_AUTO_SAVE, 
			TT_CREATE_FILE, 1, &Trace_Log[TT_LOG]);
  if (ret != TTE_SUCCESS)
    {
      printf ("%2d -- WARNING: TimeTrac file (%s) could not be opened.", 
	      rank, trace_name);
    }
  
  time_trac_reg_range_event (Trace_Log[TT_LOG], "b. Lock #1", "Red", 1, 
			     &tt_lk_bgn[1], &tt_lk_end[1]);
  time_trac_reg_range_event (Trace_Log[TT_LOG], "c. Lock #2", "Red", 1, 
			     &tt_lk_bgn[2], &tt_lk_end[2]);
  return (0);
}

/* ---------------------------------------------------------------------- */ 

/*
 >++
*
 *   int take_lock (int rank, int lock_num): When called, lock the mutex.
 *       No checks are made to verify that the thread already holds the
 *       lock or not.
 *
 *       int rank: of the calling thread, for debug only.
 *       int lock_num: which lock are we to take
 *   
 *       Returns: -1 on invalid input
 *                 0 on success.
 >--
 */

int take_lock (int rank, int lock_num)
{
  int ret = 0;
  
  switch (lock_num)
    {
    case 1:
      ret = pthread_mutex_lock (&Mlock_1);  // blocks until mutex available
      if (ret != 0)
	{
	  perror ("ERROR -- Call to pthread_mutex_lock(1) failed");
	  fflush (stdout);
	  return (-1);
	}
      break;
      
    case 2:
      ret = pthread_mutex_lock (&Mlock_2);  // blocks until mutex available
      if (ret != 0)
	{
	  perror ("ERROR -- Call to pthread_mutex_lock(2) failed");
	  fflush (stdout);
	  return (-1);
	}
      break;

    default:
      printf (" - -- Lock number is out of range (%d), in %s, line %d. ****\n", 
	      lock_num, __FILE__, __LINE__);
      fflush (stdout);
      return (-1);
    }
        
  time_trac_record (Trace_Log[TT_LOG], tt_lk_bgn[lock_num], rank);
  return (ret);
}

/* ---------------------------------------------------------------------- */ 

/*
 >++
 *
 *   int ret_lock (int rank, int lock_num): When called, unlock the mutex.
 *       No checks are made to verify that the thread actually holds the
 *       lock.
 * 
 *       int rank: of the calling thread, for debug only.
 *       int lock_num: which lock are we to take
 *   
 *       Returns: -1 on invalid input
 *                 0 on success.
 >--
 */

int ret_lock (int rank, int lock_num)
{
  int ret = 0;
  
  time_trac_record (Trace_Log[TT_LOG], tt_lk_end[lock_num], rank);

  switch (lock_num)
    {
    case 1:
      {
	ret = pthread_mutex_unlock (&Mlock_1);  // blocks until mutex available
	if (ret != 0)
	  {
	    perror ("ERROR -- Call to pthread_mutex_lock(1) failed");
	    return (-1);
	  }
      }
      break;
      
    case 2:
      ret = pthread_mutex_unlock (&Mlock_2);  // blocks until mutex available
      if (ret != 0)
	{
	  perror ("ERROR -- Call to pthread_mutex_lock(2) failed");
	  return (-1);
	}
      break;

    default:
      printf (" - -- Lock number is out of range (%d), in %s, line %d. ****\n", 
	      lock_num, __FILE__, __LINE__);
      return (-1);
    }
        
  return (ret);
}

/* ---------------------------------------------------------------------- */ 

/*
 >++
 *
 *   int current_data_block (): 
 *
 *   
 *   
 *       Returns: The current data block value.
 >--
 */

int current_data_block ()
{
  return (lk_current_data_block);
}

 
/* ---------------------------------------------------------------------- */

/*
 >++
 *
 *   int get_sync (int_rank): The module, when called, will keep the
 *       threads in sync with each other by telling the thread if it
 *       may continue or not. If it should not continue, the thread 
 *       is expected to relinquish its processor to another thread
 *       (by calling thread_yeild). 
 *   
 *       Returns: LK_WAITING when waiting for others to catch up.
 *                LK_TH_GO when this thread is not waiting for others.
 >--
 */

int get_sync (int rank, int data_cnt)
{
  int  i;
  int  index;
  int  ret = LK_TH_GO;
  
  index = rank - 1;     /* rank no is 1 based, index is 0 based */
  
  while (time_trac_record (Trace_Log[rank], tt_sync_bgn[rank], rank) != TTE_SUCCESS)
    printf ("+");
  
  /*
   *  Take the top level lock to protect the data structures here
   *  (the lock is not really needed except for illustration).
   */
  take_lock (rank, 1);
  
  /* Are we behind the others? */
  if (data_cnt < lk_current_data_block)
    {
      lk_thread_step[index] = SYNC_NSET;
      ret = LK_TH_GO;
      goto lock1_exit;
    }
  
  /* Set my flag. */
  lk_thread_step[index] = SYNC_SET;
  
  /* Check all of the sync flags, return if any are waiting. */
  for (i = 0; i < SYNC_THREADS; i++) 
    {
      if (lk_thread_step[i] == SYNC_NSET)
	{
	  ret = LK_WAITING;
	  goto lock1_exit;
	}
    }

  /* 
   *  Now perhaps we need to take a 2nd lock to protect another data value
   *  (not needed here except for illustration).
   */
  take_lock (rank, 2);

  /*
   *  When we get here, all threads are at the same place. Clear the sync 
   *  flags, bump the data block counter, and continue.
   */
  lk_current_data_block++;
  for (i = 0; i < SYNC_THREADS; i++)
    {
      lk_thread_step[i] = SYNC_NSET;
    }
  
 lock2_exit:
  ret_lock (rank, 2);

 lock1_exit:
  ret_lock (rank, 1);
  while (time_trac_record (Trace_Log[rank], tt_sync_end[rank], rank) != TTE_SUCCESS)
    printf ("-");
    
  return (ret);
}

/* ---------------------------------------------------------------------- */ 

/*
 >++
 *
 *   int lk_lock_cleanup (rank): Cleanup the ll structures freeing the
 *       mutex's and ending all TimeTrac traces (for better visibility).
 *   
 *       Returns: 0
 >--
 */
 
int lk_lock_cleanup (int rank)
{
  /* This makes the TimeTrac output look better. */
  time_trac_record (Trace_Log[TT_LOG], tt_lk_end[1], rank);
  time_trac_record (Trace_Log[TT_LOG], tt_lk_end[2], rank);

  lk_lock_done_once = NO;
  pthread_mutex_destroy (&Mlock_1);
  pthread_mutex_destroy (&Mlock_2);

  return (0);
}

/* --------------------------- End of Module ---------------------------- */

