/*---------------------------------------------------------------------------*\
	vtcore_main.c
	Core Kernel Module for Voicetronix modular cards
	Author: Ben Kramer, 24 October 2005
	        Ron Lee, 8 Jun 2006


	Copyright (C) 2005 - 2018, Voicetronix www.voicetronix.com.au
	Copyright (C) 2006 - 2018, Ron Lee <ron@voicetronix.com.au>

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	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
	General Public License for more details.

	You should have received a copy of the GNU 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

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

/* Driver constants */
#define DRIVER_DESCRIPTION  "Voicetronix device class interface"
#define DRIVER_AUTHOR       "Voicetronix <support@voicetronix.com.au>"

#define NAME        "vtcore"
#define MAX_BOARDS  8


//XXX #define FIFO_UNIT sizeof(char)		// Size of fifo unit
#define FIFO_AUDIO_SZ   sizeof(unsigned short)	// Linear audio in fifo
#define FIFO_SIZE       160*FRAME*FIFO_AUDIO_SZ	// 160 ms, each containing 8 samples

//XXX sane-itise this.
#define OK              0
#define	FIFO_FULL	1
#define	FIFO_EMPTY	2

#include "vtcommon.h"

#include <linux/init.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/sched.h>
#include <linux/uaccess.h>

#include "vtcore_ioctl.h"
#include "vtmodule.h"
#include "mu-alaw.h"


#define LINEAR2FOOLAW_LUT_SIZE  0x10000
#define LINEAR2FOOLAW_LUT_PAGES get_order(LINEAR2FOOLAW_LUT_SIZE)

static struct vtcore_device {
	short                   alaw2linear_lut[0x100];
	short                   mulaw2linear_lut[0x100];
	unsigned char          *linear2alaw_lut;     //[0x10000]
	unsigned char          *linear2mulaw_lut;    //[0x10000]
	struct vtboard         *boards[MAX_BOARDS];
	struct vtecho          *echo_can;
	struct cdev             cdev;
	dev_t                   dev;
	struct proc_dir_entry  *procfs_root;
	atomic_t                inuse;
} *vtcore;

// You must hold this lock anytime you access or modify the vtcore_device struct.
DEFINE_MUTEX(vtcore_mutex);


static int vtcore_open( struct inode*, struct file* );
static int vtcore_release( struct inode*, struct file* );
static long vtcore_ioctl(struct file *file, unsigned int cmd, unsigned long data);

static struct file_operations vtcore_fops={
	.owner		= THIS_MODULE,
	.llseek		= NULL,
	.read		= NULL,
	.write		= NULL,
	.poll		= NULL,
	.unlocked_ioctl	= vtcore_ioctl,
	.open		= vtcore_open,
	.release	= vtcore_release,
	.fasync		= NULL
};


static int debug;

module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "Set the logging verbosity");


#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)
 static struct class_simple *vt_class;
 #define device                         class_device
 #define class_create(x,y)              class_simple_create(x,y)
 #define class_destroy(x)               class_simple_destroy(x)
 #define device_create(c,p,n,d,u,...)   class_simple_device_add(c,n,NULL,u , ## __VA_ARGS__)
 #define device_destroy(c,n)            class_simple_device_remove(n)

#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
 static struct class *vt_class;
 #define device                         class_device
 #define device_create(c,p,n,d,u,...)   class_device_create(c,p,n,NULL,u , ## __VA_ARGS__)
 #define device_destroy(c,n)            class_device_destroy(c,n)

#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
 static struct class *vt_class;
 #define device_create(c,p,n,d,u,...)   device_create_drvdata(c,p,n,d,u , ## __VA_ARGS__)

#else
 static struct class *vt_class;

#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(3,9,0)
static inline void *PDE_DATA(const struct inode *inode)
{
	return PROC_I(inode)->pde->data;
}
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0)
static inline struct inode *file_inode(struct file *f)
{
	return f->f_path.dentry->d_inode;
}
#endif


/* Convenience macros for logging */
#define boardinfo(board,format,...) info("[%d] " format, (board)->boardnum , ## __VA_ARGS__)
#define portwarn(board,port,format,...)                                       \
	warn("[%d] %2d: " format, (board)->boardnum, port , ## __VA_ARGS__)
#define boardcrit(board,format,...) crit("[%d] " format, (board)->boardnum , ## __VA_ARGS__)
#define portcrit(board,port,format,...)                                       \
	crit("[%d] %2d: " format, (board)->boardnum, port , ## __VA_ARGS__)
#define dbginfo(n,format,...) if(debug>=n) info(format, ## __VA_ARGS__)
#define dbgboard(n,board,format,...) if(debug>=n)                             \
	info("[%d] " format, (board)->boardnum, ## __VA_ARGS__)
#define dbgport(n,board,port,format,...) if(debug>=n)                         \
	info("[%d] %2d: " format, (board)->boardnum, port , ## __VA_ARGS__)


//XXX Should the rest of these be static?
//    or simply replaced...  at least get rid of vmalloc here...
typedef struct {
	char   *pstart;		// first char in FIFO
	char   *pend;		// one after last char in FIFO
	char   *pwr;		// write pointer
	char   *prd; 		// read pointer
	int     size;		// total storage in FIFO
} FIFO;

void fifo_create(void **ppvfifo, int  size);
void fifo_destroy(FIFO *fifo);
int fifo_write(void *pvfifo, char *buf, int size);
int fifo_read(void *pvfifo, char *buf, int size);
void fifo_how_full(void *pvfifo, int *chars);
void fifo_how_empty(void *pvfifo, int *chars);


#define LUTOS   32768
static inline void linear2alaw(unsigned char *alaw, short *samples, int count)
{ //{{{
	unsigned char *lut = vtcore->linear2alaw_lut;
	for(; count > 0; --count,++alaw,++samples){
		*alaw = lut[LUTOS + *samples];
	}
} //}}}
static inline void alaw2linear(short *linear, unsigned char *samples, int count)
{ //{{{
	short *lut = vtcore->alaw2linear_lut;
	for(; count > 0; --count,++linear,++samples){
		*linear = lut[*samples];
	}
} //}}}
static inline void linear2mulaw(unsigned char *mulaw, short *samples, int count)
{ //{{{
	unsigned char *lut = vtcore->linear2mulaw_lut;
	for(; count > 0; --count,++mulaw,++samples){
		*mulaw = lut[LUTOS + *samples];
	}
} //}}}
static inline void mulaw2linear(short *linear, unsigned char *samples, int count)
{ //{{{
	short *lut = vtcore->mulaw2linear_lut;
	for(; count > 0; --count,++linear,++samples){
		*linear = lut[*samples];
	}
} //}}}


static inline const char *porttype(int type)
{ //{{{
	switch( type ) {
	    case VT_FXO_AN:  return "FXO";
	    case VT_FXS_AN:  return "FXS";
	    case VT_UNKNOWN: return "empty port";
	    default:         return "unknown type";
	}
} //}}}


static int vt_int_proc_show(struct seq_file *m, void *v)
{ //{{{
	seq_printf(m, "%ld", (long)m->private);
	return 0;
}

static int vt_int_proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, vt_int_proc_show, PDE_DATA(inode));
}

const struct file_operations vt_int_proc_fops = {
	.open		= vt_int_proc_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
}; //}}}

static int vt_string_proc_show(struct seq_file *m, void *v)
{ //{{{
	seq_puts(m, (const char*)m->private);
	return 0;
}

static int vt_string_proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, vt_string_proc_show, PDE_DATA(inode));
}

const struct file_operations vt_string_proc_fops = {
	.open		= vt_string_proc_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
}; //}}}


