mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-02-24 18:41:57 +00:00
Add I/O plugin for mISDN stack that is included in the linux kernel since version 2.6.27. The in-kernel mISDN stack uses a socket based interface (AF_ISDN), data and control commands are exchanged via datagram messages. This makes writing a driver that doesn't use a separate (per-span) thread to handle all incoming events a bit tricky, because responses to control messages and incoming data are mixed and interfacing with the synchronous FreeTDM I/O API is problematic. B(*)/D-channel handling: The current version uses misdn_wait() to poll() for activity on the non-blocking channel sockets and misdn_read() to receive and handle all pending events up to the first PH_DATA_IND (data) message (which is what the caller of the read method is actually after). In case no data has been received, misdn_read() returns FTDM_SUCCESS with *datalen = 0, which is OK for all the signalling modules tested (ftmod_libpri and (out-of-tree) ftmod_isdn). To send data, misdn_write() is called, which just sends a PH_DATA_REQ message to the mISDN channel socket. (*) B-channels use a per-channel timerfd as a timing reference for 'ready-for-write' poll()ing in misdn_wait(). This is a workaround for a limitation of mISDN sockets, which do not support POLLOUT waiting on b-channel sockets (in a useful way). Sending/receiving of data works the same way as on d-channels, otherwise. The module has received some minimal testing using a beronet single-port HFC E1 and a HFC4-S quad-port BRI card on linux-3.0.x. --- Limitations --- - Only the most basic features have been implemented (alarms, sending/receiving data/audio). - Spans are limited to E1 and BRI/BRI_PTMP trunk types. - D-Channels only work on 16 for PRI and 3 for BRI. - NT/TE mode information is not available from freetdm.conf / at configure_span()-time so the module assumes TE mode, which should be only a problem for cards that can change the port configuration (pin-out) from software. - Current design (b-channel timerfd / misdn_wait()/_read()/_write()) should be fine for most SoHo use-cases (scalability / cpu usage / timing precision). --- Requirements --- - mISDNif.h header (/usr/include/mISDN/mISDNif.h), provided by mISDNuser (http://isdn.eversberg.eu/download/lcr-1.7/mISDNuser-20100525.tar.gz). - Linux kernel with mISDN and timerfd enabled (>= 2.6.27) and libc with timerfd support. mISDN options can be found in the: "Device Drivers" -> "ISDN support" -> "Modular ISDN driver" section of make menuconfig. Timerfd is usually enabled by default. The FreeTDM configure script will check for missing mISDNif.h header and timerfd support and print a message. You should see the following in the summary screen on success: ftmod_misdn........................ yes NOTE: Forcing mISDN support using the "--with-misdn" configure option, will cause the configure script to fail on the first missing dependency. --- Usage --- To use the module, make sure you have mISDN support in the kernel (kernel modules loaded or kernel with built-in mISDN running), the "misdn_info" application shipped with mISDNuser will output a list of available mISDN ports on your system, e.g.: Found 5 ports Port 0 'hfc-4s.1-1': TE/NT-mode BRI S/T (for phone lines & phones) 2 B-channels: 1-2 B-protocols: RAW HDLC X75slp ... Port 4 'hfc-e1.2': TE/NT-mode PRI E1 (for phone lines & E1 devices) 30 B-channels: 1-15 17-31 B-protocols: RAW HDLC X75slp NOTE: ftmod_misdn will print an error message if mISDN support is not available, or if there are no ports installed. - Example freetdm.conf settings [span misdn BRI_1] trunk_type => BRI_PTMP b-channel => 0:1,2 d-channel => 0:3 [span misdn PRI_1] trunk_type => E1 b-channel => hfc-e1.2:1-15,17-31 d-channel => hfc-e1.2:16 Signed-off-by: Stefan Knoblich <stkn@openisdn.net>
1808 lines
49 KiB
C
1808 lines
49 KiB
C
/**
|
|
* mISDN HW interface
|
|
*
|
|
* Copyright (c) 2011, Stefan Knoblich <stkn@openisdn.net>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* NOTE: This is intended as a Layer 1 interface only, signaling
|
|
* is handled by other modules (e.g. ftmod_libpri or ftmod_isdn).
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <pthread.h>
|
|
|
|
#include <sys/timerfd.h>
|
|
|
|
/* this is how it should have been...
|
|
#ifdef HAVE_FREETDM_FREETDM_H
|
|
#include <freetdm/freetdm.h>
|
|
#else
|
|
#include <freetdm.h>
|
|
#endif
|
|
*/
|
|
/* ... and this is how it is */
|
|
#include <private/ftdm_core.h>
|
|
|
|
#include <mISDN/mISDNif.h>
|
|
|
|
/*
|
|
* mISDNcompat.h replaces these with references to an extern int,
|
|
* which is exported by libmisdn... unset them and use the official
|
|
* AF ID "34"
|
|
*/
|
|
#undef PF_ISDN
|
|
#undef AF_ISDN
|
|
#define AF_ISDN 34
|
|
#define PF_ISDN AF_ISDN
|
|
|
|
//#define MISDN_DEBUG_EVENTS
|
|
//#define MISDN_DEBUG_IO
|
|
|
|
#ifndef MIN
|
|
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
|
|
#endif
|
|
|
|
typedef enum {
|
|
MISDN_CAPS_NONE = 0,
|
|
|
|
/* D-Channel */
|
|
MISDN_CAPS_PRI = (1 << 0),
|
|
MISDN_CAPS_BRI = (1 << 1),
|
|
MISDN_CAPS_UP0 = (1 << 2),
|
|
MISDN_CAPS_NT = (1 << 3),
|
|
MISDN_CAPS_TE = (1 << 4),
|
|
|
|
/* B-Channel */
|
|
MISDN_CAPS_RAW = (1 << 10),
|
|
MISDN_CAPS_HDLC = (1 << 11),
|
|
} misdn_capability_flags_t;
|
|
|
|
#define MISDN_IS_BRI(x) (x & MISDN_CAPS_BRI)
|
|
#define MISDN_IS_PRI(x) (x & MISDN_CAPS_PRI)
|
|
|
|
#define MISDN_IS_TE(x) (x & MISDN_CAPS_TE)
|
|
#define MISDN_IS_NT(x) (x & MISDN_CAPS_NT)
|
|
|
|
#define MISDN_IS_RAW(x) (x & MISDN_CAPS_RAW)
|
|
#define MISDN_IS_HDLC(x) (x & MISDN_CAPS_HDLC)
|
|
|
|
|
|
const static struct {
|
|
const int id;
|
|
const char *name;
|
|
} misdn_event_types[] = {
|
|
{ PH_DATA_REQ, "PH_DATA_REQ" },
|
|
{ PH_DATA_IND, "PH_DATA_IND" },
|
|
{ PH_DATA_CNF, "PH_DATA_CNF" },
|
|
{ PH_CONTROL_REQ, "PH_CONTROL_REQ" },
|
|
{ PH_CONTROL_IND, "PH_CONTROL_IND" },
|
|
{ PH_CONTROL_CNF, "PH_CONTROL_CNF" },
|
|
{ PH_ACTIVATE_REQ, "PH_ACTIVATE_REQ" },
|
|
{ PH_ACTIVATE_IND, "PH_ACTIVATE_IND" },
|
|
{ PH_ACTIVATE_CNF, "PH_ACTIVATE_CNF" },
|
|
{ PH_DEACTIVATE_REQ, "PH_DEACTIVATE_REQ" },
|
|
{ PH_DEACTIVATE_IND, "PH_DEACTIVATE_IND" },
|
|
{ PH_DEACTIVATE_CNF, "PH_DEACTIVATE_CNF" },
|
|
};
|
|
|
|
static const char *misdn_event2str(const int event)
|
|
{
|
|
int x;
|
|
|
|
for (x = 0; x < ftdm_array_len(misdn_event_types); x++) {
|
|
if (event == misdn_event_types[x].id)
|
|
return misdn_event_types[x].name;
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
|
|
const static struct {
|
|
const int id;
|
|
const char *name;
|
|
} misdn_control_types[] = {
|
|
#define MISDN_CONTROL_TYPE(x) { x, #x }
|
|
MISDN_CONTROL_TYPE(DTMF_HFC_COEF),
|
|
};
|
|
|
|
#if 0 /* unused for now */
|
|
static const char *misdn_control2str(const int ctrl)
|
|
{
|
|
int x;
|
|
|
|
for (x = 0; x < ftdm_array_len(misdn_control_types); x++) {
|
|
if (ctrl == misdn_control_types[x].id)
|
|
return misdn_control_types[x].name;
|
|
}
|
|
return "unknown";
|
|
}
|
|
#endif
|
|
|
|
/***********************************************************************************
|
|
* mISDN <-> FreeTDM data structures
|
|
***********************************************************************************/
|
|
|
|
enum {
|
|
MISDN_SPAN_NONE = 0,
|
|
MISDN_SPAN_RUNNING = (1 << 0),
|
|
MISDN_SPAN_STOPPED = (1 << 1)
|
|
};
|
|
|
|
struct misdn_span_private {
|
|
int flags;
|
|
|
|
/* event conditional */
|
|
pthread_mutex_t event_cond_mutex;
|
|
pthread_cond_t event_cond;
|
|
};
|
|
|
|
#define MISDN_CHAN_STATE_CLOSED 0
|
|
#define MISDN_CHAN_STATE_OPEN 1
|
|
|
|
struct misdn_event_queue;
|
|
|
|
struct misdn_chan_private {
|
|
/* */
|
|
int state;
|
|
int debugfd;
|
|
int timerfd;
|
|
|
|
/* hw addr of channel */
|
|
struct sockaddr_mISDN addr;
|
|
|
|
/* counters */
|
|
unsigned long tx_cnt;
|
|
unsigned long tx_ack_cnt;
|
|
unsigned long rx_cnt;
|
|
unsigned long slip_rx_cnt;
|
|
unsigned long slip_tx_cnt;
|
|
|
|
struct misdn_event_queue *events;
|
|
};
|
|
|
|
#define ftdm_chan_io_private(x) ((x)->io_data)
|
|
#define ftdm_span_io_private(x) ((x)->io_data)
|
|
|
|
static ftdm_status_t misdn_handle_incoming(ftdm_channel_t *ftdmchan, const char *rbuf, const int size);
|
|
|
|
/***********************************************************************************
|
|
* mISDN interface functions
|
|
***********************************************************************************/
|
|
|
|
/*
|
|
* Event Queue
|
|
*/
|
|
#define MISDN_EVENTS_MAX 8
|
|
|
|
struct misdn_event {
|
|
int id;
|
|
};
|
|
|
|
struct misdn_event_queue {
|
|
int read_pos;
|
|
int write_pos;
|
|
pthread_mutex_t mutex;
|
|
|
|
struct misdn_event events[MISDN_EVENTS_MAX];
|
|
};
|
|
|
|
/**
|
|
* Initialize event queue
|
|
*/
|
|
static ftdm_status_t misdn_event_queue_create(struct misdn_event_queue **queue)
|
|
{
|
|
struct misdn_event_queue *tmp = NULL;
|
|
|
|
if (!queue)
|
|
return FTDM_FAIL;
|
|
|
|
tmp = calloc(1, sizeof(*tmp));
|
|
if (!tmp)
|
|
return FTDM_FAIL;
|
|
|
|
pthread_mutex_init(&tmp->mutex, NULL);
|
|
|
|
*queue = tmp;
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Destroy event queue
|
|
*/
|
|
static ftdm_status_t misdn_event_queue_destroy(struct misdn_event_queue **queue)
|
|
{
|
|
if (!queue || !*queue)
|
|
return FTDM_FAIL;
|
|
|
|
pthread_mutex_destroy(&(*queue)->mutex);
|
|
ftdm_safe_free(*queue);
|
|
*queue = NULL;
|
|
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
static ftdm_status_t misdn_event_queue_reset(struct misdn_event_queue *queue)
|
|
{
|
|
if (!queue)
|
|
return FTDM_FAIL;
|
|
pthread_mutex_lock(&queue->mutex);
|
|
|
|
memset(queue->events, 0, sizeof(queue->events));
|
|
queue->read_pos = queue->write_pos = 0;
|
|
|
|
pthread_mutex_unlock(&queue->mutex);
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
static ftdm_status_t misdn_event_queue_has_data(const struct misdn_event_queue *queue)
|
|
{
|
|
if (!queue)
|
|
return FTDM_FALSE;
|
|
|
|
return (queue->read_pos == queue->write_pos) ? FTDM_FALSE : FTDM_TRUE;
|
|
}
|
|
|
|
static struct misdn_event *misdn_event_queue_pop(struct misdn_event_queue *queue)
|
|
{
|
|
struct misdn_event *evt = NULL;
|
|
int next_idx = 0;
|
|
|
|
if (!queue)
|
|
return NULL;
|
|
|
|
pthread_mutex_lock(&queue->mutex);
|
|
|
|
next_idx = (queue->read_pos + 1) % MISDN_EVENTS_MAX;
|
|
|
|
if (queue->read_pos == queue->write_pos) {
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN queue %p: empty\n", queue);
|
|
#endif
|
|
pthread_mutex_unlock(&queue->mutex);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN queue %p: read event (read_pos: %d, write_pos: %d, next_write_pos: %d)\n",
|
|
queue, queue->read_pos, queue->write_pos, next_idx);
|
|
#endif
|
|
/* update read pos */
|
|
evt = &queue->events[queue->read_pos];
|
|
queue->read_pos = next_idx;
|
|
|
|
pthread_mutex_unlock(&queue->mutex);
|
|
return evt;
|
|
}
|
|
|
|
static ftdm_status_t misdn_event_queue_push(struct misdn_event_queue *queue, struct misdn_event *evt)
|
|
{
|
|
int next_idx = 0;
|
|
|
|
if (!queue || !evt)
|
|
return FTDM_FAIL;
|
|
|
|
pthread_mutex_lock(&queue->mutex);
|
|
|
|
next_idx = (queue->write_pos + 1) % MISDN_EVENTS_MAX;
|
|
|
|
if (next_idx == queue->read_pos) {
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN queue %p: full\n", queue);
|
|
#endif
|
|
pthread_mutex_unlock(&queue->mutex);
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN queue %p: wrote event (read_pos: %d, write_pos: %d, next_write_pos: %d)\n",
|
|
queue, queue->read_pos, queue->write_pos, next_idx);
|
|
#endif
|
|
memcpy(&queue->events[queue->write_pos], evt, sizeof(*evt));
|
|
queue->write_pos = next_idx;
|
|
|
|
pthread_mutex_unlock(&queue->mutex);
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
#if 0 /* unused for now */
|
|
static void misdn_event_queue_print_info(const struct misdn_event_queue *queue)
|
|
{
|
|
ftdm_log(FTDM_LOG_DEBUG, "Queue %p\n\tread idx: %d\n\twrite idx: %d\n", queue,
|
|
queue->read_pos, queue->write_pos);
|
|
}
|
|
#endif
|
|
|
|
/***********************************************************************************
|
|
* mISDN helper functions
|
|
***********************************************************************************/
|
|
|
|
#define MISDN_PH_ACTIVATE_TIMEOUT_MS 10000
|
|
#define MISDN_MPH_INFORMATION_TIMEOUT_MS 3000
|
|
|
|
static inline void ts_add_msec(struct timespec *a, int msec)
|
|
{
|
|
a->tv_sec += (msec / 1000);
|
|
a->tv_nsec += (msec % 1000) * 1000000;
|
|
if (a->tv_nsec >= 1000000000) {
|
|
a->tv_sec++;
|
|
a->tv_nsec -= 1000000000;
|
|
}
|
|
}
|
|
|
|
static inline int ts_sub_msec(struct timespec *a, struct timespec *b)
|
|
{
|
|
int msec = 0;
|
|
msec += (a->tv_sec - b->tv_sec) * 1000;
|
|
msec += (a->tv_nsec - b->tv_nsec) / 1000000;
|
|
return msec;
|
|
}
|
|
|
|
static inline int ts_after(struct timespec *a, struct timespec *b)
|
|
{
|
|
if (a->tv_sec > b->tv_sec) return 1;
|
|
if (a->tv_sec == b->tv_sec && a->tv_nsec > b->tv_nsec) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static inline int ts_before(struct timespec *a, struct timespec *b)
|
|
{
|
|
if (a->tv_sec < b->tv_sec) return 1;
|
|
if (a->tv_sec == b->tv_sec && a->tv_nsec < b->tv_nsec) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static ftdm_status_t misdn_activate_channel(ftdm_channel_t *chan, int activate)
|
|
{
|
|
char buf[MAX_DATA_MEM] = { 0 };
|
|
struct mISDNhead *hh = (struct mISDNhead *) buf;
|
|
struct timespec abstimeout;
|
|
int req = 0, resp = 0, ms_left = MISDN_PH_ACTIVATE_TIMEOUT_MS;
|
|
int retval;
|
|
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN sending %s request\n",
|
|
(activate) ? "activation" : "deactivation");
|
|
|
|
/* prepare + send request primitive */
|
|
req = (activate) ? PH_ACTIVATE_REQ : PH_DEACTIVATE_REQ;
|
|
hh->prim = req;
|
|
hh->id = MISDN_ID_ANY;
|
|
|
|
if ((retval = sendto(chan->sockfd, hh, sizeof(*hh), 0, NULL, 0)) < sizeof(*hh)) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN failed to send activation request: %s\n",
|
|
strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &abstimeout);
|
|
ts_add_msec(&abstimeout, ms_left);
|
|
|
|
/* wait for answer */
|
|
while (1) {
|
|
struct timespec now;
|
|
struct pollfd pfd;
|
|
|
|
pfd.fd = chan->sockfd;
|
|
pfd.events = POLLIN /* | POLLPRI */;
|
|
pfd.revents = 0;
|
|
|
|
switch ((retval = poll(&pfd, 1, ms_left))) {
|
|
case 0: /* timeout */
|
|
goto out;
|
|
case -1: /* error */
|
|
if (!(retval == EAGAIN || retval == EINTR)) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN polling for activation confirmation failed: %s\n",
|
|
strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
break;
|
|
default: /* read data */
|
|
break;
|
|
}
|
|
|
|
if (pfd.revents & (POLLIN | POLLPRI)) {
|
|
/* handle incoming message */
|
|
if ((retval = recvfrom(chan->sockfd, buf, sizeof(buf), 0, NULL, NULL)) <= 0) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN failed to receive possible answer for %s request: %s\n",
|
|
(activate) ? "activation" : "deactivation", strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
//#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN got event '%s' while waiting for %s confirmation\n",
|
|
misdn_event2str(hh->prim), (activate) ? "activation" : "deactivation");
|
|
//#endif
|
|
switch (hh->prim) {
|
|
case PH_ACTIVATE_IND: /* success (or not): save last response, */
|
|
case PH_DEACTIVATE_IND: /* stop looping if it's the one we've been waiting for */
|
|
resp = hh->prim;
|
|
if (hh->prim == (activate) ? PH_ACTIVATE_IND : PH_DEACTIVATE_IND) goto out;
|
|
break;
|
|
case PH_ACTIVATE_CNF:
|
|
case PH_DEACTIVATE_CNF:
|
|
resp = hh->prim;
|
|
if (hh->prim == (activate) ? PH_ACTIVATE_CNF : PH_DEACTIVATE_CNF) goto out;
|
|
break;
|
|
case PH_ACTIVATE_REQ: /* REQ echo, ignore */
|
|
case PH_DEACTIVATE_REQ:
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN got '%s' echo while waiting for %s confirmation (id: %#x)\n",
|
|
misdn_event2str(hh->prim), (activate) ? "activation" : "deactivation", hh->id);
|
|
break;
|
|
default: /* other messages, ignore */
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN ignoring event '%s' while waiting for %s confirmation\n",
|
|
misdn_event2str(hh->prim), (activate) ? "activation" : "deactivation");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* check timeout */
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
if (ts_after(&now, &abstimeout) || (ms_left = ts_sub_msec(&abstimeout, &now)) <= 0)
|
|
goto out;
|
|
}
|
|
out:
|
|
if (resp == 0) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN timeout waiting for %s confirmation\n",
|
|
(activate) ? "activation" : "deactivation");
|
|
return FTDM_TIMEOUT;
|
|
}
|
|
if ((req == PH_ACTIVATE_IND && !(resp == PH_ACTIVATE_CNF || resp == PH_ACTIVATE_IND)) ||
|
|
(req == PH_DEACTIVATE_IND && !(resp == PH_DEACTIVATE_CNF || resp == PH_DEACTIVATE_CNF))) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN received '%s' while waiting for %s\n",
|
|
misdn_event2str(resp), (activate) ? "activation" : "deactivation");
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN received %s confirmation\n",
|
|
(activate) ? "activation" : "deactivation");
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
|
|
#if 0 /* unused for now */
|
|
static ftdm_status_t misdn_get_ph_info(ftdm_channel_t *chan, struct ph_info *info)
|
|
{
|
|
char buf[MAX_DATA_MEM] = { 0 };
|
|
struct mISDNhead *hh;
|
|
struct timespec abstimeout;
|
|
int req = 0, resp = 0, ms_left = MISDN_MPH_INFORMATION_TIMEOUT_MS;
|
|
int retval;
|
|
|
|
/* prepare + send request primitive */
|
|
req = MPH_INFORMATION_REQ;
|
|
hh = (struct mISDNhead *)buf;
|
|
hh->prim = req;
|
|
hh->id = MISDN_ID_ANY;
|
|
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN sending %s request\n",
|
|
misdn_event2str(req));
|
|
|
|
if ((retval = sendto(chan->sockfd, &hh, sizeof(hh), 0, NULL, 0)) < sizeof(hh)) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN failed to send %s request: %s\n",
|
|
misdn_event2str(req), strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &abstimeout);
|
|
ts_add_msec(&abstimeout, ms_left);
|
|
|
|
/* wait for answer */
|
|
while (1) {
|
|
struct timespec now;
|
|
struct pollfd pfd;
|
|
|
|
pfd.fd = chan->sockfd;
|
|
pfd.events = POLLIN /* | POLLPRI */;
|
|
pfd.revents = 0;
|
|
|
|
switch ((retval = poll(&pfd, 1, ms_left))) {
|
|
case 0: /* timeout */
|
|
goto out;
|
|
case -1: /* error */
|
|
if (!(retval == EAGAIN || retval == EINTR)) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN polling for %s answer failed: %s\n",
|
|
misdn_event2str(req), strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
break;
|
|
default: /* read data */
|
|
break;
|
|
}
|
|
|
|
if (pfd.revents & (POLLIN | POLLPRI)) {
|
|
/* handle incoming message */
|
|
if ((retval = recvfrom(chan->sockfd, buf, sizeof(buf), 0, NULL, NULL)) <= 0) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN failed to receive possible answer for %s request: %s\n",
|
|
misdn_event2str(req), strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
//#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN got event '%s' while waiting for %s answer\n",
|
|
misdn_event2str(hh->prim), misdn_event2str(req));
|
|
//#endif
|
|
switch (hh->prim) {
|
|
case MPH_INFORMATION_IND: /* success */
|
|
if (retval < MISDN_HEADER_LEN + sizeof(*info)) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN answer for %s is too short\n",
|
|
misdn_event2str(req));
|
|
return FTDM_FAIL;
|
|
}
|
|
resp = hh->prim;
|
|
/* TODO */
|
|
goto out;
|
|
case MPH_INFORMATION_REQ: /* REQ echo, ignore */
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN got '%s' echo while waiting for %s answer\n",
|
|
misdn_event2str(hh->prim), misdn_event2str(req));
|
|
break;
|
|
default: /* other messages, ignore */
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN ignoring event '%s' while waiting for %s answer\n",
|
|
misdn_event2str(hh->prim), misdn_event2str(req));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* check timeout */
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
if (ts_after(&now, &abstimeout) || (ms_left = ts_sub_msec(&abstimeout, &now)) <= 0)
|
|
goto out;
|
|
}
|
|
out:
|
|
if (resp == 0) {
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN timeout waiting for %s answer\n",
|
|
misdn_event2str(req));
|
|
return FTDM_TIMEOUT;
|
|
}
|
|
|
|
return FTDM_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
static int misdn_handle_ph_control_ind(ftdm_channel_t *chan, const struct mISDNhead *hh, const void *data, const int data_len)
|
|
{
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG,
|
|
"PH_CONTROL_IND:\n"
|
|
"\tMessage:\t%s\n"
|
|
"\tPayload:\t%d\n",
|
|
misdn_control2str(hh->id), data_len);
|
|
#endif
|
|
|
|
switch (hh->id) {
|
|
case DTMF_HFC_COEF:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
static int misdn_handle_mph_information_ind(ftdm_channel_t *chan, const struct mISDNhead *hh, const void *data, const int data_len)
|
|
{
|
|
struct misdn_chan_private *priv = ftdm_chan_io_private(chan);
|
|
int alarm_flags, value;
|
|
|
|
if (data_len < sizeof(value)) {
|
|
ftdm_log_chan_msg(chan, FTDM_LOG_ERROR, "mISDN MPH_INFORMATION_IND message is too short\n");
|
|
return FTDM_FAIL;
|
|
}
|
|
value = *(int *)data;
|
|
alarm_flags = chan->alarm_flags;
|
|
|
|
switch (value) {
|
|
case L1_SIGNAL_LOS_ON:
|
|
alarm_flags |= FTDM_ALARM_RED;
|
|
break;
|
|
case L1_SIGNAL_LOS_OFF:
|
|
alarm_flags &= ~FTDM_ALARM_RED;
|
|
break;
|
|
case L1_SIGNAL_AIS_ON:
|
|
alarm_flags |= FTDM_ALARM_AIS;
|
|
break;
|
|
case L1_SIGNAL_AIS_OFF:
|
|
alarm_flags &= ~FTDM_ALARM_AIS;
|
|
break;
|
|
case L1_SIGNAL_RDI_ON:
|
|
alarm_flags |= FTDM_ALARM_YELLOW;
|
|
break;
|
|
case L1_SIGNAL_RDI_OFF:
|
|
alarm_flags &= ~FTDM_ALARM_YELLOW;
|
|
break;
|
|
case L1_SIGNAL_SLIP_RX:
|
|
priv->slip_rx_cnt++;
|
|
break;
|
|
case L1_SIGNAL_SLIP_TX:
|
|
priv->slip_tx_cnt++;
|
|
break;
|
|
default:
|
|
ftdm_log_chan(chan, FTDM_LOG_ERROR, "mISDN unknown MPH_INFORMATION_IND message: %d\n",
|
|
value);
|
|
return FTDM_FAIL;
|
|
}
|
|
if ((value = (alarm_flags ^ chan->alarm_flags))) {
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "mISDN alarm flags have changed %#x -> %#x\n",
|
|
chan->alarm_flags, alarm_flags);
|
|
chan->alarm_flags ^= value;
|
|
}
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/***********************************************************************************
|
|
* mISDN <-> FreeTDM interface functions
|
|
***********************************************************************************/
|
|
|
|
struct misdn_globals {
|
|
int sockfd;
|
|
} globals;
|
|
|
|
/**
|
|
* \brief Open channel
|
|
* \param ftdmchan FreeTDM channel to open
|
|
*/
|
|
static FIO_OPEN_FUNCTION(misdn_open)
|
|
{
|
|
struct misdn_chan_private *chan_priv = ftdm_chan_io_private(ftdmchan);
|
|
ftdm_span_t *span = ftdm_channel_get_span(ftdmchan);
|
|
struct misdn_span_private *span_priv = ftdm_span_io_private(span);
|
|
ftdm_status_t ret = 0;
|
|
|
|
assert(chan_priv);
|
|
assert(span_priv);
|
|
|
|
if (chan_priv->state == MISDN_CHAN_STATE_OPEN) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_INFO, "mISDN channel is already open, skipping activation\n");
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/* flush all events */
|
|
misdn_event_queue_reset(chan_priv->events);
|
|
|
|
/*
|
|
* Send activation request
|
|
*/
|
|
ret = misdn_activate_channel(ftdmchan, 1);
|
|
if (ret != FTDM_SUCCESS) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Failed to activate channel (socket: %d)\n",
|
|
ftdmchan->sockfd);
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "mISDN channel activation request sent\n");
|
|
|
|
switch (ftdmchan->type) {
|
|
case FTDM_CHAN_TYPE_B: {
|
|
struct itimerspec its = {
|
|
.it_interval = { 0, 0 },
|
|
.it_value = { 0, 0 },
|
|
};
|
|
|
|
its.it_interval.tv_nsec = (ftdmchan->effective_interval * 1000000);
|
|
its.it_value.tv_nsec = (ftdmchan->effective_interval * 1000000);
|
|
|
|
/* create tx timerfd */
|
|
chan_priv->timerfd = timerfd_create(CLOCK_MONOTONIC, O_NONBLOCK);
|
|
if (chan_priv->timerfd < 0) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN failed to create b-channel tx interval timer: %s\n",
|
|
strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
/* start tx timerfd */
|
|
ret = timerfd_settime(chan_priv->timerfd, 0, &its, NULL);
|
|
if (ret < 0) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN failed to start b-channel tx interval timer: %s\n",
|
|
strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "mISDN created tx interval (%d ms) timer\n",
|
|
ftdmchan->effective_interval);
|
|
}
|
|
case FTDM_CHAN_TYPE_DQ921:
|
|
chan_priv->state = MISDN_CHAN_STATE_OPEN;
|
|
break;
|
|
default:
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "mISDN invalid channel type '%s'\n",
|
|
ftdm_channel_get_type(ftdmchan));
|
|
break;
|
|
}
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Close channel
|
|
* \param ftdmchan FreeTDM channel to close
|
|
*/
|
|
static FIO_CLOSE_FUNCTION(misdn_close)
|
|
{
|
|
struct misdn_chan_private *chan_priv = ftdm_chan_io_private(ftdmchan);
|
|
ftdm_status_t ret = 0;
|
|
|
|
assert(chan_priv);
|
|
|
|
/* deactivate b-channels on close */
|
|
if (ftdm_channel_get_type(ftdmchan) == FTDM_CHAN_TYPE_B) {
|
|
/*
|
|
* Stop tx timerfd
|
|
*/
|
|
if (chan_priv->timerfd >= 0) {
|
|
close(chan_priv->timerfd);
|
|
chan_priv->timerfd = -1;
|
|
}
|
|
|
|
/*
|
|
* Send deactivation request (don't wait for answer)
|
|
*/
|
|
ret = misdn_activate_channel(ftdmchan, 0);
|
|
if (ret != FTDM_SUCCESS) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "Failed to deactivate channel\n");
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_INFO, "mISDN channel deactivated\n");
|
|
chan_priv->state = MISDN_CHAN_STATE_CLOSED;
|
|
}
|
|
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Execute command
|
|
* \param ftdmchan FreeTDM channel
|
|
* \param command Command to execute
|
|
* \param obj Additional command data
|
|
*/
|
|
static FIO_COMMAND_FUNCTION(misdn_command)
|
|
{
|
|
switch (command) {
|
|
case FTDM_COMMAND_NOOP:
|
|
break;
|
|
case FTDM_COMMAND_SET_INTERVAL:
|
|
// case FTDM_COMMAND_GET_INTERVAL:
|
|
case FTDM_COMMAND_SET_CODEC:
|
|
case FTDM_COMMAND_GET_CODEC:
|
|
case FTDM_COMMAND_SET_NATIVE_CODEC:
|
|
case FTDM_COMMAND_GET_NATIVE_CODEC:
|
|
case FTDM_COMMAND_ENABLE_DTMF_DETECT:
|
|
case FTDM_COMMAND_DISABLE_DTMF_DETECT:
|
|
case FTDM_COMMAND_SEND_DTMF:
|
|
case FTDM_COMMAND_SET_DTMF_ON_PERIOD:
|
|
case FTDM_COMMAND_GET_DTMF_ON_PERIOD:
|
|
case FTDM_COMMAND_SET_DTMF_OFF_PERIOD:
|
|
case FTDM_COMMAND_GET_DTMF_OFF_PERIOD:
|
|
case FTDM_COMMAND_SET_RX_GAIN: /* DSP_VOL_CHANGE_RX / HFC_VOL_CHANGE_RX */
|
|
case FTDM_COMMAND_GET_RX_GAIN:
|
|
case FTDM_COMMAND_SET_TX_GAIN: /* DSP_VOL_CHANGE_TX / HFC_VOL_CHANGE_TX */
|
|
case FTDM_COMMAND_GET_TX_GAIN:
|
|
case FTDM_COMMAND_FLUSH_TX_BUFFERS:
|
|
case FTDM_COMMAND_FLUSH_RX_BUFFERS:
|
|
case FTDM_COMMAND_FLUSH_BUFFERS:
|
|
case FTDM_COMMAND_FLUSH_IOSTATS:
|
|
case FTDM_COMMAND_SET_PRE_BUFFER_SIZE:
|
|
case FTDM_COMMAND_SET_LINK_STATUS:
|
|
case FTDM_COMMAND_GET_LINK_STATUS:
|
|
case FTDM_COMMAND_SET_RX_QUEUE_SIZE:
|
|
case FTDM_COMMAND_SET_TX_QUEUE_SIZE:
|
|
case FTDM_COMMAND_START_MF_PLAYBACK:
|
|
case FTDM_COMMAND_STOP_MF_PLAYBACK:
|
|
case FTDM_COMMAND_GET_IOSTATS:
|
|
case FTDM_COMMAND_SWITCH_IOSTATS:
|
|
/* Supported by mISDN */
|
|
case FTDM_COMMAND_ENABLE_ECHOCANCEL: /* DSP_ECHO_ON */
|
|
case FTDM_COMMAND_DISABLE_ECHOCANCEL: /* DSP_ECHO_OFF */
|
|
case FTDM_COMMAND_ENABLE_LOOP:
|
|
case FTDM_COMMAND_DISABLE_LOOP:
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Received unimplemented command: %d\n",
|
|
command);
|
|
break;
|
|
|
|
case FTDM_COMMAND_GET_INTERVAL:
|
|
FTDM_COMMAND_OBJ_INT = ftdm_channel_get_io_interval(ftdmchan);
|
|
ftdm_log(FTDM_LOG_NOTICE, "Interval %d ms [%d:%d]\n",
|
|
ftdm_channel_get_io_interval(ftdmchan), ftdm_channel_get_span_id(ftdmchan), ftdm_channel_get_id(ftdmchan));
|
|
break;
|
|
|
|
default:
|
|
ftdm_log(FTDM_LOG_ERROR, "Unknown command %d\n", command);
|
|
}
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Wait for new data
|
|
* \param ftdmchan FreeTDM channel to wait on
|
|
* \param flags Wait flags
|
|
* \param to Timeout
|
|
*/
|
|
static FIO_WAIT_FUNCTION(misdn_wait)
|
|
{
|
|
struct misdn_chan_private *chan_priv = ftdm_chan_io_private(ftdmchan);
|
|
struct pollfd pfds[2];
|
|
int nr_fds = 0;
|
|
int retval;
|
|
|
|
memset(pfds, 0, sizeof(pfds));
|
|
|
|
switch (ftdm_channel_get_type(ftdmchan)) {
|
|
case FTDM_CHAN_TYPE_B:
|
|
if (*flags & FTDM_WRITE) {
|
|
pfds[nr_fds].fd = chan_priv->timerfd;
|
|
pfds[nr_fds].events = POLLIN;
|
|
nr_fds++;
|
|
}
|
|
if (*flags & (FTDM_READ | FTDM_EVENTS)) {
|
|
pfds[nr_fds].fd = ftdmchan->sockfd;
|
|
pfds[nr_fds].events |= (*flags & FTDM_READ) ? POLLIN : 0;
|
|
pfds[nr_fds].events |= (*flags & FTDM_EVENTS) ? POLLPRI : 0;
|
|
nr_fds++;
|
|
}
|
|
break;
|
|
default:
|
|
if (*flags & FTDM_READ)
|
|
pfds[0].events |= POLLIN;
|
|
if (*flags & FTDM_WRITE)
|
|
pfds[0].events |= POLLOUT;
|
|
if (*flags & FTDM_EVENTS)
|
|
pfds[0].events |= POLLPRI;
|
|
pfds[0].fd = ftdmchan->sockfd;
|
|
nr_fds++;
|
|
break;
|
|
}
|
|
|
|
*flags = FTDM_NO_FLAGS;
|
|
|
|
if (!(pfds[0].events || pfds[1].events))
|
|
return FTDM_SUCCESS;
|
|
if ((retval = poll(pfds, nr_fds, to)) < 0) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN poll() failed: %s\n",
|
|
strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
if (retval == 0)
|
|
return FTDM_TIMEOUT;
|
|
|
|
switch (ftdm_channel_get_type(ftdmchan)) {
|
|
case FTDM_CHAN_TYPE_B:
|
|
if (pfds[0].fd == chan_priv->timerfd) {
|
|
if (pfds[0].revents & POLLIN) {
|
|
uint64_t tmp = 0; /* clear pending events on timerfd */
|
|
retval = read(pfds[0].fd, &tmp, sizeof(tmp));
|
|
*flags |= FTDM_WRITE;
|
|
}
|
|
if (pfds[1].revents & POLLIN)
|
|
*flags |= FTDM_READ;
|
|
if (pfds[1].revents & POLLPRI)
|
|
*flags |= FTDM_EVENTS;
|
|
break;
|
|
}
|
|
default:
|
|
if (pfds[0].revents & POLLIN)
|
|
*flags |= FTDM_READ;
|
|
if (pfds[0].revents & POLLOUT)
|
|
*flags |= FTDM_WRITE;
|
|
if (pfds[0].revents & POLLPRI)
|
|
*flags |= FTDM_EVENTS;
|
|
break;
|
|
}
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Read data
|
|
* \param ftdmchan FreeTDM channel
|
|
* \param data Buffer for data
|
|
* \param datalen Number of bytes to read (contains bytes read after return)
|
|
*/
|
|
static FIO_READ_FUNCTION(misdn_read)
|
|
{
|
|
struct misdn_chan_private *priv = ftdm_chan_io_private(ftdmchan);
|
|
char rbuf[MAX_DATA_MEM] = { 0 };
|
|
struct mISDNhead *hh = (struct mISDNhead *)rbuf;
|
|
int bytes = *datalen;
|
|
int retval;
|
|
|
|
if (priv->state == MISDN_CHAN_STATE_CLOSED) {
|
|
/* ignore */
|
|
*datalen = 0;
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* try to read all messages, as long as we haven't received a PH_DATA_IND one
|
|
* we'll get a lot of "mISDN_send: error -12" message in dmesg otherwise
|
|
* (= b-channel receive queue overflowing)
|
|
*/
|
|
while (1) {
|
|
if ((retval = recvfrom(ftdmchan->sockfd, rbuf, sizeof(rbuf), 0, NULL, NULL)) < 0) {
|
|
if (errno == EWOULDBLOCK) break;
|
|
if (errno == EAGAIN) continue;
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN failed to receive incoming message: %s\n",
|
|
strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
if (retval < MISDN_HEADER_LEN) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "mISDN received message too small\n");
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
if (hh->prim == PH_DATA_IND) {
|
|
*datalen = MIN(bytes, retval - MISDN_HEADER_LEN);
|
|
memcpy(data, rbuf + MISDN_HEADER_LEN, *datalen);
|
|
#ifdef MISDN_DEBUG_IO
|
|
if (*datalen > 0) {
|
|
char hbuf[MAX_DATA_MEM] = { 0 };
|
|
print_hex_bytes(data, *datalen, hbuf, sizeof(hbuf));
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN read data: %s\n", hbuf);
|
|
}
|
|
#endif
|
|
return FTDM_SUCCESS;
|
|
} else {
|
|
*datalen = 0;
|
|
/* event */
|
|
misdn_handle_incoming(ftdmchan, rbuf, retval);
|
|
}
|
|
}
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Write data
|
|
* \param ftdmchan FreeTDM channel
|
|
* \param data Buffer for data
|
|
* \param datalen Number of bytes to write (contains bytes written after return)
|
|
*/
|
|
static FIO_WRITE_FUNCTION(misdn_write)
|
|
{
|
|
struct misdn_chan_private *priv = ftdm_chan_io_private(ftdmchan);
|
|
char wbuf[MAX_DATA_MEM];
|
|
struct mISDNhead *hh = (struct mISDNhead *)wbuf;
|
|
int size = *datalen;
|
|
int retval = 0;
|
|
|
|
assert(priv);
|
|
|
|
/* ignore empty writes */
|
|
if (*datalen <= 0)
|
|
return FTDM_SUCCESS;
|
|
|
|
#ifdef MISDN_DEBUG_IO
|
|
{
|
|
char hbuf[MAX_DATA_MEM] = { 0 };
|
|
print_hex_bytes(data, *datalen, hbuf, sizeof(hbuf));
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN write data: %s\n", hbuf);
|
|
}
|
|
#endif
|
|
hh->prim = PH_DATA_REQ;
|
|
hh->id = MISDN_ID_ANY;
|
|
|
|
/* avoid buffer overflow */
|
|
size = MIN(size, MAX_DATA_MEM);
|
|
|
|
memcpy(wbuf + MISDN_HEADER_LEN, data, size);
|
|
size += MISDN_HEADER_LEN;
|
|
|
|
#ifdef MISDN_DEBUG_IO
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN writing %d bytes to channel %d:%d socket %d\n",
|
|
size, ftdm_channel_get_span_id(ftdmchan), ftdm_channel_get_id(ftdmchan), ftdmchan->sockfd);
|
|
#endif
|
|
if ((retval = sendto(ftdmchan->sockfd, wbuf, size, 0, NULL, 0)) != size) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN channel %d:%d socket write error: %s\n",
|
|
ftdm_channel_get_span_id(ftdmchan), ftdm_channel_get_id(ftdmchan),
|
|
strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
*datalen = retval;
|
|
|
|
// if (priv->debugfd >= 0) {
|
|
// write(priv->debugfd, wbuf + MISDN_HEADER_LEN, size - MISDN_HEADER_LEN);
|
|
// }
|
|
|
|
priv->tx_cnt++;
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
|
|
static ftdm_status_t misdn_open_range(ftdm_span_t *span, ftdm_chan_type_t type, struct mISDN_devinfo *devinfo, int start, int end)
|
|
{
|
|
int num_configured = 0;
|
|
int d_protocol, d_channel;
|
|
int x;
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN configuring card:range %d:%d->%d\n",
|
|
devinfo->id, start, end);
|
|
|
|
switch (ftdm_span_get_trunk_type(span)) {
|
|
case FTDM_TRUNK_E1:
|
|
d_protocol = ISDN_P_TE_E1;
|
|
d_channel = 16;
|
|
break;
|
|
case FTDM_TRUNK_BRI:
|
|
case FTDM_TRUNK_BRI_PTMP:
|
|
d_protocol = ISDN_P_TE_S0;
|
|
d_channel = 0;
|
|
break;
|
|
default:
|
|
ftdm_log(FTDM_LOG_ERROR, "Unsupported span type %s\n",
|
|
ftdm_span_get_trunk_type_str(span));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
for (x = start; x <= end; x++) {
|
|
struct misdn_chan_private *priv;
|
|
struct sockaddr_mISDN addr;
|
|
ftdm_channel_t *ftdmchan = NULL;
|
|
ftdm_socket_t sockfd = -1;
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN configuring card:channel => %d:%d\n",
|
|
devinfo->id, x);
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.family = AF_ISDN;
|
|
addr.dev = devinfo->id;
|
|
|
|
switch (type) {
|
|
case FTDM_CHAN_TYPE_DQ931: /* unsupported */
|
|
ftdm_log(FTDM_LOG_ERROR, "Unsupported channel type '%s'\n",
|
|
ftdm_chan_type2str(type));
|
|
return FTDM_FAIL;
|
|
|
|
case FTDM_CHAN_TYPE_DQ921:
|
|
/* No NT-mode support, since we have no idea which mode we should run in at this point */
|
|
sockfd = socket(PF_ISDN, SOCK_DGRAM, d_protocol);
|
|
addr.channel = d_channel; /* 0 for S0 and 16 for E1 */
|
|
break;
|
|
|
|
case FTDM_CHAN_TYPE_B:
|
|
if (!test_channelmap(x, devinfo->channelmap)) {
|
|
ftdm_log(FTDM_LOG_ERROR, "Invalid B-Channel specified: %d\n", x);
|
|
return FTDM_FAIL;
|
|
}
|
|
sockfd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_B_RAW);
|
|
addr.channel = x;
|
|
break;
|
|
|
|
default:
|
|
ftdm_log(FTDM_LOG_ERROR, "Invalid/unsupported channel type '%s' (%d)\n",
|
|
ftdm_chan_type2str(type), type);
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
if (sockfd < 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "Failed to open socket: %s\n", strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN opened socket (on chan:dev => %d:%d): %d\n",
|
|
addr.dev, addr.channel, sockfd);
|
|
|
|
/* set non-blocking */
|
|
if (fcntl(sockfd, F_SETFL, O_NONBLOCK) < 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN Failed to set socket fd to non-blocking: %s\n", strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
/*
|
|
* Bind socket to card:channel
|
|
*/
|
|
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "Failed to bind mISDN socket [%d:%d]: %s\n",
|
|
addr.dev, x, strerror(errno));
|
|
close(sockfd);
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
/*
|
|
* Add channel to span
|
|
*/
|
|
if (ftdm_span_add_channel(span, sockfd, type, &ftdmchan) != FTDM_SUCCESS) {
|
|
ftdm_log(FTDM_LOG_ERROR, "Failed to add mISDN ftdmchan to span\n");
|
|
close(sockfd);
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
priv = calloc(1, sizeof(*priv));
|
|
if (!priv) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN failed to allocate channel private data\n");
|
|
close(sockfd);
|
|
return FTDM_FAIL;
|
|
}
|
|
ftdm_chan_io_private(ftdmchan) = priv;
|
|
|
|
priv->addr = addr;
|
|
priv->debugfd = -1;
|
|
priv->timerfd = -1;
|
|
|
|
/*
|
|
* Create event queue
|
|
*/
|
|
misdn_event_queue_create(&priv->events);
|
|
|
|
ftdmchan->rate = 8000;
|
|
ftdmchan->physical_span_id = devinfo->id;
|
|
ftdmchan->physical_chan_id = x;
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_B) {
|
|
ftdmchan->packet_len = 10 /* ms */ * (ftdmchan->rate / 1000);
|
|
ftdmchan->effective_interval = ftdmchan->native_interval = ftdmchan->packet_len / 8;
|
|
ftdmchan->native_codec = ftdmchan->effective_codec = FTDM_CODEC_ALAW;
|
|
|
|
ftdm_channel_set_feature(ftdmchan, FTDM_CHANNEL_FEATURE_INTERVAL);
|
|
} else {
|
|
ftdmchan->native_codec = ftdmchan->effective_codec = FTDM_CODEC_NONE;
|
|
}
|
|
num_configured++;
|
|
}
|
|
|
|
return num_configured;
|
|
}
|
|
|
|
static int misdn_find_device(const char *name, int nr_devices, struct mISDN_devinfo *info)
|
|
{
|
|
struct mISDN_devinfo devinfo;
|
|
char *endp = NULL;
|
|
int port_id = -1;
|
|
int i;
|
|
|
|
port_id = strtoul(name, &endp, 10);
|
|
if (endp == name || errno == EINVAL)
|
|
port_id = -1;
|
|
if (port_id < 0 || port_id >= nr_devices)
|
|
port_id = -1;
|
|
|
|
for (i = 0; i < nr_devices; i++) {
|
|
memset(&devinfo, 0, sizeof(devinfo));
|
|
devinfo.id = i;
|
|
|
|
if (ioctl(globals.sockfd, IMGETDEVINFO, &devinfo) < 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN unable to get device %d info: %s\n",
|
|
devinfo.id, strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
if (devinfo.id == port_id)
|
|
break;
|
|
if (strlen(devinfo.name) <= 0)
|
|
continue;
|
|
if (!strcasecmp(devinfo.name, name))
|
|
break;
|
|
}
|
|
if (i == nr_devices)
|
|
return FTDM_FAIL;
|
|
|
|
if (info) *info = devinfo;
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
#define MISDN_PH_TE_PROTOCOLS(x) \
|
|
((x) & ((1 << ISDN_P_TE_S0) | (1 << ISDN_P_TE_E1) | (1 << ISDN_P_TE_UP0)))
|
|
#define MISDN_PH_NT_PROTOCOLS(x) \
|
|
((x) & ((1 << ISDN_P_NT_S0) | (1 << ISDN_P_NT_E1) | (1 << ISDN_P_NT_UP0)))
|
|
|
|
/**
|
|
* \brief Configure/open span ftmod_misdn settings
|
|
*/
|
|
static FIO_CONFIGURE_SPAN_FUNCTION(misdn_configure_span)
|
|
{
|
|
struct misdn_span_private *span_priv = ftdm_span_io_private(span);
|
|
struct mISDN_devinfo devinfo;
|
|
int range_start = 0, range_end = 0;
|
|
int nr_ports = 0, nr_items = 0;
|
|
int res = 0, i;
|
|
char *chan_str, *ptr;
|
|
char *data = strdup(str);
|
|
char *item_list[10];
|
|
|
|
/* only these are supported */
|
|
switch (ftdm_span_get_trunk_type(span)) {
|
|
case FTDM_TRUNK_E1:
|
|
case FTDM_TRUNK_BRI:
|
|
case FTDM_TRUNK_BRI_PTMP:
|
|
break;
|
|
default:
|
|
ftdm_log(FTDM_LOG_ERROR, "Unsupported span type %s\n",
|
|
ftdm_span_get_trunk_type_str(span));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
/* get port count */
|
|
if (ioctl(globals.sockfd, IMGETCOUNT, &nr_ports) < 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN unable to get port count: %s\n",
|
|
strerror(errno));
|
|
goto error;
|
|
}
|
|
if (nr_ports <= 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "No mISDN devices found\n");
|
|
goto error;
|
|
}
|
|
|
|
/* split configuration string into port ID and channel list */
|
|
if (!(chan_str = strchr(data, ':'))) {
|
|
ftdm_log(FTDM_LOG_ERROR, "Invalid configuration string: %s\nExpected format <card_id>:<channel_1>[-<channel_N>]\n", str);
|
|
goto error;
|
|
}
|
|
*chan_str++ = '\0';
|
|
|
|
/* lookup port id, by number first, then by name */
|
|
if (misdn_find_device(data, nr_ports, &devinfo) != FTDM_SUCCESS) {
|
|
ftdm_log(FTDM_LOG_ERROR, "No such mISDN device/port: %s\n",
|
|
data);
|
|
goto error;
|
|
}
|
|
if (devinfo.nrbchan == 0 || devinfo.channelmap == 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN device '%s' has no b-channels\n",
|
|
data);
|
|
goto error;
|
|
}
|
|
if (!MISDN_PH_TE_PROTOCOLS(devinfo.Dprotocols)) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN device '%s' does not support any ISDN TE modes\n",
|
|
data);
|
|
goto error;
|
|
}
|
|
|
|
/* allocate span private */
|
|
if (!span_priv) {
|
|
/*
|
|
* Not perfect, there should be something like span_create too
|
|
*/
|
|
span_priv = calloc(1, sizeof(*span_priv));
|
|
if (!span_priv) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN failed to allocate span private data\n");
|
|
return FTDM_FAIL;
|
|
}
|
|
ftdm_span_io_private(span) = span_priv;
|
|
|
|
/* init event condition */
|
|
pthread_cond_init(&span_priv->event_cond, NULL);
|
|
pthread_mutex_init(&span_priv->event_cond_mutex, NULL);
|
|
}
|
|
|
|
/* split channel list by ',' */
|
|
nr_items = ftdm_separate_string(chan_str, ',', item_list, ftdm_array_len(item_list));
|
|
|
|
for (i = 0; i < nr_items; i++) {
|
|
/* */
|
|
if (!(ptr = strchr(item_list[i], '-'))) {
|
|
/* single channel */
|
|
range_start = atoi(item_list[i]);
|
|
range_end = range_start;
|
|
} else {
|
|
*ptr++ = '\0';
|
|
/* channel range */
|
|
range_start = atoi(item_list[i]);
|
|
range_end = atoi(ptr);
|
|
}
|
|
|
|
/* check if channel range/id is valid */
|
|
if (range_start <= 0 || range_end <= 0 || range_end < range_start) {
|
|
ftdm_log(FTDM_LOG_ERROR, "Invalid configuration string: %s\n",
|
|
item_list[i]);
|
|
goto error;
|
|
}
|
|
|
|
/* add range to span */
|
|
res = misdn_open_range(span, type, &devinfo, range_start, range_end);
|
|
if (res <= 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN failed to configure channel(s)\n");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ftdm_safe_free(data);
|
|
return res;
|
|
error:
|
|
ftdm_span_io_private(span) = NULL;
|
|
ftdm_safe_free(span_priv);
|
|
ftdm_safe_free(data);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* \brief Configure global ftmod_misdn settings
|
|
*/
|
|
static FIO_CONFIGURE_FUNCTION(misdn_configure)
|
|
{
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Retrieve alarm event information (if any)
|
|
* \param ftdmchan FreeTDM channel
|
|
*/
|
|
static FIO_GET_ALARMS_FUNCTION(misdn_get_alarms)
|
|
{
|
|
#if 0
|
|
/*
|
|
Nope, this won't work...
|
|
|
|
There's no way to create a separate "control" socket for a device
|
|
that can be used to send / receive MPH_INFORMATION_REQ/_IND without
|
|
having to care about PH_* messages in between...
|
|
|
|
... well, unless we use our own event loop (= thread) and
|
|
add event queues and data fifos, so we can sift all the
|
|
messages we get to forward them to the right receiver
|
|
*/
|
|
ftdm_span_t *span = ftdm_channel_get_span(ftdmchan);
|
|
struct misdn_span_private *span_priv = ftdm_span_io_private(span);
|
|
char buf[MAX_DATA_MEM] = { 0 };
|
|
struct sockaddr_mISDN addr;
|
|
struct mISDNhead *hh;
|
|
struct ph_info *phi = NULL;
|
|
struct pollfd pfd;
|
|
socklen_t addrlen = sizeof(addr);
|
|
int retval;
|
|
|
|
/* use the global socket to query alarms */
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN getting alarms for channel %d:%d [%d:%d]\n",
|
|
ftdm_channel_get_span_id(ftdmchan), ftdm_channel_get_id(ftdmchan),
|
|
ftdm_channel_get_ph_span_id(ftdmchan), ftdm_channel_get_ph_id(ftdmchan));
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.family = AF_ISDN;
|
|
addr.dev = ftdm_channel_get_ph_span_id(ftdmchan) - 1;
|
|
addr.channel = ftdm_channel_get_ph_id(ftdmchan) - 1;
|
|
|
|
hh = (struct mISDNhead *)buf;
|
|
hh->prim = MPH_INFORMATION_REQ;
|
|
hh->id = MISDN_ID_ANY;
|
|
|
|
/* */
|
|
if ((retval = sendto(span_priv->ctrlsock, hh, sizeof(*hh), 0, (struct sockaddr *)&addr, addrlen)) <= 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN failed to send '%s' to channel %d:%d: %s\n",
|
|
misdn_event2str(hh->prim), ftdm_channel_get_span_id(ftdmchan),
|
|
ftdm_channel_get_id(ftdmchan), strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
pfd.fd = span_priv->ctrlsock;
|
|
pfd.events = POLLIN /*| POLLPRI*/;
|
|
pfd.revents = 0;
|
|
|
|
if ((retval = poll(&pfd, 1, -1)) <= 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN failed to poll for '%s' answer on channel %d:%d: %s\n",
|
|
misdn_event2str(hh->prim), ftdm_channel_get_span_id(ftdmchan),
|
|
ftdm_channel_get_id(ftdmchan), strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
if (!(pfd.revents & (POLLIN | POLLPRI))) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN failed to poll for '%s' answer on channel %d:%d: %s\n",
|
|
misdn_event2str(hh->prim), ftdm_channel_get_span_id(ftdmchan),
|
|
ftdm_channel_get_id(ftdmchan), "No read/pri flag");
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
if ((retval = recvfrom(span_priv->ctrlsock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addrlen)) < 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN failed to receive answer for '%s' on channel %d:%d: %s\n",
|
|
misdn_event2str(hh->prim), ftdm_channel_get_span_id(ftdmchan),
|
|
ftdm_channel_get_id(ftdmchan), strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
if (retval < MISDN_HEADER_LEN) {
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN short read on channel %d:%d\n",
|
|
ftdm_channel_get_span_id(ftdmchan), ftdm_channel_get_id(ftdmchan));
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
switch (hh->prim) {
|
|
case MPH_INFORMATION_IND:
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN received '%s' on channel %d:%d, size %d bytes\n",
|
|
misdn_event2str(hh->prim), ftdm_channel_get_span_id(ftdmchan),
|
|
ftdm_channel_get_id(ftdmchan), retval);
|
|
break;
|
|
default:
|
|
ftdm_log(FTDM_LOG_ERROR, "mISDN received unexpected answer '%s' on channel %d:%d: %s\n",
|
|
misdn_event2str(hh->prim), ftdm_channel_get_span_id(ftdmchan),
|
|
ftdm_channel_get_id(ftdmchan), strerror(errno));
|
|
return FTDM_FAIL;
|
|
}
|
|
#endif
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Poll for new events
|
|
* \param span FreeTDM span
|
|
* \param ms Timeout (in ms)
|
|
*/
|
|
static FIO_SPAN_POLL_EVENT_FUNCTION(misdn_poll_event)
|
|
{
|
|
struct misdn_span_private *span_priv = ftdm_span_io_private(span);
|
|
struct timespec ts;
|
|
int retval = 0, nr_events = 0;
|
|
int i;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts_add_msec(&ts, ms);
|
|
|
|
for (i = 1; i <= ftdm_span_get_chan_count(span); i++) {
|
|
ftdm_channel_t *chan = ftdm_span_get_channel(span, i);
|
|
struct misdn_chan_private *chan_priv = ftdm_chan_io_private(chan);
|
|
|
|
if (misdn_event_queue_has_data(chan_priv->events)) {
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN channel %d:%d has event(s)\n",
|
|
ftdm_channel_get_span_id(chan), ftdm_channel_get_id(chan));
|
|
#endif
|
|
ftdm_set_flag(chan, FTDM_CHANNEL_IO_EVENT);
|
|
chan->last_event_time = ftdm_current_time_in_ms();
|
|
nr_events++;
|
|
}
|
|
}
|
|
if (nr_events)
|
|
return FTDM_SUCCESS;
|
|
|
|
if ((retval = pthread_cond_timedwait(&span_priv->event_cond, &span_priv->event_cond_mutex, &ts))) {
|
|
switch (retval) {
|
|
case ETIMEDOUT:
|
|
// ftdm_log(FTDM_LOG_DEBUG, "mISDN span %d: No events within %d ms\n",
|
|
// ftdm_span_get_id(span), ms);
|
|
return FTDM_TIMEOUT;
|
|
default:
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN failed to poll for events on span %d: %s\n",
|
|
ftdm_span_get_id(span), strerror(retval));
|
|
return FTDM_FAIL;
|
|
}
|
|
}
|
|
|
|
for (i = 1; i <= ftdm_span_get_chan_count(span); i++) {
|
|
ftdm_channel_t *chan = ftdm_span_get_channel(span, i);
|
|
struct misdn_chan_private *chan_priv = ftdm_chan_io_private(chan);
|
|
|
|
if (misdn_event_queue_has_data(chan_priv->events)) {
|
|
ftdm_set_flag(chan, FTDM_CHANNEL_IO_EVENT);
|
|
chan->last_event_time = ftdm_current_time_in_ms();
|
|
nr_events++;
|
|
}
|
|
}
|
|
return (nr_events) ? FTDM_SUCCESS : FTDM_TIMEOUT; /* no events? => timeout */
|
|
}
|
|
|
|
/**
|
|
* \brief Retrieve event
|
|
* \param span FreeTDM span
|
|
* \param event FreeTDM event
|
|
*/
|
|
static FIO_SPAN_NEXT_EVENT_FUNCTION(misdn_next_event)
|
|
{
|
|
int32_t event_id = FTDM_OOB_INVALID;
|
|
int i;
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "Reading next event from span %d\n",
|
|
ftdm_span_get_id(span));
|
|
|
|
for (i = 1; i <= ftdm_span_get_chan_count(span); i++) {
|
|
ftdm_channel_t *chan = ftdm_span_get_channel(span, i);
|
|
struct misdn_chan_private *chan_priv = ftdm_chan_io_private(chan);
|
|
struct misdn_event *evt = NULL;
|
|
|
|
if (!(evt = misdn_event_queue_pop(chan_priv->events))) {
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log_chan_msg(chan, FTDM_LOG_DEBUG, "mISDN channel event queue has no events\n");
|
|
#endif
|
|
ftdm_clear_io_flag(chan, FTDM_CHANNEL_IO_EVENT);
|
|
continue;
|
|
}
|
|
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log_chan(chan, FTDM_LOG_DEBUG, "Got event '%s' from channel event queue\n",
|
|
misdn_event2str(evt->id));
|
|
#endif
|
|
switch (evt->id) {
|
|
case PH_DEACTIVATE_IND:
|
|
event_id = FTDM_OOB_ALARM_TRAP;
|
|
chan->alarm_flags |= FTDM_ALARM_RED;
|
|
break;
|
|
case PH_ACTIVATE_IND:
|
|
event_id = FTDM_OOB_ALARM_CLEAR;
|
|
chan->alarm_flags &= ~FTDM_ALARM_RED;
|
|
break;
|
|
default:
|
|
ftdm_log(FTDM_LOG_ERROR, "Unhandled event id %d (0x%x) %s\n",
|
|
evt->id, evt->id, misdn_event2str(evt->id));
|
|
continue;
|
|
}
|
|
|
|
chan->last_event_time = 0;
|
|
span->event_header.e_type = FTDM_EVENT_OOB;
|
|
span->event_header.enum_id = event_id;
|
|
span->event_header.channel = chan;
|
|
*event = &span->event_header;
|
|
return FTDM_SUCCESS;
|
|
}
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
/**
|
|
* \brief Shutdown ftmod_misdn channel
|
|
* \param ftdmchan FreeTDM channel
|
|
*/
|
|
static FIO_CHANNEL_DESTROY_FUNCTION(misdn_channel_destroy)
|
|
{
|
|
struct misdn_chan_private *chan_priv = ftdm_chan_io_private(ftdmchan);
|
|
assert(chan_priv);
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "Destroying channel %d:%d\n",
|
|
ftdm_channel_get_span_id(ftdmchan),
|
|
ftdm_channel_get_id(ftdmchan));
|
|
|
|
if (ftdmchan->sockfd >= 0) {
|
|
close(ftdmchan->sockfd);
|
|
ftdmchan->sockfd = -1;
|
|
}
|
|
|
|
/*
|
|
* Destroy fifo + event queue
|
|
*/
|
|
if (chan_priv->events)
|
|
misdn_event_queue_destroy(&chan_priv->events);
|
|
|
|
ftdm_chan_io_private(ftdmchan) = NULL;
|
|
ftdm_safe_free(chan_priv);
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN channel %d:%d destroyed\n",
|
|
ftdm_channel_get_span_id(ftdmchan), ftdm_channel_get_id(ftdmchan));
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Shutdown ftmod_misdn span
|
|
* \param span FreeTDM span
|
|
*/
|
|
static FIO_SPAN_DESTROY_FUNCTION(misdn_span_destroy)
|
|
{
|
|
struct misdn_span_private *span_priv = ftdm_span_io_private(span);
|
|
|
|
ftdm_span_io_private(span) = NULL;
|
|
ftdm_safe_free(span_priv);
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN span %d (%s) destroyed\n",
|
|
ftdm_span_get_id(span), ftdm_span_get_name(span));
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
|
|
static ftdm_status_t misdn_handle_incoming(ftdm_channel_t *ftdmchan, const char *rbuf, const int size)
|
|
{
|
|
struct mISDNhead *hh = (struct mISDNhead *)rbuf;
|
|
struct misdn_chan_private *priv = ftdm_chan_io_private(ftdmchan);
|
|
const char *data = rbuf + sizeof(*hh);
|
|
int data_len = size - sizeof(*hh);
|
|
|
|
assert(priv);
|
|
|
|
#ifdef MISDN_DEBUG_EVENTS
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "mISDN channel received '%s' message (additional data: %d bytes)\n",
|
|
misdn_event2str(hh->prim), data_len);
|
|
#endif
|
|
|
|
switch (hh->prim) {
|
|
/* data events */
|
|
case PH_DATA_CNF: /* TX ack */
|
|
priv->tx_ack_cnt++;
|
|
break;
|
|
case PH_DATA_REQ: /* request echo? */
|
|
break;
|
|
case PH_DATA_E_IND: /* TX/RX ERR(?) */
|
|
break;
|
|
|
|
/* control events */
|
|
case PH_ACTIVATE_REQ:
|
|
case PH_DEACTIVATE_REQ:
|
|
/*
|
|
* Echoed(?) L2->L1 requests, ignore...
|
|
* (something broken in mISDN or the way we setup the channel?)
|
|
*/
|
|
break;
|
|
case PH_CONTROL_IND:
|
|
return misdn_handle_ph_control_ind(ftdmchan, hh, data, data_len);
|
|
case PH_CONTROL_REQ:
|
|
case PH_CONTROL_CNF:
|
|
break;
|
|
|
|
case MPH_INFORMATION_IND:
|
|
return misdn_handle_mph_information_ind(ftdmchan, hh, data, data_len);
|
|
|
|
case PH_ACTIVATE_IND:
|
|
case PH_DEACTIVATE_IND:
|
|
{
|
|
/* other events, enqueue and let misdn_event_next handle it */
|
|
struct misdn_span_private *span_priv = ftdm_span_io_private(ftdmchan->span);
|
|
struct misdn_event evt = { 0 };
|
|
evt.id = hh->prim;
|
|
|
|
misdn_event_queue_push(priv->events, &evt);
|
|
|
|
/* wake possible readers */
|
|
pthread_cond_signal(&span_priv->event_cond);
|
|
}
|
|
break;
|
|
|
|
default: /* error? */
|
|
ftdm_log(FTDM_LOG_DEBUG, "mISDN channel %d:%d received unknown event %d\n",
|
|
ftdm_channel_get_span_id(ftdmchan), ftdm_channel_get_id(ftdmchan), hh->prim);
|
|
break;
|
|
}
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief ftmod_misdn interface
|
|
*/
|
|
//static const ftdm_io_interface_t misdn_interface = {
|
|
static const ftdm_io_interface_t misdn_interface = {
|
|
.name = "misdn",
|
|
|
|
.open = misdn_open,
|
|
.close = misdn_close,
|
|
.wait = misdn_wait,
|
|
.read = misdn_read,
|
|
.write = misdn_write,
|
|
|
|
.poll_event = misdn_poll_event,
|
|
.next_event = misdn_next_event,
|
|
|
|
.command = misdn_command,
|
|
.get_alarms = misdn_get_alarms,
|
|
.configure = misdn_configure, /* configure global parameters */
|
|
.configure_span = misdn_configure_span, /* assign channels to span */
|
|
.channel_destroy = misdn_channel_destroy, /* clean up channel */
|
|
.span_destroy = misdn_span_destroy, /* clean up span */
|
|
};
|
|
|
|
|
|
/**
|
|
* \brief ftmod_misdn module init function
|
|
*/
|
|
static FIO_IO_LOAD_FUNCTION(misdn_load)
|
|
{
|
|
struct mISDNversion ver;
|
|
struct mISDN_devinfo devinfo;
|
|
int devcnt, usecnt;
|
|
int i;
|
|
|
|
/* */
|
|
globals.sockfd = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
|
|
if (globals.sockfd < 0) {
|
|
ftdm_log(FTDM_LOG_CRIT, "Unable to create mISDN base socket (are you sure this kernel has mISDN support?)\n");
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
if (ioctl(globals.sockfd, IMGETVERSION, &ver) < 0) {
|
|
ftdm_log(FTDM_LOG_CRIT, "Unable to retrieve mISDN version\n");
|
|
goto error;
|
|
}
|
|
|
|
ftdm_log(FTDM_LOG_INFO, "mISDN Interface version %hhd.%hhd.%hd\n", ver.major, ver.minor, ver.release);
|
|
|
|
devcnt = 0;
|
|
if (ioctl(globals.sockfd, IMGETCOUNT, &devcnt) < 0) {
|
|
ftdm_log(FTDM_LOG_CRIT, "Unable to retrieve number of mISDN devices\n");
|
|
goto error;
|
|
|
|
}
|
|
|
|
if (!devcnt) {
|
|
ftdm_log(FTDM_LOG_CRIT, "No mISDN devices found\n");
|
|
goto error;
|
|
}
|
|
usecnt = devcnt;
|
|
|
|
ftdm_log(FTDM_LOG_INFO, "Found %d mISDN devices:\n", devcnt);
|
|
|
|
/* Output most important device information */
|
|
for (i = 0; i < devcnt; i++) {
|
|
int caps = MISDN_CAPS_NONE;
|
|
|
|
devinfo.id = i;
|
|
if (ioctl(globals.sockfd, IMGETDEVINFO, &devinfo) < 0) {
|
|
ftdm_log(FTDM_LOG_ERROR, "Failed to retrieve information for device %d\n", i);
|
|
continue;
|
|
}
|
|
|
|
/* print */
|
|
ftdm_log(FTDM_LOG_INFO, "<%d> Name: %s, B-Channels: %d\n",
|
|
devinfo.id,
|
|
ftdm_strlen_zero_buf(devinfo.name) ? "Unknown" : devinfo.name,
|
|
devinfo.nrbchan);
|
|
|
|
/* D-Channels capabilities */
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_TE_E1))
|
|
caps |= MISDN_CAPS_TE | MISDN_CAPS_PRI;
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_NT_E1))
|
|
caps |= MISDN_CAPS_NT | MISDN_CAPS_PRI;
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_TE_S0))
|
|
caps |= MISDN_CAPS_TE | MISDN_CAPS_BRI;
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_NT_S0))
|
|
caps |= MISDN_CAPS_NT | MISDN_CAPS_BRI;
|
|
#ifdef ISDN_P_TE_UP0
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_TE_UP0))
|
|
caps |= MISDN_CAPS_TE | MISDN_CAPS_UP0 | MISDN_CAPS_BRI;
|
|
#endif
|
|
#ifdef ISDN_P_NT_UP0
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_NT_UP0))
|
|
caps |= MISDN_CAPS_NT | MISDN_CAPS_UP0 | MISDN_CAPS_BRI;
|
|
#endif
|
|
/* B-Channel capabilities */
|
|
if (devinfo.Bprotocols & (1 << (ISDN_P_B_RAW & ISDN_P_B_MASK)))
|
|
caps |= MISDN_CAPS_RAW;
|
|
if (devinfo.Bprotocols & (1 << (ISDN_P_B_HDLC & ISDN_P_B_MASK)))
|
|
caps |= MISDN_CAPS_HDLC;
|
|
|
|
ftdm_log(FTDM_LOG_INFO, " Type: %s, Modes: %s %s\n",
|
|
MISDN_IS_PRI(caps) ? "PRI" : "BRI",
|
|
MISDN_IS_NT(caps) ? "NT" : "",
|
|
MISDN_IS_TE(caps) ? "TE" : "");
|
|
|
|
ftdm_log(FTDM_LOG_INFO, " B-Channel modes: %s %s\n",
|
|
MISDN_IS_RAW(caps) ? "RAW" : "",
|
|
MISDN_IS_HDLC(caps) ? "HDLC" : "");
|
|
|
|
if (!(MISDN_IS_NT(caps) || MISDN_IS_TE(caps)) && !MISDN_IS_RAW(caps)) {
|
|
ftdm_log(FTDM_LOG_ERROR, " This device is unusable!\n");
|
|
usecnt--;
|
|
}
|
|
}
|
|
if (!usecnt) {
|
|
ftdm_log(FTDM_LOG_CRIT, "No useable devices found!\n");
|
|
goto error;
|
|
}
|
|
|
|
ftdm_log(FTDM_LOG_INFO, "Found %d useable mISDN devices\n", usecnt);
|
|
|
|
/* assign interface struct */
|
|
*fio = (ftdm_io_interface_t *)&misdn_interface;
|
|
return FTDM_SUCCESS;
|
|
error:
|
|
if (globals.sockfd >= 0)
|
|
close(globals.sockfd);
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
/**
|
|
* \brief ftmod_misdn module shutdown
|
|
*/
|
|
static FIO_IO_UNLOAD_FUNCTION(misdn_unload)
|
|
{
|
|
if (globals.sockfd >= 0)
|
|
close(globals.sockfd);
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief ftmod_misdn module
|
|
*/
|
|
ftdm_module_t ftdm_module = {
|
|
.name = "misdn",
|
|
.io_load = misdn_load,
|
|
.io_unload = misdn_unload
|
|
};
|