/*
 *            Copyright (c) 1998-2000 by NETsilicon Inc.
 *
 *  This software is copyrighted by and is the sole property of 
 *  NETsilicon.  All rights, title, ownership, or other interests
 *  in the software remain the property of NETsilicon.  This
 *  software may only be used in accordance with the corresponding
 *  license agreement.  Any unauthorized use, duplication, transmission,  
 *  distribution, or disclosure of this software is expressly forbidden.
 *
 *  This Copyright notice may not be removed or modified without prior
 *  written consent of NETsilicon.
 *
 *  NETsilicon, reserves the right to modify this software
 *  without notice.
 *
 *  NETsilicon
 *  411 Waverley Oaks Road                  USA 781.647.1234
 *  Suite 227                               http://www.netsilicon.com
 *  Waltham, MA 02452                       AmericaSales@netsilicon.com
 *
 *************************************************************************
 *
 *     Module Name: posix_thread.c
 *         Version: 1.00
 *   Original Date: 09/17/99	
 *          Author: Shing Wu
 *        Language: Ansi C
 * Compile Options: 
 * Compile defines: 
 *       Libraries: 
 *    Link Options: 
 *
 *    Entry Points: 
 *
 * Description.
 * =======================================================================
 *  This file contains the code that implements our POSIX like support
 *  for threads.  It builds upon the thread support already in ThreadX.
 *                                                                        
 *                                                                        
 * Edit Date/Ver   Edit Description
 * ==============  ===================================================
 *  10-26-99  SW    fix a bug in g/setErrno(). and use setErrno () in case 
 *                  of error. Remove printf ().
 *
 *  09-17-99  CHG   Changed errno field in posixThreadInfoType to perrno
 *                  to resolve conflict with errno declaration in
 *                  Green Hills header files.
 * 04/16/02  DXL	Cleaned up variable declared but never reference; warnings generated by new compiler
 *					setErrno () returns correct value
 * 05/15/03  JZW    added include of stdio.h to clean up GNU gcc compiler warnings
 *
 * 06/11/03  JZW    Added functional prototype for bzero
 */

/* should later change to angle bracket */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "errno.h" 
#include "pthread.h"
/* for thread clean up */
TX_THREAD cleanup_thread;
TX_QUEUE  del_thrd_queue;
static int  gCleanup_thread_created=0;
void clean_thread_entry(ULONG thread_input);
void posix_thread_start(ULONG thread_input);

extern void	bzero (void *, size_t); /* 6/11/03 */

/* for threads*/
static int remove_from_thread_queue (posix_thread_created thread);
static pthread_t search_thread (TX_THREAD *pthread);
static posix_thread_created search_thread_node (TX_THREAD *pthread);
static int add_to_thread_queue (pthread_t thread);
/* global queue to store all created thread in a list */
static posix_thread_created _posix_thread_created_ptr = TX_NULL;

/*--------------------------------------------------------
 *      Initializes a thread attributes object with default
 *      attributes.
 *
 * PARAMETERS
 *      attr
 *              pointer to an instance of pthread_attr_t
 *
 *
 * DESCRIPTION
 *      Initializes a thread attributes object with default
 *      attributes.
 *
 *      NOTES:
 *              1)      Used to define thread attributes
 *
 * RESULTS
 *              0               successfully initialized attr,
 *              ENOMEM          insufficient memory for attr.
 *
 *-----------------------------------------------------*/
int pthread_attr_init (pthread_attr_t *attr)
{
TX_INTERRUPT_SAVE_AREA
  static int i=0;

  /* avoid data abort */
  if (attr == TX_NULL)
  {
	  setErrno (EINVAL);
      return EINVAL;
  }

  attr->attrFlags= 0;
 
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
  attr->stackSize = NA_DEFAULT_STACK_SIZE;
#endif

#ifdef _POSIX_THREAD_ATTR_STACKADDR
    attr->stackStart = TX_NULL; 
#endif

  TX_DISABLE
  sprintf (attr->name, "POSIX Thread %d", i++);

  TX_RESTORE

  return 0;
}