int __init vtcore_init(void)
{ //{{{
	int y,ret;
	short *alaw2lin, *mulaw2lin;
	unsigned char *lin2alaw, *lin2mulaw;
	struct device *d;

	info(DRIVER_DESCRIPTION " " VT_VERSION " for linux " UTS_RELEASE);

	if( mutex_lock_interruptible(&vtcore_mutex) ) return -ERESTARTSYS;
	if( vtcore ) goto exit;

	vtcore = kzalloc(sizeof(struct vtcore_device), GFP_KERNEL);
	if( !vtcore ) {
		crit("Failed to allocate vtcore structure of size %d",
		     (int)sizeof(struct vtcore_device));
		ret = -ENOMEM;
		goto hell;
	}
	atomic_set( &vtcore->inuse, 0 );
	//info("sizeof vtcore_device = %d",sizeof(struct vtcore_device));

	ret = alloc_chrdev_region(&vtcore->dev, 0, 1, NAME);
	if( ret < 0 ) {
		crit("Failed to allocate a device node (%d)", ret);
		goto hell_2;
	}
	dbginfo(1,"using device node: %d:%d", MAJOR(vtcore->dev), MINOR(vtcore->dev));
	cdev_init(&vtcore->cdev,&vtcore_fops);
	vtcore->cdev.owner = THIS_MODULE;

	/* init lookup tables */
	dbginfo(2,"setting up mu/alaw2linear lut...");
	alaw2lin  = vtcore->alaw2linear_lut;
	mulaw2lin = vtcore->mulaw2linear_lut;
	for(y=0; y<0x100; ++y,++alaw2lin,++mulaw2lin){
		*alaw2lin  = alaw_decode(y);
		*mulaw2lin = mulaw_decode(y);
	}

	dbginfo(2,"setting up linear2mu/alaw lut...");
	lin2alaw = (unsigned char*)__get_free_pages(GFP_KERNEL, LINEAR2FOOLAW_LUT_PAGES);
	if( !lin2alaw ) {
		crit("Failed to allocate linear-to-alaw lookup table");
		ret = -ENOMEM;
		goto hell_3;
	}
	vtcore->linear2alaw_lut = lin2alaw;

	lin2mulaw = (unsigned char*)__get_free_pages(GFP_KERNEL, LINEAR2FOOLAW_LUT_PAGES);
	if( !lin2mulaw ) {
		crit("Failed to allocate linear-to-mulaw lookup table");
		ret = -ENOMEM;
		goto hell_4;
	}
	vtcore->linear2mulaw_lut = lin2mulaw;

	for(y=0; y<0x10000; ++y,++lin2alaw,++lin2mulaw){
		*lin2alaw  = alaw_encode(y-LUTOS);
		*lin2mulaw = mulaw_encode(y-LUTOS);
	}

	/* register the vtcore class for sysfs and udev */
	vt_class = class_create( THIS_MODULE, NAME );
	if( IS_ERR(vt_class) ) {
		ret = PTR_ERR(vt_class);
		goto hell_5;
	}
	d = device_create(vt_class, NULL, vtcore->dev, NULL, "vt0");
	if( IS_ERR(d) ) {
		ret = PTR_ERR(d);
		goto hell_6;
	}

	/* We must be ready to handle requests before this operation */
	ret = cdev_add(&vtcore->cdev,vtcore->dev,1);
	if( ret ) {
		crit("Error adding char device (%d)", ret);
		goto hell_7;
	}

	vtcore->procfs_root = proc_mkdir("vt", NULL);
	if( vtcore->procfs_root ) {
		if (!proc_create_data("maxboards", S_IRUGO, vtcore->procfs_root,
					&vt_int_proc_fops, (void*)MAX_BOARDS))
			crit("failed to create procfs maxboards");
	} else
		crit("failed to create procfs root");

    exit:
	mutex_unlock(&vtcore_mutex);
	dbginfo(1,"module loaded");
	return 0;

    hell_7:
	device_destroy(vt_class, vtcore->dev);
    hell_6:
	class_destroy(vt_class);
	vt_class = NULL;
    hell_5:
	free_pages((unsigned long)vtcore->linear2mulaw_lut, LINEAR2FOOLAW_LUT_PAGES);
    hell_4:
	free_pages((unsigned long)vtcore->linear2alaw_lut, LINEAR2FOOLAW_LUT_PAGES);
    hell_3:
	unregister_chrdev_region(vtcore->dev, 1);
    hell_2:
	kfree(vtcore);
	vtcore = NULL;
    hell:
	mutex_unlock(&vtcore_mutex);
	return ret;
} //}}}

void __exit vtcore_exit(void)
{ //{{{
	mutex_lock(&vtcore_mutex);
	if( !vtcore ) goto done;

	if( vtcore->procfs_root ) {
		remove_proc_entry("maxboards", vtcore->procfs_root);
		remove_proc_entry("vt", NULL);
	}

	cdev_del(&vtcore->cdev);
	device_destroy(vt_class, vtcore->dev);
	class_destroy(vt_class);
	vt_class = NULL;
	free_pages((unsigned long)vtcore->linear2mulaw_lut, LINEAR2FOOLAW_LUT_PAGES);
	free_pages((unsigned long)vtcore->linear2alaw_lut, LINEAR2FOOLAW_LUT_PAGES);
	unregister_chrdev_region(vtcore->dev, 1);
	kfree(vtcore);
	vtcore = NULL;

    done:
	mutex_unlock(&vtcore_mutex);
	info("module exit");
} //}}}

static int vtcore_open( struct inode *inode, struct file *filep )
{ //{{{

	if( atomic_inc_return( &vtcore->inuse ) > 1 ){
		atomic_dec( &vtcore->inuse );
		warn("vt0 device in use");
		return -EBUSY;
	}
	dbginfo(1,"vt0 device opened");
	return 0;

} //}}}

static int vtcore_release( struct inode *inode, struct file *filep )
{ //{{{

	atomic_dec( &vtcore->inuse );
	dbginfo(1,"vt0 device released");
	return 0;

} //}}}


// Verifies that board is registered and that port is addressable on it.
static int check_channel(int board, int port)
{ //{{{
	struct vtboard *b;

	if( board >= MAX_BOARDS ){
		warn("board %d out of range, max = %d", board, MAX_BOARDS-1);
		return RET_FAIL;
	}
	//XXX lock?
	b = vtcore->boards[board];
	if( ! b ){
		warn("board %d not registered", board);
		return RET_FAIL;
	}
	if( port >= b->maxports ){
		warn("no port %d on board %d, max = %d",
		     port, board, b->maxports);
		return RET_FAIL;
	}
	return RET_OK;
} //}}}

