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

    FILE....: COMM.CPP
    TYPE....: C+ Functions
    AUTHOR..: David Rowe
    DATE....: 20/11/97
    AUTHOR..: Ron Lee
    DATE....: 10/11/06

    Functions used for PC to DSP communications, which includes passing
    messages to and from the DSP, and transfering information to and from
    FIFOs in the DSP.

         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 "comm.h"
#include "coff.h"
#include "vtcore.h"
#include "timer.h"
#include "mess.h"
#include "wobbly.h"


#ifdef _OPENPRI
 #include "openpri.h"
#endif

#include <unistd.h>
#include <cassert>
#include <cstring>
#include <cmath>


#define FS	8000	// Sample rate 8kHz.
#define WAIT	5	// time out delay in seconds		


//! @c V4PCI tone generator specialisation
class V4PCIToneGen: public ToneGen
{ //{{{
private:

    //! The maximum supported tone frequency.
    static const unsigned int FREQ_MAX = 4000;

    Comm           *m_comm;
    unsigned int    m_cardnum;
    uint16_t        m_mess[PC_LTONEG];


protected:

    State ImplStart()
    { //{{{

	Config                    &config  = GetConfig();
	const Config::Freq::List  &freqs   = config.GetNextFreqs();
	const Config::Cadence     &cadence = config.GetNextCadence();

	// Catch the special case of a hookflash and don't start the V4PCI
	// tone generator.  Unlike the others, sending a new tone while it
	// is running will not terminate the existing tone, but will queue
	// it up and wait for the first to finish.
	if(freqs.size() == 1 && freqs[0].hz == 0 && cadence.onms == 0) {
		mprintf("V4PCIToneGen: hookflash\n");
		return ONESHOT;
	}

	if(freqs.size() > 3)  throw Exception("Too many tones for V4PCIToneGen");

	memset( m_mess + 2, 0, PC_LTONEG - 2 );

	int n = 0;
	for(Config::Freq::List::const_iterator i = freqs.begin(),
					       e = freqs.end(); i != e; ++i, ++n)
	{
		const Config::Freq  &freq = *i;

		if(freq.hz > FREQ_MAX) throw Exception("frequency out of range");
		if(freq.db > 0)        throw Exception("magnitude out of range");

		m_mess[2 + n] = lrint((1<<15) * cos(freq.hz * 2 * M_PI / FS) + 0.5);
		m_mess[5 + n] = lrint((1<<15) * powf(10.0, freq.db/20.0));
	}

	// The V4PCI tone generator is always a one shot.  If the user has
	// requested a continuous tone select an arbitrarily long on-time.
	long samples_on  = (cadence.onms || cadence.offms) ? cadence.onms << 3
							   : 65536;
	long samples     = samples_on + (cadence.offms << 3); // 8 samples per ms.

	m_mess[8]  = samples_on >> 16;
	m_mess[9]  = samples_on & 0xffff;
	m_mess[10] = samples >> 16;
	m_mess[11] = samples & 0xffff;
	m_mess[12] = config.GetUserData<int>(0);

	m_comm->PutMessageVPB( m_cardnum, m_mess );

	return ONESHOT;

    } //}}}

    void ImplStop()
    { //{{{

	uint16_t    mess[PC_LTONEG_RESET] = { PC_LTONEG_RESET,
					      PC_TONEG_RESET,
					      uint16_t(GetConfig().GetUserData<int>(0)) };
	m_comm->PutMessageVPB( m_cardnum, mess );

    } //}}}


public:

    //! Constructor.
    //{{{
    //! @param comm    The @c Comm object for this card.
    //! @param cardnum The number of this card.
    //}}}
    V4PCIToneGen( Comm *comm, unsigned int cardnum )
	: m_comm(comm)
	, m_cardnum(cardnum)
    { //{{{

	m_mess[0]  = PC_LTONEG;
	m_mess[1]  = PC_TONEG;

	memset( m_mess + 2, 0, PC_LTONEG - 2 );

    } //}}}

}; //}}}


Hip *V4PCI_DSP::ms_hip;

