libosmo-abis/src/e1_input.c

1082 lines
27 KiB
C
Raw Normal View History

/* OpenBSC Abis interface to E1 */
/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "internal.h"
#include "../config.h"
#include <stdio.h>
#include <unistd.h>
#include <inttypes.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <osmocom/abis/lapd.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/signal.h>
#include <osmocom/core/endian.h>
#include <osmocom/gsm/i460_mux.h>
#include <osmocom/abis/e1_input.h>
#define NUM_E1_TS 32
static void *tall_e1inp_ctx;
/* list of all E1 drivers */
LLIST_HEAD(e1inp_driver_list);
/* list of all E1 lines */
LLIST_HEAD(e1inp_line_list);
static void *tall_sigl_ctx;
static const struct rate_ctr_desc e1inp_ctr_d[] = {
[E1I_CTR_HDLC_ABORT] = {
"hdlc:abort", "HDLC abort"
},
[E1I_CTR_HDLC_BADFCS] = {
"hdlc:bad_fcs", "HLDC Bad FCS"
},
[E1I_CTR_HDLC_OVERR] = {
"hdlc:overrun", "HDLC Overrun"
},
[E1I_CTR_ALARM] = {
"alarm", "Alarm"
},
[E1I_CTR_REMOVED] = {
"removed", "Line removed"
},
};
static const struct rate_ctr_group_desc e1inp_ctr_g_d = {
.group_name_prefix = "e1inp",
.group_description = "E1 Input subsystem",
.num_ctr = ARRAY_SIZE(e1inp_ctr_d),
.ctr_desc = e1inp_ctr_d,
};
/*
* pcap writing of the misdn load
* pcap format is from http://wiki.wireshark.org/Development/LibpcapFileFormat
*/
#define DLT_LINUX_LAPD 177
#define PCAP_INPUT 0
#define PCAP_OUTPUT 1
struct pcap_hdr {
uint32_t magic_number;
uint16_t version_major;
uint16_t version_minor;
int32_t thiszone;
uint32_t sigfigs;
uint32_t snaplen;
uint32_t network;
} __attribute__((packed));
struct pcaprec_hdr {
uint32_t ts_sec;
uint32_t ts_usec;
uint32_t incl_len;
uint32_t orig_len;
} __attribute__((packed));
struct fake_linux_lapd_header {
uint16_t pkttype;
uint16_t hatype;
uint16_t halen;
uint64_t addr;
int16_t protocol;
} __attribute__((packed));
struct lapd_header {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t ea1 : 1;
uint8_t cr : 1;
uint8_t sapi : 6;
uint8_t ea2 : 1;
uint8_t tei : 7;
uint8_t control_foo; /* fake UM's ... */
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
uint8_t sapi:6, cr:1, ea1:1;
uint8_t tei:7, ea2:1;
uint8_t control_foo;
#endif
} __attribute__((packed));
osmo_static_assert(offsetof(struct fake_linux_lapd_header, hatype) == 2, hatype_offset);
osmo_static_assert(offsetof(struct fake_linux_lapd_header, halen) == 4, halen_offset);
osmo_static_assert(offsetof(struct fake_linux_lapd_header, addr) == 6, addr_offset);
osmo_static_assert(offsetof(struct fake_linux_lapd_header, protocol) == 14, proto_offset);
osmo_static_assert(sizeof(struct fake_linux_lapd_header) == 16, lapd_header_size);
int e1_set_pcap_fd2(struct e1inp_line *line, int fd)
{
static const struct pcap_hdr header = {
.magic_number = 0xa1b2c3d4,
.version_major = 2,
.version_minor = 4,
.thiszone = 0,
.sigfigs = 0,
.snaplen = 65535,
.network = DLT_LINUX_LAPD,
};
int i;
/* write header */
if (fd >= 0) {
int rc = write(fd, &header, sizeof(header));
if (rc < 0)
return rc;
}
/* Set the PCAP file descriptor for all timeslots that have
* software LAPD instances, to ensure the osmo_lapd_pcap code is
* used to write PCAP files (if requested) */
for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
struct e1inp_ts *e1i_ts = &line->ts[i];
if (e1i_ts->lapd)
e1i_ts->lapd->pcap_fd = fd;
}
/* close previous and update */
if (line->pcap_fd >= 0)
close(line->pcap_fd);
line->pcap_fd = fd;
return 0;
}
static int pcap_fd = -1;
int e1_set_pcap_fd(int fd)
{
const struct pcap_hdr header = {
.magic_number = 0xa1b2c3d4,
.version_major = 2,
.version_minor = 4,
.thiszone = 0,
.sigfigs = 0,
.snaplen = 65535,
.network = DLT_LINUX_LAPD,
};
struct e1inp_line *line;
int i;
/* write header */
if (fd >= 0) {
int rc = write(fd, &header, sizeof(header));
if (rc < 0)
return rc;
}
/* update fd in all lines in our global list of e1 lines */
llist_for_each_entry(line, &e1inp_line_list, list) {
/* Set the PCAP file descriptor for all timeslots that have
* software LAPD instances, to ensure the osmo_lapd_pcap code is
* used to write PCAP files (if requested) */
for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
struct e1inp_ts *e1i_ts = &line->ts[i];
if (e1i_ts->lapd)
e1i_ts->lapd->pcap_fd = fd;
}
}
/* close previous and update global */
if (pcap_fd >= 0)
close(pcap_fd);
pcap_fd = fd;
return 0;
}
/* This currently only works for the D-Channel */
static void write_pcap_packet(int direction, int sapi, int tei,
struct msgb *msg, int pcap_fd) {
if (pcap_fd < 0)
return;
time_t cur_time;
struct tm *tm;
struct fake_linux_lapd_header header = {
.pkttype = 4,
.hatype = 0,
.halen = 0,
.addr = direction == PCAP_OUTPUT ? 0x0 : 0x1,
.protocol = ntohs(48),
};
struct lapd_header lapd_header = {
.ea1 = 0,
.cr = direction == PCAP_OUTPUT ? 1 : 0,
.sapi = sapi & 0x3F,
.ea2 = 1,
.tei = tei & 0x7F,
.control_foo = 0x03 /* UI */,
};
struct pcaprec_hdr payload_header = {
.ts_sec = 0,
.ts_usec = 0,
.incl_len = msgb_l2len(msg) + sizeof(struct fake_linux_lapd_header)
+ sizeof(struct lapd_header),
.orig_len = msgb_l2len(msg) + sizeof(struct fake_linux_lapd_header)
+ sizeof(struct lapd_header),
};
cur_time = time(NULL);
tm = localtime(&cur_time);
payload_header.ts_sec = mktime(tm);
write(pcap_fd, &payload_header, sizeof(payload_header));
write(pcap_fd, &header, sizeof(header));
write(pcap_fd, &lapd_header, sizeof(lapd_header));
write(pcap_fd, msg->l2h, msgb_l2len(msg));
}
const struct value_string e1inp_sign_type_names[5] = {
{ E1INP_SIGN_NONE, "None" },
{ E1INP_SIGN_OML, "OML" },
{ E1INP_SIGN_RSL, "RSL" },
{ E1INP_SIGN_OSMO, "OSMO" },
{ 0, NULL }
};
const char *e1inp_signtype_name(enum e1inp_sign_type tp)
{
return get_value_string(e1inp_sign_type_names, tp);
}
const struct value_string e1inp_ts_type_names[] = {
{ E1INP_TS_TYPE_NONE, "None" },
{ E1INP_TS_TYPE_SIGN, "Signalling" },
{ E1INP_TS_TYPE_TRAU, "TRAU" },
{ E1INP_TS_TYPE_RAW, "RAW" },
{ E1INP_TS_TYPE_HDLC, "HDLC" },
{ E1INP_TS_TYPE_I460, "I460" },
{ 0, NULL }
};
const char *e1inp_tstype_name(enum e1inp_ts_type tp)
{
return get_value_string(e1inp_ts_type_names, tp);
}
int abis_sendmsg(struct msgb *msg)
{
struct e1inp_sign_link *sign_link = msg->dst;
struct e1inp_driver *e1inp_driver;
struct e1inp_ts *e1i_ts;
msg->l2h = msg->data;
/* don't know how to route this message. */
if (sign_link == NULL) {
2011-07-21 14:57:34 +00:00
LOGP(DLINP, LOGL_ERROR, "abis_sendmsg: msg->dst == NULL: %s\n",
osmo_hexdump(msg->data, msg->len));
talloc_free(msg);
return -EINVAL;
}
e1i_ts = sign_link->ts;
if (!osmo_timer_pending(&e1i_ts->sign.tx_timer)) {
/* notify the driver we have something to write */
e1inp_driver = sign_link->ts->line->driver;
e1inp_driver->want_write(e1i_ts);
}
msgb_enqueue(&sign_link->tx_list, msg);
/* we only need to write a 'Fake LAPD' packet here, if the
* underlying driver hides LAPD from us. If we use the
* libosmocore LAPD implementation, it will take care of writing
* the _actual_ LAPD packet */
if (!e1i_ts->lapd) {
write_pcap_packet(PCAP_OUTPUT, sign_link->sapi,
sign_link->tei, msg, e1i_ts->line->pcap_fd);
}
return 0;
}
int abis_rsl_sendmsg(struct msgb *msg)
{
return abis_sendmsg(msg);
}
int e1inp_ts_send_raw(struct e1inp_ts *ts, struct msgb *msg)
{
struct e1inp_driver *driver;
OSMO_ASSERT(ts->type == E1INP_TS_TYPE_RAW);
/* notify the driver we have something to write */
driver = ts->line->driver;
driver->want_write(ts);
msgb_enqueue(&ts->raw.tx_queue, msg);
return 0;
}
int e1inp_ts_send_hdlc(struct e1inp_ts *ts, struct msgb *msg)
{
struct e1inp_driver *driver;
OSMO_ASSERT(ts->type == E1INP_TS_TYPE_HDLC);
/* notify the driver we have something to write */
driver = ts->line->driver;
driver->want_write(ts);
msgb_enqueue(&ts->hdlc.tx_queue, msg);
return 0;
}
/* Timeslot */
int e1inp_ts_config_trau(struct e1inp_ts *ts, struct e1inp_line *line,
int (*trau_rcv_cb)(struct subch_demux *dmx, int ch,
const ubit_t *data, int len, void *_priv))
{
if (ts->type == E1INP_TS_TYPE_TRAU && ts->line && line)
return 0;
ts->type = E1INP_TS_TYPE_TRAU;
ts->line = line;
subchan_mux_init(&ts->trau.mux);
ts->trau.demux.out_cb = trau_rcv_cb;
ts->trau.demux.data = ts;
subch_demux_init(&ts->trau.demux);
return 0;
}
int e1inp_ts_config_i460(struct e1inp_ts *ts, struct e1inp_line *line)
{
if (ts->type == E1INP_TS_TYPE_I460 && ts->line && line)
return 0;
ts->type = E1INP_TS_TYPE_I460;
ts->line = line;
osmo_i460_ts_init(&ts->i460.i460_ts);
return 0;
}
void e1inp_ts_name(char *out, size_t out_len, const struct e1inp_ts *ts)
{
if (ts->line->name)
snprintf(out, out_len, "%s:%u", ts->line->name, ts->num);
else
snprintf(out, out_len, "%u:%u", ts->line->num, ts->num);
}
int e1inp_ts_config_sign(struct e1inp_ts *ts, struct e1inp_line *line)
{
if (ts->type == E1INP_TS_TYPE_SIGN && ts->line && line)
return 0;
ts->type = E1INP_TS_TYPE_SIGN;
ts->line = line;
if (line && line->driver)
ts->sign.delay = line->driver->default_delay;
else
ts->sign.delay = 100000;
INIT_LLIST_HEAD(&ts->sign.sign_links);
return 0;
}
int e1inp_ts_config_raw(struct e1inp_ts *ts, struct e1inp_line *line,
void (*raw_recv_cb)(struct e1inp_ts *ts,
struct msgb *msg))
{
if (ts->type == E1INP_TS_TYPE_RAW && ts->line && line)
return 0;
ts->type = E1INP_TS_TYPE_RAW;
ts->line = line;
ts->raw.recv_cb = raw_recv_cb;
INIT_LLIST_HEAD(&ts->raw.tx_queue);
return 0;
}
int e1inp_ts_config_hdlc(struct e1inp_ts *ts, struct e1inp_line *line,
void (*hdlc_recv_cb)(struct e1inp_ts *ts,
struct msgb *msg))
{
if (ts->type == E1INP_TS_TYPE_HDLC && ts->line && line)
return 0;
ts->type = E1INP_TS_TYPE_HDLC;
ts->line = line;
ts->hdlc.recv_cb = hdlc_recv_cb;
INIT_LLIST_HEAD(&ts->hdlc.tx_queue);
return 0;
}
int e1inp_ts_config_none(struct e1inp_ts *ts, struct e1inp_line *line)
{
if (ts->type == E1INP_TS_TYPE_NONE && ts->line && line)
return 0;
ts->type = E1INP_TS_TYPE_NONE;
ts->line = line;
return 0;
}
int e1inp_ts_set_sa_bits(struct e1inp_line *line, uint8_t sa_bits)
{
struct e1inp_driver *driver;
driver = line->driver;
if (!driver->set_sa_bits)
return -ENOTSUP;
return driver->set_sa_bits(line, sa_bits);
}
static int e1inp_line_use_cb(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count,
const char *file, int file_line)
{
char buf[512];
struct osmo_use_count *uc = use_count_entry->use_count;
struct e1inp_line *line = uc->talloc_object;
LOGPSRC(DLINP, LOGL_INFO, file, file_line,
"E1L(%u) Line (%p) reference count %s changed %" PRId32 " -> %" PRId32 " [%s]\n",
(line)->num, line, use_count_entry->use,
old_use_count, use_count_entry->count,
osmo_use_count_name_buf(buf, sizeof(buf), uc));
if (!use_count_entry->count)
osmo_use_count_free(use_count_entry);
if (osmo_use_count_total(uc) > 0)
return 0;
/* Remove our counter group from libosmocore's global counter
* list if we are freeing the last remaining talloc context.
* Otherwise we get a use-after-free when libosmocore's timer
* ticks again and attempts to update these counters (OS#3011).
*
* Note that talloc internally counts "secondary" references
* _in addition to_ the initial allocation context, so yes,
* we must check for *zero* remaining secondary contexts here. */
if (talloc_reference_count(line->rate_ctr) == 0) {
rate_ctr_group_free(line->rate_ctr);
} else {
/* We are not freeing the last talloc context.
* Instead of calling talloc_free(), unlink this 'line' pointer
* which serves as one of several talloc contexts for the rate
* counters and driver private state. */
talloc_unlink(line, line->rate_ctr);
if (line->driver_data)
talloc_unlink(line, line->driver_data);
}
llist_del(&line->list);
talloc_free(line);
return 0;
}
struct e1inp_line *e1inp_line_find(uint8_t e1_nr)
{
struct e1inp_line *e1i_line;
/* iterate over global list of e1 lines */
llist_for_each_entry(e1i_line, &e1inp_line_list, list) {
if (e1i_line->num == e1_nr)
return e1i_line;
}
return NULL;
}
/*! Create a new e1inp line object.
* \param[in] e1_nr The line number of the new line to be created.
* \param[in] driver_name String identifying the driver (see e1inp_driver_register() for more info).
* \returns pointer to the new object created.
*
* The allocated object is returned with a count reference with name "ctor",
* which must be dropped in order to free the object [e1inp_line_put2(line, "ctor")].
*/
struct e1inp_line *
e1inp_line_create(uint8_t e1_nr, const char *driver_name)
{
struct e1inp_driver *driver;
struct e1inp_line *line;
int i;
line = e1inp_line_find(e1_nr);
if (line) {
LOGPIL(line, DLINP, LOGL_ERROR, "E1 Line %u already exists\n", e1_nr);
return NULL;
}
driver = e1inp_driver_find(driver_name);
if (!driver) {
LOGP(DLINP, LOGL_ERROR, "No such E1 driver '%s'\n",
driver_name);
return NULL;
}
line = talloc_zero(tall_e1inp_ctx, struct e1inp_line);
if (!line)
return NULL;
line->driver = driver;
line->num = e1_nr;
line->pcap_fd = -1;
line->keepalive_idle_timeout = E1INP_USE_DEFAULT;
line->keepalive_num_probes = E1INP_USE_DEFAULT;
line->keepalive_probe_interval = E1INP_USE_DEFAULT;
line->connect_timeout = 0;
line->rate_ctr = rate_ctr_group_alloc(line, &e1inp_ctr_g_d, line->num);
if (!line->rate_ctr) {
LOGPIL(line, DLINP, LOGL_ERROR, "Cannot allocate counter group\n");
talloc_free(line);
return NULL;
}
line->num_ts = NUM_E1_TS;
for (i = 0; i < line->num_ts; i++) {
line->ts[i].num = i+1;
line->ts[i].line = line;
}
line->use_count.talloc_object = line;
line->use_count.use_cb = e1inp_line_use_cb;
e1inp_line_get2(line, "ctor");
llist_add_tail(&line->list, &e1inp_line_list);
return line;
}
struct e1inp_line *
e1inp_line_clone(void *ctx, struct e1inp_line *line, const char *use)
{
struct e1inp_line *clone;
/* clone virtual E1 line for this new OML link. */
clone = talloc_zero(ctx, struct e1inp_line);
if (clone == NULL)
return NULL;
memcpy(clone, line, sizeof(struct e1inp_line));
if (line->name) {
clone->name = talloc_strdup(clone, line->name);
OSMO_ASSERT(clone->name);
}
if (line->sock_path) {
clone->sock_path = talloc_strdup(clone, line->sock_path);
OSMO_ASSERT(clone->sock_path);
}
/*
* Rate counters and driver data are shared between clones. These are pointers
* to dynamic memory so we use reference counting to avoid a double-free (see OS#3137).
*/
OSMO_ASSERT(line->rate_ctr);
clone->rate_ctr = talloc_reference(clone, line->rate_ctr);
if (line->driver_data)
clone->driver_data = talloc_reference(clone, line->driver_data);
clone->use_count = (struct osmo_use_count) {
.talloc_object = clone,
.use_cb = e1inp_line_use_cb,
.use_counts = {0},
};
/* initialize list so it can be safely deleted without affecting original line */
INIT_LLIST_HEAD(&clone->list);
e1inp_line_get2(clone, use); /* Clone is used internally for bfd */
return clone;
}
void e1inp_line_get(struct e1inp_line *line)
{
e1inp_line_get2(line, "unknown");
}
void e1inp_line_put(struct e1inp_line *line)
{
e1inp_line_put2(line, "unknown");
}
void
e1inp_line_bind_ops(struct e1inp_line *line, const struct e1inp_line_ops *ops)
{
line->ops = ops;
}
#if 0
struct e1inp_line *e1inp_line_find_create(uint8_t e1_nr)
{
struct e1inp_line *line;
int i;
line = e1inp_line_find(e1_nr);
if (line)
return line;
line = talloc_zero(tall_e1inp_ctx, struct e1inp_line);
if (!line)
return NULL;
line->num = e1_nr;
for (i = 0; i < NUM_E1_TS; i++) {
line->ts[i].num = i+1;
line->ts[i].line = line;
}
llist_add_tail(&line->list, &e1inp_line_list);
return line;
}
#endif
static struct e1inp_ts *e1inp_ts_get(uint8_t e1_nr, uint8_t ts_nr)
{
struct e1inp_line *e1i_line;
e1i_line = e1inp_line_find(e1_nr);
if (!e1i_line)
return NULL;
return &e1i_line->ts[ts_nr-1];
}
struct subch_mux *e1inp_get_mux(uint8_t e1_nr, uint8_t ts_nr)
{
struct e1inp_ts *e1i_ts = e1inp_ts_get(e1_nr, ts_nr);
if (!e1i_ts)
return NULL;
return &e1i_ts->trau.mux;
}
/* Signalling Link */
struct e1inp_sign_link *e1inp_lookup_sign_link(struct e1inp_ts *e1i,
uint8_t tei, uint8_t sapi)
{
struct e1inp_sign_link *link;
llist_for_each_entry(link, &e1i->sign.sign_links, list) {
if (link->sapi == sapi && link->tei == tei)
return link;
}
return NULL;
}
/* create a new signalling link in a E1 timeslot */
struct e1inp_sign_link *
e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type,
struct gsm_bts_trx *trx, uint8_t tei,
uint8_t sapi)
{
struct e1inp_sign_link *link;
if (ts->type != E1INP_TS_TYPE_SIGN)
return NULL;
link = talloc_zero(tall_sigl_ctx, struct e1inp_sign_link);
if (!link)
return NULL;
link->ts = ts;
link->type = type;
INIT_LLIST_HEAD(&link->tx_list);
link->trx = trx;
link->tei = tei;
link->sapi = sapi;
e1inp_line_get2(link->ts->line, "e1inp_sign_link");
llist_add_tail(&link->list, &ts->sign.sign_links);
return link;
}
void e1inp_sign_link_destroy(struct e1inp_sign_link *link)
{
struct msgb *msg;
llist_del(&link->list);
while (!llist_empty(&link->tx_list)) {
msg = msgb_dequeue(&link->tx_list);
msgb_free(msg);
}
if (link->ts->type == E1INP_TS_TYPE_SIGN)
osmo_timer_del(&link->ts->sign.tx_timer);
major updates in e1_input callback ops and IPA infrastructures This patch is a major update of the callback infrastructure, now the e1input_ops looks like the following: struct e1inp_sign_link * (*sign_link_up)(void *unit_info, struct e1inp_line *line, enum e1inp_sign_type type); void (*sign_link_down)(struct e1inp_line *line); int (*sign_link)(struct msgb *msg, struct e1inp_sign_link *link); The sign_link_up and sign_link_down will be used by the A-bis over IP input drivers. The sign_link_up callback is used if we receive a ID_RESP message, in that case, we have to set up the sign link for the corresponding new OML/RSL signal link. The pointer to unit_info provides a data structure that contains the BTS device details if we run as BSC, and the requested device information from the BSC if we run as BTS. The sign_link_up callback must return the new sign_link created. The sign_link_down callback is invoked if the line does down, which means that the counterpart has closed the socket. The sign_link callback is used to handle all RSL/OML messages. I have also added the following callback to the e1inp_driver: + void (*close)(struct e1inp_ts *ts); Which is invoked if you call e1inp_sign_link_destroy(). This callback is used to close the socket that is linked to that timeslot. This is useful for A-bis over IP drivers since sockets are closed if the OML/RSL signalling link is destroyed. As you can notice, I have also added all the ID_RESP parsing into libosmo-abis for both ipaccess and hsl drivers. This patch also contains a rework of the ipa_client_link whose integration with the e1_input infrastructure was broken (the transmission path was broken). This patch also contains more develop examples that act as BSC and BTS for the ipaccess driver. Sorry, I know it would be better to split all these changes into logical pieces but many of them are tightly related. This is under heavy development stage, it's anyway hard to track changes until this becomes more stable.
2011-06-30 10:19:42 +00:00
if (link->ts->line->driver->close)
link->ts->line->driver->close(link);
major updates in e1_input callback ops and IPA infrastructures This patch is a major update of the callback infrastructure, now the e1input_ops looks like the following: struct e1inp_sign_link * (*sign_link_up)(void *unit_info, struct e1inp_line *line, enum e1inp_sign_type type); void (*sign_link_down)(struct e1inp_line *line); int (*sign_link)(struct msgb *msg, struct e1inp_sign_link *link); The sign_link_up and sign_link_down will be used by the A-bis over IP input drivers. The sign_link_up callback is used if we receive a ID_RESP message, in that case, we have to set up the sign link for the corresponding new OML/RSL signal link. The pointer to unit_info provides a data structure that contains the BTS device details if we run as BSC, and the requested device information from the BSC if we run as BTS. The sign_link_up callback must return the new sign_link created. The sign_link_down callback is invoked if the line does down, which means that the counterpart has closed the socket. The sign_link callback is used to handle all RSL/OML messages. I have also added the following callback to the e1inp_driver: + void (*close)(struct e1inp_ts *ts); Which is invoked if you call e1inp_sign_link_destroy(). This callback is used to close the socket that is linked to that timeslot. This is useful for A-bis over IP drivers since sockets are closed if the OML/RSL signalling link is destroyed. As you can notice, I have also added all the ID_RESP parsing into libosmo-abis for both ipaccess and hsl drivers. This patch also contains a rework of the ipa_client_link whose integration with the e1_input infrastructure was broken (the transmission path was broken). This patch also contains more develop examples that act as BSC and BTS for the ipaccess driver. Sorry, I know it would be better to split all these changes into logical pieces but many of them are tightly related. This is under heavy development stage, it's anyway hard to track changes until this becomes more stable.
2011-06-30 10:19:42 +00:00
e1inp_line_put2(link->ts->line, "e1inp_sign_link");
talloc_free(link);
}
/* XXX */
/* the E1 driver tells us he has received something on a TS */
int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
uint8_t tei, uint8_t sapi)
{
struct e1inp_sign_link *link;
int ret = 0;
switch (ts->type) {
case E1INP_TS_TYPE_SIGN:
/* we only need to write a 'Fake LAPD' packet here, if
* the underlying driver hides LAPD from us. If we use
* the libosmocore LAPD implementation, it will take
* care of writing the _actual_ LAPD packet */
if (!ts->lapd)
write_pcap_packet(PCAP_INPUT, sapi, tei, msg, ts->line->pcap_fd);
/* consult the list of signalling links */
link = e1inp_lookup_sign_link(ts, tei, sapi);
if (!link) {
LOGPITS(ts, DLMI, LOGL_ERROR, "didn't find signalling link for "
"tei %d, sapi %d\n", tei, sapi);
msgb_free(msg);
return -EINVAL;
}
if (!ts->line->ops->sign_link) {
LOGPITS(ts, DLINP, LOGL_ERROR, "Fix your application, "
"no action set for signalling messages.\n");
msgb_free(msg);
return -ENOENT;
}
msg->dst = link;
ts->line->ops->sign_link(msg);
break;
case E1INP_TS_TYPE_TRAU:
ret = subch_demux_in(&ts->trau.demux, msg->l2h, msgb_l2len(msg));
msgb_free(msg);
break;
case E1INP_TS_TYPE_RAW:
ts->raw.recv_cb(ts, msg);
break;
case E1INP_TS_TYPE_HDLC:
ts->hdlc.recv_cb(ts, msg);
break;
case E1INP_TS_TYPE_I460:
osmo_i460_demux_in(&ts->i460.i460_ts, msg->l2h, msgb_l2len(msg));
msgb_free(msg);
break;
default:
ret = -EINVAL;
LOGPITS(ts, DLMI, LOGL_ERROR, "unknown TS type %u\n", ts->type);
msgb_free(msg);
break;
}
return ret;
}
/*! \brief Receive some data from the L1/HDLC into LAPD of a timeslot
* \param[in] e1i_ts E1 Timeslot data structure
* \param[in] msg Message buffer containing full LAPD message
*
* This is a wrapper around e1inp_rx_ts(), but feeding the incoming
* message first into our LAPD code. This allows a driver to read raw
* (HDLC decoded) data from the timeslot, instead of a LAPD stack
* present in any underlying driver.
*/
int e1inp_rx_ts_lapd(struct e1inp_ts *e1i_ts, struct msgb *msg)
{
unsigned int sapi, tei;
int ret = 0, error = 0;
sapi = msg->data[0] >> 2;
if ((msg->data[0] & 0x1))
tei = 0;
else
tei = msg->data[1] >> 1;
LOGPITS(e1i_ts, DLMI, LOGL_DEBUG, "<= len = %d, sapi(%d) tei(%d)\n", msg->len, sapi, tei);
ret = lapd_receive(e1i_ts->lapd, msg, &error);
if (ret < 0) {
switch(error) {
case LAPD_ERR_UNKNOWN_TEI:
/* We don't know about this TEI, probably the BSC
* lost local states (it crashed or it was stopped),
* notify the driver to see if it can do anything to
* recover the existing signalling links with the BTS.
*/
e1inp_event(e1i_ts, S_L_INP_TEI_UNKNOWN, tei, sapi);
return -EIO;
}
}
return 0;
}
void e1inp_dlsap_up(struct osmo_dlsap_prim *dp, uint8_t tei, uint8_t sapi,
void *rx_cbdata)
{
struct e1inp_ts *e1i_ts = rx_cbdata;
struct msgb *msg = dp->oph.msg;
switch (dp->oph.primitive) {
case PRIM_DL_EST:
LOGPITS(e1i_ts, DLMI, LOGL_DEBUG, "DL_EST: sapi(%d) tei(%d)\n", sapi, tei);
e1inp_event(e1i_ts, S_L_INP_TEI_UP, tei, sapi);
break;
case PRIM_DL_REL:
LOGPITS(e1i_ts, DLMI, LOGL_DEBUG, "DL_REL: sapi(%d) tei(%d)\n", sapi, tei);
e1inp_event(e1i_ts, S_L_INP_TEI_DN, tei, sapi);
break;
case PRIM_DL_DATA:
case PRIM_DL_UNIT_DATA:
if (dp->oph.operation == PRIM_OP_INDICATION) {
msg->l2h = msg->l3h;
LOGPITS(e1i_ts, DLMI, LOGL_DEBUG, "RX: %s sapi=%d tei=%d\n",
osmo_hexdump(msgb_l2(msg), msgb_l2len(msg)),
sapi, tei);
e1inp_rx_ts(e1i_ts, msg, tei, sapi);
return;
}
break;
case PRIM_MDL_ERROR:
LOGPITS(e1i_ts, DLMI, LOGL_DEBUG, "MDL_EERROR: cause(%d)\n", dp->u.error_ind.cause);
break;
default:
printf("ERROR: unknown prim\n");
break;
}
msgb_free(msg);
return;
}
#define TSX_ALLOC_SIZE 4096
/* called by driver if it wants to transmit on a given TS */
struct msgb *e1inp_tx_ts(struct e1inp_ts *e1i_ts,
struct e1inp_sign_link **sign_link)
{
struct e1inp_sign_link *link;
struct msgb *msg = NULL;
int len;
switch (e1i_ts->type) {
case E1INP_TS_TYPE_SIGN:
/* FIXME: implement this round robin */
llist_for_each_entry(link, &e1i_ts->sign.sign_links, list) {
msg = msgb_dequeue(&link->tx_list);
if (msg) {
if (sign_link)
*sign_link = link;
break;
}
}
break;
case E1INP_TS_TYPE_TRAU:
msg = msgb_alloc(TSX_ALLOC_SIZE, "TRAU_TX");
if (!msg)
return NULL;
len = subchan_mux_out(&e1i_ts->trau.mux, msg->data, 40);
if (len != 40) {
LOGPITS(e1i_ts, DLMI, LOGL_ERROR, "cannot transmit, failed to mux\n");
msgb_free(msg);
return NULL;
}
msgb_put(msg, 40);
break;
case E1INP_TS_TYPE_RAW:
/* Get msgb from tx_queue */
msg = msgb_dequeue(&e1i_ts->raw.tx_queue);
break;
case E1INP_TS_TYPE_HDLC:
/* Get msgb from tx_queue */
msg = msgb_dequeue(&e1i_ts->hdlc.tx_queue);
break;
case E1INP_TS_TYPE_I460:
msg = msgb_alloc(TSX_ALLOC_SIZE, "I460_TX");
if (!msg)
return NULL;
len = osmo_i460_mux_out(&e1i_ts->i460.i460_ts, msg->data, 160);
msgb_put(msg, len);
break;
default:
LOGPITS(e1i_ts, DLMI, LOGL_ERROR, "unsupported E1 TS type %u\n", e1i_ts->type);
return NULL;
}
return msg;
}
int e1inp_int_snd_event(struct e1inp_ts *ts, struct e1inp_sign_link *link, int evt)
{
struct input_signal_data isd;
isd.line = ts->line;
isd.ts_nr = ts->num;
isd.link_type = link->type;
isd.trx = link->trx;
isd.tei = link->tei;
isd.sapi = link->sapi;
/* report further upwards */
osmo_signal_dispatch(SS_L_INPUT, evt, &isd);
return 0;
}
/* called by driver in case some kind of link state event */
int e1inp_event(struct e1inp_ts *ts, int evt, uint8_t tei, uint8_t sapi)
{
struct e1inp_sign_link *link;
link = e1inp_lookup_sign_link(ts, tei, sapi);
if (!link)
return -EINVAL;
return e1inp_int_snd_event(ts, link, evt);
}
/* register a driver with the E1 core */
int e1inp_driver_register(struct e1inp_driver *drv)
{
llist_add_tail(&drv->list, &e1inp_driver_list);
return 0;
}
struct e1inp_driver *e1inp_driver_find(const char *name)
{
struct e1inp_driver *drv;
llist_for_each_entry(drv, &e1inp_driver_list, list) {
if (!strcasecmp(name, drv->name))
return drv;
}
return NULL;
}
int e1inp_line_update(struct e1inp_line *line)
{
struct input_signal_data isd;
int i, rc;
if (line->driver && line->ops && line->driver->line_update) {
rc = line->driver->line_update(line);
} else
rc = 0;
/* Set the PCAP file descriptor for all timeslots that have
* software LAPD instances, to ensure the osmo_lapd_pcap code is
* used to write PCAP files (if requested) */
for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
struct e1inp_ts *e1i_ts = &line->ts[i];
if (e1i_ts->lapd)
e1i_ts->lapd->pcap_fd = line->pcap_fd;
}
/* Send a signal to anyone who is interested in new lines being
* configured */
memset(&isd, 0, sizeof(isd));
isd.line = line;
osmo_signal_dispatch(SS_L_INPUT, S_L_INP_LINE_INIT, &isd);
return rc;
}
static int e1i_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
struct e1inp_line *line;
if (subsys != SS_L_GLOBAL ||
signal != S_L_GLOBAL_SHUTDOWN)
return 0;
llist_for_each_entry(line, &e1inp_line_list, list) {
if (line->pcap_fd >=0)
close(line->pcap_fd);
line->pcap_fd = -1;
}
return 0;
}
const struct value_string e1inp_signal_names[] = {
{ S_L_INP_NONE, "NONE" },
{ S_L_INP_TEI_UP, "TEI-UP" },
{ S_L_INP_TEI_DN, "TEI-DOWN" },
{ S_L_INP_TEI_UNKNOWN, "TEI-UNKNOWN" },
{ S_L_INP_LINE_INIT, "LINE-INIT" },
{ S_L_INP_LINE_ALARM, "LINE-ALARM" },
{ S_L_INP_LINE_NOALARM, "LINE-NOALARM" },
{ S_L_INP_LINE_LOS, "LINE-LOS" },
{ S_L_INP_LINE_NOLOS, "LINE-NOLOS" },
{ S_L_INP_LINE_AIS, "LINE-AIS" },
{ S_L_INP_LINE_NOAIS, "LINE-NOAIS" },
{ S_L_INP_LINE_RAI, "LINE-RAI" },
{ S_L_INP_LINE_NORAI, "LINE-NORAI" },
{ S_L_INP_LINE_SLIP_RX, "LINE-SLIP-RX" },
{ S_L_INP_LINE_SLIP_TX, "LINE-SLIP-TX" },
{ S_L_INP_LINE_SA_BITS, "LINE-SA-BITS" },
{ 0, NULL }
};
void e1inp_misdn_init(void);
void e1inp_dahdi_init(void);
void e1inp_e1d_init(void);
void e1inp_ipaccess_init(void);
void e1inp_rs232_init(void);
void e1inp_unixsocket_init(void);
void e1inp_init(void)
{
tall_e1inp_ctx = talloc_named_const(libosmo_abis_ctx, 1, "e1inp");
tall_sigl_ctx = talloc_named_const(tall_e1inp_ctx, 1,
"e1inp_sign_link");
osmo_signal_register_handler(SS_L_GLOBAL, e1i_sig_cb, NULL);
e1inp_misdn_init();
#ifdef HAVE_DAHDI_USER_H
e1inp_dahdi_init();
#endif
#ifdef HAVE_E1D
e1inp_e1d_init();
#endif
e1inp_ipaccess_init();
e1inp_rs232_init();
e1inp_unixsocket_init();
}