/*------------------------------------------------------
 *      Destroys a thread attributes object.
 *
 * PARAMETERS
 *      attr
 *              pointer to an instance of pthread_attr_t
 *
 *
 * DESCRIPTION
 *      Destroys a thread attributes object.
 *
 *      NOTES:
 *              1)      Does not affect threads created with 'attr'.
 *              2)      Not actually free up mem for attr
 *
 * RESULTS
 *              0               successfully destroyed attr,
 *              EINVAL          'attr' is invalid.
 *
 * ------------------------------------------------------*/
int pthread_attr_destroy (pthread_attr_t* attr)
{
  if (attr == TX_NULL)
  {
	  setErrno (EINVAL);
	  return EINVAL;
  }

  if (attr->stackStart && !(attr->attrFlags & NA_STACK_START_BIT_CHANGED))
	  free (attr->stackStart);
  memset ((char*) attr, 0, sizeof (*attr));

  return 0;
}

/*-------------------------------------------------------
 *      This function determines the size of the stack on
 *      which threads created with 'attr' will run.
 *
 * PARAMETERS
 *      attr
 *              pointer to an instance of pthread_attr_t
 *
 *      stacksize
 *              pointer to size_t into which is returned the
 *              stack size, in bytes.
 *
 *
 * DESCRIPTION
 *      This function determines the size of the stack on
 *      which threads created with 'attr' will run.
 *
 *      NOTES:
 *              1)      Function supported only if this macro is
 *                      defined:
 *
 *                              _POSIX_THREAD_ATTR_STACKSIZE
 *
 *              2)      Use on newly created attributes object to
 *                      find the default stack size.
 *
 * RESULTS
 *              0               successfully retrieved stack size,
 *              EINVAL          'attr' is invalid
 *              ENOSYS          function not supported
 *
 *------------------------------------------------------*/
int pthread_attr_getstacksize (pthread_attr_t *attr, size_t *stacksize)
{
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
  if (attr == TX_NULL)
    {
      setErrno (EINVAL);
      return EINVAL;
    }

  *stacksize = attr->stackSize;
  return 0;

#else
  setErrno (ENOSYS);
  return ENOSYS;
#endif 
}


/* ------------------------------------------------------
 *      This function specifies the size of the stack on
 *      which threads created with 'attr' will run.
 *
 * PARAMETERS
 *      attr
 *              pointer to an instance of pthread_attr_t
 *
 *      stacksize
 *              stack size, in bytes.
 *
 *
 * DESCRIPTION
 *      This function specifies the size of the stack on
 *      which threads created with 'attr' will run.
 *
 *      NOTES:
 *              1)      Function supported only if this macro is
 *                      defined:
 *
 *                              _POSIX_THREAD_ATTR_STACKSIZE
 *
 *              2)      If value of stacksize changed, set the attrFlags's
 *                      NA_STACK_SIZE_CHANGED bit
 *                      
 *
 *              3)      Only use if thread needs more than the
 *                      default.
 *
 * RESULTS
 *              0               successfully set stack size,
 *              EINVAL          'attr' is invalid or stacksize too
 *                              small or too big.
 *              ENOSYS          function not supported
 *
 *-------------------------------------------------------*/
int pthread_attr_setstacksize (pthread_attr_t *attr, size_t stacksize)
{
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
  size_t curstksize;
  /* Verify that the stack size is within range. */
  if (stacksize < NA_DEFAULT_STACK_SIZE)
    {
  	  setErrno (EINVAL);
      return EINVAL;
    }

  if (attr == TX_NULL)
    {
      setErrno (EINVAL);
      return EINVAL;
    }

  /* set attrib bit */
  if (!pthread_attr_getstacksize (attr,  &curstksize))
  {
	  if (curstksize != stacksize)
		  attr->attrFlags |= NA_STACK_SIZE_CHANGED;
  }
  attr->stackSize = stacksize;
  return 0;

#else
  setErrno (ENOSYS);
  return ENOSYS;
#endif 
}