static long vtcore_ioctl(struct file *file, unsigned int cmd, unsigned long data)
{ //{{{
	VT_DATA	         vt_data;
	VT_DATA	         vt_data2;

	struct vtboard **boards = vtcore->boards;
	int              boardnum;
	int              port;
	struct vtboard  *board;
	struct channel  *chan;

	int              intval;
	int              b,p,i;
	unsigned char    charval;
	char            *mess;

	unsigned char   *buf;

	if( copy_from_user(&vt_data,(void *)data,sizeof(VT_DATA)) ){
		crit("copy of VT_DATA failed");
		return -EFAULT;
	}
	boardnum = vt_data.board;
	port = vt_data.channel;

	if( ! check_channel(boardnum, port) )
		return -EINVAL;

	board = boards[boardnum];
	chan = &board->chans[port];

	if( cmd < VT_IOC_DRIVER_VERSION && ! chan->porttype ){
		crit("no port %d active on board %d", port, boardnum);
		return -ENODEV;
	}
	switch(cmd){
	    case VT_IOC_CHAN_READ_AUDIO_FIFO_HOW_FULL:
		if( unlikely( ! try_module_get(board->owner) ) )
			return -ENODEV;

		fifo_how_full(chan->rxfifo,&intval);
		module_put( board->owner );

		intval = intval/FIFO_AUDIO_SZ;
		dbgport(6,board,port,"VT_IOC_CHAN_READ_AUDIO_FIFO_HOW_FULL %d",
			intval);
		if( vt_data.data && copy_to_user(vt_data.data,&intval,sizeof(int)) )
			return -EFAULT;
		break;

	    case VT_IOC_CHANREADAUDIO:
		if( unlikely( ! try_module_get(board->owner) ) )
			return -ENODEV;

		fifo_how_full(chan->rxfifo,&intval);

#ifdef FIFOSTATS
		// XXX: This code only useful for debugging problems with the
		// kernel fifo.  Should never be enabled in normal use, and
		// requires that FIFOSTATS be defined manually both for this
		// file and for vtmodule.h.  It gives an idea of the status of
		// the kernel fifo without causing high load, so we can see
		// problems without causing more...
		if (intval < chan->min_rxfifo)
			chan->min_rxfifo = intval;
		if (intval > chan->max_rxfifo)
			chan->max_rxfifo = intval;
		chan->total_rxfifo += intval;
		if (chan->rxfifo_samples++ > 1000) {
			dbginfo(0, "[%d/%d] VT_IOC_CHANREADAUDIO rxfifo stats - min %d, max %d, avg %d", boardnum, port, chan->min_rxfifo, chan->max_rxfifo, chan->total_rxfifo/chan->rxfifo_samples);
			chan->min_rxfifo = FIFO_SIZE;
			chan->max_rxfifo = 0;
			chan->total_rxfifo = 0;
			chan->rxfifo_samples = 0;
		}
#endif

		if (intval < FIFO_AUDIO_SZ * vt_data.length){
			dbginfo(6,"VT_IOC_CHANREADAUDIO - Not enough data in FIFO");
			module_put( board->owner );
			return -ENODATA;
		}
		dbgport(6,board,port,"VT_IOC_CHANREADAUDIO %d bytes in fifo", intval);
		//XXX UGH ...  keep a buffer instead of always allocating.
		buf = vmalloc(FIFO_AUDIO_SZ * vt_data.length);
		if (!buf){
			portcrit(board,port,"VT_IOC_CHANREADAUDIO - Cant allocate buffer %d",
				 vt_data.length * (int)FIFO_AUDIO_SZ);
			module_put( board->owner );
			return -ENOMEM;
		}
		if( fifo_read(chan->rxfifo,buf,FIFO_AUDIO_SZ * vt_data.length) == FIFO_EMPTY){
			vfree(buf);
			portwarn(board,port,"VT_IOC_CHANREADAUDIO - Fifo EMPTY");
			module_put( board->owner );
			return -ENODATA;
		} else if( vt_data.data
			&& copy_to_user(vt_data.data, buf, vt_data.length * FIFO_AUDIO_SZ)){
			vfree(buf);
			module_put( board->owner );
			return -EFAULT;
		}
		module_put( board->owner );
	//	dbginfo(3,"[%02d] %02d: %02x %02x %02x %02x %02x %02x %02x %02x",
	//		boardnum, port,
	//		buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]);
		vfree(buf);
		break;

	    case VT_IOC_CHAN_WRITE_AUDIO_FIFO_HOW_EMPTY:
		if( unlikely( ! try_module_get(board->owner) ) )
			return -ENODEV;

		fifo_how_empty(chan->txfifo,&intval);
		module_put( board->owner );

		dbgport(6,board,port,"VT_IOC_CHAN_WRITE_AUDIO_FIFO_HOW_EMPTY %d", intval);
		intval = intval/FIFO_AUDIO_SZ;
		if( vt_data.data && copy_to_user(vt_data.data,&intval,sizeof(int)) )
			return -EFAULT;
		break;

	    case VT_IOC_CHANWRITEAUDIO:
		if( unlikely( ! try_module_get(board->owner) ) )
			return -ENODEV;

		fifo_how_empty(chan->txfifo,&intval);
		if (intval < vt_data.length*FIFO_AUDIO_SZ){
			dbgport(6,board,port,"VT_IOC_CHANWRITEAUDIO - Fifo FULL %d < %d",
				intval, vt_data.length * (int)FIFO_AUDIO_SZ);
			module_put( board->owner );
			return -ENOBUFS;
		}
		dbgport(6,board,port,"VT_IOC_CHANWRITEAUDIO %d bytes in fifo +%d",
			intval, vt_data.length * (int)FIFO_AUDIO_SZ);
		//XXX UGH ...  keep a buffer instead of always allocating.
		buf = vmalloc(FIFO_AUDIO_SZ*vt_data.length);
		if (!buf){
			portcrit(board,port,"VT_IOC_CHANWRITEAUDIO - Cant allocate buffer %d",
				 vt_data.length * (int)FIFO_AUDIO_SZ);
			module_put( board->owner );
			return -ENOMEM;
		}
		if (copy_from_user(buf, vt_data.data, vt_data.length*FIFO_AUDIO_SZ)){
			module_put( board->owner );
			return -EFAULT;
		}
	//	dbginfo(3,":IN %02x %02x %02x %02x %02x %02x %02x %02x",
	//		buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]);
		if( fifo_write(chan->txfifo,buf,vt_data.length*FIFO_AUDIO_SZ) == FIFO_FULL ){
			portwarn(board,port,"VT_IOC_CHANWRITEAUDIO - Fifo FULL");
			vfree(buf);
			module_put( board->owner );
			return -ENOBUFS;
		}
		module_put( board->owner );
		vfree(buf);
		break;

	    case VT_IOC_LISTEN:
		if( copy_from_user(&vt_data2, vt_data.data, sizeof(VT_DATA)) )
			return -EFAULT;

		dbgport(1,board,port,"VT_IOC_LISTEN to %d on board %d",
			vt_data2.channel, vt_data2.board);

		if( chan->bridgetx ){
			// XXX report to which port XXX
			dbgport(1,board,port,"already listening");
			return -EBUSY;
		}
		if( ! check_channel(vt_data2.board, vt_data2.channel) )
			return -EINVAL;

		if( boards[vt_data2.board]->chans[vt_data2.channel].porttype){
			struct channel  *chan2 = &boards[vt_data2.board]->chans[vt_data2.channel];
			unsigned long flags;

			if( chan2->bridgerx.active != -1 )
			{
				portwarn(board,port,
					 "board %d, port %d is already listened to by %d:%d",
					 vt_data2.board, vt_data2.channel,
					 chan2->bridgerx.active >> 8,
					 chan2->bridgerx.active & 0xf );
				return -EBUSY;
			}
			spin_lock_irqsave( &chan2->bridgerx.lock, flags );
			chan2->bridgerx.out = (chan2->bridgerx.in - BRIDGEBUF_LAG) & BRIDGEBUF_MAX;
			spin_unlock_irqrestore( &chan2->bridgerx.lock, flags );

			chan2->bridgerx.active = (boardnum << 8) + port;
			chan->bridgetx = &chan2->bridgerx;
		} else {
			portwarn(board,port,
				 "FAILED VT_IOC_LISTEN: channel %d on board %d inactive",
				 vt_data2.channel, vt_data2.board);
			return -ENODEV;
		}
		break;

	    case VT_IOC_UNLISTEN:
		dbgport(1,board,port,"VT_IOC_UNLISTEN");
		if( ! chan->bridgetx ){
			dbgport(2,board,port,"already not listening");
			return -EINVAL;
		}
		chan->bridgetx->active = -1;
		chan->bridgetx = NULL;
		break;

	    case VT_IOC_TAPLISTEN:
		if( copy_from_user(&vt_data2, vt_data.data, sizeof(VT_DATA)) )
			return -EFAULT;

		dbgport(1,board,port,"VT_IOC_TAPLISTEN to %d on board %d",
			vt_data2.channel, vt_data2.board);

		if( chan->bridgetx ){
			// XXX report to which port XXX
			dbgport(1,board,port,"already listening");
			return -EBUSY;
		}
		if( ! check_channel(vt_data2.board, vt_data2.channel) )
			return -EINVAL;

		if( boards[vt_data2.board]->chans[vt_data2.channel].porttype){
			struct channel  *chan2 = &boards[vt_data2.board]->chans[vt_data2.channel];
			unsigned long flags;

			if( chan2->tap_bridge.active != -1 )
			{
				portwarn(board,port,
					 "board %d, port %d is already listened to by %d:%d",
					 vt_data2.board, vt_data2.channel,
					 chan2->tap_bridge.active >> 8,
					 chan2->tap_bridge.active & 0xf );
				return -EBUSY;
			}
			spin_lock_irqsave( &chan2->tap_bridge.lock, flags );
			chan2->tap_bridge.out = (chan2->tap_bridge.in - BRIDGEBUF_LAG) & BRIDGEBUF_MAX;
			spin_unlock_irqrestore( &chan2->tap_bridge.lock, flags );

			chan2->tap_bridge.active = (boardnum << 8) + port;
			chan->bridgetx = &chan2->tap_bridge;
		} else {
			portwarn(board,port,
				 "FAILED VT_IOC_TAPLISTEN: channel %d on board %d inactive",
				 vt_data2.channel, vt_data2.board);
			return -ENODEV;
		}
		break;

	    //XXX
	    #if 0
	    //{{{
	    // Use two listens instead!
	    case VT_IOC_BRIDGE:
		printk(KERN_INFO NAME ":[%02d] Bridge IOCTL\n",port);
		if (chan->bridgetx){
			printk(KERN_INFO NAME ":[%02d] Bridge IOCTL all ready bridged!\n",port);
			return -1;
		}
		ret = copy_from_user(&vt_data2, vt_data.data, sizeof(VT_DATA));
		if (ret){
			printk(KERN_CRIT NAME ":Bridge IOCTL, copy of VT_DATA failed.\n");
			return -ENOMEM;
		}
		printk(KERN_INFO NAME ":[%02d] Listen IOCTL to %d on board %d to %d on board %d\n",
			port,boardnum,vt_data2.channel,vt_data2.board);
		if (chans[(int)vt_data.data]){
			if (chans[(int)vt_data.data]->bridgetx){
				printk(KERN_INFO NAME ":[%02d] Bridge IOCTL all ready bridged!\n",
					(int)vt_data.data);
				return -1;
			}
			chan->bridgetx = &chans[(int)vt_data.data]->bridgerx;
			chans[(int)vt_data.data]->bridgetx = &chan->bridgerx;
			printk(KERN_INFO NAME ":[%02d] Bridge IOCTL %p %p\n",
				(int)vt_data.data, chan->bridgetx,
				&chans[(int)vt_data.data]->bridgetx);
			printk(KERN_INFO NAME ":[%02d] Bridge IOCTL %p %p\n",
				(int)vt_data.data, &chan->bridgerx,
				&chans[(int)vt_data.data]->bridgerx);
		}
		else {
			printk(KERN_INFO NAME ":[%02d] Bridge IOCTL no such channel %d\n",
				port, (int)vt_data.data);
			return -1;
		}
		break;
	    case VT_IOC_UNBRIDGE:
		printk(KERN_INFO NAME ":[%02d] Un-Bridge IOCTL\n", port);
		if (!chan->bridgetx){
			printk(KERN_INFO NAME ":[%02d] Un-Bridge IOCTL Not Bridged!\n", port);
			return -1;
		}
		if (chans[(int)vt_data.data]){
			if (!chans[(int)vt_data.data]->bridgetx){
				printk(KERN_INFO NAME ":[%02d] Un-Bridge IOCTL Not bridged!\n",
					(int)vt_data.data);
				return -1;
			}
			chan->bridgetx = NULL;
			chans[(int)vt_data.data]->bridgetx = NULL;
		}
		else {
			printk(KERN_INFO NAME ":[%02d] Un-Bridge IOCTL no such channel %d\n",
				port, (int)vt_data.data);
			return -1;
		}
		break;
		//}}}
	    #endif


	    case VT_IOC_READ_MSGQ:
		if( unlikely( ! try_module_get(board->owner) ) )
			return -ENODEV;

		// Read the first char from the fifo
		if( fifo_read(boards[boardnum]->txmsgq, &charval, 1) ) {
			module_put( board->owner );
			return -ENODATA;
		}

		dbgport(1,board,port,"VT_IOC_READ_MSGQ %d bytes", charval);
		mess = vmalloc(charval);		    //XXX
		if(!mess) {
			module_put( board->owner );
			return -ENOMEM;
		}

		mess[0] = charval;
		if( fifo_read(boards[boardnum]->txmsgq,&mess[1],charval-1) ){
			module_put( board->owner );
			vfree(mess);
			return -ENODATA;
		}
		module_put( board->owner );

	    #if 0
	    //{{{
		/* Round robin read from event fifo's */
		//XXX static int ev_fifo_rr = 0;
		i=-1;
		ret = 8;
		for(tmp=0;tmp<MAX_BOARDS;tmp++){
			if(boards[ev_fifo_rr]){
				// Read the first char from the fifo
				ret = fifo_read(boards[ev_fifo_rr]->txmsgq, &charval, 1);
				if(!ret){
					i = ev_fifo_rr;
					tmp=MAX_BOARDS;
				}
			}
			ev_fifo_rr++;
			if(ev_fifo_rr >= MAX_BOARDS){
				ev_fifo_rr=0;
			}
		}
		if (-1 == i)
			return -ENODATA;
		mess = vmalloc(charval);
		if (!mess)
			return -ENOMEM;
		mess[0] = charval;
		ret = fifo_read(boards[i]->txmsgq,&mess[1],charval-1);
		if(ret)
			return -ENODATA;
		// Change the board port number to channel number
		mess[2] = boards[i]->chans[(unsigned short)mess[2]]->channel;
	    //}}}
	    #endif

		//XXX report the difference between insufficient length and
		//    failure to copy.
		if( vt_data.length < charval
		 || (vt_data.data && copy_to_user(vt_data.data,mess,charval)) ){
			vfree(mess);
			return -ENOMEM;
			//return -EFAULT;
		}
		vfree(mess);
		break;


	    // Returns the number of configured channels, see
	    // VT_IOC_BOARD_PORT_COUNT for max allocated channels.
	    case VT_IOC_CHANCOUNT:
		for(intval=0,b=0; b < MAX_BOARDS; ++b){
		    if (boards[b]) {
			for(p=0; p < boards[b]->maxports; ++p)
				if( boards[b]->chans[p].porttype ) ++intval;
		    }
		}
		dbgport(1,board,port,"VT_IOC_CHANCOUNT %d", intval);
		if( vt_data.data && copy_to_user(vt_data.data,&intval,sizeof(int)) )
			return -EFAULT;
		break;

	    case VT_IOC_BOARD_COUNT:
		for(intval=0,i=0; i < MAX_BOARDS; ++i) if (boards[i]) ++intval;
		dbgport(1,board,port,"VT_IOC_BOARD_COUNT %d", intval);
		if( vt_data.data && copy_to_user(vt_data.data,&intval,sizeof(int)) )
			return -EFAULT;
		break;

	    case VT_IOC_BOARD_PORT_COUNT:
		dbgport(1,board,port,"VT_IOC_BOARD_PORT_COUNT max = %d", board->maxports );
		if( vt_data.data && copy_to_user(vt_data.data, &board->maxports, sizeof(int)))
			return -EFAULT;
		break;

	    case VT_IOC_BOARD_PORT_TYPE:
		dbgport(1,board,port,"VT_IOC_BOARD_PORT_TYPE %s", porttype(chan->porttype) );
		if( vt_data.data && copy_to_user(vt_data.data, &chan->porttype, sizeof(int)) )
		    return -EFAULT;
		break;


	//XXX  These are all presently unused
	#if 0
	//{{{
	    case VT_IOC_LINEVOLT:
		if (vt_data.data){
			if (copy_to_user(vt_data.data,&chan->lv_last,sizeof(int))){
				return -ENOMEM;
			}
		}
		break;
	    case VT_IOC_LINEPOL:
		if (vt_data.data){
			if (copy_to_user(vt_data.data,&chan->lv_pol,sizeof(int))){
				return -ENOMEM;
			}
		}
		break;
	    case VT_IOC_LINESTATE:
		printk(KERN_INFO NAME ": VT_IOC_LINESTATE: %d\n",chan->lv);
		if (vt_data.data){
			if (copy_to_user(vt_data.data,&chan->lv,sizeof(int))){
				return -ENOMEM;
			}
		}
		break;

	    case VT_IOC_FXS_RING_CADON:
		dbgport(1,board,port,"VT_IOC_FXS_RING_CADON set %d", intval);
		chan->ringcad_on = vt_data.length;
		break;

	    case VT_IOC_FXS_RING_CADOFF:
		dbgport(1,board,port,"VT_IOC_FXS_RING_CADOFF set %d", intval);
		chan->ringcad_off = vt_data.length;
		break;

	    case VT_IOC_FXS_RING_CADON2:
		dbgport(1,board,port,"VT_IOC_FXS_RING_CADON2 set %d", intval);
		chan->ringcad_on2 = vt_data.length;
		break;

	    case VT_IOC_FXS_RING_CADOFF2:
		dbgport(1,board,port,"VT_IOC_FXS_RING_CADOFF2 set %d", intval);
		chan->ringcad_off2 = vt_data.length;
		break;
	//}}}
	#endif


	    case VT_IOC_ECHOCAN_ON:
		mutex_lock(&vtcore_mutex);
		if(vtcore->echo_can == NULL){
			portcrit(board,port,"FAILED to enable echo canceller, none loaded");
			mutex_unlock(&vtcore_mutex);
			return -ENODEV;
		}
		if(chan->echocan != NULL){
			portwarn(board,port,"echo canceller already enabled");
			mutex_unlock(&vtcore_mutex);
			break;
		}
		if( ! try_module_get( vtcore->echo_can->owner ) ){
			mutex_unlock(&vtcore_mutex);
			return -ENODEV;
		}
		vtcore->echo_can->vtecho_open(chan);
		if( ! chan->echocan ){
			portcrit(board,port,"FAILED to open echo canceller");
			module_put( vtcore->echo_can->owner );
			mutex_unlock(&vtcore_mutex);
			return -ENODEV;
		}
		mutex_unlock(&vtcore_mutex);
		dbgport(1,board,port,"echo canceller opened");
		break;

	    case VT_IOC_ECHOCAN_OFF:
		mutex_lock(&vtcore_mutex);
		if(vtcore->echo_can == NULL){
			portcrit(board,port,"FAILED to disable echo canceller, none loaded");
			mutex_unlock(&vtcore_mutex);
			return -ENODEV;
		}
		if(chan->echocan == NULL){
			portwarn(board,port,"echo canceller not enabled");
			mutex_unlock(&vtcore_mutex);
			break;
		}
		vtcore->echo_can->vtecho_close(chan);
		module_put( vtcore->echo_can->owner );
		mutex_unlock(&vtcore_mutex);
		dbgport(1,board,port,"echo canceller closed");
		break;

	    case VT_IOC_FXS_RING:
		if( chan->porttype != VT_FXS_AN ) return -EINVAL;
		if( try_module_get( board->owner ) ){
			board->set_ring( board, port, vt_data.length );
			module_put( board->owner );
			dbgport(1,board,port,"VT_IOC_FXS_RING set %d", vt_data.length);
		} else
			return -ENODEV;
		break;

	    case VT_IOC_DRIVER_VERSION:
		b = strlen(VT_VERSION) + 1;
		if( vt_data.length < b ) return -ENOBUFS;
		if( vt_data.data && copy_to_user(vt_data.data, VT_VERSION, b ) )
			return -EFAULT;
		break;

	    case VT_IOC_SETCOUNTRY:
	    {
		char cname[16];

		if(board->set_country == NULL)
			break;
		if(vt_data.length > sizeof(cname) - 1)
			return -ENOMEM;
		if(copy_from_user(cname, vt_data.data, vt_data.length))
			return -EFAULT;
		cname[vt_data.length] = '\0';

		if( try_module_get( board->owner ) ){
			board->set_country(board, port, cname);
			module_put( board->owner );
			dbgport(1,board,port,"VT_IOC_SETCOUNTRY %s", cname);
		} else
			return -ENODEV;
		break;
	    }
	    case VT_IOC_HOOK:
		// Check if we need to unbridge, then just pass this one along.
		// Action is dependent on the length parameter:
		// 0,         Go on-hook
		// 1,         Go off-hook
		// 50 - 1500, Perform a hook flash, value is the duration in ms
		if (vt_data.length == 0 && chan->bridgetx){
			chan->bridgetx->active = -1;
			chan->bridgetx = NULL;
			dbgport(1,board,port,"VT_IOC_HOOK removing bridge");
		}
	    case VT_IOC_CHAN_BALANCE_SET:
	    //case VT_IOC_REGREAD:
	    //case VT_IOC_REGWRITE:
	    default:
		if( ! try_module_get( board->owner ) )
			return -ENODEV;
		if (board->ioctl){
			i = board->ioctl(board, port, cmd,
					 vt_data.data, vt_data.length);
			module_put( board->owner );
			return i;
		} else {
			module_put( board->owner );
			portcrit(board,port,"no board ioctl registered for ioctl %d", cmd);
			return -EINVAL;
		}
	}
	return 0;
} //}}}