V4PCI_DSP::V4PCI_DSP(Comm *comm, VPBREG &reg)
    : HostDSP(false, reg)
{ //{{{
	uint16_t        data = 0;	    // copy of data variable from DSP mem
	Timer           timer;		    // timer object
	uint32_t        a_model;	    // address of model in firmware
	uint16_t        model = 0;	    // model number read from firmware
	unsigned short  eebuf[128]={};	    // buffer for card detail extraction
	char            hex[17] = {"0123456789abcdef"}; // translation table.

	// load a few constants 

	coff_get_address(V4PCI_FIRMWARE, "_model",  &a_model);
	coff_get_address(V4PCI_FIRMWARE, "_asst",   &m_addr_assert);
	coff_get_address(V4PCI_FIRMWARE, "_vpbreg", &m_addr_reg);
	m_len_reg = &m_reg.base - &m_reg.data;

	// init the HIP for the current VPB 
	if( ! ms_hip ) ms_hip = new Hip(m_reg.ddmodel);

	ms_hip->InitVpb(m_reg.base);

	// download firmware and start DSP

	mprintf("About to load firmware...\n");
	coff_load_dsp_firmware(ms_hip, m_reg.cardtypnum, V4PCI_FIRMWARE);
	mprintf("About to run...\n");
	GenericSleep(10);
	ms_hip->DspRun(m_reg.cardtypnum);
	GenericSleep(10);
	mprintf("DSP running...\n");

	//#define DSPRUN
	#ifdef DSPRUN
	mprintf("DSP running...\n");
	while(1);
	#endif

	// download config structure and start DSP

	m_reg.data = 0;
	ms_hip->WriteDspSram(m_reg.cardtypnum, (uint16_t)m_addr_reg,
			    m_len_reg, (uint16_t*)&m_reg);

	// now assert "data" to signal DSP to start its config

	m_reg.data = 1;
	ms_hip->WriteDspSram(m_reg.cardtypnum, (uint16_t)m_addr_reg, 1, (uint16_t*)&m_reg);

	// DSP then fills in certain parameters in the VPBREG
	// structure which we can upload, such as the location
	// of the message fifos in DSP memory.
	// when DSP has finished it's init, it writes a 0 to "data" 

	timer.start();
	for(;;) {
		ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)m_addr_reg, 1, &data);
		if( data ) break;

		if( timer.check_timeout(WAIT) ) {
			CheckForAssert(m_reg.cardnum);
			throw Wobbly(COMM_TIME_OUT_CONFIGURING_DSP);
		}
		usleep( 50 * 1000 );
	}

	// OK, up load reg structure to fill in DSP init info	

	ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)m_addr_reg,
			   m_len_reg, (uint16_t*)&m_reg);

	// check model number in registry matches firmware
	// we do this here to make sure DSP has enough time to set 
	// "model" static

	ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)a_model, 1,
			   (uint16_t*)&model);
	if (model != m_reg.model) {
		mprintf("model = %d  m_reg[i].model = %d\n",model, m_reg.model);
		//throw Wobbly(COMM_FIRMWARE_MODEL_NUMBER);
	}

	// now up perform PC side of message DSP FIFO initialisation

	m_reg.dnmess = new V4PCIDspFifo(ms_hip, m_reg.cardtypnum, m_reg.a_dnmess);
	m_reg.upmess = new V4PCIDspFifo(ms_hip, m_reg.cardtypnum, m_reg.a_upmess);

	mprintf("DSP [%02d] Message FIFOs booted OK\n", m_reg.cardnum);

	// now read card version data, add to struct table

	#ifndef FREEBSD

	// only supported under win32 and linux for now

	ms_hip->EeRead(0,0,64,eebuf);

	m_reg.mdate[0]=hex[((eebuf[50]>>12)& 0x0f)];    //day
	m_reg.mdate[1]=hex[((eebuf[50]>>8)& 0x0f)];
	m_reg.mdate[2]='/';
	m_reg.mdate[3]=hex[((eebuf[50]>>4)& 0x0f)];     //month
	m_reg.mdate[4]=hex[(eebuf[50] & 0x0f)];
	m_reg.mdate[5]='/';
	m_reg.mdate[6]=hex[((eebuf[51]>>12)& 0x0f)];    //year
	m_reg.mdate[7]=hex[((eebuf[51]>>8)& 0x0f)];
	m_reg.mdate[8]=hex[((eebuf[51]>>4)& 0x0f)];
	m_reg.mdate[9]=hex[(eebuf[51] & 0x0f)];
	m_reg.mdate[10]=0;
	// Decode Rev
	m_reg.revision[0]=hex[((eebuf[52]>>12)& 0x0f)];    //card
	m_reg.revision[1]=hex[((eebuf[52]>>8)& 0x0f)];
	m_reg.revision[2]='.';
	m_reg.revision[3]=hex[((eebuf[52]>>4)& 0x0f)];     //Rev.
	m_reg.revision[4]=hex[(eebuf[52] & 0x0f)];
	m_reg.revision[5]=0;
	// Decode SN:
	m_reg.serial_n[0]=hex[((eebuf[54]>>12)& 0x0f)];    //SN:high
	m_reg.serial_n[1]=hex[((eebuf[54]>>8)& 0x0f)];
	m_reg.serial_n[2]=hex[((eebuf[54]>>4)& 0x0f)];
	m_reg.serial_n[3]=hex[(eebuf[54] & 0x0f)];
	m_reg.serial_n[4]=hex[((eebuf[55]>>12)& 0x0f)];    //SN:low
	m_reg.serial_n[5]=hex[((eebuf[55]>>8)& 0x0f)];
	m_reg.serial_n[6]=hex[((eebuf[55]>>4)& 0x0f)];
	m_reg.serial_n[7]=hex[(eebuf[55] & 0x0f)];
	m_reg.serial_n[8]=0;
	#else
	memset(eebuf, 0, sizeof(eebuf));
	#endif

	m_reg.toneg.resize(4, NULL);

	for( int i = 0; i < 4; ++i )
		m_reg.toneg[i] = new V4PCIToneGen( comm, m_reg.cardnum );
} //}}}