/* ------------------------------------------------------
 *      This function determines the address of the stack
 *      on which threads created with 'attr' will run.
 *
 * PARAMETERS
 *      attr
 *              pointer to an instance of pthread_attr_t
 *
 *      stackaddr
 *              pointer into which is returned the stack address.
 *
 *
 * DESCRIPTION
 *      This function determines the address of the stack
 *      on which threads created with 'attr' will run.
 *
 *      NOTES:
 *              1)      Function supported only if this macro is
 *                      defined:
 *
 *                              _POSIX_THREAD_ATTR_STACKADDR
 *
 *
 * RESULTS
 *              0               successfully retreived stack address,
 *              EINVAL          'attr' is invalid
 *              ENOSYS          function not supported
 * ------------------------------------------------------*/

int pthread_attr_getstackaddr (pthread_attr_t *attr, void**stackaddr)
{
#if defined( _POSIX_THREAD_ATTR_STACKADDR )
  
  if (attr == TX_NULL)
    {
      setErrno (EINVAL);
      return EINVAL;
    }

  *stackaddr = attr->stackStart;
  return 0;

#else
  setErrno (ENOSYS);
  return ENOSYS;

#endif 
}


/* ------------------------------------------------------
 *      Threads created with 'attr' will run on the stack
 *      starting at 'stackaddr'.
 *
 * PARAMETERS
 *      attr
 *              pointer to an instance of pthread_attr_t
 *
 *      stacksize
 *              stack size, in bytes.
 *
 *
 * DESCRIPTION
 *      Threads created with 'attr' will run on the stack
 *      starting at 'stackaddr'.
 *      Stack must be at least PTHREAD_STACK_MIN bytes.
 *
 *      NOTES:
 *              1)      Function supported only if this macro is
 *                      defined:
 *
 *                              _POSIX_THREAD_ATTR_STACKADDR
 *
 *
 *              2)      Ensure that stackaddr is aligned.
 *
 * RESULTS
 *              0               successfully set stack address,
 *              -1              if stackaddr does not start on a 32-bit 
 *                              boundary
 *              EINVAL          'attr' is invalid
 *              ENOSYS          function not supported
 *
 * ------------------------------------------------------*/
int pthread_attr_setstackaddr (pthread_attr_t *attr, void *stackaddr)
{
#if defined( _POSIX_THREAD_ATTR_STACKADDR )
  if (attr == TX_NULL)
    {
      setErrno (EINVAL);
      return EINVAL;
    }
  if (((ULONG)stackaddr) & 0x3)
	  return -1;

  if (attr->stackStart && !(attr->attrFlags & NA_STACK_START_BIT_CHANGED))
	  free (attr->stackStart);

  /* set attrib bit */
  attr->attrFlags |= NA_STACK_START_BIT_CHANGED;
  attr->stackStart = stackaddr;
  return 0;

#else
  setErrno (ENOSYS);
  return ENOSYS;
#endif 
}

/* ------------------------------------------------------
 *      Change thread's name
 * PARAMETERS
 *      attr
 *              pointer to an instance of pthread_attr_t
 *
 *      name
 *              a null terminated string for the new name    
 *
 *
 * DESCRIPTION
 *      This function set the thread name by writing the 
 *      name into the attribute structure.If the name is too
 *      long, it is truncated.
 *
 *      NOTES:
 *              1)      Not POSIX standard 
 *
 *
 * RESULTS
 *              0       always
 *
 *-------------------------------------------------------*/
int xpthread_attr_setname (pthread_attr_t *attr, char* name)
{
  int n;
  n = strlen (name);
  if (n >= 40)
	  n = 39;
  if (attr == TX_NULL)
    {
      setErrno (EINVAL);
      return EINVAL;
    }
  
  strncpy (attr->name, name, n);
  attr->name[n]= '\0';
  return 0;
}



/*------------------------------------------------------
 *      This function creates a thread running the start function,
 *      passing it the parameter value, 'arg'.
 *
 * PARAMETERS
 *      thread
 *              pointer to an instance of pthread_t
 *
 *      attr
 *              optional pointer to an instance of pthread_attr_t
 *
 *      start_routine
 *              pointer to the starting routine for the new thread
 *
 *      arg
 *              optional parameter passed to 'start'
 *
 *
 * DESCRIPTION
 *      This function creates a thread running the start_routine function,
 *      passing it the parameter value, 'arg'. The 'attr'
 *      argument specifies optional creation attributes.
 *      The thread is identity of the new thread is returned
 *      as 'thread'
 *
 * RESULTS
 *              0               successfully created thread,
 *              EINVAL          attr invalid,
 *              EAGAIN          insufficient resources.
 *
 * ------------------------------------------------------*/
