/*---------------------------------------------------------------------------*\

    FILE....: GenericLinux.cpp
    TYPE....: C Functions
    AUTHOR..: David Rowe
    DATE....: 14/9/99

    Linux implementation of Generic functions.

         Voicetronix Voice Processing Board (VPB) Software
         Copyright (C) 1999-2007 Voicetronix www.voicetronix.com.au

         This library is free software; you can redistribute it and/or
         modify it under the terms of the GNU Lesser General Public
         License as published by the Free Software Foundation; either
         version 2.1 of the License, or (at your option) any later version.

         This library is distributed in the hope that it will be useful,
         but WITHOUT ANY WARRANTY; without even the implied warranty of
         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
         Lesser General Public License for more details.

         You should have received a copy of the GNU Lesser General Public
         License along with this library; if not, write to the Free Software
         Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>

#include "generic.h"
#include "vpb_ioctl.h"
#include "vpbapi.h"
#include "mess.h"

//#define GPROF
#ifdef GPROF
static int gprof_pthread_create(pthread_t * thread, pthread_attr_t * attr,
                                void * (*start_routine)(void *), void * arg);
#endif

void GenericInitializeCriticalSection(GENERIC_CRITICAL_SECTION *cs) {
	pthread_mutex_t     *pmut;
//        pthread_mutex_t     mut = {0, 0, 0, PTHREAD_MUTEX_RECURSIVE_NP, {0, 0}};
	pthread_mutexattr_t attr;

	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP);

	// create mutex
        pmut = new pthread_mutex_t;
	assert(pmut != NULL);
//	*pmut = mut;
	pthread_mutex_init(pmut,&attr);
	pthread_mutexattr_destroy(&attr);
	cs->v = (void*)pmut;
//	mprintf("GICS: cs->v [0x%x]\n",cs->v);
}

void GenericDeleteCriticalSection(GENERIC_CRITICAL_SECTION *cs) {
	int ret;

	// destroy mutex
	ret = pthread_mutex_destroy((pthread_mutex_t*)cs->v);
	assert(ret == 0);
	delete (pthread_mutex_t*)cs->v;
}

void GenericEnterCriticalSection(GENERIC_CRITICAL_SECTION *cs)  {
	int ret;
	ret = pthread_mutex_lock((pthread_mutex_t*)cs->v);
	assert(ret == 0);
}

void GenericLeaveCriticalSection(GENERIC_CRITICAL_SECTION *cs)  {
	int ret;
	ret = pthread_mutex_unlock((pthread_mutex_t*)cs->v);
	assert(ret == 0);
}

// do nothing in Linux
int GenericDeviceIoControl(
	void		*hndFile,	// Handle to device
        long	        IoctlCode,      // IO Control code for Read
        void		*InBuffer,      // Buffer to driver.
	long		InBufferSize,
        void		*OutBuffer,     // Buffer from driver
	long		OutBufferSize,
	unsigned long	*BytesReturned,	// no. bytes returned from driver
	void		*Overlapped	// not used, 
)
{
	(void)hndFile;
	(void)IoctlCode;
	(void)InBuffer;
	(void)InBufferSize;
	(void)OutBuffer;
	(void)OutBufferSize;
	(void)BytesReturned;
	(void)Overlapped;
	return 0;
}

// do nothing in Linux
void *GenericOpenMk0Driver()
{
	return NULL;
}

// do nothing in Linux
void *GenericOpenMk1Driver()
{
	return NULL;
}

// do nothing in Linux
int GenericCloseHandle(void *hndFile) {
	(void)hndFile;
	return 0;
}

// return time in ms since system startup
unsigned long GenerictimeGetTime() {
	struct timeval tval;
	gettimeofday(&tval, NULL);
	return ((unsigned long)(tval.tv_sec)*1000 +
		(unsigned long)(tval.tv_usec)/1000);
}

// determies OS, returns -1 for fail

int GenericGetOS() {
	return GET_OS_LINUX;
}

// sleep for ms milliseconds
void GenericSleep(unsigned int ms)
{
	long ms_l = ms;
	struct timespec ts;
	ts.tv_sec = ms_l/1000;
	ts.tv_nsec = (ms_l-ts.tv_sec*1000)*1000000l;
	nanosleep(&ts, NULL);
}

void Generic_beginthread(void(*func)(void*), int stack, void *data) {
	(void)stack;

	pthread_t	thread;

	#ifdef GPROF
	int ret = gprof_pthread_create(&thread, NULL, (void*(*)(void*))func, data);
	#else
	int ret = pthread_create(&thread, NULL, (void*(*)(void*))func, data);
	#endif

	//mprintf("generic_beginthread returned %d\n", ret);
	assert(ret == 0);
}

void Generic_endthread()
{
	pthread_detach(pthread_self());
}

// do nothing for initial implementation
void GenericSetThreadPriorityHigh() {
}

// do nothing for initial implementation
void GenericSetThreadPriorityNormal() {
}

// used for Linux device driver

int Generic_open() {
	return open("/dev/vpb0",O_RDWR);
}

int Generic_close(int fd) {
	return close(fd);
}

int Generic_pci_dsp_reset(int fd, unsigned short board) {
	VPB_DATA params;
	int      ret;

	params.port = 0;
	params.data = NULL;
	params.length = 0;
	params.dsp_addr = 0;
	params.pci_num = board;
	ret = ioctl(fd, VPB_IOC_PCI_DSP_RESET, &params);

	return ret;
}

int Generic_pci_dsp_run(int fd, unsigned short board) {
	VPB_DATA params;
	int      ret;

	params.port = 0;
	params.data = NULL;
	params.length = 0;
	params.dsp_addr = 0;
	params.pci_num = board;
	ret = ioctl(fd, VPB_IOC_PCI_DSP_RUN, &params);

	return ret;
}

int Generic_pci_block_read(
			   int fd,
			   unsigned short board,
			   unsigned short addr,
			   unsigned short length,
			   unsigned short *buf)
{
	VPB_DATA params;

	// set up ioctlparams
	params.port = 0;
	params.length = length;
	params.dsp_addr = addr;
	params.pci_num = board;
	params.data = buf;
//	mprintf("Generic_pci_block_read: data [0x%x] length [0x%x]\n",params.data,length);
	return ioctl(fd, VPB_IOC_PCI_BLOCK_READ, &params);
}

int Generic_pci_block_write(
			    int fd,
			    unsigned short board,
			    unsigned short addr,
			    unsigned short length,
			    unsigned short *buf)
{
	VPB_DATA params;

	// set up ioctlparams
	params.port = 0;
	params.length = length;
	params.dsp_addr = addr;
	params.pci_num = board;
	params.data = buf;
//	mprintf("Generic_pci_block_write: data [0x%x] length [0x%x]\n",params.data,length);

	return ioctl(fd, VPB_IOC_PCI_BLOCK_WRITE, &params);
}

int Generic_pci_block_iicread(
			   int fd,
			   unsigned short board,
			   unsigned short addr,
			   unsigned short length,
			   unsigned short *buf)
{
	VPB_DATA params;

	// set up ioctlparams
	params.port = 0;
	params.length = length;
	params.dsp_addr = addr;
	params.pci_num = board;
	params.data = buf;
//	mprintf("Generic_pci_block_iicread: data [0x%x]\n",params.data);

	return ioctl(fd, VPB_IOC_PCI_BLOCK_IICREAD, &params);
}

int Generic_pci_block_iicwrite(
			    int fd,
			    unsigned short board,
			    unsigned short addr,
			    unsigned short length,
			    unsigned short *buf)
{
	VPB_DATA params;

	// set up ioctlparams
	params.port = 0;
	params.length = length;
	params.dsp_addr = addr;
	params.pci_num = board;
	params.data = buf;
	mprintf("Generic_pci_block_iicwrite: data [%p]\n", params.data);

	return ioctl(fd, VPB_IOC_PCI_BLOCK_IICWRITE, &params);
}


int Generic_pci_block_eeread(
			   int fd,
			   unsigned short board,
			   unsigned short addr,
			   unsigned short length,
			   unsigned short *buf)
{
	VPB_DATA params;

	// set up ioctlparams
	params.port = 0;
	params.length = length;
	params.dsp_addr = addr;
	params.pci_num = board;
	params.data = buf;
	mprintf("Generic_pci_block_eeread: data [%p]\n", params.data);

	return ioctl(fd, VPB_IOC_PCI_BLOCK_EEREAD, &params);
}

//XXX
#if 0
// determines what type of Voicetronix PCI card we are using, base on
// which kernel mode drivers are installed.  We assume that the user
// has installed the correct kernel mode driver :-)

int GenericGetCardType()
{
	int fd;
	int got_ol = 0;

	// test for presence of V4PCI (OpenLine4)
	fd = open("/dev/vpb0", O_RDWR);
	if (fd != -1) {
		close(fd);
		got_ol = 1;
	}
	fd = open("/dev/vpb1", O_RDWR);
	if (fd != -1) {
		close(fd);
		got_ol = 1;
	}
	return got_ol ? VPB_V4PCI : -1;
}

// Asks kernel mode driver how many cards are in system.  This function 
// should be called before driver is opened, as it opens then quickly closes
// the kernel mode driver.

int GenericGetNumCards(int vpb_model)
{
	int fd, ret;
	int numCards = 0;

	if( vpb_model == VPB_V4PCI || vpb_model == VPB_V4LOG ) {
		fd = open("/dev/vpb0", O_RDWR);
		if (fd == -1) {
			fprintf(stderr,
				"\nERROR opening OpenLine4/OpenLog4 kernel mode "
				"driver /dev/vpb0!\n"
				"- Check to see if vpb.o is installed using "
				"'lsmod'.\n"
				"- Check that device node /dev/vpb0 "
				"exists and /dev/vpb0 permissions are correct.\n"
				"- Is an OpenLine4/OpenLog4 card installed?\n"
				"- Is the 'VPB_MODEL' environment variable set for "
				"your card (Optional)?\n"
				"\n");
			exit(1);
		}

		VPB_DATA params;

		// set up ioctlparams
		params.port = 0;
		params.length = 0;
		params.dsp_addr = 0;
		params.pci_num = 0;
		params.data = &numCards;
		mprintf("GenericGetNumCards: data [0x%x]\n",params.data);

		ret = ioctl(fd, VPB_IOC_PCI_GET_NUM_CARDS, &params);
		close(fd);
	}
        return numCards;
}

int GenericGetNumCards(int ** pcards)
{
	int fd, ret;
	unsigned short numOL = 0;
	VPB_DATA olp;
	int	*cards;

	/* Check for OL4's */
	fd = open("/dev/vpb0", O_RDWR);
	if (fd != -1) {
		// set up ioctlparams
		olp.port = 0;
		olp.length = 0;
		olp.dsp_addr = 0;
		olp.pci_num = 0;
		olp.data = &numOL;

		ret = ioctl(fd, VPB_IOC_PCI_GET_NUM_CARDS, &olp);
		close(fd);
		mprintf("GetNumCards: Found [%d] OL's\n",numOL);
	}
	mprintf("GetNumCards: OL[%d]\n", numOL);
	if (numOL > 0){
		*pcards = new int[numOL];
		cards = *pcards;
		for(int i=0; i < numOL; ++i) cards[i] = VPB_V4PCI;
	}
	return numOL;
}
#endif