void V4PCI_DSP::GetStringFromDSP(unsigned short board, char *s, const char *name)
{ //{{{
	uint32_t    a_sword;	        // address of string in DSP
	uint16_t    sword[MAX_STR];	// string in DSP 16 bit word per char format
	uint16_t    addr;		// address of string

	assert(board < MAX_VPB);
	assert(s != NULL);
	assert(name != NULL);

	// get address of ptr, load ptr and hence string
	coff_get_address(V4PCI_FIRMWARE, name, &a_sword);
	ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)a_sword, 1, &addr);
	ms_hip->ReadDspSram(m_reg.cardtypnum, addr, MAX_STR, sword);

	// convert to string made of 8 bit chars
	int i = 0;
	do {
		s[i] = (char)sword[i];
		i++;
	} while ( s[i-1] && (i < (MAX_STR-1)) );

	s[i] = 0;
} //}}}

void V4PCI_DSP::SetHookState( int port, HookState hookstate )
{ //{{{
	extern Comm *vpb_c;

	switch( hookstate )
	{
	    case VPB_ONHOOK:
	    {
		uint16_t mess1[] = { PC_LCODEC_ONHK, PC_CODEC_ONHK, uint16_t(port) };
		uint16_t mess2[] = { PC_LCODEC_GEN, PC_CODEC_GEN, uint16_t(port), 0x12, 0x1c };

		mprintf("[%d/%d] Set On Hook\n", m_reg.cardnum, port);
		vpb_c->PutMessageVPB(m_reg.cardtypnum, mess1);
		vpb_c->PutMessageVPB(m_reg.cardtypnum, mess2);
		break;
	    }
	    case VPB_OFFHOOK:
	    case VPB_FASTOFFHOOK:
	    {
		uint16_t mess1[] = { PC_LCODEC_GEN, PC_CODEC_GEN, uint16_t(port), 0x12, 0x7c };
		uint16_t mess2[] = { PC_LCODEC_OFFHK, PC_CODEC_OFFHK, uint16_t(port) };

		mprintf("[%d/%d] Set Off Hook\n", m_reg.cardnum, port);
		vpb_c->PutMessageVPB(m_reg.cardtypnum, mess1);
		vpb_c->PutMessageVPB(m_reg.cardtypnum, mess2);
		break;
	    }
	}
} //}}}

