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

	FILE....: vtcore.cpp
	AUTHOR..: Ben Kramer, Ron Lee
	DATE....: 01/12/2005
	DESC....: Main part of the vtcore driver for vtcore kernel module cards


         Voicetronix Voice Processing Board (VPB) Software
         Copyright (C) 1999 - 2008 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 <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>

#include "vtcore.h"
#include "vtcore_ioctl.h"
#include "apifunc.h"
#include "playrec.h"
#include "mess.h"
#include "vpbdial.h"
#include "hostvox.h"
#include "v4logagc.h"
#include "wobbly.h"

#include "dtmf.h"

extern "C" {
  #include "toned.h"
}

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>


/* VPB Driver stuff */
#define ALAW_CODES	256
#define MAX_BOARDS	12
#define FRAME 		8
#define PRI_PORTS	32

// AUDIO_SLEEP is ms to sleep between reads/writes from/to fifo, should
//    be set to around half the amount (ms) of audio read from the kernel
//    driver at once.
// NBUF is the number of _samples_ we read/write from/to kernel driver
#ifdef SOFTBRIDGE
#define AUDIO_SLEEP	3
#define NBUF		40
#else
#define AUDIO_SLEEP	20
#define NBUF		(8*AUDIO_SLEEP*2)
#endif

#define TDLOOP		(NBUF/40)
#define HOSTDSP_SLEEP	5

#define MAX_BALS	10


static int  pci_fd;
static char pcidev[] = "/dev/vt0";

/* Channel state defines */
#define CH_IDLE		0
#define CH_RING		1
#define CH_PROCEEDING	2
#define CH_RINGING	3
#define CH_SETUP	4
#define CH_NEEDANS	5
#define CH_NEEDHANG	6
#define CH_HUNGEDUP	7
#define CH_NEEDRING	8
#define CH_CONNECTED	10


#if 0
//! ProSLIC hardware tone generator implementation.
//{{{
//! The ProSLIC hardware supports dual tone generation.  If more than two
//! frequency components are required, then this implementation will fall
//! back to using the host based tone generator automatically.
//}}}
class ProslicToneGen: public VT::ToneGen
{ //{{{
private:

public:

    void SetTone( unsigned num, unsigned freq, unsigned mag )
    { //{{{
    } //}}}

    void RemoveTone( unsigned num )
    { //{{{
    } //}}}

    void SetCadence( unsigned mson, unsigned msoff )
    { //{{{
    } //}}}

}; //}}}
#else
typedef HostToneGen ProslicToneGen; //XXX
#endif

static void vtcore_set_ringing(uint8_t board, uint8_t ch, int rings)
{ //{{{
	VT_DATA  mess = { board:   board,
			  channel: ch,
			  length:  unsigned(rings),
			  data:    NULL
			};

	if (rings < 0 || rings > 255)
		mprintf("[%u/%u] Set ringing failed! Requires 0 <= rings"
			" <= 255, but rings is %d\n", board, ch, rings);
	else if( ioctl(pci_fd, VT_IOC_FXS_RING, &mess) < 0 )
		mprintf("[%u/%u] Set ringing failed!\n",board, ch);
	else
		mprintf("[%u/%u] Set ringing to %d\n",board, ch, rings);
} //}}}

static int vtcore_sethook(uint8_t board, uint8_t port, unsigned state)
{ //{{{
	VT_DATA  mess = { board:   board,
			  channel: port,
			  length:  state,
			  data:    NULL
			};
	return ioctl(pci_fd, VT_IOC_HOOK, &mess);
} //}}}

static int vtcore_echocan_on(int fd, uint8_t board, uint8_t port)
{ //{{{
	VT_DATA  mess = { board:   board,
			  channel: port,
			  length:  0,
			  data:    NULL
			};
	return ioctl(fd, VT_IOC_ECHOCAN_ON, &mess) < 0 ? -1 : 0;
} //}}}

static int vtcore_echocan_off(int fd, uint8_t board, uint8_t port)
{ //{{{
	VT_DATA  mess = { board:   board,
			  channel: port,
			  length:  0,
			  data:    NULL
			};
	return ioctl(fd, VT_IOC_ECHOCAN_OFF, &mess) < 0 ? -1 : 0;
} //}}}

static int vtcore_listenon(int fd, uint8_t board, uint16_t *m)
{ //{{{
	VT_DATA  msg2 = { board:   uint8_t(m[3]),
			  channel: uint8_t(m[4]),
			  length:  0,
			  data:    NULL
			};
	VT_DATA  mess = { board:   board,
			  channel: uint8_t(m[2]),
			  length:  sizeof(VT_DATA),
			  data:    &msg2
			};
	return ioctl(fd, VT_IOC_LISTEN, &mess) < 0 ? -1 : 0;
} //}}}

static void vtcore_set_rec_gain(VPBREG &v, uint8_t port, unsigned gain)
{ //{{{
	VT_DATA  mess = { board:   uint8_t(v.cardtypnum),
			  channel: port,
			  length:  gain,
			  data:    NULL
			};
	ioctl(pci_fd, VT_IOC_SET_RECGAIN, &mess);
} //}}}

static void vtcore_set_play_gain(VPBREG &v, uint8_t port, unsigned gain)
{ //{{{
	VT_DATA  mess = { board:   uint8_t(v.cardtypnum),
			  channel: port,
			  length:  gain,
			  data:    NULL
			};
	ioctl(pci_fd, VT_IOC_SET_PLAYGAIN, &mess);
} //}}}


