/* mt/cb/mt_vxw.c */

/* Copyright (C) 1999-2000, RSA Security Development Pty Ltd.
 *
 * This file shall only be used to demonstrate how to interface to an
 * RSA Security Development Pty Ltd licensed development product.
 *
 * You have a royalty-free right to use, reproduce and distribute this 
 * demonstration file, provided that you agree that RSA Security 
 * Development Pty Ltd has no warranty, implied or otherwise, or
 * liability for this demonstration file.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* standard VxWorks header files */
#include <vxWorks.h>
#include <taskLib.h>
#include <semLib.h>

#include <kernelLib.h>        /* for kernelTimeSlice */
#include <sysLib.h>           /* for sysClk{Get,Set}Rate */

#include "sslc.h"

#ifndef THREAD_STACK_SIZE
#define THREAD_STACK_SIZE	(16*1024)
#endif

/* controls the thread scheduling parameters */
/* #define SET_CLOCK_RATE          1000 */
#define SET_KERNEL_TIME_SLICE      1 

#ifndef HEADER_MT_CB_MT_VXW_H 
typedef unsigned long APP_THREAD_ID;
#endif /* HEADER_MT_CB_MT_VXW_H */

static SEM_ID *lock_cs=NULL;
static SEM_ID reap_sem=0;

static long *lock_count=NULL;
static int num_locks=0;
static int num_threads=0;

static int show_trace=0;

#ifdef SET_CLOCK_RATE
static int oldClockRate=0;
#endif /* SET_CLOCK_RATE */

/* we "wrap" the application function to catch the return from the 
 * main function to handle separately here - so we have to know what 
 * to call and with what arg
 */
struct thread_wrapper_args {
    void (*call_func)(void *arg);    /* func to call */
    void *call_func_arg;             /* arg to pass to func */
};

static struct thread_ctx_t {
    APP_THREAD_ID              thread_id;
    struct thread_wrapper_args startup_args;
} *thread_ctx=NULL;
static int N_thread_ctx=0;

static void thread_wrapper(arg)
struct thread_wrapper_args *arg;
	{
	if (arg!=NULL)
	  arg->call_func(arg->call_func_arg);

	/* when the thread exists we release the reap semaphore */
	semGive(reap_sem);
	}

int APP_locking_cb_add()
	{
	int i;

	num_locks=CRYPTO_get_num_locks()+1;

#ifdef APP_LOCK_TRACE
        if (show_trace)
	    fprintf(stderr,"APP_locking_cb_add: num_locks %d\n",                                                            num_locks);
#endif /* APP_LOCK_TRACE */

	/* create a table to hold a lock counter (stats) and the 
	 * locks themselves (semaphores)
	 */
	lock_count=(long *)malloc(sizeof(long)*num_locks);
	lock_cs=(SEM_ID *)malloc(sizeof(SEM_ID)*num_locks);

	/* initialise each lock */
	for (i=0; i<num_locks; i++)
		{
		lock_count[i]=0;
		lock_cs[i]=semMCreate(0x0);
		}

	/* register the callbacks now */
	CRYPTO_set_id_cb((unsigned long (*)())APPCB_thread_id);
	CRYPTO_set_locking_cb((int (*)())APPCB_locking_cb);
	return(1);
	}

int APP_locking_cb_delete()
	{
	int i;

	/* clear the callback */
	/* note: we leave in the id_callback for the error stack */
	CRYPTO_set_locking_cb(NULL);

	/* destroy all the locks (semaphores) */
	for (i=0; i<num_locks; i++)
		semDelete(lock_cs[i]);

	/* free the tables */
	free(lock_cs);
	free(lock_count);
	lock_cs=NULL;
	lock_count=NULL;
	return(1);
	}

unsigned long APPCB_thread_id()
	{
	unsigned long ret;

	ret=(unsigned long)taskIdSelf();
	return(ret);
	}

int APPCB_locking_cb(mode,lockid,file,line)
int mode;
int lockid;
char *file;
int line;
	{

#ifdef APP_LOCK_TRACE
        if (show_trace)
	    fprintf(stderr,"thread=%4ld mode=%s lock=%s %d(%s) %s:%d\n",
		CRYPTO_thread_id(),
		(mode&CRYPTO_LOCK)?"l":"u",
		(lockid&CRYPTO_READ)?"r":"w",
		lockid,CRYPTO_get_lock_name(lockid),file,line);
#endif /* APP_LOCK_TRACE */

	if (mode & CRYPTO_LOCK)
		{
		semTake(lock_cs[lockid],WAIT_FOREVER);
		lock_count[lockid]++;
		}
	else
		{
		semGive(lock_cs[lockid]);

#ifndef SET_KERNEL_TIME_SLICE
		/* to maximise concurrency we "yield" at unlock time 
		 * as a rather simplistic way of handle task scheduling
		 */
		taskDelay(0); 
#endif /* SET_KERNEL_TIME_SLICE */

		}

#ifdef APP_LOCK_TRACE
        if (show_trace)
	    fprintf(stderr,"thread=%4ld mode=%s lock=%s %d(%s) %s:%d DONE\n",
		CRYPTO_thread_id(),
		(mode&CRYPTO_LOCK)?"l":"u",
		(lockid&CRYPTO_READ)?"r":"w",
		lockid,CRYPTO_get_lock_name(lockid),file,line);
#endif /* APP_LOCK_TRACE */
	return(1);
	}