static uint16_t silence[V4PCI_LSF];

void V4PCI_DSP::PadTxFrame( int port )
{ //{{{
	size_t remains = m_reg.txdf[port]->HowFull() % V4PCI_LSF;

	if(remains)
		m_reg.txdf[port]->Write(silence, V4PCI_LSF - remains);
} //}}}

void V4PCI_DSP::WaitForTxEmpty( int port )
{ //{{{
	size_t remains;

	while((remains = m_reg.txdf[port]->HowFull()) >= V4PCI_LSF)
		usleep( 10 * 1000 );

	if( remains ) {
		m_reg.txdf[port]->Write(silence, V4PCI_LSF - remains);
		usleep( 20 * 1000 );
	}
} //}}}


void V4PCI_DSP::UNListen( unsigned int port )
{ //{{{
	mprintf("[%d/%d] V4PCI_DSP::UNListen: not implemented\n",
		m_reg.cardnum, port);
} //}}}

void V4PCI_DSP::CheckForAssert(unsigned short board)
{ //{{{
	uint16_t    asst=0;		// asst value read from DSP
	char	    gmsg[MAX_STR];	// assert message from DSP
	char	    gcnd[MAX_STR];	// assert condition from DSP
	char	    gfile[MAX_STR];	// assert file from DSP
	uint16_t    gline;		// assert line number from DSP
	uint32_t    a_gline;	        // address of gline in DSP

	assert( board < MAX_VPB );

	ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)m_addr_assert, 1, &asst);

	if (asst) {
		// if asserted get assert params from DSP

		GetStringFromDSP(board, gmsg, "_gmsg");
		GetStringFromDSP(board, gcnd, "_gcnd");
		GetStringFromDSP(board, gfile, "_gfile");

		coff_get_address(V4PCI_FIRMWARE, "_gline", &a_gline);
		ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)a_gline, 1, &gline);

		// output string length must be < MAX_STR

		assert((strlen(gfile) + 5) < MAX_STR);

		// print debug data
	    #ifdef TMP
		coff_get_address(V4PCI_FIRMWARE, "_ad1", &a_data);
		ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)a_data, 1, &data);
		printf("\nassert data1 = %d [0x%x]\n",data,data);

		coff_get_address(V4PCI_FIRMWARE, "_ad2", &a_data);
		ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)a_data, 1, &data);
		printf("assert data2 = %d [0x%x]\n",data,data);
		coff_get_address(V4PCI_FIRMWARE, "_ad3", &a_data);
		ms_hip->ReadDspSram(m_reg.cardtypnum, (uint16_t)a_data, 1, &data);
		printf("assert data3 = %d [0x%x]\n",data,data);
	    #endif
		// throw wobbly (note only using file and line num)

	    #ifdef Wobbly
	    #undef Wobbly		// turn off macro
		throw Wobbly(COMM_DSP_ASSERT, gfile, gline);
		#include "wobbly.h"	// turn macro back on
	    #else
		throw Wobbly(COMM_DSP_ASSERT, gfile, gline);
	    #endif
	}
} //}}}


Comm::~Comm()
{ //{{{
	mprintf("Destroying Comm\n");

	// free storage used by message FIFOs 
	unsigned int numvpb = m_reg.GetBoardCount();
	for(unsigned int i=0; i < numvpb; ++i)
	{
		VPBREG &vr = m_reg.GetReg(i);

		delete vr.hostdsp;
		delete vr.dnmess;
		delete vr.upmess;
	}
} //}}}