/* 
 * pthread_create wrapper for gprof compatibility
 *
 * needed headers: <pthread.h>
 *                 <sys/time.h>
 */

typedef struct wrapper_s
{
    void * (*start_routine)(void *);
    void * arg;

    pthread_mutex_t lock;
    pthread_cond_t  wait;

    struct itimerval itimer;

} wrapper_t;

/*

  DR: this code below is a mod to make gprof work with multithreaded code.
  Ref: http://sam.zoy.org/doc/programming/gprof.html

*/
#ifdef GPROF

static void * wrapper_routine(void *);

/* Same prototype as pthread_create; use some #define magic to
 * transparently replace it in other files */
int gprof_pthread_create(pthread_t * thread, pthread_attr_t * attr,
                         void * (*start_routine)(void *), void * arg)
{
    wrapper_t wrapper_data;
    int i_return;

    /* Initialize the wrapper structure */
    wrapper_data.start_routine = start_routine;
    wrapper_data.arg = arg;
    getitimer(ITIMER_PROF, &wrapper_data.itimer);
    pthread_cond_init(&wrapper_data.wait, NULL);
    pthread_mutex_init(&wrapper_data.lock, NULL);
    pthread_mutex_lock(&wrapper_data.lock);

    /* The real pthread_create call */
    i_return = pthread_create(thread, attr, &wrapper_routine,
                                            &wrapper_data);

    /* If the thread was successfully spawned, wait for the data
     * to be released */
    if(i_return == 0)
    {
        pthread_cond_wait(&wrapper_data.wait, &wrapper_data.lock);
    }

    pthread_mutex_unlock(&wrapper_data.lock);
    pthread_mutex_destroy(&wrapper_data.lock);
    pthread_cond_destroy(&wrapper_data.wait);

    return i_return;
}

/* The wrapper function in charge for setting the itimer value */
static void * wrapper_routine(void * data)
{
    /* Put user data in thread-local variables */
    void * (*start_routine)(void *) = ((wrapper_t*)data)->start_routine;
    void * arg = ((wrapper_t*)data)->arg;

    /* Set the profile timer value */
    setitimer(ITIMER_PROF, &((wrapper_t*)data)->itimer, NULL);

    /* Tell the calling thread that we don't need its data anymore */
    pthread_mutex_lock(&((wrapper_t*)data)->lock);
    pthread_cond_signal(&((wrapper_t*)data)->wait);
    pthread_mutex_unlock(&((wrapper_t*)data)->lock);

    /* Call the real function */
    return start_routine(arg);
}

#endif