int pthread_create (pthread_t *thread,
                                const pthread_attr_t *attr,
                                void *(*start_routine) (void *),
                                void *arg)
{
#if defined( _POSIX_THREADS)
TX_INTERRUPT_SAVE_AREA
	UINT status;
	pthread_attr_t  real_attr;
	void *stkstart;
    CHAR *qptr;

	/* check to see if the bkgd thread exists, if not, create it now */
     TX_DISABLE
     if (!gCleanup_thread_created)
	 {
		 qptr = (CHAR*) malloc (1024);
		 if (qptr)
		 {
	    status = tx_thread_create(&cleanup_thread, "Clean Up Thread", clean_thread_entry, 
	 	        (ULONG)0, (VOID *) qptr, 1024, NA_CLEAN_THREAD_PRIORITY, NA_CLEAN_THREAD_PRIORITY,
				/*NA_DEFAULT_THREAD_PRIORITY,
				NA_DEFAULT_THREAD_PREEMPTION, */
				NA_DEFAULT_THREAD_TIMESLICE
				, TX_DONT_START);
		qptr = (CHAR*) malloc (100*sizeof(ULONG));
	    tx_queue_create(&del_thrd_queue, "threads to kill", TX_1_ULONG, (VOID *) qptr,
			/*(CHAR*) malloc (100*sizeof(ULONG)),*/
			 100*sizeof(ULONG));

		if (status == TX_SUCCESS)
			gCleanup_thread_created =1;
		tx_thread_resume (&cleanup_thread);
		 }
	 }
	 TX_RESTORE

	/* if attr is NULL, then the default attr should be used */
	if (attr == TX_NULL)
        pthread_attr_init (&real_attr);
    else
		memcpy ((char*) &real_attr, (char*) attr, sizeof (*attr));

    /* create and start the thread here */

	/* allocate the mem for thread */
	*thread = (pthread_t) malloc (sizeof (posixThreadInfoType));

	if (*thread == TX_NULL)
	{
		setErrno (EAGAIN);
		return EAGAIN;
	}

	/* allocate stack mem */
	if (real_attr.attrFlags & NA_STACK_START_BIT_CHANGED) /* caller sets stack start */
	{
         (*thread)->stack = TX_NULL;
	     stkstart = real_attr.stackStart;
	}
	else
	{
		(*thread)->stack = (CHAR*) malloc (real_attr.stackSize);
		stkstart = (*thread)->stack;

	}

	(*thread)->controlBlock = (TX_THREAD*) malloc (sizeof(TX_THREAD));
	bzero ((*thread)->controlBlock, sizeof (TX_THREAD));
	(*thread)->entryFunction = start_routine;
	(*thread)->entryArgument = arg;
	(*thread)->perrno = 0;
    strcpy ((*thread)->name, real_attr.name);    
	status = tx_thread_create ((*thread)->controlBlock,
		              (*thread)->name,
					  /*start_routine,*/
					  posix_thread_start,
					  (ULONG) (*thread),
					  stkstart,
					  (ULONG) real_attr.stackSize,
					  NA_DEFAULT_THREAD_PRIORITY,
					  NA_DEFAULT_THREAD_PREEMPTION,
					  NA_DEFAULT_THREAD_TIMESLICE,
					  TX_DONT_START);  

    switch (status)
    {
	case TX_SUCCESS:
		add_to_thread_queue (*thread);
		tx_thread_resume ((*thread)->controlBlock);
		return 0;

	case TX_THREAD_ERROR:
		setErrno (EAGAIN);
		return EAGAIN;

	case TX_PTR_ERROR:
	case TX_SIZE_ERROR:
		setErrno (EINVAL);
		return EINVAL;
    /* the following should never happen */
	case TX_PRIORITY_ERROR:
	case TX_THRESH_ERROR:
	case TX_START_ERROR:
	case TX_CALLER_ERROR:
    default:
		 return EAGAIN;
	}
#else
  setErrno (ENOSYS);
  return ENOSYS;
#endif 
}
 