static int vt_country_proc_show(struct seq_file *m, void *v)
{ //{{{
	struct vtboard *board;
	unsigned	boardnum = (long)m->private >> 16;
	unsigned	portnum  = (long)m->private & 0xff;

	mutex_lock(&vtcore_mutex);
	board = vtcore->boards[boardnum];

	if (try_module_get(board->owner)) {
		seq_puts(m, board->chans[portnum].country);
		module_put(board->owner);
	}

	mutex_unlock(&vtcore_mutex);
	return 0;
}

static int vt_country_proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, vt_country_proc_show, PDE_DATA(inode));
}

static ssize_t vt_country_proc_write(struct file *file, const char *buffer,
						size_t count, loff_t *pos)
{
	struct vtboard	*board;
	void		*data		= PDE_DATA(file_inode(file));
	unsigned	boardnum	= (long)data >> 16;
	unsigned	portnum		= (long)data & 0xff;
	char    	buf[16]		= { 0 };
	char   		*b;
	int     	ret;

	if (count >= sizeof(buf))
		return -EINVAL;

	if (copy_from_user(buf, buffer, count))
		return -EFAULT;

	for (b = buf; *b != '\0'; ++b) {
		if (*b == '\n') {
			*b = '\0';
			break;
		}
	}

	mutex_lock(&vtcore_mutex);
	board = vtcore->boards[boardnum];

	if (try_module_get(board->owner)) {
		ret = board->set_country(board, portnum, buf);
		if (ret == 0)
			ret = count;
		module_put(board->owner);
	} else
		ret = -ENODEV;

	mutex_unlock(&vtcore_mutex);
	return ret;
}

static const struct file_operations vt_country_proc_fops = {
	.owner		= THIS_MODULE,
	.open		= vt_country_proc_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
	.write		= vt_country_proc_write,
}; //}}}

