From 09a61f5025b1c7dee05259db0e169f39a565b62d Mon Sep 17 00:00:00 2001 From: Stefan Knoblich Date: Wed, 7 Sep 2011 22:51:29 +0200 Subject: [PATCH] [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 --- libs/freetdm/Makefile.am | 8 + libs/freetdm/configure.ac | 37 + .../src/ftmod/ftmod_misdn/ftmod_misdn.c | 1807 +++++++++++++++++ 3 files changed, 1852 insertions(+) create mode 100644 libs/freetdm/src/ftmod/ftmod_misdn/ftmod_misdn.c diff --git a/libs/freetdm/Makefile.am b/libs/freetdm/Makefile.am index b4eb76d6fd..3a88b5f521 100644 --- a/libs/freetdm/Makefile.am +++ b/libs/freetdm/Makefile.am @@ -248,6 +248,14 @@ ftmod_r2_la_LDFLAGS = -shared -module -avoid-version -lopenr2 ftmod_r2_la_LIBADD = libfreetdm.la endif +if HAVE_MISDN +mod_LTLIBRARIES += ftmod_misdn.la +ftmod_misdn_la_SOURCES = $(SRC)/ftmod/ftmod_misdn/ftmod_misdn.c +ftmod_misdn_la_CFLAGS = $(FTDM_CFLAGS) $(AM_CFLAGS) $(MISDN_CFLAGS) +ftmod_misdn_la_LDFLAGS = -shared -module -avoid-version +ftmod_misdn_la_LIBADD = libfreetdm.la +endif + dox doxygen: doxygen $(FT_SRCDIR)/docs/Doxygen.conf diff --git a/libs/freetdm/configure.ac b/libs/freetdm/configure.ac index 493f0da3b0..70033a7a0a 100644 --- a/libs/freetdm/configure.ac +++ b/libs/freetdm/configure.ac @@ -364,6 +364,42 @@ then fi AM_CONDITIONAL([HAVE_LIBISDN], [test "${HAVE_LIBISDN}" = "yes"]) + +## +# mISDN dependencies +# +HAVE_MISDN="no" +AC_ARG_WITH([misdn], + [AS_HELP_STRING([--with-misdn], [Install ftmod_misdn (mISDN I/O plugin)])], + [case "${withval}" in + no|yes) with_misdn="${withval}" ;; + *) AC_MSG_ERROR([Invalid value \"${with_misdn}\" for --with-misdn option]) ;; + esac], + [with_misdn="auto"] +) +AS_IF([test "${with_misdn}" != "no"], + [AC_MSG_RESULT([${as_nl}<<>> ftmod_misdn (Linux mISDN I/O plugin)]) + AC_CHECK_FUNCS([timerfd_create],, + [AS_IF([test "${with_misdn}" = "yes"], + [AC_MSG_ERROR([no timerfd support in libc])], + [AC_MSG_NOTICE([no timerfd support in libc])] + )] + ) + AC_CHECK_HEADER([mISDN/mISDNif.h],, + [AS_IF([test "${with_misdn}" = "yes"], + [AC_MSG_ERROR([mISDN/mISDNif.h not found])], + [AC_MSG_NOTICE([mISDN/mISDNif.h not found])] + )], + [#include ] + ) + AS_IF([test "${ac_cv_func_timerfd_create}" = "yes" -a "${ac_cv_header_mISDN_mISDNif_h}" = "yes"], + [HAVE_MISDN="yes"], + [AC_MSG_NOTICE([Some required dependencies are missing, module disabled])] + )] +) +AM_CONDITIONAL([HAVE_MISDN], [test "${HAVE_MISDN}" = "yes"]) + + AC_MSG_RESULT([${as_nl}<<>> Creating output files]) AC_CONFIG_FILES([ Makefile @@ -389,6 +425,7 @@ AC_MSG_RESULT([ ftmod_pritap....................... ${HAVE_PRITAP} I/O: ftmod_wanpipe...................... ${HAVE_LIBSANGOMA} + ftmod_misdn........................ ${HAVE_MISDN} =============================================================================== ]) diff --git a/libs/freetdm/src/ftmod/ftmod_misdn/ftmod_misdn.c b/libs/freetdm/src/ftmod/ftmod_misdn/ftmod_misdn.c new file mode 100644 index 0000000000..9673549f79 --- /dev/null +++ b/libs/freetdm/src/ftmod/ftmod_misdn/ftmod_misdn.c @@ -0,0 +1,1807 @@ +/** + * mISDN HW interface + * + * Copyright (c) 2011, Stefan Knoblich + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* this is how it should have been... +#ifdef HAVE_FREETDM_FREETDM_H +#include +#else +#include +#endif +*/ +/* ... and this is how it is */ +#include + +#include + +/* + * 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 :[-]\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 +};