Stefan Knoblich 09a61f5025 [FreeTDM] Add (experimental) ftmod_misdn
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>
2011-09-08 00:16:02 +02:00

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
};