// Initialises the VPBs to the desired configuration, and boots the
// DSP programs in each VPB.  After this function is called,
// communications I/O can proceed between the VPB and the PC.
void Comm::InitBoards()
{ //{{{
	unsigned int numvpb = m_reg.GetBoardCount();

	for(unsigned int i=0; i < numvpb; ++i)
	{
		VPBREG &vr = m_reg.GetReg(i);

		mprintf("[%d/-] Init board %d of type %d\n",
			vr.cardnum, vr.cardtypnum, vr.model);

		switch(vr.model) {
		    case VPB_OSW:
		    case VPB_OPCI:
			new VTCore(vr);
			break;

		    case VPB_V4PCI:
		    case VPB_V4LOG:
			new V4PCI_DSP(this, vr);
			break;

		    #ifdef _OPENPRI
		    case VPB_PRI:
			new OpenPri(vr);
			break;
		    #endif

		    default:
			mprintf("[%d/-] type %d unrecognised\n", vr.cardnum,
								 vr.model);
			throw Wobbly(COMM_UNKNOWN_BOARD_TYPE);
		}
	}
} //}}}

void Comm::PutMessageVPB(unsigned short board, uint16_t *mess)
{ //{{{
	// If the queue fills up and multiple threads are trying to put a
	// message simultaneously then there is no guarantee as to which
	// one will get its message on the queue first.  Locking the retry
	// loop here is little help because if two threads are waiting on
	// the mutex there will be no assurances which will get it first
	// when it is released.  The only good answer is to never let the
	// queue fill to overflowing.  Watch for that with the warning below.

	VPBREG  &vr = m_reg.GetReg(board);

	//mprintf("[%d/-] PutMessageVPB %d\n", board, mess[1]);
	for(;;) {
		if( vr.dnmess->Write(mess,mess[0]) != Fifo::FULL )
			break;

		mprintf("[%d/-] WARNING: DSP fifo full, "
			"messages may be queued out of order\n", board);
		usleep( 5 * 1000 );
	}

	if(vr.ddmodel == DD_PCI)
		dynamic_cast<V4PCI_DSP*>(vr.hostdsp)->CheckForAssert(board);
} //}}}

int Comm::GetMessageVPB(unsigned short board, uint16_t *mess)
{ //{{{
	VPBREG  &vr = m_reg.GetReg(board);

	assert(mess != NULL);

	if(vr.ddmodel == DD_PCI)
		dynamic_cast<V4PCI_DSP*>(vr.hostdsp)->CheckForAssert(board);

	//XXX This code could be dangerous if more than one thread was to call
	//    GetMessageVPB.  Fortunately here it is only ever called by the
	//    MonitorMessageQueue thread.  We can't just wrap a mutex around
	//    the two reads here as other functions may call Read() also.
	//    Fortunately that does not happen in practice either.  If any of
	//    these conditions cease to be true we will need a ReadMsg function
	//    in the DspFifo itself to ensure the length and the message body
	//    can in fact be read atomically.

	// get length of message (first word in message) 
	if( vr.upmess->Read(mess, 1) != VPB_OK )
		return COMM_EMPTY;

	// get rest of message
	if( vr.upmess->Read(&mess[1], mess[0]-1) != VPB_OK )
		throw Wobbly(COMM_DSP_MESSAGE_CORRUPT);
		// this should never happen unless DSP crashed

	return VPB_OK;
} //}}}

void Comm::WaitForMessageVPB(unsigned short board, uint16_t *mess,
			     unsigned short mtype, unsigned short wait)
{ //{{{
	Timer	        timer;
	uint16_t        m[COMM_MAX_MESSDSPPC];	// big enough to hold any mess

	assert(board < MAX_VPB);
	assert(mess != NULL);

	timer.start();
	for(;;) {
		if( GetMessageVPB(board, m) == VPB_OK ) {
			if (m[1] == mtype) {
				memcpy(mess, m, sizeof(word)*m[0]);
				break;
			}
		}
		if( timer.check_timeout(wait) ) {
			VPBREG  &vr = m_reg.GetReg(board);

			if(vr.ddmodel == DD_PCI)
				dynamic_cast<V4PCI_DSP*>(vr.hostdsp)->CheckForAssert(board);

			throw Wobbly(COMM_TIME_OUT_WAITING_FOR_MESS);
		}
		usleep( 5 * 1000 );
	}
} //}}}


VPBREG *Comm::vpbreg(unsigned short board)
{ //{{{
	if( board >= m_reg.GetBoardCount() )
		throw Wobbly(VPBHANDLE_BOARD_NUMBER);

	return &m_reg.GetReg(board);
} //}}}


