yate/modules/zapchan.cpp

456 lines
12 KiB
C++

/**
* zapchan.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Zapata telephony driver
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <modules/libypri.h>
#ifdef _WINDOWS
#error This module is not for Windows
#else
extern "C" {
#include <linux/zaptel.h>
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifndef _WINDOWS
#include <sys/ioctl.h>
#include <fcntl.h>
#endif
#ifndef ZT_EVENT_DTMFDIGIT
#ifdef ZT_EVENT_DTMFDOWN
#define ZT_EVENT_DTMFDIGIT ZT_EVENT_DTMFDOWN
#else
#define ZT_EVENT_DTMFDIGIT 0
#endif
#endif
#ifndef ZT_EVENT_PULSEDIGIT
#define ZT_EVENT_PULSEDIGIT 0
#endif
using namespace TelEngine;
namespace { // anonymous
/* Zaptel formats */
static TokenDict dict_str2ztlaw[] = {
{ "slin", -1 },
{ "default", ZT_LAW_DEFAULT },
{ "mulaw", ZT_LAW_MULAW },
{ "alaw", ZT_LAW_ALAW },
{ 0, -2 }
};
class ZapChan;
class ZapSpan : public PriSpan, public Thread
{
friend class ZapDriver;
public:
virtual ~ZapSpan();
virtual void run();
private:
ZapSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, int fd);
int m_fd;
};
class ZapSource : public PriSource, public Thread
{
public:
ZapSource(ZapChan *owner, const char* format, unsigned int bufsize);
~ZapSource();
virtual void run();
private:
DataBlock m_data;
};
class ZapConsumer : public PriConsumer
{
public:
ZapConsumer(ZapChan *owner, const char* format, unsigned int bufsize);
~ZapConsumer();
virtual void Consume(const DataBlock &data, unsigned long tStamp);
private:
unsigned int m_bufsize;
DataErrors m_overruns;
};
class ZapChan : public PriChan
{
friend class ZapSource;
friend class ZapConsumer;
public:
ZapChan(const PriSpan *parent, int chan, unsigned int bufsize);
virtual ~ZapChan();
virtual bool openData(const char* format, int echoTaps);
virtual void closeData();
inline int fd() const
{ return m_fd; }
inline int law() const
{ return m_law; }
private:
int m_fd;
int m_law;
};
class ZapDriver : public PriDriver
{
friend class PriSpan;
friend class ZapHandler;
public:
ZapDriver();
virtual ~ZapDriver();
virtual void initialize();
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect);
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize);
};
INIT_PLUGIN(ZapDriver);
static int zt_get_event(int fd)
{
/* Avoid the silly zt_getevent which ignores a bunch of events */
int j = 0;
if (::ioctl(fd, ZT_GETEVENT, &j) == -1)
return -1;
return j;
}
static int zt_open_dchan(int channo, int bsize = 1024, int nbufs = 16)
{
DDebug(&__plugin,DebugInfo,"Opening zap d-channel %d with %d x %d buffers",channo,nbufs,bsize);
int fd = ::open("/dev/zap/channel", O_RDWR, 0600);
if (fd < 0) {
Debug("Zaptel",DebugGoOn,"Failed to open device: error %d: %s",errno,::strerror(errno));
return -1;
}
if (::ioctl(fd,ZT_SPECIFY,&channo) == -1) {
Debug("Zaptel",DebugGoOn,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno));
::close(fd);
return -1;
}
ZT_PARAMS par;
if (::ioctl(fd, ZT_GET_PARAMS, &par) == -1) {
Debug("Zaptel",DebugGoOn,"Failed to get params of chan %d: error %d: %s",channo,errno,::strerror(errno));
::close(fd);
return -1;
}
if (par.sigtype != ZT_SIG_HDLCFCS) {
Debug("Zaptel",DebugGoOn,"Channel %d is not in HDLC/FCS mode",channo);
::close(fd);
return -1;
}
ZT_BUFFERINFO bi;
bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
bi.numbufs = nbufs;
bi.bufsize = bsize;
if (::ioctl(fd, ZT_SET_BUFINFO, &bi) == -1)
Debug("Zaptel",DebugWarn,"Could not set buffering on %d: error %d: %s",channo,errno,::strerror(errno));
return fd;
}
static int zt_open_bchan(int channo, bool subchan, unsigned int blksize)
{
DDebug(&__plugin,DebugInfo,"Opening zap b-channel %d with block size=%d",channo,blksize);
int fd = ::open(subchan ? "/dev/zap/pseudo" : "/dev/zap/channel",O_RDWR|O_NONBLOCK);
if (fd < 0) {
Debug("Zaptel",DebugGoOn,"Failed to open device: error %d: %s",errno,::strerror(errno));
return -1;
}
if (channo) {
if (::ioctl(fd, subchan ? ZT_CHANNO : ZT_SPECIFY, &channo)) {
Debug("Zaptel",DebugGoOn,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno));
::close(fd);
return -1;
}
}
if (blksize) {
if (::ioctl(fd, ZT_SET_BLOCKSIZE, &blksize) == -1) {
Debug("Zaptel",DebugGoOn,"Failed to set block size %d: error %d: %s",blksize,errno,::strerror(errno));
::close(fd);
return -1;
}
}
return fd;
}
static bool zt_set_law(int fd, int law)
{
if (law < 0) {
int linear = 1;
if (::ioctl(fd, ZT_SETLINEAR, &linear) != -1)
return true;
}
else
if (::ioctl(fd, ZT_SETLAW, &law) != -1)
return true;
DDebug("Zaptel",DebugInfo,"Failed to set law %d: error %d: %s",law,errno,::strerror(errno));
return false;
}
static bool zt_echo_cancel(int fd, int taps)
{
if (::ioctl(fd, ZT_ECHOCANCEL, &taps) != -1)
return true;
DDebug("Zaptel",DebugInfo,"Failed to set %d echo cancellation taps: error %d: %s",taps,errno,::strerror(errno));
return false;
}
ZapSpan::ZapSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, int fd)
: PriSpan(_pri,driver,span,first,chans,dchan,cfg,sect), Thread("ZapSpan"),
m_fd(fd)
{
Debug(m_driver,DebugAll,"ZapSpan::ZapSpan() [%p]",this);
}
ZapSpan::~ZapSpan()
{
Debug(m_driver,DebugAll,"ZapSpan::~ZapSpan() [%p]",this);
m_ok = false;
::close(m_fd);
m_fd = -1;
}
void ZapSpan::run()
{
Debug(m_driver,DebugAll,"ZapSpan::run() [%p]",this);
fd_set rdfds;
fd_set errfds;
for (;;) {
FD_ZERO(&rdfds);
FD_SET(m_fd, &rdfds);
FD_ZERO(&errfds);
FD_SET(m_fd, &errfds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100;
int sel = ::select(m_fd+1, &rdfds, NULL, &errfds, &tv);
Thread::check();
if (!sel)
runEvent(true);
else if (sel > 0) {
if (FD_ISSET(m_fd, &errfds)) {
int zev = zt_get_event(m_fd);
if (zev)
Debug(DebugInfo,"Zapata event %d on span %d",zev,span());
}
if (FD_ISSET(m_fd, &rdfds))
runEvent(false);
}
else if (errno != EINTR)
Debug("ZapSpan",DebugGoOn,"select() error %d: %s",
errno,::strerror(errno));
}
}
ZapSource::ZapSource(ZapChan *owner, const char* format, unsigned int bufsize)
: PriSource(owner,format,bufsize), Thread("ZapSource")
{
Debug(m_owner,DebugAll,"ZapSource::ZapSource(%p) [%p]",owner,this);
}
ZapSource::~ZapSource()
{
Debug(m_owner,DebugAll,"ZapSource::~ZapSource() [%p]",this);
}
void ZapSource::run()
{
int rd = 0;
for (;;) {
Thread::yield(true);
int fd = static_cast<ZapChan*>(m_owner)->fd();
if (fd != -1) {
rd = ::read(fd,m_buffer.data(),m_buffer.length());
XDebug(m_owner,DebugAll,"ZapSource read %d bytes [%p]",rd,this);
if (rd > 0)
Forward(m_buffer);
else if (rd < 0) {
if ((errno != EAGAIN) && (errno != EINTR)) {
int zev = zt_get_event(fd);
if (zev) {
Debug(m_owner,DebugInfo,"ZapSource event %d [%p]",zev,this);
// driver-decoded digit arrived
if (zev & (ZT_EVENT_DTMFDIGIT | ZT_EVENT_PULSEDIGIT)) {
char buf[2];
buf[0] = zev & 0xff;
buf[1] = '\0';
m_owner->gotDigits(buf);
}
}
else
break;
}
}
}
else
break;
}
Debug(m_owner,DebugWarn,"ZapSource at EOF (read %d) [%p]",rd,this);
// TODO: find a better way of dealing with this abnormal condition
for (;;)
Thread::yield(true);
}
ZapConsumer::ZapConsumer(ZapChan *owner, const char* format, unsigned int bufsize)
: PriConsumer(owner,format,bufsize), m_bufsize(bufsize)
{
Debug(m_owner,DebugAll,"ZapConsumer::ZapConsumer(%p) [%p]",owner,this);
}
ZapConsumer::~ZapConsumer()
{
Debug(m_owner,DebugAll,"ZapConsumer::~ZapConsumer() [%p]",this);
if (m_overruns.events())
Debug(m_owner,DebugMild,"Consumer had %u overruns (%lu bytes)",
m_overruns.events(),m_overruns.bytes());
}
void ZapConsumer::Consume(const DataBlock &data, unsigned long tStamp)
{
int fd = static_cast<ZapChan*>(m_owner)->fd();
XDebug(DebugAll,"ZapConsumer fd=%d datalen=%u",fd,data.length());
if ((fd != -1) && !data.null()) {
if (m_buffer.length()+data.length() <= m_bufsize*4)
m_buffer += data;
else {
m_overruns.update(data.length());
DDebug(m_owner,DebugAll,"ZapConsumer skipped %u bytes, buffer is full",data.length());
}
if (m_buffer.null())
return;
if (m_buffer.length() >= m_bufsize) {
int wr = ::write(fd,m_buffer.data(),m_bufsize);
if (wr < 0) {
if ((errno != EAGAIN) && (errno != EINTR))
Debug(DebugGoOn,"ZapConsumer write error %d: %s",
errno,::strerror(errno));
}
else {
if ((unsigned)wr != m_bufsize)
Debug(m_owner,DebugInfo,"ZapConsumer short write, %d of %u bytes",wr,m_bufsize);
m_buffer.cut(-wr);
}
}
}
}
ZapChan::ZapChan(const PriSpan *parent, int chan, unsigned int bufsize)
: PriChan(parent,chan,bufsize), m_fd(-1), m_law(-1)
{
}
ZapChan::~ZapChan()
{
closeData();
}
bool ZapChan::openData(const char* format, int echoTaps)
{
m_fd = zt_open_bchan(m_abschan,false,m_bufsize);
if (m_fd == -1)
return false;
int defLaw = ZT_LAW_ALAW;
if (m_span->chans() == 24)
defLaw = ZT_LAW_MULAW;
defLaw = lookup(format,dict_str2ztlaw,defLaw);
if (zt_set_law(m_fd,defLaw)) {
m_law = defLaw;
format = lookup(m_law,dict_str2ztlaw,"unknown");
Debug(this,DebugInfo,"Opened Zap channel %d, law is: %s",m_abschan,format);
}
zt_echo_cancel(m_fd,echoTaps);
ZapSource* src = new ZapSource(this,format,m_bufsize);
setSource(src);
src->startup();
src->deref();
setConsumer(new ZapConsumer(this,format,m_bufsize));
getConsumer()->deref();
return true;
}
void ZapChan::closeData()
{
PriChan::closeData();
if (m_fd != -1) {
::close(m_fd);
m_fd = -1;
}
}
PriSpan* ZapDriver::createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect)
{
Debug(this,DebugAll,"ZapDriver::createSpan(%p,%d,%d,%d) [%p]",driver,span,first,chans,this);
int netType = -1;
int swType = -1;
int dchan = -1;
netParams(cfg,sect,chans,&netType,&swType,&dchan);
if (dchan < 0)
return 0;
int fd = zt_open_dchan(dchan+first-1);
if (fd < 0)
return 0;
pri* p = ::pri_new(fd,netType,swType);
if (!p)
return 0;
ZapSpan *zs = new ZapSpan(p,driver,span,first,chans,dchan,cfg,sect,fd);
zs->startup();
return zs;
}
PriChan* ZapDriver::createChan(const PriSpan* span, int chan, unsigned int bufsize)
{
Debug(this,DebugAll,"ZapDriver::createChan(%p,%d,%u) [%p]",span,chan,bufsize,this);
return new ZapChan(span,chan,bufsize);
}
ZapDriver::ZapDriver()
: PriDriver("zap")
{
Output("Loaded module Zapchan");
}
ZapDriver::~ZapDriver()
{
Output("Unloading module Zapchan");
}
void ZapDriver::initialize()
{
Output("Initializing module Zapchan");
init("zapchan");
}
}; // anonymous namespace
#endif /* _WINDOWS */
/* vi: set ts=8 sw=4 sts=4 noet: */