// Template definitions for port ops that communicate a single integer value.
// {{{
#define PROC_READ_PORT_(attrib)							\
static int vt_##attrib##_proc_show(struct seq_file *m, void *v)			\
{										\
	struct vtboard *board;							\
	unsigned	boardnum = (long)m->private >> 16;			\
	unsigned	portnum  = (long)m->private & 0xff;			\
										\
	mutex_lock(&vtcore_mutex);						\
	board = vtcore->boards[boardnum];					\
										\
	if (try_module_get(board->owner)) {					\
		int err;							\
		int ret = board->get_##attrib(board, portnum, &err);		\
		if (err)							\
			seq_printf(m, "ERR: %d", err);				\
		else								\
			seq_printf(m, "%d", ret);				\
		module_put(board->owner);					\
	}									\
	mutex_unlock(&vtcore_mutex);						\
	return 0;								\
}										\
										\
static int vt_##attrib##_proc_open(struct inode *inode, struct file *file)	\
{										\
	return single_open(file, vt_##attrib##_proc_show, PDE_DATA(inode));	\
}

#define PROC_WRITE_PORT_(attrib)						\
static ssize_t vt_##attrib##_proc_write(struct file *file, const char *buffer,	\
						size_t count, loff_t *pos)	\
{										\
	struct vtboard *board;							\
	void		*data		= PDE_DATA(file_inode(file));		\
	unsigned	boardnum	= (long)data >> 16;			\
	unsigned	portnum		= (long)data & 0xff;			\
	long		val;							\
	char		buf[8] = { 0 };						\
	char		*end;							\
	int		ret;							\
										\
	if (count >= sizeof(buf))						\
		return -EINVAL;							\
										\
	if (copy_from_user(buf, buffer, count))					\
		return -EFAULT;							\
										\
	val = simple_strtol(buf, &end, 0);					\
	if (buf == end)								\
		return -EINVAL;							\
										\
	mutex_lock(&vtcore_mutex);						\
	board = vtcore->boards[boardnum];					\
										\
	if (try_module_get(board->owner)) {					\
		ret = board->set_##attrib(board, portnum, val);			\
		if (ret == 0)							\
			ret = count;						\
		module_put(board->owner);					\
	} else									\
		ret = -ENODEV;							\
										\
	mutex_unlock(&vtcore_mutex);						\
	return ret;								\
}

#define PROC_READ_PORT(attrib)							\
	PROC_READ_PORT_(attrib)							\
										\
static const struct file_operations vt_##attrib##_proc_fops = {			\
	.open		= vt_##attrib##_proc_open,				\
	.read		= seq_read,						\
	.llseek		= seq_lseek,						\
	.release	= single_release,					\
};

#define PROC_READWRITE_PORT(attrib)						\
	PROC_READ_PORT_(attrib)							\
	PROC_WRITE_PORT_(attrib)						\
										\
static const struct file_operations vt_##attrib##_proc_fops = {			\
	.owner		= THIS_MODULE,						\
	.open		= vt_##attrib##_proc_open,				\
	.read		= seq_read,						\
	.llseek		= seq_lseek,						\
	.release	= single_release,					\
	.write		= vt_##attrib##_proc_write,				\
};
//}}}

PROC_READWRITE_PORT(playgain)
PROC_READWRITE_PORT(recgain)
PROC_READWRITE_PORT(hook)
PROC_READWRITE_PORT(ring)
PROC_READWRITE_PORT(polarity)
PROC_READ_PORT(linevolt)

// Create a read only proc entry 'node' for 'board',
// returning the constant integer 'val'.
void vt_create_board_proc_const_int(struct vtboard *board, const char *node, long val)
{ //{{{
	if (!proc_create_data(node, S_IRUGO, board->procfs_root,
				 &vt_int_proc_fops, (void*)val))
		crit("failed to create procfs board%d/%s", board->boardnum, node);
} //}}}


static int vtboard_tap_open( struct inode *inode, struct file *filep )
{ //{{{
	int             port  = iminor(inode);
	struct vtboard *board = container_of(inode->i_cdev, struct vtboard, tap_cdev);
	struct channel *chan  = &board->chans[ port ];
	unsigned long   flags;

	if( (filep->f_mode & FMODE_READ) == 0 )
		return -EINVAL;

	spin_lock_irqsave( &chan->tap_read_lock, flags );

	if( chan->tap_buf_state != TAP_BUF_UNUSED ) {
		portwarn(board,port,"tap device in use");
		spin_unlock_irqrestore( &chan->tap_read_lock, flags );
		return -EBUSY;
	}
	// Just mark it as used (but not functional) so we can
	// release the lock.  The buffer allocation may sleep.
	chan->tap_buf_state = TAP_BUF_INIT;

	spin_unlock_irqrestore( &chan->tap_read_lock, flags );

	if( ! init_lring_buf( &chan->tap_buf ) ) {
		chan->tap_buf_state = TAP_BUF_UNUSED;
		return -ENOMEM;
	}

	filep->private_data = chan;
	chan->tap_buf_state = TAP_BUF_ACTIVE;

	dbginfo(1,"tap device %d:%d opened", imajor(inode), iminor(inode));

	return 0;
} //}}}

static int vtboard_tap_release( struct inode *inode, struct file *filep )
{ //{{{
	struct vtboard *board = container_of(inode->i_cdev, struct vtboard, tap_cdev);
	struct channel *chan  = &board->chans[ iminor(inode) ];
	unsigned long   flags;

	spin_lock_irqsave( &chan->tap_read_lock, flags );
	chan->tap_buf_state = TAP_BUF_INIT;
	spin_unlock_irqrestore( &chan->tap_read_lock, flags );

	release_lring_buf( &chan->tap_buf );
	chan->tap_buf_state = TAP_BUF_UNUSED;

	dbginfo(1,"tap device %d:%d released", imajor(inode), iminor(inode));
	return 0;
} //}}}

static ssize_t vtboard_tap_read( struct file *filep, char *buf, size_t n, loff_t *pos )
{ //{{{
	struct channel *chan  = filep->private_data;
	lring_buf      *rbuf  = &chan->tap_buf;
	int             bfull = lring_buf_how_full( rbuf );
	int             count;
	int             waitfull;

	if( n == 0 ) return n;

    wake_retry:

	//count = (n < PAGE_SIZE) ? n : PAGE_SIZE;
	count = (n < bfull) ? n : bfull;

	//dbginfo(1,"tap device read n = %d, pos = %d, full = %d, count = %d",
	//	(int)n, (int)*pos, bfull, count);

	// Copy data to the user if we can supply them with:
	// - all the data they asked for, or
	// - more than half a page right now, or
	// - anything at all if they opened the device non-blocking.
	// Else we block until one of the above is true, or return an error
	// for non-blocking devices.

	if( count == n || count >= (PAGE_SIZE >> 1)
	 || (count && (filep->f_flags & O_NONBLOCK)) )
	{
		unsigned long  flags;
		u32            readloc;

		// Take a copy of the current read pointer.  We hold the lock
		// so we know it is valid at the time the copy is made.
		spin_lock_irqsave( &chan->tap_read_lock, flags );
		readloc = rbuf->read;
		spin_unlock_irqrestore( &chan->tap_read_lock, flags );

	    overflow_retry:

		//dbginfo(1,"tap device copy %d", count);
		if( copy_to_user( buf, lring_buf_read(rbuf), count ) )
			return -EFAULT;

		// Check the read pointer again, to see if it has changed.
		// If it has, then:
		//  - a write overflow occurred while we were doing the copy to
		//    user space, and some or all of that data may be corrupt.
		//  - we know the buffer is now completely full.
		// So we can simply skip ahead to the new read location and try
		// to copy as much data as the user originally requested, and
		// we only suffer a double copy under actual overflow conditions
		// rather than at every transaction as we would with a bounce
		// buffer filled with the lock held.
		spin_lock_irqsave( &chan->tap_read_lock, flags );
		if( readloc != rbuf->read ) {
			readloc = rbuf->read;
			spin_unlock_irqrestore( &chan->tap_read_lock, flags );
			count = (n < PAGE_SIZE) ? n : PAGE_SIZE;
			goto overflow_retry;
		}
		rbuf->read += count;
		spin_unlock_irqrestore( &chan->tap_read_lock, flags );

		*pos       += count;
		return count;
	}
	else if( filep->f_flags & O_NONBLOCK )
		return -EAGAIN;

	//XXX If we really need to do this, we are probably better off
	//    making this buffer two pages in length instead.  We make
	//    userspace work much harder if we return less than n bytes,
	//    and n is typically (or should be) a full page.
	//waitfull = (n < (PAGE_SIZE >> 1)) ? n : (PAGE_SIZE >> 1);
	waitfull = (n < PAGE_SIZE) ? n : PAGE_SIZE;

	//dbginfo(1,"tap device wait for %d", waitfull);

	if( wait_event_interruptible(chan->tap_wait,
				     (bfull = lring_buf_how_full(rbuf)) >= waitfull) )
		return -ERESTARTSYS;

	goto wake_retry;
} //}}}

static struct file_operations board_tap_fops =
{ //{{{
	.owner        = THIS_MODULE,
	.llseek       = NULL,
	.poll         = NULL,
	.open         = vtboard_tap_open,
	.release      = vtboard_tap_release,
	.read         = vtboard_tap_read,
	.write        = NULL,
	.fasync       = NULL
}; //}}}