int APP_thread_initialize(thread_count) 
int thread_count; 
{
	int ret=0;

	/* 
	 * This semaphore is used to implement the unix style wait(2) 
	 * semantics via a semaphone for task cleanup
	 * 
	 * Each thread calls semGive() when it returns from the function
	 * (via thread_wrapper) and we call semTake() in the finalize 
	 * which will block until all the threads have returned
	 *
	 */
	reap_sem=semCCreate(SEM_Q_FIFO,0);

#ifdef SET_CLOCK_RATE
        oldClockRate=sysClkRateGet();
        sysClkRateSet(SET_CLOCK_RATE);
#endif
#ifdef SET_KERNEL_TIME_SLICE
        kernelTimeSlice(SET_KERNEL_TIME_SLICE);
#endif

        thread_ctx=(struct thread_ctx_t *)malloc(sizeof(struct thread_ctx_t)*
	                                            thread_count);
	if (thread_ctx!=NULL) 
	        {
		N_thread_ctx=thread_count;
		memset(thread_ctx,0,sizeof(struct thread_ctx_t)*thread_count);
		ret=1;
		}
	num_threads=0;

	return(ret);
}

int APP_thread_create(void (*func)(void *arg),void *func_arg,
                            APP_THREAD_ID *thread_id)
{
        APP_THREAD_ID tid;
	struct thread_wrapper_args *args;

	/* we have to keep the startup args between thread launch and 
	 * shutdown here as we "rewrite" them to call the wrapper
	 */
	if (num_threads<N_thread_ctx)
	    {
	    args=&(thread_ctx[num_threads].startup_args);
	    }
	else
	    {
	    /* malloc on the fly - and this isn't cleaned up at all! */
	    args=(struct thread_wrapper_args *)
		        malloc(sizeof(struct thread_wrapper_args));
	    }

	args->call_func=func;
	args->call_func_arg=func_arg;

        /* name, priority, options, stacksize, func, arg1, arg2, ... arg10 */
	tid=taskSpawn(NULL,250,0,THREAD_STACK_SIZE,
	        (FUNCPTR)thread_wrapper,(int)args,0,0,0,0,0,0,0,0,0);
	if (((int)tid) == ERROR)
	    return(0);

	/* track the thread id in case we want to use it elsewhere */
	if (num_threads<N_thread_ctx)
	    thread_ctx[num_threads].thread_id=tid;

	num_threads++;

	/* copy the thread id back if the caller wanted it */
	if (thread_id!=NULL)
		(*thread_id)=tid;

        return(1);
}

int APP_thread_finalize(thread_number)
int thread_number;
{
        int i;

#ifdef APP_LOCK_TRACE
	if (show_trace)
	    fprintf(stderr,"APP_thread_finalize: reaping\n");
#endif /* APP_LOCK_TRACE */

	if (thread_number>num_threads)
	        thread_number=num_threads;

	/* wait for each thread to have returned ... */
	for (i=0; i<thread_number; i++)
		{
		semTake(reap_sem,WAIT_FOREVER);
		}

	/* remove the reap semaphore */
        semDelete(reap_sem);
	reap_sem=0;

        /* finished with thread table */
        if (thread_ctx!=NULL)
                {
                free(thread_ctx);
                thread_ctx=NULL;
                }
        num_threads=0;

#ifdef SET_CLOCK_RATE
        sysClkRateSet(oldClockRate);
#endif
#ifdef SET_KERNEL_TIME_SLICE
        kernelTimeSlice(0);
#endif

#ifdef APP_LOCK_TRACE
	if (show_trace)
	    fprintf(stderr,"APP_thread_finalize: done\n");
#endif /* APP_LOCK_TRACE */
	
	return(1);
}

int APP_thread_show_stats()
        {
	int i;

	for (i=1; i<num_locks; i++)
		fprintf(stderr,"%8ld:%s\n",lock_count[i],
			CRYPTO_get_lock_name(i));
	return(1);
	}

int APP_thread_trace(val)
int val;
        {
	show_trace=val;
#ifdef APP_LOCK_TRACE
	return(1);
#else /* !APP_LOCK_TRACE */
	return(0);
#endif /* APP_LOCK_TRACE */
	}