// Reads commands sent to "DSP" and processes them.
bool VTCore::ProcessCommands()
{ //{{{
	uint16_t     mess[COMM_MAX_MESSPCDSP];
	vtcore_chan *chans   = (vtcore_chan*)m_reg.chans;
	int          cardnum = m_reg.cardtypnum;

	if(m_reg.dnmess->Read(&mess[0], 1) == Fifo::EMPTY)
		return true;  // DSP idle

	if(m_reg.dnmess->Read(&mess[1], mess[0]-1) != Fifo::OK) {
		mprintf("[%d/-] Error on second read of fifo!\n",m_reg.cardnum);
		//XXX We actually may be totally fubar if this has happened...
		//    There is no guarantee we can regain fifo consistency.
		return true;
	}

	int ch = mess[2];
	//mprintf("message bytes[%d]: on ch[%d]\n",mess[0],mess[2]);
	switch(mess[1]) {
	  #if 0
	    // API uses Listens instead AKA SBRIDGE
	    case PC_SPI_BRIDGE:
		assert(mess[0] == PC_LSPI_BRIDGE);
		mprintf("[%d/%d] Bridge on\n",m_reg.cardnum,ch);
		vtcore_bridgeon(pci_fd, cardnum, mess);
		break;

	    case PC_SPI_BRIDGE_OFF:
		assert(mess[0] == PC_LSPI_BRIDGE_OFF);
		mprintf("[%d/%d] Bridge off\n",m_reg.cardnum,ch);
		vtcore_bridgeoff(pci_fd,mess);
		break;
	  #endif

	    case PC_SPI_SBRIDGE:
		assert(mess[0] == PC_LSPI_SBRIDGE);
		mprintf("[%d/%d] Listen on\n",m_reg.cardnum,ch);
		vtcore_listenon(pci_fd, cardnum, mess);
		break;

	    case PC_SPI_SBRIDGE_OFF:
		assert(mess[0] == PC_LSPI_SBRIDGE_OFF);
		mprintf("[%d/%d] Listen off\n",m_reg.cardnum,ch);
		UNListen(mess[2]);
		break;


	    case PC_CODEC_RECGAIN:
		assert(mess[0] == PC_LCODEC_RECGAIN);
		// TX gain
		mprintf("[%d/%d] Setting record gain %3d\n",
			m_reg.cardnum,mess[2],mess[3]);
		vtcore_set_rec_gain(m_reg, mess[2], mess[3]);
		break;

	    case PC_CODEC_PLAYGAIN:
		assert(mess[0] == PC_LCODEC_PLAYGAIN);
		mprintf("[%d/%d] Setting play gain   %3d\n",
			m_reg.cardnum,mess[2],mess[3]);
		vtcore_set_play_gain(m_reg, mess[2], mess[3]);
		break;

	    case PC_CODEC_BREAK:
		assert(mess[0] == PC_LCODEC_BREAK);
		mprintf("[%d/%d] Codec Break (hook flash) %d\n",
			m_reg.cardnum, ch, mess[3]);
		if( vtcore_sethook(cardnum, ch, mess[3]) < 0 )
			mprintf("[%d/%d] Codec Break: ERROR: %m\n",
				m_reg.cardnum, ch);
		break;

	    case PC_LOOPBACK_ON:
		assert(mess[0] == PC_LLOOPBACK_ON);
		mprintf("[%d/%d] LoopBack On!\n",m_reg.cardnum,ch);
		mprintf("[%d/-] Not supported yet\n",m_reg.cardnum);
		break;

	    case PC_LOOPBACK_OFF:
		assert(mess[0] == PC_LLOOPBACK_OFF);
		mprintf("[%d/%d] LoopBack Off!\n",m_reg.cardnum,ch);
		mprintf("[%d/-] Not supported yet\n",m_reg.cardnum);
		break;

	    case PC_HOSTECHO_ON:
		assert(mess[0] == PC_LHOSTECHO_ON);
		mprintf("[%d/%d] Echo canceller enabled \n",m_reg.cardnum,ch);
		vtcore_echocan_on(pci_fd, cardnum, ch);
		break;

	    case PC_HOSTECHO_OFF:
		assert(mess[0] == PC_LHOSTECHO_OFF);
		mprintf("[%d/%d] Echo canceller disabled \n",m_reg.cardnum,ch);
		vtcore_echocan_off(pci_fd, cardnum, ch);
		break;

	  #if 0
	    case PC_HOSTECHO_OPT:
		assert(mess[0] == PC_LHOSTECHO_OFF);
		/* none at the moment! */
		mprintf("[%d/%d] Echo canceller - no optimization at the moment \n",
			m_reg.cardnum,ch);
		mprintf("[%d/-] Not supported yet\n",m_reg.cardnum);
		break;
	  #endif

	    case PC_TONED_ST:
		// call progress detector messages 
		toned_add_tone(m_reg.toned[ch], mess);
		break;

	    case PC_TONED_ST_DEL:
		toned_del_tone(m_reg.toned[ch], mess);
		break;

	    case PC_CODEC_GEN:
	    {
		if( m_reg.model != VPB_OSW )
			throw VpbException("set_codec_reg: Not an OpenSwitch card");

		VT_REG_RW   bal  = { 0, uint8_t(mess[4]) };
		VT_DATA     data = { board:   uint8_t(m_reg.cardtypnum),
				     channel: uint8_t(ch),
				     length:  sizeof(VT_REG_RW),
				     data:    &bal
				   };
		switch(mess[3]) {
		    case 0x32: bal.reg = 1; break;
		    case 0x3a: bal.reg = 2; break;
		    case 0x42: bal.reg = 3; break;

		    default:
			mprintf("[%d/%d] ERROR: Unsupported codec reg %#x",
				m_reg.cardnum, ch, mess[3]);
			return false;
		}
		ioctl(pci_fd, VT_IOC_CHAN_BALANCE_SET, &data);
		break;
	    }

	    case PC_TONED_DEBUG:
		assert(mess[0] == PC_LTONED_DEBUG);
		toned_start_logging(m_reg.toned[ch], mess);
		break;

	    case PC_VOX_UPDATE:
		assert(mess[0] == PC_LVOX_UPDATE);
		if(chans[ch].vox_states != NULL){
			hostvox_update_levels(chans[ch].vox_states, mess);
		}
		break;

	    case PC_RING_ON:
		if(chans[ch].interface != VPB_FXS){
			mprintf("[%d/-] ERROR: RING_ON on a FXO port\n",
				m_reg.cardnum);
			break;
		}
		vtcore_set_ringing(cardnum, ch, mess[3]);
		break;

	    case PC_RING_OFF:
		if(chans[ch].interface != VPB_FXS){
			mprintf("[%d/-] ERROR: RING_OFF on a FXO port\n",
				m_reg.cardnum);
			break;
		}
		vtcore_set_ringing(cardnum, ch, 0);
		break;

	    default:
		mprintf("[%d/-] VTCore DSP message %d unrecognised\n",
			m_reg.cardnum, mess[1]);
		//assert(0);
		break;
	}
	return false;
} //}}}