/* ------------------------------------------------------
 *      This function returns a reference to the current running
 *      thread.
 *
 * PARAMETERS
 *      N/A
 *
 *
 * DESCRIPTION
 *      This function returns a reference to the current running
 *      thread.
 *
 * RESULTS
 *              pthread_t       reference to the current thread
 *
 * ------------------------------------------------------*/
pthread_t pthread_self (void)
{
  return  search_thread (tx_thread_identify ());
}

/* ------------------------------------------------------
 *      This function returns zero if t1 and t2 are equal, else
 *      returns nonzero
 *
 * PARAMETERS
 *      t1,
 *      t2
 *              references to an instances of thread_t
 *
 *
 * DESCRIPTION
 *      This function returns nonzero if t1 and t2 are equal, else
 *      returns zero.
 *
 *      NOTES:
 *              1)      Function supported only if this macro is
 *                      defined:
 *
 *                              _POSIX_THREADS
 *
 * RESULTS
 *              non-zero        if t1 and t2 refer to the same thread,
 *              0               t1 and t2 do not refer to the same thread
 *
 * ------------------------------------------------------*/

int pthread_equal (pthread_t t1, pthread_t t2)
{
#if defined( _POSIX_THREADS)
  return  t1 == t2;
#else
  return ENOSYS;
#endif 
}

/* ------------------------------------------------------
 *      Terminate current thread
 *
 * PARAMETERS
 *      p       not used
 *
 * DESCRIPTION
 *      This function can be called to terminate the current 
 *      thread, 
 *
 *      NOTES:
 *              1)      Function supported only if this macro is
 *                      defined:
 *
 *                              _POSIX_THREADS
 *
 * RESULTS
 *      This function does not return.
 *
 * ------------------------------------------------------*/
void pthread_exit (void *p)
{
#if defined( _POSIX_THREADS)

	posix_thread_created thread_node;
	
	thread_node = search_thread_node (tx_thread_identify());
	if (thread_node->pad != 0xdead)
		printf("wrong magic number");
 	/* Since tx_thread_delete cannot be called from the current
	   executing thread, a bkgrd thread is used to do the job */
    tx_queue_send(&del_thrd_queue, &thread_node, TX_WAIT_FOREVER);
    tx_thread_suspend ((TX_THREAD*)  thread_node->thread->controlBlock);	
#else
#endif 
}

/* ------------------------------------------------------
 *      Change thread's priority
 * PARAMETERS
 *      thread
 *              pointer to thread handle
 *
 *      new_pri
 *              a valid priority number for ThreadX   
 *
 *
 * DESCRIPTION
 *      This function set the thread priority 
 *
 *      NOTES:
 *              1)      Not POSIX standard 
 *
 *
 * RESULTS
 *              0       always
 *
 *-------------------------------------------------------*/
int xpthread_set_priority (pthread_t thread, int new_pri)
{
   UINT  old_pri;
   UINT  status;

   /* sanity check */
   if (thread == TX_NULL)
   {
      setErrno (EINVAL);
	  return EINVAL;
   }
   if (new_pri <0 || new_pri > 31)
   {
      setErrno (EINVAL);
	  return EINVAL;
   }

   status= tx_thread_priority_change (thread->controlBlock, new_pri, &old_pri);

   if (status == TX_SUCCESS)
	   return 0;
   else
   {
      setErrno (EINVAL);
	  return EINVAL;
   }
}


/* This function MUST be re-entrant */
void posix_thread_start (ULONG thread_input)
{
	((pthread_t) thread_input)->entryFunction ( ((pthread_t) thread_input)->entryArgument);
	pthread_exit ((void*)0);  /* make sure the thread ends gracefully */
}