int vt_board_register(struct vtboard *board)
{ //{{{
	int ret, i = 0;

	mutex_lock(&vtcore_mutex);
	for(; i < MAX_BOARDS && vtcore->boards[i]; ++i);
	if (i == MAX_BOARDS){
		warn("Can't register board [%s], maximum boards reached",board->name);
		goto hell;
	}

	board->boardnum = i;
	dbgboard(1,board,"registering %d port %s", board->maxports, board->name);


#define ALLOC_DEV_REGION(name,error)					    \
	ret = alloc_chrdev_region( &board->name##_dev, 0, board->maxports,  \
				   NAME " " #name );			    \
	if( ret ) {							    \
		boardcrit(board, "Failed to allocate " #name " devices (%d)", ret); \
		goto error;						    \
	}								    \
	dbgboard(1,board,"using " #name " device nodes: %d:%d-%d",	    \
		   MAJOR(board->name##_dev), MINOR(board->name##_dev),	    \
		   board->maxports);

	ALLOC_DEV_REGION(tap, hell);
	cdev_init(&board->tap_cdev, &board_tap_fops);
	board->tap_cdev.owner = board->owner;

	vtcore->boards[i] = board;

	if( vtcore->procfs_root ) { //{{{
		char  dirname[8] = { 0 };

		snprintf(dirname, sizeof(dirname)-1, "board%d", i);
		board->procfs_root = proc_mkdir(dirname, vtcore->procfs_root);
		if( board->procfs_root ) {
			if (!proc_create_data("name", S_IRUGO, board->procfs_root,
					&vt_string_proc_fops, (void*)board->name))
				crit("failed to create procfs board%d/name", i);

			if (!proc_create_data("id", S_IRUGO, board->procfs_root,
					&vt_string_proc_fops, board->serial))
				crit("failed to create procfs board%d/id", i);

			vt_create_board_proc_const_int(board,"maxports",
						       board->maxports);
		} else
			crit("failed to create procfs board%d entry", board->boardnum);
	} //}}}

	for(i=0; i < board->maxports; ++i)
	{
		struct channel *chan = &board->chans[i];
		struct device  *d;

		chan->state   = CH_IDLE;
		chan->echocan = NULL;

		if( ! chan->porttype ){
			dbgport(2,board,i,"port is inactive");
			continue;
		}
		if( chan->codec == VT_UNKNOWN ){
			portwarn(board,i,"codec unset defaulting to linear");
			chan->codec = VT_LINEAR;
		}

		/* FIFO stuff */
		chan->tx_empty        = 0;
		chan->bridgerx.in     = 0;
		chan->bridgerx.out    = 0;
		chan->bridgerx.active = -1;
		spin_lock_init( &chan->bridgerx.lock );

#ifdef FIFOSTATS
		chan->min_rxfifo = FIFO_SIZE;
		chan->max_rxfifo = 0;
		chan->total_rxfifo = 0;
		chan->rxfifo_samples = 0;
#endif

		//XXX Check these allocations actually succeed.
		fifo_create(&chan->txfifo,FIFO_SIZE);
		fifo_create(&chan->rxfifo,FIFO_SIZE);

		spin_lock_init(&chan->tap_read_lock);
		init_waitqueue_head(&chan->tap_wait);

		chan->tap_bridge.in     = 0;
		chan->tap_bridge.out    = 0;
		chan->tap_bridge.active = -1;
		spin_lock_init( &chan->tap_bridge.lock );


	#define CREATE_PORT_DEVICE(name,i)					    \
		d = device_create( vt_class, NULL,				    \
				   MKDEV( MAJOR(board->name##_dev),		    \
					  MINOR(board->name##_dev) + i ),	    \
				   NULL,					    \
				   "vt!board%d!" #name "%d", board->boardnum, i );    \
		if( IS_ERR(d) ) {						    \
			portcrit(board,i,"FAILED to register " #name " device (%ld)", \
				 PTR_ERR(d));					    \
		}

		CREATE_PORT_DEVICE(tap, i);


		if( board->procfs_root ){ //{{{
			char  dirname[8] = { 0 };

			snprintf(dirname, sizeof(dirname)-1, "port%d", i);
			chan->procfs_root = proc_mkdir(dirname, board->procfs_root);
			if( chan->procfs_root ) {
				int mode;

				if (!proc_create_data("type", S_IRUGO, chan->procfs_root,
							&vt_string_proc_fops,
							(void*)porttype(chan->porttype))) {
					crit("failed to create board%d/port%d/type",
								board->boardnum, i);
				}

				if (board->set_country || chan->country) {
					if (!proc_create_data("country",
						S_IRUGO | (board->set_country ? S_IWUSR : 0),
						chan->procfs_root,
						&vt_country_proc_fops,
						(void*)(((long)board->boardnum << 16) | i))) {

						crit("failed to create board%d/port%d/country",
									board->boardnum, i);
					}
				}

		// Template for a port op passing a single int value.
		#define DEFINE_PROC_PORT_OP_(attrib,mode)				\
			if (mode) {							\
				if (!proc_create_data(#attrib, mode, chan->procfs_root,	\
							&vt_##attrib##_proc_fops,	\
					(void*)(((long)board->boardnum << 16) | i))) {	\
					crit("failed to create board%d/port%d/"#attrib, \
								board->boardnum, i);    \
				}							\
			}
		#define DEFINE_PROC_PORT_OP(attrib)			\
			mode  = board->set_##attrib ? S_IWUSR : 0;	\
			mode |= board->get_##attrib ? S_IRUGO : 0;	\
			DEFINE_PROC_PORT_OP_(attrib,mode)

				DEFINE_PROC_PORT_OP(playgain)
				DEFINE_PROC_PORT_OP(recgain)

				switch( chan->porttype ){
				    case VT_FXO_AN:
					DEFINE_PROC_PORT_OP(hook)
					DEFINE_PROC_PORT_OP_(ring, S_IRUGO)

					if (board->get_linevolt)
						DEFINE_PROC_PORT_OP_(linevolt, S_IRUGO)
					break;

				    case VT_FXS_AN:
					DEFINE_PROC_PORT_OP_(hook, S_IRUGO)
					DEFINE_PROC_PORT_OP(ring)
					DEFINE_PROC_PORT_OP(polarity)
				}
			} else
				crit("failed to create procfs board%d/port%d entry",
				     board->boardnum, i);
		} //}}}
		dbgport(2,board,i,"%s port active",porttype(chan->porttype));
	}

	/* Initialise MSG fifos */
	//XXX Check these allocations actually succeed.
	//    Do we need to check (again?) that at least one port is active?
	fifo_create(&board->txmsgq,FIFO_SIZE);
	//fifo_create(&board->rxmsgq,FIFO_SIZE);

	/* We must be ready to handle requests before this operation */
	ret = cdev_add( &board->tap_cdev, board->tap_dev, board->maxports );
	if(ret){ boardcrit(board,"ERROR adding tap char devices (%d)", ret); }

	mutex_unlock(&vtcore_mutex);

	boardinfo(board,"registered %s", board->name);
	return RET_OK;

    //hell_2:
	// XXX unwind fifo allocations
	vtcore->boards[board->boardnum] = NULL;
    hell:
	mutex_unlock(&vtcore_mutex);
	return RET_FAIL;
} //}}}

void vt_board_unregister(struct vtboard *board)
{ //{{{
	int i;

	dbginfo(1,"unregistering board %d [%s]", board->boardnum, board->name);
	mutex_lock(&vtcore_mutex);

	for(i=0; i < board->maxports; ++i){
		struct channel *chan = &board->chans[i];

		if( chan->state != CH_IDLE )
			portwarn(board,i,"channel not idle, unregistering anyway");

		if( ! chan->porttype ){
			dbgport(2,board,i,"was inactive");
			continue;
		}

		device_destroy( vt_class, MKDEV( MAJOR(board->tap_dev),
						 MINOR(board->tap_dev) + i ) );

		if( chan->procfs_root ) {
			char  dirname[8] = { 0 };

			snprintf(dirname, sizeof(dirname)-1, "port%d", i);
			remove_proc_entry("type", chan->procfs_root);
			remove_proc_entry("country", chan->procfs_root);
			remove_proc_entry("playgain", chan->procfs_root);
			remove_proc_entry("recgain", chan->procfs_root);
			remove_proc_entry("hook", chan->procfs_root);
			remove_proc_entry("ring", chan->procfs_root);
			if( chan->porttype == VT_FXS_AN )
				remove_proc_entry("polarity", chan->procfs_root);
			if( chan->porttype == VT_FXO_AN )
				remove_proc_entry("linevolt", chan->procfs_root);

			remove_proc_entry(dirname, board->procfs_root);
		}

		fifo_destroy(chan->txfifo);
		fifo_destroy(chan->rxfifo);
	}

	if( board->procfs_root ){
		char  dirname[8] = { 0 };

		// We could just replace all of this (and the above lot) with:
		// proc_remove(board->procfs_root);
		// but it and remove_proc_subtree didn't appear until v3.9-rc5
		// so stick with what works with older kernels for a bit longer.

		snprintf(dirname, sizeof(dirname)-1, "board%d", board->boardnum);
		remove_proc_entry("name", board->procfs_root);
		remove_proc_entry("id", board->procfs_root);
		remove_proc_entry("maxports", board->procfs_root);
		remove_proc_entry(dirname, vtcore->procfs_root);
	}

	cdev_del( &board->tap_cdev );
	unregister_chrdev_region( board->tap_dev, board->maxports );

	fifo_destroy(board->txmsgq);
	//fifo_destroy(board->rxmsgq);
	vtcore->boards[board->boardnum] = NULL;

	mutex_unlock(&vtcore_mutex);
	boardinfo(board,"unregistered %s", board->name);
} //}}}

void vt_echo_register(struct vtecho *ec)
{ //{{{
	mutex_lock(&vtcore_mutex);
	vtcore->echo_can = ec;
	if( vtcore->procfs_root ) {
		if (!proc_create_data("echo_canceller", S_IRUGO, vtcore->procfs_root,
						&vt_string_proc_fops, ec->desc))
			crit("failed to create procfs echo_canceller");
	}
	info("echo canceller [%s] registered",ec->desc);
	mutex_unlock(&vtcore_mutex);
} //}}}

void vt_echo_unregister(struct vtecho *ec)
{ //{{{
	int b=0, p;

	mutex_lock(&vtcore_mutex);
	if(ec != vtcore->echo_can){
		crit("vt_echo_unregister for '%s', but '%s' is registered",
		     ec ? ec->desc : "None",
		     vtcore->echo_can ? vtcore->echo_can->desc : "None" );
	}
	if( vtcore->procfs_root ) {
		remove_proc_entry("echo_canceller", vtcore->procfs_root);
	}
	for(; b < MAX_BOARDS; ++b) {
	    struct vtboard *board = vtcore->boards[b];
	    if (board) {
		for(p=0; p < board->maxports; ++p){
		    struct channel *chan = &board->chans[p];
		    if( chan->echocan != NULL ){
			    vtcore->echo_can->vtecho_close(chan);
			    module_put( vtcore->echo_can->owner );
		    }
		}
	    }
	}
	info("echo canceller [%s] un-registered",ec->desc);
	vtcore->echo_can = NULL;
	mutex_unlock(&vtcore_mutex);
} //}}}

// Fill the bridge buffer
inline void bridge_write(struct bridge *b, short *buf)
{ //{{{
	unsigned long flags;
	int in = b->in;

	spin_lock_irqsave( &b->lock, flags );
	// if buffer is full push the output pointer around too.
	if( ((in - b->out) & BRIDGEBUF_MAX) == BRIDGEBUF_MAX )
		++b->out;
	spin_unlock_irqrestore( &b->lock, flags );

	++in;
	in &= BRIDGEBUF_MAX;
	memcpy(&b->buffer[FRAME * in], buf, FRAME * sizeof(short));
	b->in = in;
} //}}}

// Empty the bridge buffer
inline void bridge_read(struct channel *chan, short *buf)
{ //{{{
	unsigned long flags;
	struct bridge *b = chan->bridgetx;

	spin_lock_irqsave( &b->lock, flags );
	memcpy(buf, &b->buffer[FRAME * b->out], FRAME * sizeof(short));
	// if buffer is empty repeat the last audio sample
	if( (b->in - b->out) & BRIDGEBUF_MAX ) {
		++b->out;
		b->out &= BRIDGEBUF_MAX;
	}
	spin_unlock_irqrestore( &b->lock, flags );
} //}}}

// This function must be called with the buffer read lock held
// since the read pointer will be advanced if it overflows.
static inline void advance_lringbuf_frame(lring_buf *buf)
{ //{{{
	if( lring_buf_how_empty(buf) < FRAME * sizeof(u16) )
	{
		buf->write += FRAME * sizeof(u16);
		buf->read   = buf->write - PAGE_SIZE;
	}
	else	buf->write += FRAME * sizeof(u16);
} //}}}

void vt_write(struct vtboard *board)
{ //{{{
	short buf[FRAME];
	long tmp;
	int i=0, x;

	for(; i < board->maxports; ++i){
		struct channel *chan = &board->chans[i];
		lring_buf      *tbuf = &chan->tap_buf;
		int             wake = 0;
		unsigned long   flags;

		//XXX What (else?) to do here for empty ports?
		if( ! chan->porttype ) continue;

		chan->tx_empty = fifo_read(chan->txfifo, (unsigned char *)buf,
					   FRAME*sizeof(short)) == FIFO_EMPTY
				 ? 1 : 0;

		/* We should also check for bridging to other ports */
		if (chan->bridgetx) {
			/* ok, we should combine the audio from the fifo and bridge */
			if ( ! chan->tx_empty ){
				short bbuf[FRAME];

				bridge_read( chan, bbuf );
				for(x=0; x < FRAME; ++x) {
					tmp = buf[x] + bbuf[x];
					if(unlikely(tmp > 32767))       buf[x] =  32767;
					else if(unlikely(tmp < -32768)) buf[x] = -32768;
					else                            buf[x] = tmp;
				}
			} else  bridge_read( chan, buf );

		} else if( chan->tx_empty ) {
			if(chan->state == CH_IDLE)
				memset(chan->txbuf, 0x00, FRAME);
			else {
				switch( chan->codec ){
				    case VT_MULAW:  memset(chan->txbuf, 0xff, FRAME);
						    break;
				    case VT_ALAW:   memset(chan->txbuf, 0xd5, FRAME);
						    break;
				    case VT_LINEAR: memset(chan->txbuf, 0x00, FRAME);
				}
			}
			//if(chan->state != CH_IDLE)
			//  info("vt_write - FIFO empty [%d]",chan->channel);

			// Lock the tap buffer to ensure it is not released while we
			// are trying to write to it and because we may need to modify
			// the read pointer in the case of overflow here.
			spin_lock_irqsave( &chan->tap_read_lock, flags );

			if( chan->tap_buf_state != TAP_BUF_ACTIVE ) {
				spin_unlock_irqrestore( &chan->tap_read_lock, flags );

				if( chan->tap_bridge.active != -1 ) {
					switch( chan->tap_state ) {
					    case 0:
						memset(chan->tap_frame, 0, FRAME * sizeof(u16));
						chan->tap_state = TAP_WRITE_HALF;
						break;

					    case TAP_WRITE_HALF:
						bridge_write(&chan->tap_bridge, chan->tap_frame);
						memset(chan->tap_frame, 0, FRAME * sizeof(u16));
						break;

					    case TAP_READ_HALF:
						bridge_write(&chan->tap_bridge, chan->tap_frame);
						chan->tap_state = 0;
						break;
					}
				}
				continue;
			}

			switch( chan->tap_state ) {
			    case 0:
				memset(lring_buf_write(tbuf), 0, FRAME * sizeof(u16));
				memset(chan->tap_frame, 0, FRAME * sizeof(u16));
				chan->tap_state = TAP_WRITE_HALF;
				break;

			    case TAP_WRITE_HALF:
				advance_lringbuf_frame(tbuf);
				memset(lring_buf_write(tbuf), 0, FRAME * sizeof(u16));

				if( chan->tap_bridge.active != -1 ) {
					bridge_write(&chan->tap_bridge, chan->tap_frame);
					memset(chan->tap_frame, 0, FRAME * sizeof(u16));
				}

				wake = 1;
				break;

			    case TAP_READ_HALF:
				advance_lringbuf_frame(tbuf);

				if( chan->tap_bridge.active != -1 )
					bridge_write(&chan->tap_bridge, chan->tap_frame);

				chan->tap_state = 0;
				wake = 1;
				break;
			}
			spin_unlock_irqrestore( &chan->tap_read_lock, flags );

			if(wake) wake_up_interruptible(&chan->tap_wait);

			continue;
		}

		switch( chan->codec ){
		    case VT_MULAW:  linear2mulaw(chan->txbuf, buf, FRAME); break;
		    case VT_ALAW:   linear2alaw(chan->txbuf, buf, FRAME); break;
		    case VT_LINEAR: memcpy(chan->txbuf, buf, FRAME*sizeof(short));
		}

		// Lock the tap buffer to ensure it is not released while we
		// are trying to write to it and because we may need to modify
		// the read pointer in the case of overflow here.
		spin_lock_irqsave( &chan->tap_read_lock, flags );

		if( chan->tap_buf_state != TAP_BUF_ACTIVE ) {
			spin_unlock_irqrestore( &chan->tap_read_lock, flags );

			if( chan->tap_bridge.active != -1 ) {
				switch( chan->tap_state ) {
				    case 0:
					memcpy(chan->tap_frame, buf, FRAME * sizeof(u16));
					chan->tap_state = TAP_WRITE_HALF;
					break;

				    case TAP_WRITE_HALF:
					bridge_write(&chan->tap_bridge, chan->tap_frame);
					memcpy(chan->tap_frame, buf, FRAME * sizeof(u16));
					break;

				    case TAP_READ_HALF:
				    {
					u16 *b = buf;
					u16 *p = chan->tap_frame;
					u16 *e = p + FRAME;

					for(; p != e; ++p, ++b) *p += *b;
					bridge_write(&chan->tap_bridge, chan->tap_frame);

					chan->tap_state = 0;
					break;
				    }
				}
			}
			continue;
		}

		switch( chan->tap_state ) {
		    case 0:
			memcpy(lring_buf_write(tbuf), buf, FRAME * sizeof(u16));
			memcpy(chan->tap_frame, buf, FRAME * sizeof(u16));
			chan->tap_state = TAP_WRITE_HALF;
			break;

		    case TAP_WRITE_HALF:
			advance_lringbuf_frame(tbuf);
			memcpy(lring_buf_write(tbuf), buf, FRAME * sizeof(u16));

			if( chan->tap_bridge.active != -1 ) {
				bridge_write(&chan->tap_bridge, chan->tap_frame);
				memcpy(chan->tap_frame, buf, FRAME * sizeof(u16));
			}

			wake = 1;
			break;

		    case TAP_READ_HALF:
		    {
			u16 *b = buf;
			u16 *p = lring_buf_write(tbuf);
			u16 *e = p + FRAME;

			for(; p != e; ++p, ++b) *p += *b;
			advance_lringbuf_frame(tbuf);

			if( chan->tap_bridge.active != -1 ) {
				b = buf;
				p = chan->tap_frame;
				e = p + FRAME;
				for(; p != e; ++p, ++b) *p += *b;
				bridge_write(&chan->tap_bridge, chan->tap_frame);
			}

			chan->tap_state = 0;
			wake = 1;
			break;
		    }
		}
		spin_unlock_irqrestore( &chan->tap_read_lock, flags );

		if(wake) wake_up_interruptible(&chan->tap_wait);
	}
} //}}}

void vt_read(struct vtboard *board)
{ //{{{
	short buf[FRAME];	//XXX size, type?  casts below (and above)
	short ecbuf[FRAME];
	int i=0;

	for(; i < board->maxports; ++i) {
		struct channel *chan = &board->chans[i];
		lring_buf      *tbuf = &chan->tap_buf;
		int             wake = 0;
		unsigned long   flags;

		//XXX What (else?) to do here for empty ports?
		if( ! chan->porttype ) continue;
	  //XXX	if(chan->state == CH_IDLE) continue;

		switch( chan->codec ){
		    case VT_MULAW:  mulaw2linear(buf,chan->rxbuf,FRAME); break;
		    case VT_ALAW:   alaw2linear(buf,chan->rxbuf,FRAME);  break;
		    case VT_LINEAR: memcpy(buf,chan->rxbuf,FRAME*sizeof(short));
		}
		if (chan->echocan != NULL){
			vtcore->echo_can->vtecho_proc(chan, ecbuf,
						      (short*)chan->txbuf,
						      (short*)buf, FRAME);
			memcpy(buf, ecbuf, sizeof(short)*FRAME);
		}
		fifo_write(chan->rxfifo, (unsigned char*)buf, FRAME*sizeof(short));
		bridge_write( &chan->bridgerx, buf );

		// Lock the tap buffer to ensure it is not released while we
		// are trying to write to it and because we may need to modify
		// the read pointer in the case of overflow here.
		spin_lock_irqsave( &chan->tap_read_lock, flags );

		if( chan->tap_buf_state != TAP_BUF_ACTIVE ) {
			spin_unlock_irqrestore( &chan->tap_read_lock, flags );

			if( chan->tap_bridge.active != -1 ) {
				switch( chan->tap_state ) {
				    case 0:
					memcpy(chan->tap_frame, buf, FRAME * sizeof(u16));
					chan->tap_state = TAP_READ_HALF;
					break;

				    case TAP_WRITE_HALF:
				    {
					u16 *b = buf;
					u16 *p = chan->tap_frame;
					u16 *e = p + FRAME;

					for(; p != e; ++p, ++b) *p += *b;
					bridge_write(&chan->tap_bridge, chan->tap_frame);

					chan->tap_state = 0;
					break;
				    }
				    case TAP_READ_HALF:
					bridge_write(&chan->tap_bridge, chan->tap_frame);
					memcpy(chan->tap_frame, buf, FRAME * sizeof(u16));
					break;
				}
			}
			continue;
		}

		switch( chan->tap_state ) {
		    case 0:
			memcpy(lring_buf_write(tbuf), buf, FRAME * sizeof(u16));
			memcpy(chan->tap_frame, buf, FRAME * sizeof(u16));
			chan->tap_state = TAP_READ_HALF;
			break;

		    case TAP_WRITE_HALF:
		    {
			u16 *b = buf;
			u16 *p = lring_buf_write(tbuf);
			u16 *e = p + FRAME;

			for(; p != e; ++p, ++b) *p += *b;
			advance_lringbuf_frame(tbuf);

			if( chan->tap_bridge.active != -1 ) {
				b = buf;
				p = chan->tap_frame;
				e = p + FRAME;
				for(; p != e; ++p, ++b) *p += *b;
				bridge_write(&chan->tap_bridge, chan->tap_frame);
			}

			chan->tap_state = 0;
			wake = 1;
			break;
		    }
		    case TAP_READ_HALF:
			advance_lringbuf_frame(tbuf);
			memcpy(lring_buf_write(tbuf), buf, FRAME * sizeof(u16));

			if( chan->tap_bridge.active != -1 ) {
				bridge_write(&chan->tap_bridge, chan->tap_frame);
				memcpy(chan->tap_frame, buf, FRAME * sizeof(u16));
			}

			wake = 1;
			break;
		}
		spin_unlock_irqrestore( &chan->tap_read_lock, flags );

		if(wake) wake_up_interruptible(&chan->tap_wait);
	}
} //}}}

int vt_send_event(struct vtboard *board, char *mess, int size)
{
	int ret;
	ret = fifo_write(board->txmsgq, mess, size);
	return ret;
}


/*---------------------------------------------------------------------------*\
	Fifo functions
\*---------------------------------------------------------------------------*/
void *fifo_malloc(int size);
void fifo_free(void *mem);
void fifo_memcpy(char *dest, char *src, int length);


/*--------------------------------------------------------------------------*\
	Creates a FIFO.
\*--------------------------------------------------------------------------*/

void fifo_create(void **ppvfifo, int  size)
{
	FIFO   *fifo;

	*ppvfifo = (void*)fifo_malloc(sizeof(FIFO));
	fifo = (FIFO*)(*ppvfifo);

	fifo->pstart = (char*)fifo_malloc(size);
	fifo->pend = fifo->pstart + size;

	fifo->pwr = fifo->pstart;
	fifo->prd = fifo->pstart;
	fifo->size = size;
}

/*--------------------------------------------------------------------------*\
	Destroys a previously created FIFO.
\*--------------------------------------------------------------------------*/

void fifo_destroy(FIFO *fifo)
{
	if( fifo ) {
		fifo_free(fifo->pstart);
		fifo_free(fifo);
	}
}

/*--------------------------------------------------------------------------*\
	Writes a block of chars to a FIFO.  Returns OK if successful.
\*--------------------------------------------------------------------------*/

int fifo_write(void *pvfifo, char *buf, int size)
{
	FIFO   *fifo;
	int    chars_used;	// used space in FIFO
	int    chars_free;	// free space in FIFO
	int    copy_first;	// size of first block move
	char   *new_pwr;	// modified pwr
	char   *tmp;
	char   *prd;

	fifo = (FIFO*)pvfifo;

	// determine if there is data in FIFO to fill buf

	prd = fifo->prd;
	if (fifo->pwr >= prd)
		chars_used = fifo->pwr - prd;
	else
		chars_used = fifo->size - (prd - fifo->pwr);
	chars_free = fifo->size - chars_used - 1;
	if (chars_free < size)
		return(FIFO_FULL);

	// If buf overlaps end of linear array split into two block moves

	if ((fifo->pwr + size) > fifo->pend) {
		copy_first = (fifo->pend-fifo->pwr);

		fifo_memcpy(fifo->pwr, buf, copy_first);
		fifo_memcpy(fifo->pstart, &buf[copy_first], size-copy_first);
	}
	else {
		fifo_memcpy(fifo->pwr, buf, size);
	}

	// increment pwr and wrap around if required

	new_pwr = fifo->pwr + size;
	if (new_pwr >= fifo->pend) {
		tmp = fifo->pstart + (new_pwr - fifo->pend);
	}
	else {
		tmp = new_pwr;
	}

	fifo->pwr = tmp;
	return(OK);
}

/*--------------------------------------------------------------------------*\
	Reads a block of chars from a FIFO.  Returns OK if successful.
\*--------------------------------------------------------------------------*/

int fifo_read(void *pvfifo, char *buf, int size)
{
	FIFO   *fifo;
	int    copy_first;	// size of first copy
	int    chars_used;	// used space in FIFO
	char   *new_prd;	// modified prd

	fifo = (FIFO*)pvfifo;

	// determine if there is data in FIFO for buf

	if (fifo->pwr >= fifo->prd)
		chars_used = fifo->pwr - fifo->prd;
	else
		chars_used = fifo->size - (fifo->prd - fifo->pwr);
	if (chars_used < size)
		return(FIFO_EMPTY);

	// If buf overlaps end of linear array split into two block moves

	if ((fifo->prd + size) > fifo->pend) {
		copy_first = (fifo->pend-fifo->prd);

		fifo_memcpy(buf, fifo->prd, copy_first);
		fifo_memcpy(&buf[copy_first], fifo->pstart, size-copy_first);
	}
	else {
		fifo_memcpy(buf, fifo->prd, size);
	}

	// increment prd and wrap around if required

	new_prd = fifo->prd + size;
	if (new_prd >= fifo->pend)
		fifo->prd = fifo->pstart + (new_prd - fifo->pend);
	else
		fifo->prd = new_prd;

	return(OK);
}

/*--------------------------------------------------------------------------*\
	Determines number of used chars in FIFO.
\*--------------------------------------------------------------------------*/

void fifo_how_full(void *pvfifo, int *chars)
{
	FIFO   *fifo;

	fifo = (FIFO*)pvfifo;

	if (fifo->pwr >= fifo->prd)
		*chars = fifo->pwr - fifo->prd;
	if (fifo->prd > fifo->pwr)
		*chars = fifo->size - (fifo->prd - fifo->pwr);
}

void fifo_how_empty(void *pvfifo, int *chars)
{
	FIFO   *fifo;

	fifo = (FIFO*)pvfifo;

	if (fifo->pwr >= fifo->prd)
		*chars = fifo->size - (fifo->pwr - fifo->prd);
	if (fifo->prd > fifo->pwr)
		*chars = fifo->prd - fifo->pwr;
}

void *fifo_malloc(int size)
{
	return vmalloc(size);
}

void fifo_free(void *mem)
{
	vfree(mem);
}

void fifo_memcpy(char *dest, char *src, int length)
{
	int i;
	for(i=0; i<length; i++) {
		*dest++ = *src++;
	}
}


EXPORT_SYMBOL(vt_board_register);
EXPORT_SYMBOL(vt_board_unregister);
EXPORT_SYMBOL(vt_echo_register);
EXPORT_SYMBOL(vt_echo_unregister);
EXPORT_SYMBOL(vt_write);
EXPORT_SYMBOL(vt_read);
EXPORT_SYMBOL(vt_send_event);
EXPORT_SYMBOL(vt_int_proc_fops);
EXPORT_SYMBOL(vt_string_proc_fops);
EXPORT_SYMBOL(vt_create_board_proc_const_int);

module_init(vtcore_init);
module_exit(vtcore_exit);

MODULE_DESCRIPTION(DRIVER_DESCRIPTION);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_VERSION(VT_VERSION);
MODULE_LICENSE("GPL");