void VTCore::CheckMsgQ()
{ //{{{
	uint16_t    rmess[DSP_LDROP];
	char        msgq[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
	VT_DATA     mess  = { board:   uint8_t(m_reg.cardtypnum),
			      channel: 0,
			      length:  sizeof(msgq),
			      data:    msgq
			    };

	while(ioctl(pci_fd, VT_IOC_READ_MSGQ, &mess) == 0) {
		switch( msgq[1] )
		{
		    case DSP_CODEC_RING:
			mprintf("[%d/%d] Ring ON\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LCODEC_RING;
			rmess[1] = DSP_CODEC_RING;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_RING_OFF:
			mprintf("[%d/%d] Ring OFF\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LRING_OFF;
			rmess[1] = DSP_RING_OFF;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_LOOP_ONHOOK:
			mprintf("[%d/%d] LOOP ON hook\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LLOOP_ONHOOK;
			rmess[1] = DSP_LOOP_ONHOOK;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_LOOP_OFFHOOK:
			mprintf("[%d/%d] LOOP OFF hook\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LLOOP_OFFHOOK;
			rmess[1] = DSP_LOOP_OFFHOOK;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_CODEC_BKEN:
			mprintf("[%d/%d] LOOP flash completed\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LCODEC_BKEN;
			rmess[1] = DSP_CODEC_BKEN;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_LOOP_POLARITY:
			mprintf("[%d/%d] LOOP POLARITY\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LLOOP_POLARITY;
			rmess[1] = DSP_LOOP_POLARITY;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_DROP:
			mprintf("[%d/%d] LOOP DROP\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LDROP;
			rmess[1] = DSP_DROP;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_LOOP_NOBATT:
			mprintf("[%d/%d] LOOP No battery\n",m_reg.cardnum,msgq[2]);
                        //XXX We don't do anything useful with this yet...
                        #if 0
			rmess[0] = DSP_LLOOP_NOBATT;
			rmess[1] = DSP_LOOP_NOBATT;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
                        #endif
			break;

		    case DSP_LOOP_FLASH:
			mprintf("[%d/%d] Loop FLASH\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LLOOP_FLASH;
			rmess[1] = DSP_LOOP_FLASH;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_CODEC_HKOFF:
			mprintf("[%d/%d] Station OffHook\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LCODEC_HKOFF;
			rmess[1] = DSP_CODEC_HKOFF;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_CODEC_HKON:
			mprintf("[%d/%d] Station OnHook\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LCODEC_HKON;
			rmess[1] = DSP_CODEC_HKON;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_CODEC_FLASH:
			mprintf("[%d/%d] Station FLASH\n",m_reg.cardnum,msgq[2]);
			rmess[0] = DSP_LCODEC_FLASH;
			rmess[1] = DSP_CODEC_FLASH;
			rmess[2] = msgq[2];
			m_reg.upmess->Write(rmess, rmess[0]);
			break;

		    case DSP_TONEG:
			mprintf("[%d/%d] Tone end\n",m_reg.cardnum,msgq[2]);
			//XXX We don't use the hardware tonegen yet.
			break;

		    default:
			mprintf("[%d/%d] CheckMsgQ: Unknown message [%d]\n",
				m_reg.cardnum, msgq[2], msgq[1]);
		}
	}
} //}}}

// Simulates the DSP on host processing cards:
//  - polls card for signalling events
//  - audio signal I/O is handled by different threads
void *VTCore::SimulateDSP(void *p)
{ //{{{
	VTCore *vtcore = (VTCore*)p;

	mprintf("[%d/-] Starting VTCore SimulateDSP thread %#lx\n",
		vtcore->m_reg.cardnum, pthread_self());
	for(;;) {
		bool idle = vtcore->ProcessCommands();
		vtcore->CheckMsgQ();
		if( errno != ENODATA ) {
			mprintf("[%d/-] ERROR reading message queue: %m\n",
				vtcore->m_reg.cardnum);
			mprintf("[%d/-] sleeping for 10 sec.\n",
				vtcore->m_reg.cardnum);
			GenericSleep(10000);
			//XXX should this just terminate the
			//    thread and/or the app instead?
		}
		if(idle) GenericSleep(HOSTDSP_SLEEP);
		else     pthread_testcancel();
	}
	return NULL;
} //}}}


// Reads audio from the play buffer and combines it with audio from the host
// side tone generator if it is active.
int VTCore::prepare_play_buf(int16_t *buf, int ch)
{ //{{{
	// Read play fifos, add in tone, convert to alaw
	HostToneGen *tonegen = dynamic_cast<HostToneGen*>(m_reg.toneg[ch]);
	int          retread = m_reg.txdf[ch]->Read( (uint16_t*)buf, NBUF);

	return (tonegen && tonegen->MixTone(buf,NBUF)) ? VPB_OK : retread;
} //}}}

static int vtcore_play_audio(int fd, uint8_t board, uint8_t port, int16_t *buf, unsigned samples)
{ //{{{
	VT_DATA  mess = { board:    board,
			  channel:  port,
			  length:   samples,
			  data:     buf
			};
	return ioctl(fd, VT_IOC_CHANWRITEAUDIO, &mess) < 0 ? -1 : 0;
} //}}}

static int vtcore_play_fifo_how_empty(int fd, uint8_t board, uint8_t port)
{ //{{{
	int tmp = 0;
	VT_DATA  mess = { board:    board,
			  channel:  port,
			  length:   sizeof(int),
			  data:     &tmp
			};
	return ioctl(fd, VT_IOC_CHAN_WRITE_AUDIO_FIFO_HOW_EMPTY, &mess) < 0 ? -1 : tmp;
} //}}}

// Moves audio samples between the kernel mode driver FIFO and the PC FIFOs.
// Play side.
void *VTCore::PlayThread(void *p)
{ //{{{
	int16_t  buf[NBUF];
	VTCore  *vtcore  = (VTCore*)p;
	VPBREG  &reg     = vtcore->m_reg;
	int      chans   = reg.numch;
	int      cardnum = reg.cardtypnum;

	mprintf("[%d/-] Starting Play Thread %#lx\n",reg.cardnum, pthread_self());
	while( !reg.hostdsp ) GenericSleep(5);

#ifdef REALTIME_THREADS
	// Set this thread to run with realtime priority
	struct sched_param mysched;
	mysched.sched_priority = 99;
	if (pthread_setschedparam(pthread_self(), SCHED_RR, &mysched) != 0) {
		mprintf("[%d/-] Failed to set realtime priority in Play Thread %#lx\n", reg.cardnum, pthread_self());
	}
#endif

	for(;;) {
	    for(int i=0; i < chans; ++i) {
		int tmp = vtcore_play_fifo_how_empty(pci_fd, cardnum, i);
		if(tmp > NBUF) {
			memset(buf, 0, NBUF * sizeof(int16_t));
			if(vtcore->prepare_play_buf(buf,i) == VPB_OK)
				vtcore_play_audio(pci_fd, cardnum, i, buf, NBUF);
		}
		else if( tmp == -1 ) {
			mprintf("[%d/%d] ERROR reading play fifo: %m\n",
				reg.cardnum, i);
			mprintf("[%d/%d] sleeping for 10 sec.\n",
				reg.cardnum, i);
			GenericSleep(10000);
			//XXX should this just terminate the
			//    thread and/or the app instead?
		}
	    }
	    // use select here instead of GenericSleep (which uses nanosleep)
	    // because select guarantees that it will sleep for at MOST the time
	    // specified, whereas nanosleep (and usleep) guarantee that they
	    // will sleep for at LEAST the time specified.  It is critical that
	    // we service the kernel fifo buffers in time, and this sleep is
	    // only here to ensure that we don't hog all of the CPU time, so
	    // select is more appropriate
	    struct timeval sleepms;
	    sleepms.tv_sec = 0;
	    sleepms.tv_usec = AUDIO_SLEEP * 1000;
	    select(0, NULL, NULL, NULL, &sleepms);
	}
	return NULL;
} //}}}

// Transfers buffers of recorded samples to the driver FIFOs.
// Also runs DTMF decoders on each channel.
void VTCore::prepare_rec_buf(int16_t *buf, int ch)
{ //{{{
	uint16_t     mess[COMM_MAX_MESSDSPPC];
	int	     ret;
	vtcore_chan *chans = (vtcore_chan*)m_reg.chans;

	//XXX
	//uint16_t     agcbuf[NBUF];
	//if (chans[ch].agc_states){
	//	v4log_agc_apply(chans[ch].agc_states,
	//			(short*)agcbuf, (short*)buf, NBUF);
	//	memcpy(buf, agcbuf, NBUF * sizeof(short));
	//}

#ifdef SOFTBRIDGE
	int hostvox_ret = -1;

	if (m_reg.rxdf[ch]->HowFull() > DROP_AUDIO_THRESHOLD) {
		// drop samples during silence if the buffer gets too big
		if (chans[ch].vox_states)
			// save return value in case it is needed below
			hostvox_ret = hostvox_analyse(chans[ch].vox_states,buf,NBUF);

		/* write it to the buffer */
		if (hostvox_energy(chans[ch].vox_states) < 3000000) {
			m_reg.rxdf[ch]->Write( ((uint16_t*)buf) + 1, NBUF - 1);
		} else {
			m_reg.rxdf[ch]->Write( (uint16_t*)buf, NBUF);
		}
	} else {
		m_reg.rxdf[ch]->Write( (uint16_t*)buf, NBUF);
	}
#else
	m_reg.rxdf[ch]->Write( (uint16_t*)buf, NBUF);
#endif

	// DTMF processing
	for(int i=0; i < TDLOOP; ++i){
		char key[2];
		int  keydown    = 0;
		int  digits_ret = dtmf_decoder(chans[ch].dtmf_dec, key, 2,
					       &buf[i*(NBUF/TDLOOP)],
					       NBUF/TDLOOP, &keydown);
		if (digits_ret) {
			// it should be impossible to detect > 1 digit in NBUF sample buffer
			//assert(digits_ret == 1);
	//		mprintf("ch = %d bd = %d DTMF[%d].....\n", ch, v->cardnum,key[0]);
			mess[0] = DSP_LDTMF;
			mess[1] = DSP_DTMF;
			mess[2] = ch;
			mess[3] = key[0];
			ret = m_reg.upmess->Write(mess, mess[0]);
		}
		else if (keydown){
			mess[0] = DSP_LDTMF_DOWN;
			mess[1] = DSP_DTMF_DOWN;
			mess[2] = ch;
			mess[3] = keydown;
			ret = m_reg.upmess->Write(mess, mess[0]);
		}
		// tone decoder processing
		toned_analyse(m_reg.toned[ch], &buf[i*(NBUF/TDLOOP)], NBUF/TDLOOP);
	}
	if (chans[ch].vox_states){
#ifdef SOFTBRIDGE
		if (hostvox_ret == -1)
			ret = hostvox_analyse(chans[ch].vox_states,buf,NBUF);
		else
			ret = hostvox_ret;
#else
		ret = hostvox_analyse(chans[ch].vox_states,buf,NBUF);
#endif

		if (ret == 1){ // Vox OFF
			mess[0] = DSP_LVOXOFF;
			mess[1] = DSP_VOXOFF;
			mess[2] = ch;
			ret = m_reg.upmess->Write(mess, mess[0]);
			mprintf("ch = %d bd = %d VOX OFF\n", ch, m_reg.cardnum);
		}
		else if (ret ==2){ // Vox ON
			mess[0] = DSP_LVOXON;
			mess[1] = DSP_VOXON;
			mess[2] = ch;
			ret = m_reg.upmess->Write(mess, mess[0]);
			mprintf("ch = %d bd = %d VOX ON\n", ch, m_reg.cardnum);
		}
	}
} //}}}

static int vtcore_record_audio(int fd, uint8_t board, uint8_t port, int16_t *buf, unsigned samples)
{ //{{{
	VT_DATA  mess = { board:    board,
			  channel:  port,
			  length:   samples,
			  data:     buf
			};
	return ioctl(fd, VT_IOC_CHANREADAUDIO, &mess) < 0 ? -1 : 0;
} //}}}

// Moves audio samples between the kernel mode driver FIFO and the PC FIFOs.
// Record side.
void *VTCore::RecordThread(void *p)
{ //{{{
	int16_t  buf[NBUF];
	VTCore  *vtcore  = (VTCore*)p;
	VPBREG	&reg     = vtcore->m_reg;
	int      cardnum = reg.cardtypnum;
	int      chans   = reg.numch;

	mprintf("[%d/-] Starting Record Thread %#lx\n", reg.cardnum, pthread_self());
	while( !reg.hostdsp ) GenericSleep(5);

#ifdef REALTIME_THREADS
	// Set this thread to run with realtime priority
	struct sched_param mysched;
	mysched.sched_priority = 99;
	if (pthread_setschedparam(pthread_self(), SCHED_RR, &mysched) != 0) {
		mprintf("[%d/-] Failed to set realtime priority in Record Thread %#lx\n", reg.cardnum, pthread_self());
	}
#endif

	for(;;) {
	    for(int i=0; i < chans; ++i) {
		//memset(buf, 0, NBUF * sizeof(int16_t));
		while( vtcore_record_audio(pci_fd, cardnum, i, buf, NBUF) != -1 ) {
			vtcore->prepare_rec_buf(buf,i);
		}
		if( errno != ENODATA ) {
			mprintf("[%d/%d] ERROR reading audio data: %m\n",
				reg.cardnum, i);
			mprintf("[%d/%d] sleeping for 10 sec.\n",
				reg.cardnum, i);
			GenericSleep(10000);
			//XXX should this just terminate the
			//    thread and/or the app instead?
		}
	    }
	    // use select here instead of GenericSleep (which uses nanosleep)
	    // because select guarantees that it will sleep for at MOST the time
	    // specified, whereas nanosleep (and usleep) guarantee that they
	    // will sleep for at LEAST the time specified.  It is critical that
	    // we service the kernel fifo buffers in time, and this sleep is
	    // only here to ensure that we don't hog all of the CPU time, so
	    // select is more appropriate
	    struct timeval sleepms;
	    sleepms.tv_sec = 0;
	    sleepms.tv_usec = AUDIO_SLEEP * 1000;
	    select(0, NULL, NULL, NULL, &sleepms);
	}
	return NULL;
} //}}}

static VPB_PORT vtcore_get_interface_type(uint8_t board, uint8_t port)
{ //{{{
	int tmp = VPB_PORT_UNKNOWN;
	VT_DATA  mess = { board:    board,
			  channel:  port,
			  length:   sizeof(int),
			  data:     &tmp
			};
	if( ioctl(pci_fd, VT_IOC_BOARD_PORT_TYPE, &mess) >= 0 )
		switch( tmp ) {
		    case VT_FXO_AN: return VPB_FXO;
		    case VT_FXS_AN: return VPB_FXS;
		}
	return VPB_PORT_UNKNOWN;
} //}}}

// This function is called by the tone detector when a tone is detected
// or a tone detector log message is generated.
static void toned_callback(uint16_t mess[], void *board)
{ //{{{
	VPBREG *vr = (VPBREG *)board;

	mprintf("[%2d] toned_callback: got tone %d\n", mess[2], mess[3]);
	vr->upmess->Write(mess, mess[0]);
} //}}}


VTCore::VTCore(VPBREG &reg)
	: HostDSP( reg )
{ //{{{
	HostDSPOpen();
} //}}}

VTCore::~VTCore()
{ //{{{
	HostDSPClose();
} //}}}

void VTCore::Reset( unsigned int port )
{ //{{{
	static const char *vpb_country = getenv("VPB_COUNTRY");

	UNListen(port);

	const Country *c = m_reg.country[port];

	if( c ) goto part_2;
	if( vpb_country ) {
		c = vpb_get_country_data(vpb_country);
		if( c ) goto part_2;

		int country_code = atoi(vpb_country);
		if( country_code ) {
			c = vpb_get_country_data(country_code);
			if( c ) goto part_2;
		}
		mprintf("Unrecognised country '%s'\n", vpb_country);
	}
	if( vpb_c->GetCountryCode() ) {
		c = vpb_get_country_data(vpb_c->GetCountryCode());
		if( c ) goto part_2;

		mprintf("Unrecognised country code %d\n", vpb_c->GetCountryCode());
	}
	c = get_country(m_reg.cardnum, port);

    part_2:

	set_country( m_reg.cardnum, port, c );

	play_reset_hw_gain( m_reg.handles[port] );
	record_reset_hw_gain( m_reg.handles[port] );
} //}}}

void VTCore::SetCountry( int port, const Country *country )
{ //{{{
	// This array is a rather nasty hack to map an impedance override
	// for an FXS port to a country selection that will provide that
	// impedance.  We need something cleaner than this, but better to
	// do that in a later release now.
	const char *ohms[] = { "USA",
			       "OHM900",
			       "CAPOHM600",
			       "CAPOHM900",
			       "CHINA",
			       "AUSTRALIA",
			       "SOUTHAFRICA",
			       "NEWZEALAND"
			     };
	vtcore_chan &chan  = ((vtcore_chan*)m_reg.chans)[port];
	VT_DATA	     mess  = { board:    uint8_t(m_reg.cardtypnum),
			       channel:  uint8_t(port),
			       length:   unsigned(strlen(country->name)),
			       data:     const_cast<char*>(country->name)
			     };

	if( chan.fxs_ohms >= 0 && chan.fxs_ohms < 8 ) {
		mess.data   = const_cast<char*>(ohms[chan.fxs_ohms]);
		mess.length = strlen(ohms[chan.fxs_ohms]);
	}
	if( ioctl(pci_fd, VT_IOC_SETCOUNTRY, &mess) < 0 )
		mprintf("[%d/%d] VT_IOC_SETCOUNTRY FAILED: %m\n",
			m_reg.cardnum, port);

	mess.data = NULL;

	if( chan.fxo_ohms >= 0 && chan.fxo_ohms < 16 ) {
		mess.length = chan.fxo_ohms;
		if( ioctl(pci_fd, VT_IOC_SETVDAAIMPEDANCE, &mess) < 0 )
			mprintf("[%d/%d] VT_IOC_SETVDAAIMPEDANCE FAILED: %m\n",
				m_reg.cardnum, port);
	}

	if( chan.interface == VPB_FXS ) {
		mess.length = (country->flash_max << 16) | country->flash_min;
		if( ioctl(pci_fd, VT_IOC_SETFLASH, &mess) < 0 )
			mprintf("[%d/%d] VT_IOC_SETFLASH FAILED: %m\n",
				m_reg.cardnum, port);
	}
	else if( chan.interface == VPB_FXO ) {
		mess.length = (country->lv_onhook << 16) | country->lv_offhook;
		if( ioctl(pci_fd, VT_IOC_SETHOOKTHRESH, &mess) < 0 )
			mprintf("[%d/%d] VT_IOC_SETHOOKTHRESH FAILED: %m\n",
				m_reg.cardnum, port);

		mess.length = (country->drop_max << 16) | country->drop_min;
		if( ioctl(pci_fd, VT_IOC_SETLOOPDROP, &mess) < 0 )
			mprintf("[%d/%d] VT_IOC_SETLOOPDROP FAILED: %m\n",
				m_reg.cardnum, port);
	}
} //}}}

void VTCore::SetHookState( int port, HookState hookstate )
{ //{{{
	switch( hookstate )
	{
	    case VPB_ONHOOK:
		mprintf("[%d/%d] Set On Hook\n", m_reg.cardnum, port);
		((vtcore_chan*)m_reg.chans)[port].state = CH_IDLE;
		break;

	    case VPB_OFFHOOK:
		mprintf("[%d/%d] Set Off Hook\n", m_reg.cardnum, port);
		break;

	    case VPB_FASTOFFHOOK:
		mprintf("[%d/%d] Set Off Hook (fast)\n", m_reg.cardnum, port);
		break;
	}
	//XXX Should this throw if an error occurs?
	int ret = vtcore_sethook(m_reg.cardtypnum, port, hookstate);
	if( ret < 0 )
		mprintf("[%d/%d] FAILED to set hook state %u: (%d) %s\n",
			m_reg.cardnum, port, hookstate, ret, strerror(ret));
} //}}}

static uint16_t silence[NBUF];

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

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

void VTCore::WaitForTxEmpty( int port )
{ //{{{
	static const int vtcore_fifo_size = 160 * 8;

	for(;;) {
		int remains = m_reg.txdf[port]->HowFull();

		if(remains == 0)   break;
		if(remains < NBUF) m_reg.txdf[port]->Write(silence, NBUF - remains);

		//mprintf("[%d/%d] WaitForTxEmpty: %d remain to flush\n",
		//	m_reg.cardnum, port, remains);
		GenericSleep(AUDIO_SLEEP);
	}
	while( vtcore_play_fifo_how_empty(pci_fd, m_reg.cardtypnum, port) < vtcore_fifo_size )
		GenericSleep(AUDIO_SLEEP);
} //}}}

void VTCore::SetPSImpedance( int port, int impedance )
{ //{{{
	vtcore_chan &chan = ((vtcore_chan*)m_reg.chans)[port];
	chan.fxs_ohms     = impedance;
	SetCountry( port, get_country(m_reg.cardtypnum, port) );
} //}}}

void VTCore::SetVDAAImpedance( int port, int impedance )
{ //{{{
	vtcore_chan &chan = ((vtcore_chan*)m_reg.chans)[port];
	chan.fxo_ohms     = impedance;
	SetCountry( port, get_country(m_reg.cardtypnum, port) );
} //}}}

void VTCore::SetFlashTime( int port, uint16_t min, uint16_t max )
{ //{{{
	VT_DATA	     mess  = { board:    uint8_t(m_reg.cardtypnum),
			       channel:  uint8_t(port),
			       length:   unsigned(max) << 16 | min,
			       data:     NULL
			     };

	if( ioctl(pci_fd, VT_IOC_SETFLASH, &mess) < 0 )
		mprintf("[%d/%d] VT_IOC_SETFLASH %u - %u FAILED: %m\n",
					m_reg.cardnum, port, min, max);
} //}}}

float VTCore::GetHWPlayGain( unsigned int port )
{ //{{{
	char procnode[128];
	char val[4];

	snprintf(procnode, sizeof(procnode)-1,
		 "/proc/vt/board%d/port%d/playgain",
		 m_reg.cardtypnum, port);

	if( ! GetProcfsInfo( procnode, val, sizeof(val) ) )
		throw VpbException("Failed to read '%s'", procnode);

	return (atoi(val) - 0x80) / 10.0;
} //}}}

float VTCore::GetHWRecordGain( unsigned int port )
{ //{{{
	char procnode[128];
	char val[4];

	snprintf(procnode, sizeof(procnode)-1,
		 "/proc/vt/board%d/port%d/recgain",
		 m_reg.cardtypnum, port);

	if( ! GetProcfsInfo( procnode, val, sizeof(val) ) )
		throw VpbException("Failed to read '%s'", procnode);

	return (atoi(val) - 0x80) / 10.0;
} //}}}

void VTCore::TapListen( unsigned int port, unsigned int srcboard, unsigned int srcport )
{ //{{{
	VT_DATA  msg2 = { board:   uint8_t(srcboard),
			  channel: uint8_t(srcport),
			  length:  0,
			  data:    NULL
			};
	VT_DATA  mess = { board:   uint8_t(m_reg.cardtypnum),
			  channel: uint8_t(port),
			  length:  sizeof(VT_DATA),
			  data:    &msg2
			};
	if( ioctl(pci_fd, VT_IOC_TAPLISTEN, &mess) < 0 )
		throw VpbException("[%d/%d] VTCore::TapListen to %d:%d failed: %m",
				    m_reg.cardnum, port, srcboard, srcport);
} //}}}

void VTCore::UNListen( unsigned int port )
{ //{{{
	VT_DATA  mess = { board:   uint8_t(m_reg.cardtypnum),
			  channel: uint8_t(port),
			  length:  0,
			  data:    NULL
			};
	ioctl(pci_fd, VT_IOC_UNLISTEN, &mess);
} //}}}

void VTCore::config_bals( int port, int *bals )
{ //{{{
	VT_REG_RW   bal;
	VT_DATA     mess = { board:   uint8_t(m_reg.cardtypnum),
			     channel: uint8_t(port),
			     length:  sizeof(VT_REG_RW),
			     data:    &bal
			   };
	for(int i=0; i < MAX_BALS; ++i){
		if(bals[i] != -1){
			bal.reg   = i;
			bal.value = bals[i];
			ioctl(pci_fd, VT_IOC_CHAN_BALANCE_SET, &mess);
		}
	}
} //}}}

void VTCore::config_logging( int port, int logging )
{ //{{{
	VT_DATA     mess = { board:   uint8_t(m_reg.cardtypnum),
			     channel: uint8_t(port),
			     length:  unsigned(logging),
			     data:    NULL
			   };
	mprintf("VTCore: setting logging to %d\n", logging);
	ioctl(pci_fd, VT_IOC_SET_LOGGING, &mess);

	vtcore_chan * chans = (vtcore_chan*)m_reg.chans;
	if(logging){
		/* open up vox (if it hasn't already been opened) */
		if (chans[port].vox_states == NULL)
			hostvox_open(&chans[port].vox_states,port);
		//XXX v4log_agc_open(&chans[ch].agc_states);
	}
	else if(chans[port].vox_states != NULL){
		/* close up vox */
		hostvox_close(chans[port].vox_states);
		chans[port].vox_states = NULL;
		//XXX v4log_agc_close(chans[ch].agc_states);
		//chans[port].agc_states = NULL;
	}
} //}}}

// Imports configuration options from the config file
void VTCore::Configure()
{ //{{{
	VTCORE_CARD *card = (VTCORE_CARD*)m_reg.cardinfo;

	if( ! card ) return;

	int  bals[MAX_BALS];
	int  got_bals    = 0;
	int  dtmfms      = 0;
	int  cutthrough  = 0;
	int  logging     = 0;
	bool got_logging = false;
	int  country     = 0;
	int  fxs_ohms    = -1;
	int  fxo_ohms    = -1;
	int  tmp;

	mprintf("[%d/-] Checking for extra VTCore config options...\n",
		m_reg.cardnum);

	for(int i=0; i < MAX_BALS; ++i) bals[i]=-1;
	for(llc_var *var = (llc_var*)card->config; var; var = var->next)
	{
		if(strncasecmp(var->name,"bal",3)==0) {
			got_bals++;
			tmp = atoi(&var->name[3]);
			if(tmp < MAX_BALS){
				//bals[tmp] = atoi(var->value);
				sscanf(var->value,"%x",&(bals[tmp]));
				//mprintf("[%d/-] got value for bal reg %d of %d(%s)\n",
				//	m_reg.cardnum, tmp, bals[tmp],var->value);
			}
			else {
				mprintf("[%d/-] too many balance registers %d > % d\n",
					m_reg.cardnum, tmp, MAX_BALS);
			}
		}
		else if(strcasecmp(var->name,"dtmfms")==0) {
			dtmfms = atoi(var->value);
			vpbdial_change_dtmf_length(dtmfms, dtmfms);
			//mprintf("[%d/-] got value for dtmfms of %d\n",
			//	m_reg.cardnum, dtmfms);
		}
		else if(strcasecmp(var->name,"cutthrough")==0) {
			cutthrough              = atoi(var->value);
			m_reg.defRecordGainDown = cutthrough;
			//mprintf("[%d/-] got value for cutthrough of %d\n",
			//	m_reg.cardnum, cutthrough);
		}
		else if(strcasecmp(var->name,"logging")==0) {
			logging     = atoi(var->value);
			got_logging = true;
			//mprintf("[%d/-] got value for logging of %d\n",
			//	m_reg.cardnum, logging);
		}
		else if(strcasecmp(var->name,"country")==0) {
			country = atoi(var->value);
			//mprintf("[%d/-] got value for country of %d\n",
			//	m_reg.cardnum, country);
		}
		else if(strcasecmp(var->name,"fxs_impedance")==0) {
			fxs_ohms = atoi(var->value);
		}
		else if(strcasecmp(var->name,"fxo_impedance")==0) {
			fxo_ohms = atoi(var->value);
		}
		else if(strcasecmp(var->name,"chan")==0) {
			tmp = atoi(var->value);

			vtcore_chan &chan = ((vtcore_chan*)m_reg.chans)[tmp];

			if(got_bals)    config_bals(tmp, bals);
			if(got_logging) config_logging(tmp, logging);
			if(chan.interface == VPB_FXS) chan.fxs_ohms = fxs_ohms;
			if(chan.interface == VPB_FXO) chan.fxo_ohms = fxo_ohms;
			if(country)     m_reg.country[tmp] =
					    vpb_get_country_data(country);

			//mprintf("[%d/%d] applied config options...\n",
			//	m_reg.cardnum, tmp);
		}
	}
} //}}}

void VTCore::HostDSPOpen()
{ //{{{
	mprintf("[%d/-] Init %d channel VTCore Host DSP %d for %s...\n",
		m_reg.cardnum, m_reg.numch, m_reg.cardtypnum, vpb_model_desc(m_reg.model));

	if (pci_fd == 0){
		pci_fd = open(pcidev,O_RDWR);
		if(pci_fd == -1)
			throw VpbException("Couldn't open VTCore device node (%s): %s\n",
					   pcidev, strerror(errno));
	}

	m_reg.toned = new TD*[m_reg.numch];

	vtcore_chan *chans = new vtcore_chan[m_reg.numch];
	m_reg.chans = chans;

	for(int i=0 ; i < m_reg.numch; ++i)
	{
		toned_open(&m_reg.toned[i], i , &toned_callback, &m_reg);

		/* setup chans structure */
		chans[i].dtmf_dec   = dtmf_init();
		chans[i].vox_states = NULL;
		hostvox_open(&chans[i].vox_states,i);
		//XXX chans[i].agc_states = NULL;
		chans[i].chan_num   = i;
		chans[i].state      = CH_IDLE;
		chans[i].v          = &m_reg;
		chans[i].interface  = vtcore_get_interface_type(m_reg.cardtypnum, i);
		//mprintf("[%d/%d] Init VTCore fifos ...\n",m_reg.cardtypnum,i);
		chans[i].fxs_ohms   = -1;
		chans[i].fxo_ohms   = -1;

		//vtcore_port_rst(pci_fd, m_reg.cardnum, i);
		m_reg.rxdf[i] = new HostFifo(m_reg.szrxdf[i]);
		m_reg.txdf[i] = new HostFifo(m_reg.sztxdf[i]);
		//mprintf("[%d/%d] DSP Audio FIFOs opened\n",m_reg.cardnum,i);

		if( m_reg.model == VPB_OPCI && chans[i].interface == VPB_FXS )
			m_reg.toneg[i] = new ProslicToneGen;
		else    m_reg.toneg[i] = new HostToneGen;
	}

	char procnode[128];
	snprintf(procnode, sizeof(procnode)-1, "/proc/vt/board%d/id",
					       m_reg.cardtypnum);
	GetProcfsInfo( procnode, m_reg.serial_n, sizeof(m_reg.serial_n) );

	// These aren't exported simply by vtcore yet either.
	//m_reg.mdate    =
	//m_reg.revision = 

	/* Configure ports from config file! */
	Configure();

	int ret = pthread_create(&m_dspthread, NULL, SimulateDSP, this);
	if( ret )
		mprintf("[%d/-] FAILED to start DSP thread: %s\n",
			m_reg.cardnum, strerror(ret));

	ret = pthread_create(&m_playthread, NULL, PlayThread, this);
	if( ret )
		mprintf("[%d/-] FAILED to start DSP play thread: %s\n",
			m_reg.cardnum, strerror(ret));

	ret = pthread_create(&m_recordthread, NULL, RecordThread, this);
	if( ret )
		mprintf("[%d/-] FAILED to start DSP record thread: %s\n",
			m_reg.cardnum, strerror(ret));
} //}}}

void VTCore::HostDSPClose()
{ //{{{
	vtcore_chan *chans = (vtcore_chan *)m_reg.chans;

	mprintf("[%d/-] Stopping VTCore Host DSP threads ...\n",m_reg.cardnum);
	pthread_cancel(m_dspthread);
	pthread_cancel(m_playthread);
	pthread_cancel(m_recordthread);
	pthread_join(m_dspthread, NULL);
	pthread_join(m_playthread, NULL);
	pthread_join(m_recordthread, NULL);

	//mprintf("[%d/-] Releasing VTCore Host DSP fifos...\n",m_reg.cardnum);
	for(int i=0; i < m_reg.numch; ++i) {
		delete m_reg.rxdf[i];
		delete m_reg.txdf[i];
	}

	mprintf("[%d/-] Closing dtmf and tone detectors...\n",m_reg.cardnum);
	for(int i=0; i < m_reg.numch; ++i) {
		dtmf_close(chans[i].dtmf_dec);
		delete m_reg.toneg[i];
		toned_close(m_reg.toned[i]);
	}
	//mprintf("[%d/-] Freeing structures...\n",m_reg.cardnum);
	delete [] m_reg.toned;
	delete [] chans;

	//mprintf("[%d/-] Closed VTCore Host DSP\n",m_reg.cardnum);
} //}}}

int VTCore::chan_state(int ch)
{ //{{{
	vtcore_chan *chans;
	chans = (vtcore_chan *)m_reg.chans;
	return chans[ch].state;
} //}}}

VPB_PORT VTCore::chan_type(int ch)
{ //{{{
	vtcore_chan *chans;
	chans = (vtcore_chan *)m_reg.chans;
	return chans[ch].interface;
} //}}}

HookState VTCore::hook_state(int ch)
{ //{{{
	char    node[64];
	char    buf[4] = { 0, 0, 0, 0 };

	snprintf(node, sizeof(node), "/proc/vt/board%d/port%d/hook",
		 m_reg.cardtypnum, ch);

	int fd = open(node, O_RDONLY);
	if( fd == -1 )
	{
		mprintf("Failed to open '%s': %m", node);
		// Not the best thing to throw here, but don't add more
		// enumerated types if we can help it.  We should replace
		// all this with a more generic and flexible exception
		// handling system later.
		throw Wobbly(VPBAPI_DEVICE_BUSY);
	}
	switch( read(fd, buf, sizeof(buf) - 1) ){
	    case -1:
		mprintf("Failed to read '%s': %m", node);
		break;

	    case 0:
		mprintf("Failed to read from '%s'", node);
		break;

	    default:
		switch( *buf ) {
		    case '0':       // CH_IDLE
		    case '2':       // CH_MONITOR
			close(fd);
			return VPB_ONHOOK;

		    case '1':       // CH_OFFHOOK
			close(fd);
			return VPB_OFFHOOK;
		}
		mprintf("Invalid value '%s' from '%s'", buf, node);
	}
	close(fd);
	throw Wobbly(VPBAPI_DEVICE_BUSY);
} //}}}


//XXX
#if 0
static int vtcore_port_rst(int fd, int cardnum, int ch)
{ //{{{
	mprintf("[%d/%d] Reseting port\n",cardnum, ch);
	VT_DATA mess;
	mess.board = cardnum;
	mess.channel = ch;
	mess.length = 0;
	mess.data = NULL;
	if( -1 == ioctl(fd, VT_IOC_PORT_RST, &mess))
		return -1;
	else {
		return 0;
	}
} //}}}

static int sleepms(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;
	if(nanosleep(&ts, NULL)){
		mprintf("nanosleep failed!\n");
		return -1;
	}
	return 0;
} //}}}

static int vtcore_record_fifo_how_full(int fd, int cardnum, int ch)
{ //{{{
	VT_DATA mess;
	int tmp;
	mess.board = cardnum;
	mess.channel = ch;
	mess.length = sizeof(int);
	mess.data = (void*)&tmp;
	if( -1 == ioctl(fd, VT_IOC_CHAN_READ_AUDIO_FIFO_HOW_FULL, &mess))
		return -1;
	else
		return tmp;
} //}}}

static int vtcore_channel_count(int fd, int cardnum)
{ //{{{
	VT_DATA mess;
	int chans;
	mess.board = cardnum;
	mess.channel = 0;
	mess.length = sizeof(int);
	mess.data = (void *)&chans;
	if( -1 == ioctl(fd, VT_IOC_CHANCOUNT, &mess))
		return -1;
	else
		return chans;
} //}}}

static int vtcore_reg_read(int fd, int cardnum, int ch, int reg, int * value)
{ //{{{
	VT_DATA mess;
	VT_REG_RW data;
	mess.board = cardnum;
	mess.channel = ch;
	mess.length = sizeof(VT_REG_RW);
	mess.data = (void *)&data;
	data.reg = reg;
	if( -1 == ioctl(fd, VT_IOC_REGREAD, &mess))
		return -1;
	else {
		*value= data.value;
		return 0;
	}
} //}}}

static int vtcore_reg_write(int fd, int cardnum, int ch, int reg, int value)
{ //{{{
	VT_DATA mess;
	VT_REG_RW data;
	mess.board = cardnum;
	mess.channel = ch;
	mess.length = sizeof(VT_REG_RW);
	mess.data = (void *)&data;
	data.reg = reg;
	data.value = value;
	if( -1 == ioctl(fd, VT_IOC_REGWRITE, &mess))
		return -1;
	else
		return 0;
} //}}}

static int vtcore_bridgeon(int fd, int cardnum, uint16_t m[COMM_MAX_MESSPCDSP])
{ //{{{
	VT_DATA mess;
	VT_DATA mess2;
	mess.board = cardnum;    
	mess.channel = m[2];    
	mess.length = sizeof(VT_DATA);
	mess.data = &mess2;
	mess2.board = (short)m[3];
	mess2.channel = (short)m[4];
	mess.data = (void *)(int)m[3];
	if( -1 == ioctl(fd, VT_IOC_BRIDGE, &mess))
		return 1;
	else
		return 0;
} //}}}

static int vtcore_bridgeoff(int fd, uint16_t m[COMM_MAX_MESSPCDSP])
{ //{{{
	VT_DATA mess;
	mess.channel = m[2];    
	mess.length = sizeof(int);
	mess.data = (void *)(int)m[3];
	if( -1 == ioctl(fd, VT_IOC_UNBRIDGE, &mess))
		return 1;
	else
		return 0;
} //}}}
#endif