/* Clean up background thread */
void    clean_thread_entry(ULONG thread_input)
{
pthread_t thread;
posix_thread_created thread_node;
UINT    status;

    for (;;)
    {
        /* Retrieve a message from the queue.  */
        status = tx_queue_receive(&del_thrd_queue, (ULONG*)&thread_node, TX_WAIT_FOREVER);

        if (status == TX_SUCCESS) 
		{
	        thread = thread_node->thread;
	        /*while ((thread)->controlBlock->tx_state != TX_TERMINATED &&
             (thread)->controlBlock->tx_state != TX_COMPLETED)
                tx_thread_sleep (5);*/
		    status = tx_thread_terminate (thread->controlBlock);
			tx_thread_delete ((TX_THREAD*) (thread)->controlBlock);
            remove_from_thread_queue (thread_node);
			free ((void*) (thread)->controlBlock);
		  	if ((thread)->stack != TX_NULL)
			{
				free ((thread)->stack);
			}
			free ((void*) (thread));
		}
    }
}


/*==============================================================================*/
/* helper functions */
/* for simplicity,  add to end of queue */
int add_to_thread_queue (pthread_t thread)
{
	posix_thread_created tail_ptr, node;
TX_INTERRUPT_SAVE_AREA

  node = (posix_thread_created) malloc (16);

  if (node == TX_NULL)
	  return -1;
  node->thread = thread;
  node->pad = 0xdead;

  TX_DISABLE
    /* Place the thread on the list of created threads.  First,
       check for an empty list.  */
    if (_posix_thread_created_ptr)
    {
        tail_ptr = _posix_thread_created_ptr->prev;

        /* Place the new thread in the list.  */
        _posix_thread_created_ptr->prev =  node;
	    tail_ptr ->next =  node;

        /* Setup this thread's created links.  */
        node->prev = tail_ptr;
        node->next = _posix_thread_created_ptr;
    }
    else
    {
     /* The created thread list is empty.  Add thread to empty list.  */
        _posix_thread_created_ptr = node;
        node->next = node;
        node->prev = node;
    }

  TX_RESTORE
  return 0;
}

/* search for the right thread */
pthread_t search_thread (TX_THREAD *thread)
{
  posix_thread_created node;
  pthread_t ret = TX_NULL;
TX_INTERRUPT_SAVE_AREA

   TX_DISABLE
   node = _posix_thread_created_ptr;   
   if (node->pad != 0xdead)
   {
	   ret = TX_NULL;
   }
  if (thread == TX_NULL || _posix_thread_created_ptr == TX_NULL)
  {
	  TX_RESTORE;
	  return TX_NULL;
  }
  /* do a linear search for the thread */
  do {
     if (node->thread->controlBlock == thread) /* found */
	 { 
        ret = node->thread;
		break;
	 }
	 node = node->next;
  } while (node != _posix_thread_created_ptr);
  TX_RESTORE

  return ret;
}

/* search for the right thread */
posix_thread_created search_thread_node (TX_THREAD *pthread)
{
  posix_thread_created node;
TX_INTERRUPT_SAVE_AREA

   TX_DISABLE
   node = _posix_thread_created_ptr;   
   if (node->pad != 0xdead)
   {
	    TX_RESTORE;
	   return TX_NULL;
   }
  if (pthread == TX_NULL || _posix_thread_created_ptr == TX_NULL)
  {
	  TX_RESTORE;
	  return TX_NULL;
  }
  /* do a linear search for the thread */
  do {
     if (node->thread->controlBlock == pthread) /* found */
	 { 
		break;
	 }
	 node = node->next;
  } while (node != _posix_thread_created_ptr);
  TX_RESTORE

  return node;
}

int remove_from_thread_queue (posix_thread_created thread)
{
TX_INTERRUPT_SAVE_AREA
        if (thread == TX_NULL)
			return -1;

        /* Lockout interrupts while the thread is being deleted.  */
        TX_DISABLE


        /* See if the thread is the only one on the list.  */
        if (thread == thread->next)
        {

            /* Only created thread, just set the created list to NULL.  */
			_posix_thread_created_ptr = TX_NULL;
		}
        else
        {
            /* Otherwise, not the only created thread, link-up the neighbors.  */
            (thread -> next) -> prev = thread -> prev;
            (thread -> prev) -> next = thread -> next;

            /* See if we have to update the created list head pointer.  */
            if ( _posix_thread_created_ptr == thread)   
                /* Yes, move the head pointer to the next link. */
                _posix_thread_created_ptr =  thread -> next; 
        }
        /* Restore interrupts.  */
        TX_RESTORE
    	/* free up node */
	free ((void*) thread);
	
   return 0;
}


