]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
[FreeTDM] Add (experimental) ftmod_misdn
authorStefan Knoblich <stkn@openisdn.net>
Wed, 7 Sep 2011 20:51:29 +0000 (22:51 +0200)
committerStefan Knoblich <stkn@openisdn.net>
Wed, 7 Sep 2011 22:16:02 +0000 (00:16 +0200)
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>
libs/freetdm/Makefile.am
libs/freetdm/configure.ac
libs/freetdm/src/ftmod/ftmod_misdn/ftmod_misdn.c [new file with mode: 0644]

index b4eb76d6fdf396b0174cad22a2d424db795c4a3b..3a88b5f52172fe7c193f1ebdb0b9a87f20706026 100644 (file)
@@ -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
 
index 493f0da3b0c882d707b9710fe7855d86e3ee5c29..70033a7a0a2e9a7c5139de36f45429a1813f1032 100644 (file)
@@ -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 <sys/socket.h>]
+        )
+        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 (file)
index 0000000..9673549
--- /dev/null
@@ -0,0 +1,1807 @@
+/**
+ * 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
+};