Add preliminary voicetronix support
authorMark Spencer <markster@digium.com>
Tue, 15 Apr 2003 14:39:06 +0000 (14:39 +0000)
committerMark Spencer <markster@digium.com>
Tue, 15 Apr 2003 14:39:06 +0000 (14:39 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@853 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
channels/Makefile
channels/chan_vpb.c [new file with mode: 0755]
configs/vpb.conf.sample [new file with mode: 0755]

diff --git a/CHANGES b/CHANGES
index 39db2dc..97583fb 100755 (executable)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,4 @@
+ -- Add preliminary Voicetronix support
  -- Add iLBC codec
 Asterisk 0.4.0
  -- Merge and edit Nick's FXO dial support
index b0e9164..bf20133 100755 (executable)
@@ -35,6 +35,7 @@ ZAPR2=$(shell [ -f /usr/lib/libmfcr2.so.1 ] && echo "-lmfcr2")
 CHANZAP=$(shell if [ -f .oldzap ]; then echo "chan_zap_old.c"; else echo "chan_zap.c"; fi)
 ZAPLIB=$(shell if [ -f .oldzap ]; then echo "-lzap"; fi)
 CFLAGS+=$(shell [ -f /usr/include/linux/zaptel.h ] && echo "-DIAX_TRUNKING")
+CHANNEL_LIBS+=$(shell [ -f /usr/include/vpbapi.h ] && echo "chan_vpb.so" )
 
 ALSA_SRC=chan_alsa.c
 ALSA_SRC+=$(shell [ -f alsa-monitor.h ] && echo "alsa-monitor.h")
@@ -85,12 +86,19 @@ chan_zap.so: chan_zap.o
 
 chan_alsa.o: $(ALSA_SRC)
 
+
 chan_alsa.so: chan_alsa.o
        $(CC) -shared -Xlinker -x -o $@ $< -lasound -lm -ldl
 
 chan_nbs.so: chan_nbs.o
        $(CC) -shared -Xlinker -x -o $@ $< -lnbs
 
+chan_vpb.o: chan_vpb.c
+       $(CC) -c $(CFLAGS) -o $@ chan_vpb.c
+
+chan_vpb.so: chan_vpb.o
+        $(CCC) -shared -Xlinker -x -o $@ $< -lvpb -lpthread -lm -ldl
+
 #chan_modem.so : chan_modem.o
 #      $(CC) -rdynamic -shared -Xlinker -x -o $@ $<
 
diff --git a/channels/chan_vpb.c b/channels/chan_vpb.c
new file mode 100755 (executable)
index 0000000..e034c00
--- /dev/null
@@ -0,0 +1,996 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * VoiceTronix Interface driver
+ * 
+ * Copyright (C) 2003, Paul Bagyenda
+ *
+ * Paul Bagyenda <bagyenda@dsmagic.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <stdio.h>
+#include <pthread.h>
+#include <string.h>
+#include <asterisk/lock.h>
+#include <asterisk/channel.h>
+#include <asterisk/channel_pvt.h>
+#include <asterisk/config.h>
+#include <asterisk/logger.h>
+#include <asterisk/module.h>
+#include <asterisk/pbx.h>
+#include <asterisk/options.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include <vpbapi.h>
+
+
+#define DEFAULT_GAIN 1.0
+#define VPB_SAMPLES 160 /* 20ms for recording and playback.*/
+#define VPB_MAX_BUF VPB_SAMPLES*4
+
+#define VPB_NULL_EVENT 200
+
+#define VPB_DIALTONE_WAIT 2000
+#define VPB_RINGWAIT 2000
+#define VPB_WAIT_TIMEOUT 10
+
+#if defined(__cplusplus) || defined(c_plusplus)
+ extern "C" {
+#endif
+
+static char *desc = "VoiceTronix V6PCI/V12PCI  API Support";
+static char *type = "vpb";
+static char *tdesc = "Standard VoiceTronix API Driver";
+static char *config = "vpb.conf";
+
+/* Default context for dialtone mode */
+static char context[AST_MAX_EXTENSION] = "default";
+
+/* Default language */
+static char language[MAX_LANGUAGE] = "";
+static int usecnt =0;
+
+static int echocancel = 1; 
+static int setrxgain = 0, settxgain = 0;
+
+static int silencesupression = 0;
+
+static const int prefformat = AST_FORMAT_ALAW | AST_FORMAT_SLINEAR | AST_FORMAT_ULAW | AST_FORMAT_ADPCM;
+
+static pthread_mutex_t usecnt_lock = AST_MUTEX_INITIALIZER;
+
+/* Protect the interface list (of vpb_pvt's) */
+static pthread_mutex_t iflock = AST_MUTEX_INITIALIZER;
+
+/* Protect the monitoring thread, so only one process can kill or start it, and not
+   when it's doing something critical. */
+static pthread_mutex_t monlock = AST_MUTEX_INITIALIZER;
+
+/* This is the thread for the monitor which checks for input on the channels
+   which are not currently in use.  */
+static pthread_t monitor_thread;
+
+static int mthreadactive = -1; /* Flag for monitoring monitorthread.*/
+
+
+static int restart_monitor(void);
+
+/* The private structures of the VPB channels are linked for
+   selecting outgoing channels */
+   
+#define MODE_DIALTONE  1
+#define MODE_IMMEDIATE 2
+#define MODE_FXO       3
+
+
+
+
+static VPB_TONE Dialtone     = {450, 425, 400, -10,   -10,  -10, 10000, 0   };
+static VPB_TONE Busytone     = {425,   0,   0, -10,  -100, -100,   500,  500};
+
+#if 0
+static VPB_TONE Ringbacktone = {425,   0,   0, -10,  -100, -100,  1000, 3000};
+#endif
+
+static struct vpb_pvt {
+
+     struct ast_channel *owner;                /* Channel we belong to, possibly NULL */
+     int mode;                                         /* Is this in the  */
+     int handle;  /* Handle for vpb interface */
+
+     char dev[256];
+
+     char buf[VPB_MAX_BUF];                    /* Static buffer for reading frames */
+
+     int dialtone;
+     float txgain, rxgain;             /* gain control for playing, recording  */
+     
+     int wantdtmf; /* Waiting for DTMF. */
+     int silencesupression;
+     char context[AST_MAX_EXTENSION];
+
+     char ext[AST_MAX_EXTENSION];
+     char language[MAX_LANGUAGE];
+     char callerid[AST_MAX_EXTENSION];
+
+     int lastinput;
+     int lastoutput;
+
+    
+     pthread_mutex_t lock;
+
+     int hangup;
+     pthread_t readthread;    /* For monitoring read channel. One per owned channel. */
+
+     struct vpb_pvt *next;                     /* Next channel in list */
+} *iflist = NULL;
+
+static char callerid[AST_MAX_EXTENSION];
+
+
+static inline int monitor_handle_owned(struct vpb_pvt *p, VPB_EVENT *e)
+{
+     struct ast_frame f = {AST_FRAME_CONTROL}; /* default is control, Clear rest. */
+
+     if (option_verbose > 4) 
+         ast_verbose(VERBOSE_PREFIX_3 " %s handle_owned got event: [%d=>%d]\n",
+                     p->dev, e->type, e->data);
+     
+     f.src = type;
+     switch (e->type) {
+     case VPB_RING:
+         if (p->mode == MODE_FXO) 
+              f.subclass = AST_CONTROL_RING;
+         else
+              f.frametype = -1; /* ignore ring on station port. */
+         break;
+
+     case VPB_DTMF:
+         if (p->owner->_state == AST_STATE_UP) {
+              f.frametype = AST_FRAME_DTMF;
+              f.subclass = e->data;
+         } else
+              f.frametype = -1;
+         break;
+
+     case VPB_TONEDETECT:
+         if (e->data == VPB_BUSY || e->data == VPB_BUSY_308)
+              f.subclass = AST_CONTROL_BUSY;
+         else 
+              f.frametype = -1;
+         break;
+
+     case VPB_CALLEND:
+         if (e->data == VPB_CALL_CONNECTED)
+              f.subclass = AST_CONTROL_ANSWER;
+         else if (e->data == VPB_CALL_NO_DIAL_TONE ||
+                  e->data == VPB_CALL_NO_RING_BACK)
+              f.subclass =  AST_CONTROL_CONGESTION;
+         else if (e->data == VPB_CALL_NO_ANSWER ||
+                  e->data == VPB_CALL_BUSY)
+              f.subclass = AST_CONTROL_BUSY;
+         else if (e->data  == VPB_CALL_DISCONNECTED) 
+              f.subclass = AST_CONTROL_HANGUP;
+         break;
+
+     case VPB_STATION_OFFHOOK:
+          f.subclass = AST_CONTROL_ANSWER;
+         break;
+         
+     case VPB_STATION_ONHOOK:
+         f.subclass = AST_CONTROL_HANGUP;
+         break;
+         
+     case VPB_STATION_FLASH:
+         f.subclass = AST_CONTROL_FLASH;
+         break;
+
+     default:
+         f.frametype = -1;
+         break;
+     }
+
+     if (option_verbose > 2) 
+         ast_verbose(VERBOSE_PREFIX_3 " handle_owned: putting frame: [%d=>%d]\n",
+                     f.frametype, f.subclass);
+     
+     if (f.frametype >= 0 && f.frametype != AST_FRAME_NULL) 
+         ast_queue_frame(p->owner, &f, 0);
+     return 0;
+}
+
+static struct ast_channel *vpb_new(struct vpb_pvt *i, int state, char *context);
+
+static inline int monitor_handle_notowned(struct vpb_pvt *p, VPB_EVENT *e)
+{
+     char s[2] = {0};
+
+     if (option_verbose > 2) 
+         ast_verbose(VERBOSE_PREFIX_3 " %s: In not owned, mode=%d, [%d=>%d]\n",
+                     p->dev, p->mode, e->type, e->data);
+          
+     switch(e->type) {
+     case VPB_RING:
+         if (p->mode == MODE_FXO) /* FXO port ring, start * */
+              vpb_new(p, AST_STATE_RING, p->context);
+         break;
+     case VPB_STATION_OFFHOOK:
+         if (p->mode == MODE_IMMEDIATE) 
+              vpb_new(p,AST_STATE_RING, p->context);
+         else {
+              vpb_playtone_async(p->handle, &Dialtone);
+              p->wantdtmf = 1;
+              p->ext[0] = 0; /* Just to be sure & paranoid.*/
+         }
+         break;
+
+     case VPB_STATION_ONHOOK: /*, clear ext */
+         vpb_tone_terminate(p->handle);
+         p->wantdtmf = 1;
+         p->ext[0] = 0;
+         break;
+
+     case VPB_DTMF:
+         if (p->wantdtmf == 1) {
+              vpb_tone_terminate(p->handle);
+              p->wantdtmf = 0;
+         }
+         s[0] = e->data;
+         strcat(p->ext, s);
+         
+         if (ast_exists_extension(NULL, p->context, p->ext, 1, p->callerid)) 
+              vpb_new(p,AST_STATE_RING, p->context);
+         else if (!ast_canmatch_extension(NULL, p->context, p->ext, 1, p->callerid)) 
+              if (ast_exists_extension(NULL, "default", p->ext, 1, p->callerid)) 
+                   vpb_new(p,AST_STATE_RING, "default");             
+              else if (!ast_canmatch_extension(NULL, "default", p->ext, 1, p->callerid)) {
+                   if (option_debug)
+                        ast_log(LOG_DEBUG, 
+                                "%s can't match anything in %s or default\n", 
+                                p->ext, p->context);
+                   vpb_playtone_async(p->handle, &Busytone);
+              }
+         
+         break;
+         
+     default:
+         /* Ignore.*/
+         break;
+     }
+     
+     if (option_verbose > 2) 
+        ast_verbose(VERBOSE_PREFIX_3 " %s: Done not owned, mode=%d, [%d=>%d]\n",
+                     p->dev, p->mode, e->type, e->data);
+     
+     return 0;
+ }
+
+static void *do_monitor(void *unused)
+{
+    
+     /* Monitor thread, doesn't die until explicitly killed. */
+     
+     if (option_verbose > 2) 
+         ast_verbose(VERBOSE_PREFIX_3 "Starting vpb monitor thread[%ld]\n",
+                     pthread_self());
+     pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+     
+     do {
+         VPB_EVENT e;
+         char str[VPB_MAX_STR];
+         
+         int res = vpb_get_event_sync(&e, VPB_WAIT_TIMEOUT);
+         
+         
+         str[0] = 0;
+         ast_pthread_mutex_lock(&monlock),
+              ast_pthread_mutex_lock(&iflock); {
+              struct vpb_pvt *p = iflist; /* Find the pvt structure */       
+
+             if (res == VPB_OK) { /* Got an actual event! */ 
+                  
+
+                  vpb_translate_event(&e, str);
+                  
+                  if (e.type == VPB_TIMEREXP || e.type == VPB_NULL_EVENT) 
+                       goto done; /* Nothing to do, just a wakeup call.*/
+                  while (p && p->handle != e.handle)
+                       p = p->next;
+                  
+                  if (!p) {
+                          ast_log(LOG_WARNING, 
+                                  "Got event %s, no matching iface!\n", str);    
+                       goto done;
+                  } 
+                  
+                  if (option_verbose > 2)
+                       ast_verbose(VERBOSE_PREFIX_3 " Event [%d=>%s] on %s\n", 
+                                   e.type, str, p->dev);
+                  
+                  /* Two scenarios: Are you owned or not. */
+                  
+                  if (p->owner) 
+                       monitor_handle_owned(p, &e);
+                  else 
+                       monitor_handle_notowned(p, &e);
+             } 
+             done: (void)0;
+         } ast_pthread_mutex_unlock(&iflock);
+         ast_pthread_mutex_unlock(&monlock);
+         
+
+     } while(1);
+     
+     
+     return NULL;
+}
+
+static int restart_monitor(void)
+{
+     int error = 0;
+     
+     /* If we're supposed to be stopped -- stay stopped */
+     if (mthreadactive == -2)
+         return 0;
+     ast_pthread_mutex_lock(&monlock); {
+         if (monitor_thread == pthread_self()) {
+              ast_log(LOG_WARNING, "Cannot kill myself\n");
+              error = -1;
+              goto done;
+         }
+         if (mthreadactive != -1) {
+              /* Why do other drivers kill the thread? No need says I, simply awake thread with event. */
+              VPB_EVENT e;
+              e.handle = 0;
+              e.type = VPB_NULL_EVENT;
+              e.data = 0;
+              
+              vpb_put_event(&e);
+         } else {
+              /* Start a new monitor */
+              int pid = pthread_create(&monitor_thread, NULL, do_monitor, NULL); 
+              if (pid < 0) {
+                   ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+                   error = -1;
+                   goto done;
+              } else
+                   mthreadactive = 0; /* Started the thread!*/
+
+#if 0         
+              if (option_verbose > 2) 
+                   ast_verbose(VERBOSE_PREFIX_3 
+                               "Starting vpb restast: thread[%d]\n",
+                               pid);
+#endif
+         }
+     done: (void)0;
+     } ast_pthread_mutex_unlock(&monlock);
+     
+     return error;
+}
+
+struct vpb_pvt *mkif(int board, int channel, int mode, float txgain, float rxgain)
+{
+     struct vpb_pvt *tmp;
+
+
+     tmp = (struct vpb_pvt *)calloc(1, sizeof *tmp);
+
+     if (!tmp)
+         return NULL;
+
+     tmp->handle = vpb_open(board, channel);
+     
+     if (tmp->handle < 0) {      
+         ast_log(LOG_WARNING, "Unable to create channel vpb/%d-%d: %s\n",
+                 board, channel, strerror(errno));
+         free(tmp);
+         return NULL;
+     }
+          
+     if (echocancel) {
+         if (option_verbose > 2)
+              ast_verbose(VERBOSE_PREFIX_3 " vpb turned on echo cancel.\n");
+         vpb_echo_canc_enable();
+         vpb_echo_canc_force_adapt_on();
+         echocancel = 0; /* So we do not initialise twice! */
+     }
+     
+     if (option_verbose > 2) 
+         ast_verbose(VERBOSE_PREFIX_3 " vpb created channel: [%d:%d]\n",
+                     board, channel);
+
+     sprintf(tmp->dev, "vpb/%d-%d", board, channel);
+
+     tmp->mode = mode;
+
+     strcpy(tmp->language, language);
+     strcpy(tmp->context, context);
+     
+     strcpy(tmp->callerid, callerid);
+     tmp->txgain = txgain;
+
+     tmp->rxgain = rxgain;
+
+     tmp->lastinput = -1;
+     tmp->lastoutput = -1;
+
+     tmp->readthread = 0;
+
+     if (setrxgain)      
+         vpb_record_set_gain(tmp->handle, rxgain);
+     if (settxgain)  
+         vpb_play_set_gain(tmp->handle, txgain);
+     
+     return tmp;
+}
+
+static int vpb_digit(struct ast_channel *ast, char digit)
+{
+    char s[2];
+
+    s[0] = digit;
+    s[1] = '\0';
+   
+    return vpb_dial_sync(((struct vpb_pvt *)ast->pvt->pvt)->handle, s);
+}
+
+
+static int vpb_call(struct ast_channel *ast, char *dest, int timeout)
+{
+    struct vpb_pvt *p = (struct vpb_pvt *)ast->pvt->pvt;
+    int res = 0;
+    char *s = strrchr(dest, '/');
+
+    if (s)
+        s = s + 1;
+    else
+        s = dest;
+
+    if (ast->_state != AST_STATE_DOWN && ast->_state != AST_STATE_RESERVED) {
+       ast_log(LOG_WARNING, "vpb_call on %s neither down nor reserved!\n", 
+               ast->name);
+       return -1;
+    }
+    if (p->mode != MODE_FXO)  /* Station port, ring it. */
+       res = vpb_ring_station_async(p->handle, VPB_RING_STATION_ON);       
+     else {
+         VPB_CALL call;
+
+         vpb_get_call(p->handle, &call);
+         
+         call.dialtone_timeout = VPB_DIALTONE_WAIT;
+         call.answer_timeout = timeout;
+         call.ringback_timeout = VPB_RINGWAIT;
+
+         vpb_set_call(p->handle, &call);
+
+         if (option_verbose > 2)
+              ast_verbose(VERBOSE_PREFIX_3 " Calling %s on %s \n", dest, ast->name); 
+         
+         res = vpb_dial_async(p->handle, s);
+
+        if (res != VPB_OK) {
+             ast_log(LOG_DEBUG, "Call on %s to %s failed: %s\n", 
+                     ast->name, dest, vpb_strerror(res));            
+             res = -1;
+        } else 
+             res = 0;
+    }
+    
+    if (res == 0) {
+       ast_setstate(ast, AST_STATE_RINGING);
+       ast_queue_control(ast,AST_CONTROL_RINGING, 0);          
+    }
+
+    return res;
+}
+
+static int vpb_hangup(struct ast_channel *ast)
+{
+    struct vpb_pvt *p;
+
+    if (option_verbose > 2) 
+       ast_verbose(VERBOSE_PREFIX_3 " hangup on vpb (%s)\n", ast->name);
+    
+    if (!ast->pvt || !ast->pvt->pvt) {
+       ast_log(LOG_WARNING, "channel (%s) not connected?\n", ast->name);
+       return 0;
+    }
+
+    p = (struct vpb_pvt *)ast->pvt->pvt;
+
+    vpb_play_terminate(p->handle);
+    vpb_record_terminate(p->handle);
+    
+    if (p->mode != MODE_FXO) { /* station port. */
+       vpb_ring_station_async(p->handle, VPB_RING_STATION_OFF);        
+       vpb_playtone_async(p->handle, &Busytone);
+
+    } else
+       vpb_sethook_sync(p->handle, VPB_ONHOOK);
+
+    ast_setstate(ast,AST_STATE_DOWN);
+    
+    p->lastinput = p->lastoutput  = -1;
+    p->ext[0]  = 0;
+    p->owner = NULL;
+    p->dialtone = 0;
+    ast->pvt->pvt = NULL; 
+       
+    ast_pthread_mutex_lock(&usecnt_lock); {
+       usecnt--;
+    } ast_pthread_mutex_unlock(&usecnt_lock);
+    ast_update_use_count();
+
+    /* Stop thread doing reads. */
+    p->hangup = 1;
+    pthread_join(p->readthread, NULL); 
+
+    if (option_verbose > 2)
+       ast_verbose(VERBOSE_PREFIX_3 " Hungup on %s complete\n", ast->name);
+    
+    restart_monitor();
+    return 0;
+}
+
+static int vpb_answer(struct ast_channel *ast)
+{
+    struct vpb_pvt *p = (struct vpb_pvt *)ast->pvt->pvt;
+
+    if (p->mode == MODE_FXO)
+       vpb_sethook_sync(p->handle, VPB_OFFHOOK);
+    
+    if (option_debug)
+       ast_log(LOG_DEBUG, "vpb answer on %s\n", ast->name);
+    ast->rings = 0;
+    ast_setstate(ast, AST_STATE_UP);
+    
+    return 0;
+}
+
+static struct ast_frame  *vpb_read(struct ast_channel *ast)
+{
+    struct vpb_pvt *p = (struct vpb_pvt *)ast->pvt->pvt; 
+    static struct ast_frame f = {AST_FRAME_NULL}; 
+    
+    f.src = type;
+    ast_log(LOG_NOTICE, "vpb_read should never be called (chan=%s)!\n", p->dev);
+       
+    return &f;
+}
+
+static inline int ast2vpbformat(int ast_format)
+{
+
+    switch(ast_format) {
+       case AST_FORMAT_ALAW:
+           return VPB_ALAW;
+       case AST_FORMAT_SLINEAR:
+           return VPB_LINEAR;
+       case AST_FORMAT_ULAW:
+           return VPB_MULAW;
+       case AST_FORMAT_ADPCM:
+
+           return VPB_OKIADPCM;
+       default:
+           return -1;
+    }
+}
+
+static inline int astformatbits(int ast_format)
+{
+
+    switch(ast_format) {
+       case AST_FORMAT_ALAW:
+       case AST_FORMAT_ULAW:
+           return 8;
+       case AST_FORMAT_SLINEAR:
+           return 16;
+       case AST_FORMAT_ADPCM:
+           return 4;
+       default:
+           return 8;
+    }   
+}
+
+static int vpb_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+    struct vpb_pvt *p = (struct vpb_pvt *)ast->pvt->pvt; 
+    int res = 0, fmt = 0;
+
+    if (frame->frametype != AST_FRAME_VOICE) {
+       ast_log(LOG_WARNING, "Don't know how to handle from type %d\n",
+               frame->frametype);
+       return 0;
+    } else if (ast->_state != AST_STATE_UP) {
+       if (option_verbose  > 4)
+           ast_log(LOG_WARNING, "Writing frame type [%d,%d] on chan %s not up\n",
+                   frame->frametype, frame->subclass, ast->name);
+       return 0;
+       
+    }
+    
+
+    fmt = ast2vpbformat(frame->subclass);
+
+    if (option_verbose > 4)
+        ast_verbose(VERBOSE_PREFIX_3 
+                    " Write chan %s: got frame type = %d, "
+                    "samples=%d, len=%d, oldfmt=%d,new=%d,rawwrite=%d\n",
+                    p->dev, frame->subclass, 
+                    frame->samples, frame->datalen, p->lastoutput, fmt, 
+                    ast->pvt->rawwriteformat);
+    
+    if (fmt < 0) {
+       ast_log(LOG_WARNING, "vpb_write Cannot handle frames of %d format!\n", 
+               frame->subclass);
+       return -1;
+    }
+    
+
+    if (p->lastoutput != fmt) {
+        vpb_play_buf_start(p->handle, fmt);
+        p->lastoutput = fmt;
+    }
+    
+
+    res = vpb_play_buf_sync(p->handle, (char *)frame->data, frame->datalen);
+    if (res != VPB_OK)
+        return -1;
+    else 
+        return 0;
+}
+
+/* Read monitor thread function. */
+static void *do_chanreads(void *pvt)
+{
+     struct vpb_pvt *p = (struct vpb_pvt *)pvt;
+     char buf[VPB_MAX_BUF];
+     struct ast_frame fr = {AST_FRAME_VOICE};
+     
+     fr.src = type;
+
+     while (!p->hangup && p->owner) {   
+        int res = -1, fmt;
+        struct ast_channel *owner = p->owner;
+        int afmt = (owner) ? owner->pvt->rawreadformat : AST_FORMAT_SLINEAR;
+        int state = (owner) ? owner->_state : AST_STATE_DOWN;
+        int readlen;
+        
+        fmt = ast2vpbformat(afmt);
+        readlen = VPB_SAMPLES * astformatbits(afmt) / 8;
+
+        if (p->lastinput != fmt) {
+            if (option_verbose > 2) 
+                ast_verbose(" Read_channel ##  %s: Setting record mode\n", 
+                            p->dev);                
+            vpb_record_buf_start(p->handle, fmt);
+            p->lastinput = fmt;
+        } 
+
+
+        if (state == AST_STATE_UP)  /* Read only if up. */
+             res = vpb_record_buf_sync(p->handle, buf, readlen);
+       
+        if (res == VPB_OK) {
+            fr.subclass = afmt;
+            fr.samples = VPB_SAMPLES;
+            fr.datalen = readlen;
+            fr.data = buf;
+            
+            ast_queue_frame(p->owner, &fr, 0);
+        } else 
+            vpb_sleep(10);
+        if (option_verbose > 4)
+            if (state == AST_STATE_UP)
+                ast_verbose(" Read_channel  %s (state=%d): got frame: res=%d, rawread=%d, rlen=%d\n", 
+                            p->dev, state, res, owner ? owner->pvt->rawreadformat : -1, readlen);     
+     }
+     
+     /* When hangup seen, go away! */
+     vpb_record_buf_finish(p->handle);
+
+     return NULL;
+}
+
+
+static struct ast_channel *vpb_new(struct vpb_pvt *i, int state, char *context)
+{
+       struct ast_channel *tmp; 
+
+       if (i->owner) {
+           ast_log(LOG_WARNING, "Called vpb_new on owned channel (%s) ?!\n", i->dev);
+           return NULL;
+       }
+           
+       tmp = ast_channel_alloc(1);
+       if (tmp) {
+               strncpy(tmp->name, i->dev, sizeof(tmp->name));
+               tmp->type = type;
+              
+               tmp->nativeformats = prefformat;
+               tmp->pvt->rawreadformat = AST_FORMAT_SLINEAR;
+               tmp->pvt->rawwriteformat =  AST_FORMAT_SLINEAR;
+               ast_setstate(tmp, state);
+               if (state == AST_STATE_RING)
+                       tmp->rings = 1;
+               tmp->pvt->pvt = i;
+               tmp->pvt->send_digit = vpb_digit;
+               tmp->pvt->call = vpb_call;
+               tmp->pvt->hangup = vpb_hangup;
+               tmp->pvt->answer = vpb_answer;
+               tmp->pvt->read = vpb_read;
+               tmp->pvt->write = vpb_write;
+
+               strncpy(tmp->context, context, sizeof(tmp->context)-1);
+               if (strlen(i->ext))
+                       strncpy(tmp->exten, i->ext, sizeof(tmp->exten)-1);
+               else
+                       strncpy(tmp->exten, "s",  sizeof(tmp->exten) - 1);
+               if (strlen(i->language))
+                       strncpy(tmp->language, i->language, sizeof(tmp->language)-1);
+               if (strlen(i->callerid))
+                       tmp->callerid = strdup(i->callerid);
+               i->owner = tmp;
+
+               
+               i->lastinput = i->lastoutput = -1;
+               
+               ast_pthread_mutex_lock(&usecnt_lock);
+               usecnt++;
+               ast_pthread_mutex_unlock(&usecnt_lock);
+               ast_update_use_count();
+               if (state != AST_STATE_DOWN) 
+                    if (ast_pbx_start(tmp)) {
+                         ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+                         ast_hangup(tmp);
+                    }
+               pthread_mutex_init(&i->lock, NULL);
+               i->hangup = 0; /* So read thread runs. */       
+               /* Finally start read monitoring thread. */
+               pthread_create(&i->readthread, NULL, do_chanreads, (void *)i);
+       } else
+               ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+       return tmp;
+}
+
+static struct ast_channel *vpb_request(char *type, int format, void *data) 
+{
+     int oldformat;
+     struct vpb_pvt *p;
+     struct ast_channel *tmp = NULL;
+     char *name = strdup(data ? (char *)data : "");
+     char *s, *sepstr;
+     
+     oldformat = format;
+     format &= prefformat;
+     if (!format) {
+         ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", oldformat);
+         return NULL;
+     }
+     
+     sepstr = name;
+     s = strsep(&sepstr, "/"); /* Handle / issues */
+     if (!s) 
+         s = "";
+     /* Search for an unowned channel */
+     ast_pthread_mutex_lock(&iflock); {
+         p = iflist;
+         while(p) {
+              if (strncmp(s, p->dev + 4, sizeof p->dev) == 0) 
+                   if (!p->owner) {
+                        tmp = vpb_new(p, AST_STATE_DOWN, p->context);
+                        break;
+                   }
+              p = p->next;
+         }
+     } ast_pthread_mutex_unlock(&iflock);
+
+
+     if (option_verbose > 2) 
+         ast_verbose(VERBOSE_PREFIX_3 " %s requested, got: [%s]\n",
+                     name, tmp ? tmp->name : "None");
+
+     free(name);
+     
+     restart_monitor();
+     return tmp;
+}
+
+static float parse_gain_value(char *gain_type, char *value)
+{
+       float gain;
+
+       /* try to scan number */
+       if (sscanf(value, "%f", &gain) != 1)
+       {
+               ast_log(LOG_ERROR, "Invalid %s value '%s' in '%s' config\n",
+                       value, gain_type, config);
+               return DEFAULT_GAIN;
+       }
+
+
+       /* percentage? */
+       if (value[strlen(value) - 1] == '%')
+               return gain / (float)100;
+
+       return gain;
+}
+
+int load_module()
+{
+       struct ast_config *cfg;
+       struct ast_variable *v;
+       struct vpb_pvt *tmp;
+       int board = 0, group = 0;
+       int mode = MODE_IMMEDIATE;
+        float txgain = DEFAULT_GAIN, rxgain = DEFAULT_GAIN; 
+       
+       int error = 0; /* Error flag */
+
+
+       setrxgain = settxgain = 0;
+
+       cfg = ast_load(config);
+       
+       /* We *must* have a config file otherwise stop immediately */
+       if (!cfg) {
+            ast_log(LOG_ERROR, "Unable to load config %s\n", config);
+            return -1;
+       }  
+       
+       vpb_seterrormode(VPB_DEVELOPMENT);
+       
+       ast_pthread_mutex_lock(&iflock); {
+            v = ast_variable_browse(cfg, "interfaces");
+            while(v) {
+                 /* Create the interface list */
+                 if (strcasecmp(v->name, "board") == 0) 
+                      board = atoi(v->value);
+                 else  if (strcasecmp(v->name, "group") == 0)
+                      group = atoi(v->value);
+                 else if (strcasecmp(v->name, "channel") == 0) {
+                      int channel = atoi(v->value);
+                      tmp = mkif(board, channel, mode, txgain, rxgain);
+                      if (tmp) {
+                           tmp->next = iflist;
+                           iflist = tmp;
+                           
+                      } else {
+                           ast_log(LOG_ERROR, 
+                                   "Unable to register channel '%s'\n", v->value);
+                           error = -1;
+                           goto done;
+                           
+                      }
+                 } else if (strcasecmp(v->name, "silencesupression") == 0) 
+                      silencesupression = ast_true(v->value);
+                 else if (strcasecmp(v->name, "language") == 0) 
+                      strncpy(language, v->value, sizeof(language)-1);
+                 else if (strcasecmp(v->name, "callerid") == 0) 
+                      strncpy(callerid, v->value, sizeof(callerid)-1);
+                 else if (strcasecmp(v->name, "mode") == 0) {
+                      if (strncasecmp(v->value, "di", 2) == 0) 
+                           mode = MODE_DIALTONE;
+                      else if (strncasecmp(v->value, "im", 2) == 0)
+                           mode = MODE_IMMEDIATE;
+                      else if (strncasecmp(v->value, "fx", 2) == 0)
+                           mode = MODE_FXO;
+                      else
+                           ast_log(LOG_WARNING, "Unknown mode: %s\n", v->value);
+                 } else if (!strcasecmp(v->name, "context")) 
+                      strncpy(context, v->value, sizeof(context)-1);
+                 else if (!strcasecmp(v->name, "echocancel")) {
+                      if (!strcasecmp(v->value, "off")) 
+                           echocancel = 0;
+                      else 
+                           echocancel = 1;
+                 } else if (strcasecmp(v->name, "txgain") == 0) {
+                      settxgain = 1;
+                      txgain = parse_gain_value(v->name, v->value);
+                 } else if (strcasecmp(v->name, "rxgain") == 0) {
+                      setrxgain = 1;
+                      rxgain = parse_gain_value(v->name, v->value);
+                 }
+                 v = v->next;
+            }
+
+
+       done: (void)0;
+       } ast_pthread_mutex_unlock(&iflock);
+
+       
+       ast_destroy(cfg);
+       
+       if (!error && 
+           ast_channel_register(type, tdesc, 
+                               prefformat, vpb_request) != 0) {
+            ast_log(LOG_ERROR, "Unable to register channel class %s\n", type);
+            
+            error = -1;
+       }
+
+
+       if (error)
+            unload_module();
+       else         
+            restart_monitor(); /* And start the monitor for the first time */
+       
+       return error;
+}
+
+
+int unload_module()
+{
+     struct vpb_pvt *p;
+       /* First, take us out of the channel loop */
+       ast_channel_unregister(type);
+
+       ast_pthread_mutex_lock(&iflock); {
+            /* Hangup all interfaces if they have an owner */
+            p = iflist;
+            while(p) {
+                 if (p->owner)
+                      ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+                 p = p->next;
+            }
+            iflist = NULL;
+       } ast_pthread_mutex_unlock(&iflock);
+
+       ast_pthread_mutex_lock(&monlock); {
+            if (mthreadactive > -1) {
+                 pthread_cancel(monitor_thread);
+                 pthread_join(monitor_thread, NULL);
+            }
+            mthreadactive = -2;
+       } ast_pthread_mutex_unlock(&monlock);
+
+       ast_pthread_mutex_lock(&iflock); {
+               /* Destroy all the interfaces and free their memory */
+
+               while(iflist) {
+                    p = iflist;                    
+
+                    iflist = iflist->next;
+                    
+                    free(p);
+               }
+               iflist = NULL;
+       } ast_pthread_mutex_unlock(&iflock);
+       
+       return 0;
+}
+
+int usecount()
+{
+       int res;
+       ast_pthread_mutex_lock(&usecnt_lock);
+       res = usecnt;
+       ast_pthread_mutex_unlock(&usecnt_lock);
+       return res;
+}
+
+char *description()
+{
+       return desc;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
+
+#if defined(__cplusplus) || defined(c_plusplus)
+ }
+#endif
diff --git a/configs/vpb.conf.sample b/configs/vpb.conf.sample
new file mode 100755 (executable)
index 0000000..9772dcb
--- /dev/null
@@ -0,0 +1,27 @@
+; V6PCI/V12PCI config file for VoiceTronix Hardware
+; Options
+; board = board_number (1, 2, 3, ...)
+; channel = channel_number (1,2,3...)
+; mode = fxo|immediate|dialtone -- for type of line and line handling
+; context = starting context
+; 
+
+[interfaces]
+
+echocancel = on
+board = 1
+
+context = vpbtest
+
+; Note that V6PCI channel numbers start at 7!
+mode = fxo
+;channel = 7
+;channel = 8
+
+mode = dialtone
+;channel = 9
+;channel = 10
+;channel = 11
+;channel = 12
+
+