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