2073 lines
56 KiB
C
2073 lines
56 KiB
C
/* layer 1/2 handling
|
|
*
|
|
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
|
|
* All Rights Reserved
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <stddef.h>
|
|
#include <mISDN/mbuffer.h>
|
|
#include "../libdebug/debug.h"
|
|
#include "../libg711/g711.h"
|
|
#include "isdn.h"
|
|
#include "dss1.h"
|
|
#include "ie.h"
|
|
#ifndef u_char
|
|
#define u_char unsigned char
|
|
#endif
|
|
#include <mISDN/mlayer3.h>
|
|
#include <mISDN/q931.h>
|
|
|
|
#ifndef container_of
|
|
#define container_of(ptr, type, member) ({ \
|
|
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
|
|
(type *)( (char *)__mptr - offsetof(type,member) );})
|
|
#endif
|
|
|
|
#define B_TIMER_ACTIVATING 1 // seconds
|
|
#define B_TIMER_DEACTIVATING 1 // seconds
|
|
|
|
int check_mISDN_dsp(void)
|
|
{
|
|
char buffer[256];
|
|
int found = 0;
|
|
FILE *fp = fopen("/proc/modules", "r");
|
|
|
|
if (!fp) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Failed to read /proc/modules.\n");
|
|
return -errno;
|
|
}
|
|
|
|
while((fgets(buffer, sizeof(buffer), fp))) {
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
if (strstr(buffer, "mISDN_dsp"))
|
|
found = 1;
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
if (found)
|
|
return 0;
|
|
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Required module 'mISDN_dsp' is not loaded. Run 'modprobe mISDN_dsp' to load the module!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Channel selection
|
|
*/
|
|
|
|
/* set default out_channel */
|
|
static void default_out_channel(isdn_t *isdn_ep)
|
|
{
|
|
struct select_channel *selchannel;
|
|
|
|
selchannel = calloc(1, sizeof(struct select_channel));
|
|
if (isdn_ep->ntmode)
|
|
selchannel->channel = CHANNEL_FREE;
|
|
else
|
|
selchannel->channel = CHANNEL_ANY;
|
|
isdn_ep->out_channel = selchannel;
|
|
|
|
/* additional channel selection for multipoint NT ports */
|
|
if (!isdn_ep->ptp && isdn_ep->ntmode) {
|
|
selchannel->next = calloc(1, sizeof(struct select_channel));
|
|
selchannel = selchannel->next;
|
|
selchannel->channel = CHANNEL_NO; // call waiting
|
|
}
|
|
}
|
|
|
|
/* set default in_channel */
|
|
static void default_in_channel(isdn_t *isdn_ep)
|
|
{
|
|
struct select_channel *selchannel;
|
|
|
|
selchannel = calloc(1, sizeof(struct select_channel));
|
|
selchannel->channel = CHANNEL_FREE;
|
|
|
|
isdn_ep->in_channel = selchannel;
|
|
}
|
|
|
|
/* parse string for a positive number */
|
|
static int get_number(char *value)
|
|
{
|
|
int val = 0;
|
|
char text[10];
|
|
|
|
val = atoi(value);
|
|
|
|
sprintf(text, "%d", val);
|
|
|
|
if (!strcmp(value, text))
|
|
return val;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* remove element from buffer
|
|
* and return pointer to next element in buffer */
|
|
static char *get_separated(char *buffer)
|
|
{
|
|
while(*buffer) {
|
|
if (*buffer==',' || *buffer<=32) { /* separate */
|
|
*buffer++ = '\0';
|
|
while((*buffer>'\0' && *buffer<=32) || *buffer==',')
|
|
buffer++;
|
|
return(buffer);
|
|
}
|
|
buffer++;
|
|
}
|
|
return(buffer);
|
|
}
|
|
|
|
|
|
/* parse outgoing channel list */
|
|
static int parse_out_channel(isdn_t *isdn_ep, const char *_list)
|
|
{
|
|
char list[strlen(_list) + 1];
|
|
struct select_channel *selchannel, **selchannel_p = &isdn_ep->out_channel;
|
|
int val;
|
|
char *p, *el;
|
|
|
|
strcpy(list, _list);
|
|
p = list;
|
|
while(*p) {
|
|
el = p;
|
|
p = get_separated(p);
|
|
if (!strcasecmp(el, "force")) {
|
|
isdn_ep->out_channel_exclusive = 1;
|
|
if (isdn_ep->out_channel) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Error outgoing channel string: value 'force' may only appear as first element in list.\n");
|
|
return(-1);
|
|
}
|
|
} else
|
|
if (!strcasecmp(el, "any")) {
|
|
val = CHANNEL_ANY;
|
|
goto selchannel;
|
|
} else
|
|
if (!strcasecmp(el, "free")) {
|
|
val = CHANNEL_FREE;
|
|
goto selchannel;
|
|
} else
|
|
if (!strcasecmp(el, "no")) {
|
|
val = CHANNEL_NO;
|
|
goto selchannel;
|
|
} else {
|
|
val = get_number(el);
|
|
if (val == -1) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Error outgoing channel string: expecting a comma separated list of 'force', 'any', 'free', 'no' and any channel number.\n");
|
|
return(-1);
|
|
}
|
|
|
|
if (val<1 || val==16 || val>126) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Error outgoing channel string: channel '%d' out of range.\n", val);
|
|
return(-1);
|
|
}
|
|
selchannel:
|
|
/* add to select-channel list */
|
|
selchannel = calloc(1, sizeof(struct select_channel));
|
|
/* set value */
|
|
selchannel->channel = val;
|
|
/* tail port */
|
|
*selchannel_p = selchannel;
|
|
selchannel_p = &((*selchannel_p)->next);
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/* parse incoming channel list */
|
|
static int parse_in_channel(isdn_t *isdn_ep, const char *_list)
|
|
{
|
|
char list[strlen(_list) + 1];
|
|
struct select_channel *selchannel, **selchannel_p = &isdn_ep->in_channel;
|
|
int val;
|
|
char *p, *el;
|
|
|
|
strcpy(list, _list);
|
|
p = list;
|
|
while(*p) {
|
|
el = p;
|
|
p = get_separated(p);
|
|
if (isdn_ep->in_channel) if (isdn_ep->in_channel->channel == CHANNEL_FREE) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Error incoming channel list: values behind 'free' keyword have no effect.\n");
|
|
return(-1);
|
|
}
|
|
if (!strcasecmp(el, "free")) {
|
|
val = CHANNEL_FREE;
|
|
goto selchannel;
|
|
} else {
|
|
val = get_number(el);
|
|
if (val == -1) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Error incoming channel list: expectng a comma separated list of channel numbers and 'free'.\n");
|
|
return(-1);
|
|
}
|
|
|
|
if (val<1 || val==16 || val>126) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Error incoming channel list: channel '%d' out of range.\n", val);
|
|
return(-1);
|
|
}
|
|
selchannel:
|
|
/* add to select-channel list */
|
|
selchannel = calloc(1, sizeof(struct select_channel));
|
|
/* set value */
|
|
selchannel->channel = val;
|
|
/* tail port */
|
|
*selchannel_p = selchannel;
|
|
selchannel_p = &((*selchannel_p)->next);
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/* hunt bchannel for incoming setup */
|
|
int hunt_bchannel_in(isdn_t *isdn_ep, int channel, int exclusive)
|
|
{
|
|
struct select_channel *selchannel;
|
|
int i;
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (incoming call)\n");
|
|
|
|
if (exclusive<0)
|
|
exclusive = 0;
|
|
if (channel == CHANNEL_NO)
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> request = no-channel\n");
|
|
else if (channel > 0)
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> request = channel %d%s\n", channel, exclusive?" (forced)":"");
|
|
else
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> request = any channel\n");
|
|
if (channel==CHANNEL_NO && !isdn_ep->ntmode) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = incoming call-waiting not supported for TE-mode\n");
|
|
return(-6); // channel unacceptable
|
|
}
|
|
if (channel <= 0) /* not given, no channel, whatever.. */
|
|
channel = CHANNEL_ANY; /* any channel */
|
|
if (isdn_ep->b_reserved >= isdn_ep->b_num) { // of out chan..
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = all channels are reserved\n");
|
|
return(-34); // no channel
|
|
}
|
|
if (channel == CHANNEL_ANY)
|
|
goto get_from_list;
|
|
if (channel > 0) {
|
|
/* check for given channel in selection list */
|
|
selchannel = isdn_ep->in_channel;
|
|
while(selchannel) {
|
|
if (selchannel->channel == channel || selchannel->channel == CHANNEL_FREE)
|
|
break;
|
|
selchannel = selchannel->next;
|
|
}
|
|
if (!selchannel)
|
|
channel = 0;
|
|
|
|
/* exclusive channel requests must be in the list */
|
|
if (exclusive) {
|
|
/* no exclusive channel */
|
|
if (!channel) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = exclusively requested channel not in list\n");
|
|
return(-6); // channel unacceptable
|
|
}
|
|
/* get index for channel */
|
|
i = channel-1-(channel>=17);
|
|
if (i < 0 || i >= isdn_ep->b_num || channel == 16) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = exclusively requested channel outside interface range\n");
|
|
return(-6); // channel unacceptable
|
|
}
|
|
/* check if busy */
|
|
if (isdn_ep->b_call[i] == NULL)
|
|
goto use_channel;
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = exclusively requested channel is busy\n");
|
|
return(-6); // channel unacceptable
|
|
}
|
|
|
|
/* requested channels in list will be used */
|
|
if (channel) {
|
|
/* get index for channel */
|
|
i = channel-1-(channel>=17);
|
|
if (i < 0 || i >= isdn_ep->b_num || channel == 16) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> quested channel %d outside interface range\n", channel);
|
|
} else /* if inside range (else) check if available */
|
|
if (isdn_ep->b_call[i] == NULL)
|
|
goto use_channel;
|
|
}
|
|
|
|
/* if channel is not available or not in list, it must be searched */
|
|
get_from_list:
|
|
/* check for first free channel in list */
|
|
channel = 0;
|
|
selchannel = isdn_ep->in_channel;
|
|
while(selchannel) {
|
|
switch(selchannel->channel) {
|
|
case CHANNEL_FREE: /* free channel */
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> hunting free channel\n");
|
|
if (isdn_ep->b_reserved >= isdn_ep->b_num)
|
|
break; /* all channel in use or reserved */
|
|
/* find channel */
|
|
i = 0;
|
|
while(i < isdn_ep->b_num) {
|
|
if (isdn_ep->b_call[i] == NULL) {
|
|
channel = i+1+(i>=15);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> hunting channel %d\n", selchannel->channel);
|
|
if (selchannel->channel<1 || selchannel->channel==16)
|
|
break; /* invalid channels */
|
|
i = selchannel->channel-1-(selchannel->channel>=17);
|
|
if (i >= isdn_ep->b_num)
|
|
break; /* channel not in port */
|
|
if (isdn_ep->b_call[i] == NULL) {
|
|
channel = selchannel->channel;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (channel)
|
|
break; /* found channel */
|
|
selchannel = selchannel->next;
|
|
}
|
|
if (!channel) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel available\n");
|
|
return(-6); // channel unacceptable
|
|
}
|
|
}
|
|
use_channel:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = channel available\n");
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> connect to channel %d\n", channel);
|
|
return(channel);
|
|
}
|
|
|
|
/* hunt bchannel for outgoing setup */
|
|
int hunt_bchannel_out(isdn_t *isdn_ep, int *channel, int *exclusive)
|
|
{
|
|
struct select_channel *selchannel;
|
|
int i;
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (outgoing call)\n");
|
|
|
|
/* see if link is up on PTP*/
|
|
if (isdn_ep->l2hold && isdn_ep->l2link<1) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = layer 2 is down\n");
|
|
return -27;
|
|
}
|
|
|
|
/* check for channel form selection list */
|
|
*exclusive = isdn_ep->out_channel_exclusive;
|
|
*channel = 0;
|
|
selchannel = isdn_ep->out_channel;
|
|
while(selchannel) {
|
|
switch(selchannel->channel) {
|
|
case CHANNEL_FREE: /* free channel */
|
|
if (isdn_ep->b_reserved >= isdn_ep->b_num)
|
|
break; /* all channel in use or reserved */
|
|
/* find channel */
|
|
i = 0;
|
|
while(i < isdn_ep->b_num) {
|
|
if (isdn_ep->b_call[i] == NULL) {
|
|
*channel = i+1+(i>=15);
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = free channel %d\n", *channel);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
if (*channel)
|
|
break;
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> no free channel found\n");
|
|
break;
|
|
|
|
case CHANNEL_ANY: /* don't ask for channel */
|
|
if (isdn_ep->b_reserved >= isdn_ep->b_num) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> cannot ask for any channel, because all channel are reserved\n");
|
|
break; /* all channel in use or reserved */
|
|
}
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = any channel\n");
|
|
*channel = CHANNEL_ANY;
|
|
break;
|
|
|
|
case CHANNEL_NO: /* call waiting */
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel\n");
|
|
*channel = CHANNEL_NO;
|
|
break;
|
|
|
|
default:
|
|
if (selchannel->channel<1 || selchannel->channel==16) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> channel %d is out of range\n", selchannel->channel);
|
|
break; /* invalid channels */
|
|
}
|
|
i = selchannel->channel-1-(selchannel->channel>=17);
|
|
if (i >= isdn_ep->b_num) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> channel %d is out of range\n", selchannel->channel);
|
|
break; /* channel not in port */
|
|
}
|
|
if (isdn_ep->b_call[i] == NULL) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = channel %d\n", selchannel->channel);
|
|
*channel = selchannel->channel;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (*channel)
|
|
break; /* found channel */
|
|
selchannel = selchannel->next;
|
|
}
|
|
|
|
if (*channel)
|
|
return 0;
|
|
|
|
/* if channel was found, return channel */
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel found\n");
|
|
|
|
return -34;
|
|
}
|
|
|
|
/* open channel of incoming setup */
|
|
int open_bchannel_in(call_t *call, int channel, int exclusive)
|
|
{
|
|
int rc;
|
|
|
|
rc = seize_bchannel(call, channel, exclusive);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* open channel of outgoing setup */
|
|
int open_bchannel_out(call_t *call, unsigned int cmd, int channel, int exclusive)
|
|
{
|
|
int rc;
|
|
|
|
/* correct exclusive to 0, if no explicit channel was given */
|
|
if (exclusive < 0 || channel <= 0)
|
|
exclusive = 0;
|
|
|
|
/* select scenario */
|
|
if (call->b_channel && call->b_exclusive) {
|
|
/*** we gave an exclusive channel (or if we are done) ***/
|
|
|
|
/* if not first reply, we are done */
|
|
if (call->state != ISDN_STATE_OUT_SETUP)
|
|
return(0);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (reply from outgoing call)\n");
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> request = %d (forced)\n", call->b_channel);
|
|
PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply = %d\n":" -> reply = (none)\n", channel);
|
|
|
|
/* if give channel not accepted or not equal */
|
|
if (channel != -1 && call->b_channel != channel) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = forced channel not accepted\n");
|
|
return -44;
|
|
}
|
|
|
|
rc = seize_bchannel(call, channel, 1); // exclusively
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "result = requested channel not available anymore\n");
|
|
return -47;
|
|
}
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = channel was accepted\n");
|
|
|
|
/* activate our exclusive channel */
|
|
bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE);
|
|
} else
|
|
if (call->b_channel) {
|
|
/*** we gave a non-exclusive channel ***/
|
|
|
|
/* if not first reply, we are done */
|
|
if (call->state != ISDN_STATE_OUT_SETUP)
|
|
return(0);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (reply from outgoing call)\n");
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> request = %d (suggest)\n", call->b_channel);
|
|
PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply = %d\n":" -> reply = (none)\n", channel);
|
|
|
|
/* if channel was accepted as given */
|
|
if (channel==-1 || call->b_channel==channel) {
|
|
call->b_exclusive = 1; // we are done
|
|
|
|
/* if channel was accepted, seize_bchannel shall simply return, because given channel is already set */
|
|
rc = seize_bchannel(call, call->b_channel, 1); // exclusively
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel not available\n");
|
|
return -47;
|
|
}
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = channel was accepted as given\n");
|
|
|
|
/* activate channel accepted by remote */
|
|
bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE);
|
|
return(0);
|
|
}
|
|
|
|
/* if channel value is faulty */
|
|
if (channel <= 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = illegal reply\n");
|
|
return -111; // protocol error
|
|
}
|
|
|
|
/* if channel was not accepted, try to get a different one */
|
|
rc = seize_bchannel(call, channel, 1); // exclusively
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel not available\n");
|
|
return -47;
|
|
}
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel accepted\n");
|
|
|
|
/* activate channel given by remote */
|
|
bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE);
|
|
} else
|
|
if (call->b_reserve) {
|
|
/*** we sent 'any channel acceptable' ***/
|
|
|
|
/* if not first reply, we are done */
|
|
if (call->state != ISDN_STATE_OUT_SETUP)
|
|
return(0);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (reply from outgoing call)\n");
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> request = any\n");
|
|
PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply = %d\n":" -> reply = (none)\n", channel);
|
|
/* if no channel was replied */
|
|
if (channel <= 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel, protocol error\n");
|
|
return -111; // protocol error
|
|
}
|
|
|
|
/* we will see, if our received channel is available */
|
|
rc = seize_bchannel(call, channel, 1); // exclusively
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel not available\n");
|
|
return -47;
|
|
}
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel accepted\n");
|
|
|
|
/* activate channel given by remote */
|
|
bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE);
|
|
} else {
|
|
/*** we sent 'no channel available' ***/
|
|
|
|
/* if not the first reply, but a connect, we are forced */
|
|
if (cmd == MT_CONNECT && call->state != ISDN_STATE_OUT_SETUP) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (connect reply from outgoing call)\n");
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> request = no channel\n");
|
|
PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply %d%s\n":" -> reply (none)\n", channel, exclusive?" (forced)":"");
|
|
if (channel > 0) {
|
|
goto use_from_connect;
|
|
}
|
|
rc = seize_bchannel(call, CHANNEL_ANY, 0); // any channel
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel available during call-waiting\n");
|
|
return -34;
|
|
}
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = using channel %d\n", call->b_channel);
|
|
call->b_exclusive = 1; // we are done
|
|
|
|
/* activate channel given by remote */
|
|
bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE);
|
|
return(0);
|
|
}
|
|
|
|
/* if not first reply, we are done */
|
|
if (call->state != ISDN_STATE_OUT_SETUP)
|
|
return(0);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (reply from outgoing call)\n");
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> request = no channel\n");
|
|
PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply = %d\n":" -> reply = (none)\n", channel);
|
|
/* if first reply has no channel, we are done */
|
|
if (channel <= 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel until connect\n");
|
|
return(0);
|
|
}
|
|
|
|
/* we will see, if our received channel is available */
|
|
use_from_connect:
|
|
rc = seize_bchannel(call, channel, exclusive);
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel not available\n");
|
|
return -47;
|
|
}
|
|
PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel accepted\n");
|
|
call->b_exclusive = 1; // we are done
|
|
|
|
/* activate channel given by remote */
|
|
bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE);
|
|
}
|
|
return(0);
|
|
|
|
}
|
|
|
|
/*
|
|
* ISDN instance
|
|
*/
|
|
|
|
/* create isdn interface instance */
|
|
isdn_t *isdn_create(void)
|
|
{
|
|
isdn_t *isdn_ep;
|
|
|
|
isdn_ep = calloc(1, sizeof(*isdn_ep));
|
|
if (!isdn_ep) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "No memory!\n");
|
|
abort();
|
|
}
|
|
|
|
if (pthread_mutex_init(&isdn_ep->upqueue_lock, NULL)) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Cannot init mutex!\n");
|
|
abort();
|
|
}
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "ISDN instance created\n");
|
|
|
|
return isdn_ep;
|
|
}
|
|
|
|
/* destroy isdn interface instance and free all resource */
|
|
void isdn_destroy(isdn_t *isdn_ep)
|
|
{
|
|
struct msn_list *msn;
|
|
|
|
/* remove stack instance */
|
|
isdn_close(isdn_ep);
|
|
|
|
/* close mISDN socket */
|
|
if (isdn_ep->socket > 0)
|
|
close(isdn_ep->socket);
|
|
|
|
/* destroy all calls */
|
|
while (isdn_ep->call_list)
|
|
call_destroy(isdn_ep->call_list);
|
|
|
|
/* free msn list */
|
|
while (isdn_ep->msn_list) {
|
|
msn = isdn_ep->msn_list;
|
|
isdn_ep->msn_list = msn->next;
|
|
free(msn);
|
|
}
|
|
|
|
pthread_mutex_destroy(&isdn_ep->upqueue_lock);
|
|
|
|
timer_exit(&isdn_ep->l2establish_timer);
|
|
|
|
free(isdn_ep);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "ISDN instance destroyed\n");
|
|
}
|
|
|
|
/* initialization and configuration to isdn interface instance */
|
|
int isdn_initialize(isdn_t *isdn_ep, char law, const char *portname, int ntmode, int ptp, int layer1hold, int layer2hold, const char *channel_out, const char *channel_in, const char *timeouts, int tx_delay, int tx_gain, int rx_gain, const char *pipeline, int dtmf, int local_tones, int serving_location)
|
|
{
|
|
int rc;
|
|
|
|
/* open mISDN socket */
|
|
rc = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Cannot open mISDN due to errno=%d:%s. (Does your Kernel support socket based mISDN? Protocol family is %d.)\n", errno, strerror(errno), PF_ISDN);
|
|
return -errno;
|
|
}
|
|
isdn_ep->socket = rc;
|
|
|
|
/* store settings */
|
|
isdn_ep->law = law;
|
|
isdn_ep->portname = strdup(portname);
|
|
isdn_ep->ntmode = ntmode;
|
|
isdn_ep->ptp = ptp;
|
|
isdn_ep->l1hold = layer1hold;
|
|
isdn_ep->l2hold = layer2hold;
|
|
isdn_ep->timeouts = timeouts;
|
|
isdn_ep->tx_delay = tx_delay;
|
|
isdn_ep->tx_gain = tx_gain;
|
|
isdn_ep->rx_gain = rx_gain;
|
|
isdn_ep->pipeline = pipeline;
|
|
isdn_ep->dtmf = dtmf;
|
|
isdn_ep->local_tones = local_tones;
|
|
isdn_ep->serving_location = serving_location;
|
|
|
|
/* channel selection list */
|
|
if (channel_out) {
|
|
rc = parse_out_channel(isdn_ep, channel_out);
|
|
if (rc < 0)
|
|
return rc;
|
|
} else
|
|
default_out_channel(isdn_ep);
|
|
if (channel_in) {
|
|
rc = parse_in_channel(isdn_ep, channel_in);
|
|
if (rc < 0)
|
|
return rc;
|
|
} else
|
|
default_in_channel(isdn_ep);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "ISDN instance initialized\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* add MSN number */
|
|
void isdn_add_msn(isdn_t *isdn_ep, const char *msn)
|
|
{
|
|
struct msn_list *m, **m_p;
|
|
|
|
m = calloc(1, sizeof(*m) + strlen(msn) + 1);
|
|
if (!m) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "No memory!\n");
|
|
abort();
|
|
}
|
|
|
|
m_p = &isdn_ep->msn_list;
|
|
while (*m_p)
|
|
m_p = &((*m_p)->next);
|
|
*m_p = m;
|
|
|
|
strcpy(m->msn, msn);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "added MSDN number '%s'\n", msn);
|
|
}
|
|
|
|
/*
|
|
* call instance
|
|
*/
|
|
|
|
call_t *call_create(isdn_t *isdn_ep, int channel, int exclusive, int mode)
|
|
{
|
|
call_t *call, **call_p;
|
|
int rc;
|
|
|
|
call = calloc(1, sizeof(*call));
|
|
if (!call) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "No memory!\n");
|
|
abort();
|
|
}
|
|
|
|
call_p = &isdn_ep->call_list;
|
|
while (*call_p)
|
|
call_p = &((*call_p)->next);
|
|
*call_p = call;
|
|
|
|
call->isdn_ep = isdn_ep;
|
|
call->b_index = -1;
|
|
call->b_channel = 0;
|
|
call->b_exclusive = 0;
|
|
call->b_reserve = 0;
|
|
call->b_mode = mode;
|
|
call->hold = 0;
|
|
call->tx_gain = isdn_ep->tx_gain;
|
|
call->rx_gain = isdn_ep->rx_gain;
|
|
call->conf = 0;
|
|
call->mute = 0;
|
|
call->txdata = 0;
|
|
call->tx_delay = isdn_ep->tx_delay;
|
|
call->echo = 0;
|
|
call->tone = 0;
|
|
call->rxoff = 0;
|
|
call->dtmf = isdn_ep->dtmf;
|
|
call->dtmf_threshold = 0;
|
|
call->pipeline = isdn_ep->pipeline;
|
|
|
|
/* if any channel requested by constructor */
|
|
if (channel == CHANNEL_ANY) {
|
|
/* reserve channel */
|
|
call->b_reserve = 1;
|
|
isdn_ep->b_reserved++;
|
|
}
|
|
|
|
/* reserve channel */
|
|
if (channel > 0) // only if constructor was called with a channel resevation
|
|
seize_bchannel(call, channel, exclusive);
|
|
|
|
/* allocate jitter buffer */
|
|
rc = jitter_create(&call->dejitter, 8000 / 10); // FIXME: size
|
|
if (rc < 0)
|
|
abort();
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Created new call on port #%d:%s\n", isdn_ep->portnum, isdn_ep->portname);
|
|
|
|
return call;
|
|
}
|
|
|
|
void call_destroy(call_t *call)
|
|
{
|
|
call_t **call_p;
|
|
|
|
/* free jitter buffer */
|
|
jitter_destroy(&call->dejitter);
|
|
|
|
/* remove B-channel relation */
|
|
drop_bchannel(call);
|
|
|
|
/* free session description */
|
|
if (call->cc_session)
|
|
osmo_cc_free_session(call->cc_session);
|
|
free((char *)call->sdp);
|
|
|
|
/* detach */
|
|
call_p = &call->isdn_ep->call_list;
|
|
while (*call_p) {
|
|
if (*call_p == call)
|
|
break;
|
|
call_p = &((*call_p)->next);
|
|
}
|
|
*call_p = call->next;
|
|
|
|
free(call);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "destroyed call instance\n");
|
|
}
|
|
|
|
/*
|
|
* bchannel handling
|
|
*/
|
|
|
|
/* send control information to the channel (dsp-module) */
|
|
static void ph_control(int sock, uint32_t c1, uint32_t c2)
|
|
{
|
|
uint8_t buffer[MISDN_HEADER_LEN + 4 + 4];
|
|
struct mISDNhead *ctrl = (struct mISDNhead *)buffer;
|
|
uint32_t *d = (uint32_t *)(buffer + MISDN_HEADER_LEN);
|
|
int len = 8;
|
|
int rc;
|
|
|
|
if (sock < 0)
|
|
return;
|
|
|
|
if (c1 == DTMF_TONE_START && c2 == 0)
|
|
len = 4;
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "sending PH_CONTROL_REQ %d, %d\n", c1, c2);
|
|
|
|
ctrl->prim = PH_CONTROL_REQ;
|
|
ctrl->id = 0;
|
|
*d++ = c1;
|
|
*d++ = c2;
|
|
rc = sendto(sock, buffer, MISDN_HEADER_LEN + len, 0, NULL, 0);
|
|
if (rc <= 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Failed to send to socket %d (errno=%d:%s)\n", sock, errno, strerror(errno));
|
|
abort();
|
|
}
|
|
}
|
|
|
|
/* send control information, but with different length */
|
|
static void ph_control_block(int sock, uint32_t c1, const void *c2, int c2_len)
|
|
{
|
|
uint8_t *buffer[MISDN_HEADER_LEN + 4 + c2_len];
|
|
struct mISDNhead *ctrl = (struct mISDNhead *)buffer;
|
|
uint32_t *d = (uint32_t *)(buffer + MISDN_HEADER_LEN);
|
|
int rc;
|
|
|
|
if (sock < 0)
|
|
return;
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "sending PH_CONTROL_REQ %d\n", c1);
|
|
|
|
ctrl->prim = PH_CONTROL_REQ;
|
|
ctrl->id = 0;
|
|
*d++ = c1;
|
|
memcpy(d, c2, c2_len);
|
|
rc = sendto(sock, buffer, sizeof(buffer), 0, NULL, 0);
|
|
if (rc <= 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Failed to send to socket %d (errno=%d:%s)\n", sock, errno, strerror(errno));
|
|
abort();
|
|
}
|
|
}
|
|
|
|
/* create B-channel stack */
|
|
static int bchannel_create(isdn_t *isdn_ep, int index)
|
|
{
|
|
int channel = index + 1 + (index >= 15);
|
|
struct sockaddr_mISDN addr;
|
|
int flags;
|
|
int rc;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
|
|
if (isdn_ep->b_sock[index] > 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Socket already created for index %d\n", index);
|
|
return -EIO;
|
|
}
|
|
|
|
/* open socket */
|
|
if (isdn_ep->b_mode[index] == B_MODE_HDLC) {
|
|
PDEBUG(DISDN, DEBUG_NOTICE, "Use B-Channel with HDLC l!!!\n");
|
|
}
|
|
rc = socket(PF_ISDN, SOCK_DGRAM, (isdn_ep->b_mode[index] == B_MODE_HDLC) ? ISDN_P_B_L2DSPHDLC : ISDN_P_B_L2DSP);
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Failed to open bchannel-socket for index %d with mISDN-DSP layer. Did you load mISDN_dsp.ko?\n", index);
|
|
return(0);
|
|
}
|
|
isdn_ep->b_sock[index] = rc;
|
|
|
|
/* set nonblocking io */
|
|
flags = fcntl(isdn_ep->b_sock[index], F_GETFL);
|
|
flags |= O_NONBLOCK;
|
|
fcntl(isdn_ep->b_sock[index], F_SETFL, flags);
|
|
|
|
/* bind socket to bchannel */
|
|
addr.family = AF_ISDN;
|
|
addr.dev = isdn_ep->portnum;
|
|
addr.channel = channel;
|
|
rc = bind(isdn_ep->b_sock[index], (struct sockaddr *)&addr, sizeof(addr));
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Failed to bind bchannel-socket for index %d with mISDN-DSP layer (errno=%d). Did you load mISDN_dsp.ko?\n", index, errno);
|
|
close(isdn_ep->b_sock[index]);
|
|
return -errno;
|
|
}
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "created socket #%d for B-channel %d\n", isdn_ep->b_sock[index], channel);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* activate or deactivate B-channel */
|
|
static void bchannel_activate(isdn_t *isdn_ep, int index, int activate, int timeout)
|
|
{
|
|
struct mISDNhead act;
|
|
int channel = index + 1 + (index >= 15);
|
|
int rc;
|
|
|
|
|
|
if (isdn_ep->b_sock[index] <= 0)
|
|
return;
|
|
|
|
act.prim = (activate) ? PH_ACTIVATE_REQ : PH_DEACTIVATE_REQ;
|
|
act.id = 0;
|
|
rc = sendto(isdn_ep->b_sock[index], &act, MISDN_HEADER_LEN, 0, NULL, 0);
|
|
if (rc < 0)
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Failed to send to socket #%d, of B-channel %d\n", isdn_ep->b_sock[index], channel);
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "%s B-channel %d%s\n", (activate) ? "activating" : "deactivating", channel, (timeout) ? " after timeout recovery" : "");
|
|
|
|
/* reset rx buffer */
|
|
isdn_ep->b_buffer_pos[index] = 0;
|
|
}
|
|
|
|
/* configure B-channel */
|
|
static void bchannel_configure(isdn_t *isdn_ep, int index)
|
|
{
|
|
call_t *call;
|
|
int handle, mode;
|
|
|
|
if (isdn_ep->b_sock[index] <= 0)
|
|
return;
|
|
|
|
handle = isdn_ep->b_sock[index];
|
|
call = isdn_ep->b_call[index];
|
|
mode = isdn_ep->b_mode[index];
|
|
|
|
/* set dsp features */
|
|
if (call->txdata) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TXDATA*\n");
|
|
ph_control(handle, (call->txdata) ? DSP_TXDATA_ON : DSP_TXDATA_OFF, 0);
|
|
}
|
|
if (call->tx_delay && mode == B_MODE_TRANSPARENT) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_DELAY\n");
|
|
ph_control(handle, DSP_DELAY, call->tx_delay);
|
|
}
|
|
if (!call->tx_delay && mode == B_MODE_TRANSPARENT) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TX_DEJITTER\n");
|
|
ph_control(handle, DSP_TX_DEJITTER, 1);
|
|
}
|
|
if (call->tx_gain && mode == B_MODE_TRANSPARENT) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_VOL_CHANGE_TX\n");
|
|
ph_control(handle, DSP_VOL_CHANGE_TX, call->tx_gain);
|
|
}
|
|
if (call->rx_gain && mode == B_MODE_TRANSPARENT) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_VOL_CHANGE_RX\n");
|
|
ph_control(handle, DSP_VOL_CHANGE_RX, call->rx_gain);
|
|
}
|
|
if (call->pipeline && call->pipeline[0] && mode == B_MODE_TRANSPARENT) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_PIPELINE_CFG\n");
|
|
ph_control_block(handle, DSP_PIPELINE_CFG, call->pipeline, strlen(call->pipeline) + 1);
|
|
}
|
|
if (call->conf && !call->mute) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_CONF_JOIN\n");
|
|
ph_control(handle, DSP_CONF_JOIN, call->conf);
|
|
}
|
|
if (call->echo) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_ECHO_ON\n");
|
|
ph_control(handle, DSP_ECHO_ON, 0);
|
|
}
|
|
if (call->tone && mode == B_MODE_TRANSPARENT) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TONE_PATT_ON (tone=%d)\n", call->tone);
|
|
ph_control(handle, DSP_TONE_PATT_ON, call->tone);
|
|
}
|
|
if (call->rxoff) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_RECEIVE_OFF\n");
|
|
ph_control(handle, DSP_RECEIVE_OFF, 0);
|
|
}
|
|
if (call->dtmf && mode == B_MODE_TRANSPARENT) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DTMF_TONE_START\n");
|
|
ph_control(handle, DTMF_TONE_START, call->dtmf_threshold);
|
|
}
|
|
}
|
|
|
|
void bchannel_tone(call_t *call, int tone)
|
|
{
|
|
call->tone = tone;
|
|
|
|
if (call->b_index < 0)
|
|
return;
|
|
|
|
int handle = call->isdn_ep->b_sock[call->b_index];
|
|
int mode = call->isdn_ep->b_mode[call->b_index];
|
|
|
|
if (mode != B_MODE_TRANSPARENT)
|
|
return;
|
|
|
|
if (call->tone) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TONE_PATT_ON (tone=%d)\n", call->tone);
|
|
ph_control(handle, DSP_TONE_PATT_ON, call->tone);
|
|
} else {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TONE_PATT_OFF\n");
|
|
ph_control(handle, DSP_TONE_PATT_OFF, 0);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* set bridge ID for B-channel */
|
|
static void bchannel_conference(call_t *call, int oldconf, int newconf)
|
|
{
|
|
int *sock_p = &call->isdn_ep->b_sock[call->b_index];
|
|
int index, channel;
|
|
|
|
index = call->b_index;
|
|
channel = index + 1 + (index >= 15);
|
|
|
|
if (*sock_p <= 0)
|
|
return;
|
|
|
|
if (oldconf != newconf) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "change conference from conf=%d to conf=%d for channel %d\n", oldconf, newconf, channel);
|
|
if (index > -1 && call->isdn_ep->b_state[index] == B_STATE_ACTIVE)
|
|
ph_control(*sock_p, (newconf) ? DSP_CONF_JOIN : DSP_CONF_SPLIT, newconf);
|
|
} else
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "we already have conf=%d at channel %d\n", newconf, channel);
|
|
|
|
}
|
|
#endif
|
|
|
|
/* destroy B-channel stack */
|
|
static void bchannel_destroy(isdn_t *isdn_ep, int index)
|
|
{
|
|
int channel = index + 1 + (index >= 15);
|
|
|
|
if (isdn_ep->b_sock[index] <= 0)
|
|
return;
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "destroyed socket #%d for B-channel %d\n", isdn_ep->b_sock[index], channel);
|
|
|
|
close(isdn_ep->b_sock[index]);
|
|
isdn_ep->b_sock[index] = 0;
|
|
}
|
|
|
|
/*
|
|
* bchannel procedure
|
|
* ------------------
|
|
*
|
|
* A bchannel goes through the following states in this order:
|
|
*
|
|
* - B_STATE_IDLE
|
|
* No one is using the bchannel.
|
|
* It is available and not linked to call instance, nor reserved.
|
|
*
|
|
* - B_STATE_ACTIVATING
|
|
* The bchannel stack is created and an activation request is sent.
|
|
* It MAY be linked to a call, but already unlinked due to call release.
|
|
*
|
|
* - B_STATE_ACTIVE
|
|
* The bchannel is active and configured for the need of the call.
|
|
* Also it is linked to a call, otherwise it would be deactivated.
|
|
*
|
|
* - B_STATE_DEACTIVATING
|
|
* The bchannel is in deactivating state, due to deactivation request.
|
|
* It may be linked to a call, that likes to reactivate it.
|
|
*
|
|
* - B_STATE_IDLE
|
|
* See above.
|
|
* After deactivating bchannel, and if not used, the bchannel becomes idle again.
|
|
*
|
|
* A bchannel can have the following events:
|
|
*
|
|
* - B_EVENT_USE
|
|
* A bchannel is required by a call instance.
|
|
*
|
|
* - B_EVENT_ACTIVATED
|
|
* The bchannel beomes active.
|
|
*
|
|
* - B_EVENT_DROP
|
|
* The bchannel is not required by a call anymore
|
|
*
|
|
* - B_EVENT_DEACTIVATED
|
|
* The bchannel becomes inactive.
|
|
*
|
|
* All actions taken on these events depend on the current bchannel's state and
|
|
* whether it is linked to a call instance.
|
|
*
|
|
*/
|
|
|
|
/* process bchannel events */
|
|
void bchannel_event(isdn_t *isdn_ep, int index, int event)
|
|
{
|
|
call_t *call;
|
|
int state;
|
|
int channel;
|
|
int timer = -1; // no change
|
|
|
|
channel = index + 1 + (index >= 15);
|
|
call = isdn_ep->b_call[index];
|
|
state = isdn_ep->b_state[index];
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "handling event %d at state %d for B-channel %d\n", event, state, channel);
|
|
|
|
switch(event) {
|
|
case B_EVENT_USE:
|
|
/* call must be linked in order to allow activation */
|
|
if (!call) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "bchannel must be linked to a call instance\n");
|
|
abort();
|
|
}
|
|
switch(state) {
|
|
case B_STATE_IDLE:
|
|
/* create stack and send activation request */
|
|
if (bchannel_create(isdn_ep, index) == 0) {
|
|
bchannel_activate(isdn_ep, index, 1, 0);
|
|
state = B_STATE_ACTIVATING;
|
|
timer = B_TIMER_ACTIVATING;
|
|
}
|
|
break;
|
|
|
|
case B_STATE_ACTIVATING:
|
|
/* do nothing, because it is already activating */
|
|
break;
|
|
|
|
default:
|
|
/* problems that might occur:
|
|
* B_EVENT_USE is received when channel already in use.
|
|
*/
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state);
|
|
}
|
|
break;
|
|
|
|
|
|
case B_EVENT_ACTIVATED:
|
|
timer = 0;
|
|
switch(state) {
|
|
case B_STATE_ACTIVATING:
|
|
if (call) {
|
|
/* bchannel is active and used by a call instance, so we configure bchannel */
|
|
bchannel_configure(isdn_ep, index);
|
|
state = B_STATE_ACTIVE;
|
|
//FIXME: init buffer state for delay
|
|
} else {
|
|
/* bchannel is active, but not used anymore (or has wrong stack config), so we deactivate */
|
|
bchannel_activate(isdn_ep, index, 0, 0);
|
|
state = B_STATE_DEACTIVATING;
|
|
timer = B_TIMER_DEACTIVATING;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state);
|
|
}
|
|
break;
|
|
|
|
case B_EVENT_DROP:
|
|
if (!call) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "bchannel must be linked to a call instance\n");
|
|
abort();
|
|
}
|
|
switch(state) {
|
|
case B_STATE_IDLE:
|
|
/* bchannel is idle due to an error, so we do nothing */
|
|
break;
|
|
|
|
case B_STATE_ACTIVATING:
|
|
/* do nothing because we must wait until bchanenl is active before deactivating */
|
|
break;
|
|
|
|
case B_STATE_ACTIVE:
|
|
/* bchannel is active, so we deactivate */
|
|
bchannel_activate(isdn_ep, index, 0, 0);
|
|
state = B_STATE_DEACTIVATING;
|
|
timer = B_TIMER_DEACTIVATING;
|
|
break;
|
|
|
|
case B_STATE_DEACTIVATING:
|
|
/* we may have taken an already deactivating bchannel, but do not require it anymore, so we do nothing */
|
|
break;
|
|
|
|
default:
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state);
|
|
}
|
|
break;
|
|
|
|
case B_EVENT_DEACTIVATED:
|
|
timer = 0;
|
|
switch(state) {
|
|
case B_STATE_IDLE:
|
|
/* ignore due to deactivation confirm after unloading */
|
|
break;
|
|
|
|
case B_STATE_DEACTIVATING:
|
|
bchannel_destroy(isdn_ep, index);
|
|
state = B_STATE_IDLE;
|
|
if (call) {
|
|
/* bchannel is now deactivate, but is required by call instance, so we reactivate */
|
|
if (bchannel_create(isdn_ep, index) == 0) {
|
|
bchannel_activate(isdn_ep, index, 1, 0);
|
|
state = B_STATE_ACTIVATING;
|
|
timer = B_TIMER_ACTIVATING;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state);
|
|
}
|
|
break;
|
|
|
|
case B_EVENT_TIMEOUT:
|
|
timer = 0;
|
|
switch(state) {
|
|
case B_STATE_IDLE:
|
|
/* ignore due to deactivation confirm after unloading */
|
|
break;
|
|
|
|
case B_STATE_ACTIVATING:
|
|
bchannel_activate(isdn_ep, index, 1, 1);
|
|
timer = B_TIMER_ACTIVATING;
|
|
break;
|
|
|
|
case B_STATE_DEACTIVATING:
|
|
bchannel_activate(isdn_ep, index, 0, 1);
|
|
timer = B_TIMER_DEACTIVATING;
|
|
break;
|
|
|
|
default:
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d, please correct.\n", event);
|
|
}
|
|
|
|
isdn_ep->b_state[index] = state;
|
|
if (timer == 0)
|
|
timer_stop(&isdn_ep->b_timer[index]);
|
|
else if (timer > 0)
|
|
timer_start(&isdn_ep->b_timer[index], timer);
|
|
}
|
|
|
|
static void bchannel_receive(isdn_t *isdn_ep, int index, struct mISDNhead *hh, unsigned char *data, int len);
|
|
|
|
/* handle frames from B-channel */
|
|
static int bchannel_work(isdn_t *isdn_ep, int index)
|
|
{
|
|
int channel = index + 1 + (index >= 15);
|
|
unsigned char buffer[2048+MISDN_HEADER_LEN];
|
|
struct mISDNhead *hh = (struct mISDNhead *)buffer;
|
|
int rc;
|
|
|
|
rc = recv(isdn_ep->b_sock[index], buffer, sizeof(buffer), 0);
|
|
if (rc < 0) {
|
|
if (errno == EAGAIN)
|
|
return 0;
|
|
PDEBUG(DISDN, DEBUG_ERROR, "read error B-channel data (socket #%d errno=%d:%s)\n", isdn_ep->b_sock[index], errno, strerror(errno));
|
|
return 0;
|
|
}
|
|
if (rc < (int)MISDN_HEADER_LEN) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "read short frame, got %d, expected %d\n", rc, (int)MISDN_HEADER_LEN);
|
|
return 0;
|
|
}
|
|
switch (hh->prim) {
|
|
/* we don't care about confirms, we use rx data to sync tx */
|
|
case PH_DATA_CNF:
|
|
break;
|
|
|
|
/* we receive audio data, we respond to it AND we send tones */
|
|
case PH_DATA_IND:
|
|
case DL_DATA_IND:
|
|
case PH_DATA_REQ:
|
|
case DL_DATA_REQ:
|
|
case PH_CONTROL_IND:
|
|
if (isdn_ep->b_call[index])
|
|
bchannel_receive(isdn_ep, index, hh, buffer + MISDN_HEADER_LEN, rc - MISDN_HEADER_LEN);
|
|
else
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "b-channel is not associated to an ISDNPort (channel %d), ignoring.\n", channel);
|
|
break;
|
|
|
|
case PH_ACTIVATE_IND:
|
|
case DL_ESTABLISH_IND:
|
|
case PH_ACTIVATE_CNF:
|
|
case DL_ESTABLISH_CNF:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "DL_ESTABLISH confirm: bchannel is now activated (channel %d).\n", channel);
|
|
bchannel_event(isdn_ep, index, B_EVENT_ACTIVATED);
|
|
break;
|
|
|
|
case PH_DEACTIVATE_IND:
|
|
case DL_RELEASE_IND:
|
|
case PH_DEACTIVATE_CNF:
|
|
case DL_RELEASE_CNF:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "DL_RELEASE confirm: bchannel is now de-activated (channel %d).\n", channel);
|
|
bchannel_event(isdn_ep, index, B_EVENT_DEACTIVATED);
|
|
break;
|
|
|
|
default:
|
|
PDEBUG(DISDN, DEBUG_ERROR, "child message not handled: prim(0x%x) channel(%d) msg->len(%d)\n", hh->prim, channel, rc - (int)MISDN_HEADER_LEN);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void b_timer_timeout(struct timer *timer)
|
|
{
|
|
struct b_timer_inst *ti = timer->priv;
|
|
|
|
bchannel_event(ti->isdn_ep, ti->index, B_EVENT_TIMEOUT);
|
|
}
|
|
|
|
/*
|
|
* check for available channel and reserve+set it.
|
|
* give channel number or SEL_CHANNEL_ANY or SEL_CHANNEL_NO
|
|
* give exclusive flag
|
|
* returns -(cause value) or x = channel x or 0 = no channel
|
|
* NOTE: no activation is done here
|
|
*/
|
|
int seize_bchannel(call_t *call, int channel, int exclusive)
|
|
{
|
|
isdn_t *isdn_ep;
|
|
int index;
|
|
|
|
isdn_ep = call->isdn_ep;
|
|
|
|
/* the channel is what we already have */
|
|
if (call->b_channel == channel)
|
|
return channel;
|
|
|
|
/* if channel already in use, release it */
|
|
if (call->b_channel)
|
|
drop_bchannel(call);
|
|
|
|
/* if CHANNEL_NO */
|
|
if (channel == CHANNEL_NO || channel == 0)
|
|
return 0;
|
|
|
|
/* is channel out of range ? */
|
|
if (channel == 16
|
|
|| (channel > isdn_ep->b_num && channel < 16)
|
|
|| ((channel - 1) > isdn_ep->b_num && channel > 16)) /* channel-1 because channel 16 is not counted */
|
|
return -6; /* channel unacceptable */
|
|
|
|
/* request exclusive channel */
|
|
if (exclusive && channel > 0) {
|
|
index = channel - 1 - (channel > 16);
|
|
if (isdn_ep->b_call[index])
|
|
return -44; /* requested channel not available */
|
|
goto seize;
|
|
}
|
|
|
|
/* ask for channel */
|
|
if (channel > 0) {
|
|
index = channel - 1 - (channel > 16);
|
|
if (!isdn_ep->b_call[index])
|
|
goto seize;
|
|
}
|
|
|
|
/* search for channel */
|
|
for (index = 0; index < isdn_ep->b_num; index++) {
|
|
if (!isdn_ep->b_call[index]) {
|
|
channel = index + 1 + (index >= 15);
|
|
goto seize;
|
|
}
|
|
}
|
|
return -34; /* no free channel */
|
|
|
|
seize:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "seizing bchannel %d (index %d)\n", channel, index);
|
|
|
|
/* link Port, set parameters */
|
|
isdn_ep->b_call[index] = call;
|
|
isdn_ep->b_mode[index] = call->b_mode;
|
|
call->b_index = index;
|
|
call->b_channel = channel;
|
|
call->b_exclusive = exclusive;
|
|
|
|
/* reserve channel */
|
|
if (!call->b_reserve) {
|
|
call->b_reserve = 1;
|
|
isdn_ep->b_reserved++;
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
/*
|
|
* drop reserved channel and unset it.
|
|
* deactivation is also done
|
|
*/
|
|
void drop_bchannel(call_t *call)
|
|
{
|
|
isdn_t *isdn_ep;
|
|
|
|
isdn_ep = call->isdn_ep;
|
|
|
|
/* unreserve channel */
|
|
if (call->b_reserve) {
|
|
isdn_ep->b_reserved--;
|
|
call->b_reserve = 0;
|
|
}
|
|
|
|
/* if not in use */
|
|
if (call->b_index < 0)
|
|
return;
|
|
if (!call->b_channel)
|
|
return;
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "dropping bchannel %d (index %d)\n", call->b_channel, call->b_index);
|
|
|
|
if (isdn_ep->b_state[call->b_index] != B_STATE_IDLE)
|
|
bchannel_event(isdn_ep, call->b_index, B_EVENT_DROP);
|
|
isdn_ep->b_call[call->b_index] = NULL;
|
|
isdn_ep->b_mode[call->b_index] = 0;
|
|
call->b_index = -1;
|
|
call->b_channel = 0;
|
|
call->b_exclusive = 0;
|
|
}
|
|
|
|
static void send_to_rtp(call_t *call, unsigned char *data, int len)
|
|
{
|
|
call_t *other;
|
|
|
|
if (!call || !call->audio_path)
|
|
return;
|
|
|
|
if (call->conference_3pty) {
|
|
int16_t *audio;
|
|
int audio_len;
|
|
sample_t samples_local[len], samples_remote_active[len], samples_remote_hold[len], mix[len];
|
|
int i;
|
|
|
|
/* there should be no call on hold with audio coming from */
|
|
if (call->hold)
|
|
return;
|
|
|
|
/* convert local audio from interface to samples */
|
|
if (call->isdn_ep->law == 'a')
|
|
g711_decode_alaw_flipped(data, len, (uint8_t **)&audio, &audio_len);
|
|
else
|
|
g711_decode_ulaw_flipped(data, len, (uint8_t **)&audio, &audio_len);
|
|
int16_to_samples(samples_local, audio, len);
|
|
// don't free audio, because we need that later when encoding
|
|
|
|
/* convert remote RTP to samples */
|
|
jitter_load(&call->dejitter, samples_remote_active, len);
|
|
|
|
/* search other party on hold */
|
|
other = call->isdn_ep->call_list;
|
|
while (other) {
|
|
if (other != call
|
|
&& other->l3_ces == call->l3_ces
|
|
&& other->hold && other->conference_3pty)
|
|
break;
|
|
other = other->next;
|
|
}
|
|
|
|
/* convert remote RTP to samples */
|
|
if (other)
|
|
jitter_load(&other->dejitter, samples_remote_hold, len);
|
|
else
|
|
memset(samples_remote_hold, 0, sizeof(sample_t) * len);
|
|
|
|
/* mix audio for local interface and forward */
|
|
for (i = 0; i < len; i++)
|
|
mix[i] = samples_remote_active[i] + samples_remote_hold[i]; /* both remote parties */
|
|
samples_to_int16(audio, mix, len);
|
|
if (call->isdn_ep->law == 'a')
|
|
g711_encode_alaw_flipped((uint8_t *)audio, audio_len, &data, &len);
|
|
else
|
|
g711_encode_ulaw_flipped((uint8_t *)audio, audio_len, &data, &len);
|
|
if (call->b_index >= 0) {
|
|
unsigned char buf[MISDN_HEADER_LEN + len];
|
|
struct mISDNhead *frm = (struct mISDNhead *)buf;
|
|
int rc;
|
|
memcpy(buf + MISDN_HEADER_LEN, data, len);
|
|
frm->prim = PH_DATA_REQ;
|
|
frm->id = 0;
|
|
rc = send(call->isdn_ep->b_sock[call->b_index], buf, MISDN_HEADER_LEN + len, 0);
|
|
if (rc < 0)
|
|
PDEBUG(DISDN, DEBUG_ERROR, "write error B-channel data (socket #%d errno=%d:%s)\n", call->isdn_ep->b_sock[call->b_index], errno, strerror(errno));
|
|
}
|
|
free(data);
|
|
|
|
/* mix audio for (active) remote interface and forward */
|
|
for (i = 0; i < len; i++)
|
|
mix[i] = samples_local[i] + samples_remote_hold[i]; /* local + remote (hold) party */
|
|
samples_to_int16(audio, mix, len);
|
|
if (call->isdn_ep->law == 'a')
|
|
g711_encode_alaw_flipped((uint8_t *)audio, audio_len, &data, &len);
|
|
else
|
|
g711_encode_ulaw_flipped((uint8_t *)audio, audio_len, &data, &len);
|
|
osmo_cc_rtp_send(call->codec, data, len, 1, len);
|
|
free(data);
|
|
|
|
/* mix audio for (hold) remote interface and forward */
|
|
if (other) {
|
|
for (i = 0; i < len; i++)
|
|
mix[i] = samples_local[i] + samples_remote_active[i]; /* local + remote (active) party */
|
|
samples_to_int16(audio, mix, len);
|
|
if (call->isdn_ep->law == 'a')
|
|
g711_encode_alaw_flipped((uint8_t *)audio, audio_len, &data, &len);
|
|
else
|
|
g711_encode_ulaw_flipped((uint8_t *)audio, audio_len, &data, &len);
|
|
osmo_cc_rtp_send(other->codec, data, len, 1, len);
|
|
free(data);
|
|
}
|
|
|
|
free(audio);
|
|
return;
|
|
}
|
|
|
|
osmo_cc_rtp_send(call->codec, data, len, 1, len);
|
|
}
|
|
|
|
/* receive audio and control from B-channel */
|
|
static void bchannel_receive(isdn_t *isdn_ep, int index, struct mISDNhead *hh, unsigned char *data, int len)
|
|
{
|
|
uint8_t *buffer = isdn_ep->b_buffer[index];
|
|
int *buffer_pos = &(isdn_ep->b_buffer_pos[index]);
|
|
unsigned int cont = *((unsigned int *)data);
|
|
|
|
if (hh->prim == PH_CONTROL_IND) {
|
|
if (len < 4) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "SHORT READ OF PH_CONTROL INDICATION\n");
|
|
return;
|
|
}
|
|
if ((cont & (~DTMF_TONE_MASK)) == DTMF_TONE_VAL) {
|
|
// send_cc_dtmf(call, cont & DTMF_TONE_MASK);
|
|
// FIXME: DTMF via telephony events??
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (hh->prim != PH_DATA_IND && hh->prim != DL_DATA_IND)
|
|
return;
|
|
|
|
/* add to buffer */
|
|
while (len) {
|
|
buffer[(*buffer_pos)++] = *data++;
|
|
len--;
|
|
if (*buffer_pos == 160) {
|
|
*buffer_pos = 0;
|
|
send_to_rtp(isdn_ep->b_call[index], buffer, 160);
|
|
}
|
|
}
|
|
}
|
|
|
|
void isdn_rtp_work(isdn_t *isdn_ep)
|
|
{
|
|
call_t *call;
|
|
|
|
call = isdn_ep->call_list;
|
|
while (call) {
|
|
if (call->cc_session)
|
|
osmo_cc_session_handle(call->cc_session);
|
|
call = call->next;
|
|
}
|
|
}
|
|
|
|
/* send audio to B-channel */
|
|
void bchannel_send(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len)
|
|
{
|
|
call_t *call = codec->media->session->priv;
|
|
|
|
/* conference parties store their audio in jitter buffer */
|
|
if (call->conference_3pty) {
|
|
int16_t *audio;
|
|
int audio_len;
|
|
sample_t samples[len];
|
|
/* alaw/ulaw to linear */
|
|
if (call->isdn_ep->law == 'a')
|
|
g711_decode_alaw_flipped(data, len, (uint8_t **)&audio, &audio_len);
|
|
else
|
|
g711_decode_ulaw_flipped(data, len, (uint8_t **)&audio, &audio_len);
|
|
int16_to_samples(samples, audio, len);
|
|
free(audio);
|
|
/* enqueue data to jitter buffer */
|
|
jitter_save(&call->dejitter, samples, len);
|
|
return;
|
|
}
|
|
|
|
/* no conference, just forward to ISDN interface */
|
|
if (call->b_index >= 0) {
|
|
unsigned char buf[MISDN_HEADER_LEN + len];
|
|
struct mISDNhead *frm = (struct mISDNhead *)buf;
|
|
int rc;
|
|
memcpy(buf + MISDN_HEADER_LEN, data, len);
|
|
frm->prim = PH_DATA_REQ;
|
|
frm->id = 0;
|
|
rc = send(call->isdn_ep->b_sock[call->b_index], buf, MISDN_HEADER_LEN + len, 0);
|
|
if (rc < 0)
|
|
PDEBUG(DISDN, DEBUG_ERROR, "write error B-channel data (socket #%d errno=%d:%s)\n", call->isdn_ep->b_sock[call->b_index], errno, strerror(errno));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* isdn stack handling
|
|
*/
|
|
|
|
int do_layer3(struct mlayer3 *ml3, unsigned int cmd, unsigned int pid, struct l3_msg *l3m)
|
|
{
|
|
isdn_t *isdn_ep = (isdn_t *)ml3->priv;
|
|
struct mbuffer *mb;
|
|
|
|
/* queue message, create, if required */
|
|
if (!l3m) {
|
|
l3m = alloc_l3_msg();
|
|
if (!l3m) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "No memory for layer 3 message\n");
|
|
abort();
|
|
}
|
|
}
|
|
mb = container_of(l3m, struct mbuffer, l3);
|
|
l3m->type = cmd;
|
|
l3m->pid = pid;
|
|
|
|
pthread_mutex_lock(&isdn_ep->upqueue_lock);
|
|
mqueue_tail(&isdn_ep->upqueue, mb);
|
|
pthread_mutex_unlock(&isdn_ep->upqueue_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mISDN_getportbyname(int sock, int cnt, const char *portname)
|
|
{
|
|
struct mISDN_devinfo devinfo;
|
|
int port = 0, rc;
|
|
|
|
memset(&devinfo, 0, sizeof(devinfo));
|
|
|
|
/* resolve name */
|
|
while (port < cnt) {
|
|
devinfo.id = port;
|
|
rc = ioctl(sock, IMGETDEVINFO, &devinfo);
|
|
if (rc < 0)
|
|
return rc;
|
|
if (!strcasecmp(devinfo.name, portname))
|
|
break;
|
|
port++;
|
|
}
|
|
if (port == cnt)
|
|
return -EIO;
|
|
|
|
return port;
|
|
}
|
|
|
|
static void l2establish_timeout(struct timer *timer);
|
|
|
|
/* open mISDN port */
|
|
int isdn_open(isdn_t *isdn_ep)
|
|
{
|
|
const char *portname = isdn_ep->portname;
|
|
int portnum;
|
|
int ptp = isdn_ep->ptp;
|
|
int force_nt = isdn_ep->ntmode;
|
|
int l1hold = isdn_ep->l1hold;
|
|
int l2hold = isdn_ep->l2hold;
|
|
int i, cnt;
|
|
int pri, bri;
|
|
int nt, te;
|
|
struct mISDN_devinfo devinfo;
|
|
unsigned int protocol, prop;
|
|
int rc;
|
|
|
|
/* queue must be initializes, because l3-thread may send messages during open_layer3() */
|
|
mqueue_init(&isdn_ep->upqueue);
|
|
isdn_ep->upqueue_initialized = 1;
|
|
|
|
/* port number given ? */
|
|
for (i = 0; i < (int)strlen(portname); i++){
|
|
if (portname[i] < '0' || portname[i] > '9')
|
|
break;
|
|
}
|
|
if (i == (int)strlen(portname))
|
|
portnum = atoi(portname);
|
|
else
|
|
portnum = -1;
|
|
|
|
memset(&devinfo, 0, sizeof(devinfo));
|
|
|
|
/* check port counts */
|
|
rc = ioctl(isdn_ep->socket, IMGETCOUNT, &cnt);
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Cannot get number of mISDN devices. (ioctl IMGETCOUNT failed errno = %d)\n", errno);
|
|
goto error;
|
|
}
|
|
|
|
if (cnt <= 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Found no card. Please be sure to load card drivers.\n");
|
|
goto error;
|
|
}
|
|
if (portnum < 0) {
|
|
portnum = mISDN_getportbyname(isdn_ep->socket, cnt, portname);
|
|
if (portnum < 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port name '%s' not found, use 'misdn_info' tool to list all existing ports.\n", portname);
|
|
goto error;
|
|
}
|
|
// note: 'portnum' has now the port number
|
|
}
|
|
if (portnum > cnt || portnum < 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port (%d) given at 'ports' (options.conf) is out of existing port range (%d-%d)\n", portnum, 0, cnt);
|
|
goto error;
|
|
}
|
|
|
|
/* get port attributes */
|
|
pri = bri = nt = te = 0;
|
|
devinfo.id = portnum;
|
|
rc = ioctl(isdn_ep->socket, IMGETDEVINFO, &devinfo);
|
|
if (rc < 0) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Cannot get device information for port %d. (ioctl IMGETDEVINFO failed errno=%d)\n", portnum, errno);
|
|
goto error;
|
|
}
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_TE_S0)) {
|
|
bri = 1;
|
|
te = 1;
|
|
}
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_NT_S0)) {
|
|
bri = 1;
|
|
nt = 1;
|
|
}
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_TE_E1)) {
|
|
pri = 1;
|
|
te = 1;
|
|
}
|
|
if (devinfo.Dprotocols & (1 << ISDN_P_NT_E1)) {
|
|
pri = 1;
|
|
nt = 1;
|
|
}
|
|
if (force_nt && !nt) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port %d does not support NT-mode\n", portnum);
|
|
goto error;
|
|
}
|
|
if (bri && pri) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port %d supports BRI and PRI?? What kind of controller is that?. (Can't use this!)\n", portnum);
|
|
goto error;
|
|
}
|
|
if (!bri && !pri) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port %d does not support BRI nor PRI\n", portnum);
|
|
goto error;
|
|
}
|
|
if (!nt && !te) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port %d does not support NT-mode nor TE-mode!\n", portnum);
|
|
goto error;
|
|
}
|
|
/* set NT by turning off TE */
|
|
if (force_nt && nt)
|
|
te = 0;
|
|
/* if TE an NT is supported (and not forced to NT), turn off NT */
|
|
if (te && nt)
|
|
nt = 0;
|
|
|
|
|
|
/* check for continuous channelmap with no bchannel on slot 16 */
|
|
if (test_channelmap(0, devinfo.channelmap)) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port %d provides channel 0, but we cannot access it!\n", portnum);
|
|
goto error;
|
|
}
|
|
i = 1;
|
|
while(i < (int)devinfo.nrbchan + 1) {
|
|
if (i == 16) {
|
|
if (test_channelmap(i, devinfo.channelmap)) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port %d provides bchannel 16. Please upgrade mISDN, if this port is mISDN loopback interface.\n", portnum);
|
|
goto error;
|
|
}
|
|
} else {
|
|
if (!test_channelmap(i, devinfo.channelmap)) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "Port %d has no channel on slot %d!\n", portnum, i);
|
|
goto error;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
timer_init(&isdn_ep->l2establish_timer, l2establish_timeout, isdn_ep);
|
|
|
|
isdn_ep->l1link = -1;
|
|
isdn_ep->l2link = -1;
|
|
|
|
/* if pri, must set PTP */
|
|
if (pri)
|
|
ptp = 1;
|
|
|
|
/* set l2hold */
|
|
switch (l2hold) {
|
|
case -1: // off
|
|
l2hold = 0;
|
|
break;
|
|
case 1: // on
|
|
l2hold = 1;
|
|
break;
|
|
default:
|
|
if (ptp)
|
|
l2hold = 1;
|
|
else
|
|
l2hold = 0;
|
|
break;
|
|
}
|
|
|
|
/* allocate resources of port */
|
|
protocol = (nt) ? L3_PROTOCOL_DSS1_NET : L3_PROTOCOL_DSS1_USER;
|
|
prop = (1 << MISDN_FLG_L2_CLEAN);
|
|
if (ptp) // ptp forced
|
|
prop |= (1 << MISDN_FLG_PTP);
|
|
if (nt) // supports hold/retrieve on nt-mode
|
|
prop |= (1 << MISDN_FLG_NET_HOLD);
|
|
if (l1hold) // supports layer 1 hold
|
|
prop |= (1 << MISDN_FLG_L1_HOLD);
|
|
if (l2hold) // supports layer 2 hold
|
|
prop |= (1 << MISDN_FLG_L2_HOLD);
|
|
/* open layer 3 */
|
|
isdn_ep->ml3 = open_layer3(portnum, protocol, prop, do_layer3, isdn_ep);
|
|
if (!isdn_ep->ml3) {
|
|
PDEBUG(DISDN, DEBUG_ERROR, "open_layer3() failed for port %d\n", portnum);
|
|
goto error;
|
|
}
|
|
|
|
isdn_ep->b_num = devinfo.nrbchan;
|
|
isdn_ep->portnum = portnum;
|
|
if (isdn_ep->portname)
|
|
free(isdn_ep->portname);
|
|
isdn_ep->portname = strdup(devinfo.name);
|
|
isdn_ep->ntmode = nt;
|
|
isdn_ep->pri = pri;
|
|
isdn_ep->ptp = ptp;
|
|
isdn_ep->l1hold = l1hold;
|
|
isdn_ep->l2hold = l2hold;
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "Port has %d b-channels.\n", isdn_ep->b_num);
|
|
for (i = 0; i < isdn_ep->b_num; i++) {
|
|
isdn_ep->b_state[i] = B_STATE_IDLE;
|
|
timer_init(&isdn_ep->b_timer[i], b_timer_timeout, &isdn_ep->b_timer_inst);
|
|
isdn_ep->b_timer_inst[i].isdn_ep = isdn_ep;
|
|
isdn_ep->b_timer_inst[i].index = i;
|
|
}
|
|
|
|
/* if ptp, pull up the link */
|
|
if (isdn_ep->l2hold && (isdn_ep->ptp || !isdn_ep->ntmode)) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "reqzest layer 2 to be establised for tei 0\n");
|
|
timer_start(&isdn_ep->l2establish_timer, 5.0); /* 5 seconds */
|
|
}
|
|
|
|
/* for nt-mode ptmp the link is always up */
|
|
if (isdn_ep->ntmode && !isdn_ep->ptp)
|
|
isdn_ep->l2link = 1;
|
|
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "using 'mISDN_dsp.o' module\n");
|
|
|
|
return 0;
|
|
|
|
error:
|
|
isdn_close(isdn_ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* close mISDN port */
|
|
void isdn_close(isdn_t *isdn_ep)
|
|
{
|
|
int i;
|
|
struct select_channel *selchannel;
|
|
|
|
/* free bchannels */
|
|
for (i = 0; i < isdn_ep->b_num; i++) {
|
|
if (isdn_ep->b_sock[i] > 0) {
|
|
bchannel_destroy(isdn_ep, i);
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "freeing %s port %s bchannel (index %d).\n", (isdn_ep->ntmode) ? "NT" : "TE", isdn_ep->portname, i);
|
|
}
|
|
timer_exit(&isdn_ep->b_timer[i]);
|
|
}
|
|
timer_exit(&isdn_ep->l2establish_timer);
|
|
|
|
/* close layer 3, if open */
|
|
if (isdn_ep->ml3) {
|
|
close_layer3(isdn_ep->ml3);
|
|
}
|
|
|
|
/* purge upqueue */
|
|
if (isdn_ep->upqueue_initialized)
|
|
mqueue_purge(&isdn_ep->upqueue);
|
|
|
|
if (isdn_ep->portname) {
|
|
free(isdn_ep->portname);
|
|
isdn_ep->portname = NULL;
|
|
}
|
|
|
|
while (isdn_ep->in_channel) {
|
|
selchannel = isdn_ep->in_channel;
|
|
isdn_ep->in_channel = selchannel->next;
|
|
free(selchannel);
|
|
}
|
|
while (isdn_ep->out_channel) {
|
|
selchannel = isdn_ep->out_channel;
|
|
isdn_ep->out_channel = selchannel->next;
|
|
free(selchannel);
|
|
}
|
|
}
|
|
|
|
/* receive message from ISDN protocol stack (out of queue) */
|
|
int isdn_dchannel_work(isdn_t *isdn_ep)
|
|
{
|
|
struct mbuffer *mb;
|
|
struct l3_msg *l3m;
|
|
int w = 0;
|
|
|
|
/* handle queued up-messages (d-channel) */
|
|
pthread_mutex_lock(&isdn_ep->upqueue_lock);
|
|
mb = mdequeue(&isdn_ep->upqueue);
|
|
pthread_mutex_unlock(&isdn_ep->upqueue_lock);
|
|
if (mb) {
|
|
w |= 1;
|
|
l3m = &mb->l3;
|
|
switch(l3m->type) {
|
|
case MPH_ACTIVATE_IND:
|
|
if (isdn_ep->l1link != 1) {
|
|
PDEBUG(DISDN, DEBUG_INFO, "layer 1 becomes active\n");
|
|
isdn_ep->l1link = 1;
|
|
}
|
|
break;
|
|
|
|
case MPH_DEACTIVATE_IND:
|
|
if (isdn_ep->l1link != 0) {
|
|
PDEBUG(DISDN, DEBUG_INFO, "layer 1 becomes inactive\n");
|
|
isdn_ep->l1link = 0;
|
|
}
|
|
break;
|
|
|
|
case MPH_INFORMATION_IND:
|
|
switch (l3m->pid) {
|
|
case L1_SIGNAL_LOS_ON:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "received LOS\n");
|
|
isdn_ep->los = 1;
|
|
break;
|
|
case L1_SIGNAL_LOS_OFF:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "LOS is gone\n");
|
|
isdn_ep->los = 0;
|
|
break;
|
|
case L1_SIGNAL_AIS_ON:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "received AIS\n");
|
|
isdn_ep->ais = 1;
|
|
break;
|
|
case L1_SIGNAL_AIS_OFF:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "AIS is gone\n");
|
|
isdn_ep->ais = 0;
|
|
break;
|
|
case L1_SIGNAL_RDI_ON:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "received RDI\n");
|
|
isdn_ep->rdi = 1;
|
|
break;
|
|
case L1_SIGNAL_RDI_OFF:
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "RDI is gone\n");
|
|
isdn_ep->rdi = 0;
|
|
break;
|
|
case L1_SIGNAL_SLIP_TX:
|
|
isdn_ep->slip_tx++;
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "received TX slip #%d\n", isdn_ep->slip_tx);
|
|
break;
|
|
case L1_SIGNAL_SLIP_RX:
|
|
isdn_ep->slip_rx++;
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "received RX slip #%d\n", isdn_ep->slip_rx);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case MT_L2ESTABLISH:
|
|
PDEBUG(DISDN, DEBUG_INFO, "layer 2 becomes active (tei = %d)\n", l3m->pid);
|
|
isdn_ep->l2link = 1;
|
|
if (l3m->pid < 128)
|
|
isdn_ep->l2mask[l3m->pid >> 3] |= (1 << (l3m->pid & 7));
|
|
if ((!isdn_ep->ntmode || isdn_ep->ptp) && l3m->pid < 127) {
|
|
if (timer_running(&isdn_ep->l2establish_timer))
|
|
timer_stop(&isdn_ep->l2establish_timer);
|
|
}
|
|
break;
|
|
|
|
case MT_L2RELEASE:
|
|
if (l3m->pid < 128)
|
|
isdn_ep->l2mask[l3m->pid >> 3] &= ~(1 << (l3m->pid & 7));
|
|
if (!timer_running(&isdn_ep->l2establish_timer)) {
|
|
PDEBUG(DISDN, DEBUG_INFO, "layer 2 becomes inactive (tei = %d)\n", l3m->pid);
|
|
/* down if not nt-ptmp */
|
|
if (!isdn_ep->ntmode || isdn_ep->ptp)
|
|
isdn_ep->l2link = 0;
|
|
}
|
|
if ((!isdn_ep->ntmode || isdn_ep->ptp) && l3m->pid < 127) {
|
|
if (!timer_running(&isdn_ep->l2establish_timer) && isdn_ep->l2hold) {
|
|
timer_start(&isdn_ep->l2establish_timer, 5.0); /* 5 seconds */
|
|
isdn_ep->ml3->to_layer3(isdn_ep->ml3, MT_L2ESTABLISH, 0, NULL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* l3-data is sent to DSS1 handling */
|
|
dss1_receive(isdn_ep, l3m->type, l3m->pid, l3m);
|
|
}
|
|
/* free message */
|
|
free_l3_msg(l3m);
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
/* l2 establish timer fires */
|
|
static void l2establish_timeout(struct timer *timer)
|
|
{
|
|
isdn_t *isdn_ep = timer->priv;
|
|
|
|
if (isdn_ep->l2hold && (isdn_ep->ptp || !isdn_ep->ntmode)) {
|
|
PDEBUG(DISDN, DEBUG_DEBUG, "the L2 establish timer expired, we try to establish the link portnum=%d.\n", isdn_ep->portnum);
|
|
isdn_ep->ml3->to_layer3(isdn_ep->ml3, MT_L2ESTABLISH, 0, NULL);
|
|
timer_start(&isdn_ep->l2establish_timer, 5.0); /* 5 seconds */
|
|
}
|
|
}
|
|
|
|
void isdn_bchannel_work(isdn_t *isdn_ep)
|
|
{
|
|
int w, i;
|
|
|
|
for (i = 0; i < isdn_ep->b_num; i++) {
|
|
do {
|
|
if (isdn_ep->b_sock[i] > 0)
|
|
w = bchannel_work(isdn_ep, i);
|
|
else
|
|
w = 0;
|
|
} while (w);
|
|
}
|
|
}
|