diff options
author | Pablo Neira Ayuso <pablo@gnumonks.org> | 2011-06-05 18:32:44 +0200 |
---|---|---|
committer | Pablo Neira Ayuso <pablo@gnumonks.org> | 2011-06-05 18:32:44 +0200 |
commit | 0ba77d53a97906ee70a262083fb30f67fe8801c5 (patch) | |
tree | 8065c7c43c339458e9ef6425a2d45f3bedf777ad /src/input |
initial commit of libosmo-abis
still many things to get fixed
Diffstat (limited to 'src/input')
-rw-r--r-- | src/input/Makefile.am | 11 | ||||
-rw-r--r-- | src/input/dahdi.c | 495 | ||||
-rw-r--r-- | src/input/hsl.c | 296 | ||||
-rw-r--r-- | src/input/ipaccess.c | 499 | ||||
-rw-r--r-- | src/input/lapd.c | 713 | ||||
-rw-r--r-- | src/input/misdn.c | 547 |
6 files changed, 2561 insertions, 0 deletions
diff --git a/src/input/Makefile.am b/src/input/Makefile.am new file mode 100644 index 0000000..8e9e336 --- /dev/null +++ b/src/input/Makefile.am @@ -0,0 +1,11 @@ +noinst_LTLIBRARIES = libosmoabis-input.la + +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS= -fPIC -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(COVERAGE_LDFLAGS) + +libosmoabis_input_la_SOURCES = dahdi.c \ + hsl.c \ + ipaccess.c \ + lapd.c \ + misdn.c diff --git a/src/input/dahdi.c b/src/input/dahdi.c new file mode 100644 index 0000000..a431a3a --- /dev/null +++ b/src/input/dahdi.c @@ -0,0 +1,495 @@ +/* OpenBSC Abis input driver for DAHDI */ + +/* (C) 2008-2011 by Harald Welte <laforge@gnumonks.org> + * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com> + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +//#include "../../../bscconfig.h" + +#ifdef HAVE_DAHDI_USER_H + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/fcntl.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <dahdi/user.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/msgb.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/abis_nm.h> +#include <openbsc/abis_rsl.h> +#include <openbsc/subchan_demux.h> +#include <openbsc/e1_input.h> +#include <openbsc/signal.h> +#include <talloc.h> + +#include "lapd.h" + +#define TS1_ALLOC_SIZE 300 + +/* Corresponds to dahdi/user.h, only PRI related events */ +static const struct value_string dahdi_evt_names[] = { + { DAHDI_EVENT_NONE, "NONE" }, + { DAHDI_EVENT_ALARM, "ALARM" }, + { DAHDI_EVENT_NOALARM, "NOALARM" }, + { DAHDI_EVENT_ABORT, "HDLC ABORT" }, + { DAHDI_EVENT_OVERRUN, "HDLC OVERRUN" }, + { DAHDI_EVENT_BADFCS, "HDLC BAD FCS" }, + { DAHDI_EVENT_REMOVED, "REMOVED" }, + { 0, NULL } +}; + +static void handle_dahdi_exception(struct e1inp_ts *ts) +{ + int rc, evt; + struct input_signal_data isd; + + rc = ioctl(ts->driver.dahdi.fd.fd, DAHDI_GETEVENT, &evt); + if (rc < 0) + return; + + LOGP(DMI, LOGL_NOTICE, "Line %u(%s) / TS %u DAHDI EVENT %s\n", + ts->line->num, ts->line->name, ts->num, + get_value_string(dahdi_evt_names, evt)); + + isd.line = ts->line; + + switch (evt) { + case DAHDI_EVENT_ALARM: + /* we should notify the code that the line is gone */ + osmo_signal_dispatch(SS_INPUT, S_INP_LINE_ALARM, &isd); + break; + case DAHDI_EVENT_NOALARM: + /* alarm has gone, we should re-start the SABM requests */ + osmo_signal_dispatch(SS_INPUT, S_INP_LINE_NOALARM, &isd); + break; + } +} + +static int handle_ts1_read(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "DAHDI TS1"); + lapd_mph_type prim; + unsigned int sapi, tei; + int ilen, ret; + uint8_t *idata; + + if (!msg) + return -ENOMEM; + + ret = read(bfd->fd, msg->data, TS1_ALLOC_SIZE - 16); + if (ret == -1) + handle_dahdi_exception(e1i_ts); + else if (ret < 0) { + perror("read "); + } + msgb_put(msg, ret - 2); + if (ret <= 3) { + perror("read "); + } + + sapi = msg->data[0] >> 2; + tei = msg->data[1] >> 1; + + DEBUGP(DMI, "<= len = %d, sapi(%d) tei(%d)", ret, sapi, tei); + + idata = lapd_receive(e1i_ts->driver.dahdi.lapd, msg->data, msg->len, &ilen, &prim); + if (!idata && prim == 0) + return -EIO; + + msgb_pull(msg, 2); + + DEBUGP(DMI, "prim %08x\n", prim); + + switch (prim) { + case 0: + break; + case LAPD_MPH_ACTIVATE_IND: + DEBUGP(DMI, "MPH_ACTIVATE_IND: sapi(%d) tei(%d)\n", sapi, tei); + ret = e1inp_event(e1i_ts, S_INP_TEI_UP, tei, sapi); + break; + case LAPD_MPH_DEACTIVATE_IND: + DEBUGP(DMI, "MPH_DEACTIVATE_IND: sapi(%d) tei(%d)\n", sapi, tei); + ret = e1inp_event(e1i_ts, S_INP_TEI_DN, tei, sapi); + break; + case LAPD_DL_DATA_IND: + case LAPD_DL_UNITDATA_IND: + if (prim == LAPD_DL_DATA_IND) + msg->l2h = msg->data + 2; + else + msg->l2h = msg->data + 1; + DEBUGP(DMI, "RX: %s\n", osmo_hexdump(msgb_l2(msg), ret)); + ret = e1inp_rx_ts(e1i_ts, msg, tei, sapi); + break; + default: + printf("ERROR: unknown prim\n"); + break; + } + + DEBUGP(DMI, "Returned ok\n"); + return ret; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + /* We never include the DAHDI B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + if (e1i_ts->type == E1INP_TS_TYPE_TRAU) { + fprintf(stderr, "Trying to write TRAU ts\n"); + return 0; + } + + e1i_ts->driver.dahdi.fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static void dahdi_write_msg(uint8_t *data, int len, void *cbdata) +{ + struct osmo_fd *bfd = cbdata; + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + int ret; + + ret = write(bfd->fd, data, len + 2); + if (ret == -1) + handle_dahdi_exception(e1i_ts); + else if (ret < 0) + LOGP(DMI, LOGL_NOTICE, "%s write failed %d\n", __func__, ret); +} + +static int handle_ts1_write(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *sign_link; + struct msgb *msg; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + return 0; + } + + DEBUGP(DMI, "TX: %s\n", osmo_hexdump(msg->data, msg->len)); + lapd_transmit(e1i_ts->driver.dahdi.lapd, sign_link->tei, + sign_link->sapi, msg->data, msg->len); + msgb_free(msg); + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + osmo_timer_schedule(&e1i_ts->sign.tx_timer, 0, 50000); + + return 0; +} + + +static int invertbits = 1; + +static uint8_t flip_table[256]; + +static void init_flip_bits(void) +{ + int i,k; + + for (i = 0 ; i < 256 ; i++) { + uint8_t sample = 0 ; + for (k = 0; k<8; k++) { + if ( i & 1 << k ) sample |= 0x80 >> k; + } + flip_table[i] = sample; + } +} + +static uint8_t * flip_buf_bits ( uint8_t * buf , int len) +{ + int i; + uint8_t * start = buf; + + for (i = 0 ; i < len; i++) { + buf[i] = flip_table[(uint8_t)buf[i]]; + } + + return start; +} + +#define D_BCHAN_TX_GRAN 160 +/* write to a B channel TS */ +static int handle_tsX_write(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + uint8_t tx_buf[D_BCHAN_TX_GRAN]; + struct subch_mux *mx = &e1i_ts->trau.mux; + int ret; + + ret = subchan_mux_out(mx, tx_buf, D_BCHAN_TX_GRAN); + + if (ret != D_BCHAN_TX_GRAN) { + fprintf(stderr, "Huh, got ret of %d\n", ret); + if (ret < 0) + return ret; + } + + DEBUGP(DMIB, "BCHAN TX: %s\n", + osmo_hexdump(tx_buf, D_BCHAN_TX_GRAN)); + + if (invertbits) { + flip_buf_bits(tx_buf, ret); + } + + ret = write(bfd->fd, tx_buf, ret); + if (ret < D_BCHAN_TX_GRAN) + fprintf(stderr, "send returns %d instead of %d\n", ret, + D_BCHAN_TX_GRAN); + + return ret; +} + +#define D_TSX_ALLOC_SIZE (D_BCHAN_TX_GRAN) +/* FIXME: read from a B channel TS */ +static int handle_tsX_read(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct msgb *msg = msgb_alloc(D_TSX_ALLOC_SIZE, "DAHDI TSx"); + int ret; + + if (!msg) + return -ENOMEM; + + ret = read(bfd->fd, msg->data, D_TSX_ALLOC_SIZE); + if (ret < 0 || ret != D_TSX_ALLOC_SIZE) { + fprintf(stderr, "read error %d %s\n", ret, strerror(errno)); + return ret; + } + + if (invertbits) { + flip_buf_bits(msg->data, ret); + } + + msgb_put(msg, ret); + + msg->l2h = msg->data; + DEBUGP(DMIB, "BCHAN RX: %s\n", + osmo_hexdump(msgb_l2(msg), ret)); + ret = e1inp_rx_ts(e1i_ts, msg, 0, 0); + /* physical layer indicates that data has been sent, + * we thus can send some more data */ + ret = handle_tsX_write(bfd); + msgb_free(msg); + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int dahdi_fd_cb(struct osmo_fd *bfd, unsigned int what) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + unsigned int idx = ts_nr-1; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + int rc = 0; + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_SIGN: + if (what & BSC_FD_EXCEPT) + handle_dahdi_exception(e1i_ts); + if (what & BSC_FD_READ) + rc = handle_ts1_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_ts1_write(bfd); + break; + case E1INP_TS_TYPE_TRAU: + if (what & BSC_FD_EXCEPT) + handle_dahdi_exception(e1i_ts); + if (what & BSC_FD_READ) + rc = handle_tsX_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_tsX_write(bfd); + /* We never include the DAHDI B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + break; + default: + fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type); + break; + } + + return rc; +} + +static int +dahdi_e1_line_update(struct e1inp_line *line, enum e1inp_line_role role); + +struct e1inp_driver dahdi_driver = { + .name = "dahdi", + .want_write = ts_want_write, + .line_update = &dahdi_e1_line_update, +}; + +void dahdi_set_bufinfo(int fd, int as_sigchan) +{ + struct dahdi_bufferinfo bi; + int x = 0; + + if (ioctl(fd, DAHDI_GET_BUFINFO, &bi)) { + fprintf(stderr, "Error getting bufinfo\n"); + exit(-1); + } + + if (as_sigchan) { + bi.numbufs = 4; + bi.bufsize = 512; + } else { + bi.numbufs = 8; + bi.bufsize = D_BCHAN_TX_GRAN; + bi.txbufpolicy = DAHDI_POLICY_WHEN_FULL; + } + + if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) { + fprintf(stderr, "Error setting bufinfo\n"); + exit(-1); + } + + if (!as_sigchan) { + if (ioctl(fd, DAHDI_AUDIOMODE, &x)) { + fprintf(stderr, "Error setting bufinfo\n"); + exit(-1); + } + } else { + int one = 1; + ioctl(fd, DAHDI_HDLCFCSMODE, &one); + /* we cannot reliably check for the ioctl return value here + * as this command will fail if the slot _already_ was a + * signalling slot before :( */ + } +} + +static int dahdi_e1_setup(struct e1inp_line *line) +{ + int ts, ret; + + /* TS0 is CRC4, don't need any fd for it */ + for (ts = 1; ts < NUM_E1_TS; ts++) { + unsigned int idx = ts-1; + char openstr[128]; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + struct osmo_fd *bfd = &e1i_ts->driver.dahdi.fd; + + bfd->data = line; + bfd->priv_nr = ts; + bfd->cb = dahdi_fd_cb; + snprintf(openstr, sizeof(openstr), "/dev/dahdi/%d", ts); + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_NONE: + continue; + break; + case E1INP_TS_TYPE_SIGN: + bfd->fd = open(openstr, O_RDWR | O_NONBLOCK); + if (bfd->fd == -1) { + fprintf(stderr, "%s could not open %s %s\n", + __func__, openstr, strerror(errno)); + exit(-1); + } + bfd->when = BSC_FD_READ | BSC_FD_EXCEPT; + dahdi_set_bufinfo(bfd->fd, 1); + e1i_ts->driver.dahdi.lapd = lapd_instance_alloc(1, dahdi_write_msg, bfd); + break; + case E1INP_TS_TYPE_TRAU: + bfd->fd = open(openstr, O_RDWR | O_NONBLOCK); + if (bfd->fd == -1) { + fprintf(stderr, "%s could not open %s %s\n", + __func__, openstr, strerror(errno)); + exit(-1); + } + dahdi_set_bufinfo(bfd->fd, 0); + /* We never include the DAHDI B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + bfd->when = BSC_FD_READ | BSC_FD_EXCEPT;// | BSC_FD_WRITE; + break; + } + + if (bfd->fd < 0) { + fprintf(stderr, "%s could not open %s %s\n", + __func__, openstr, strerror(errno)); + return bfd->fd; + } + + ret = osmo_fd_register(bfd); + if (ret < 0) { + fprintf(stderr, "could not register FD: %s\n", + strerror(ret)); + return ret; + } + } + + return 0; +} + +static int +dahdi_e1_line_update(struct e1inp_line *line, enum e1inp_line_role role) +{ + if (line->driver != &dahdi_driver) + return -EINVAL; + + return dahdi_e1_setup(line); +} + +int e1inp_dahdi_init(void) +{ + init_flip_bits(); + + /* register the driver with the core */ + return e1inp_driver_register(&dahdi_driver); +} + +#endif /* HAVE_DAHDI_USER_H */ diff --git a/src/input/hsl.c b/src/input/hsl.c new file mode 100644 index 0000000..2ff9d39 --- /dev/null +++ b/src/input/hsl.c @@ -0,0 +1,296 @@ +/* OpenBSC Abis input driver for HSL Femto */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (C) 2011 by On-Waves + * + * All Rights Reserved + * + * 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/>. + * + */ + +/* HSL uses a much more primitive/simplified version of the IPA multiplex. + * + * They have taken out the nice parts like the ID_GET / ID_RESP for resolving + * the UNIT ID, as well as the keepalive ping/pong messages. Furthermore, the + * Stream Identifiers are fixed on the BTS side (RSL always 0, OML always 0xff) + * and both OML+RSL share a single TCP connection. + * + * Other oddities include the encapsulation of BSSGP messages in the L3_INFO IE + * of RSL + */ + +#include "internal.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/fcntl.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> + +#include <osmocom/core/select.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/abis/e1_input.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/protocol/ipaccess.h> +/*#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/abis_nm.h> +#include <openbsc/abis_rsl.h> +#include <openbsc/subchan_demux.h> +#include <openbsc/e1_input.h> +#include <openbsc/ipaccess.h> +#include <openbsc/socket.h> +#include <openbsc/signal.h> */ +#include <talloc.h> + +#define HSL_TCP_PORT 2500 +#define HSL_PROTO_DEBUG 0xdd + +#define PRIV_OML 1 +#define PRIV_RSL 2 + +static void *tall_bsc_ctx; + +/* data structure for one E1 interface with A-bis */ +struct hsl_e1_handle { + struct osmo_fd listen_fd; + struct gsm_network *gsmnet; +}; + +static struct hsl_e1_handle *e1h; + + +#define TS1_ALLOC_SIZE 900 + +static int handle_ts1_read(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct msgb *msg; + int ret = 0, error; + + msg = ipaccess_read_msg(bfd, &error); + if (!msg) { + if (e1i_ts->line->rx_err) + e1i_ts->line->rx_err(error); + if (error == 0) { + osmo_fd_unregister(bfd); + close(bfd->fd); + bfd->fd = -1; + } + return error; + } + DEBUGP(DMI, "RX %u: %s\n", ts_nr, osmo_hexdump(msgb_l2(msg), msgb_l2len(msg))); + + /* XXX better use e1inp_rx_ts. */ + if (e1i_ts->line->rx) + e1i_ts->line->rx(msg, e1i_ts); + + return ret; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static int handle_ts1_write(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *sign_link; + struct msgb *msg; + uint8_t proto; + int ret; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + return 0; + } + + switch (sign_link->type) { + case E1INP_SIGN_OML: + proto = IPAC_PROTO_OML; +#ifdef HSL_SR_1_0 + /* HSL uses 0x81 for FOM for some reason */ + if (msg->data[0] == ABIS_OM_MDISC_FOM) + msg->data[0] = ABIS_OM_MDISC_FOM | 0x01; +#endif + break; + case E1INP_SIGN_RSL: + proto = IPAC_PROTO_RSL; + break; + default: + msgb_free(msg); + bfd->when |= BSC_FD_WRITE; /* come back for more msg */ + return -EINVAL; + } + + msg->l2h = msg->data; + ipaccess_prepend_header(msg, sign_link->tei); + + DEBUGP(DMI, "TX %u: %s\n", ts_nr, osmo_hexdump(msg->l2h, msgb_l2len(msg))); + + ret = send(bfd->fd, msg->data, msg->len, 0); + msgb_free(msg); + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + + /* Reducing this might break the nanoBTS 900 init. */ + osmo_timer_schedule(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay); + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int hsl_fd_cb(struct osmo_fd *bfd, unsigned int what) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + unsigned int idx = ts_nr-1; + struct e1inp_ts *e1i_ts; + int rc = 0; + + /* In case of early RSL we might not yet have a line */ + + if (line) + e1i_ts = &line->ts[idx]; + + if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) { + if (what & BSC_FD_READ) + rc = handle_ts1_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_ts1_write(bfd); + } else + LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type); + + return rc; +} + +static int hsl_line_update(struct e1inp_line *line, enum e1inp_line_role role); + +struct e1inp_driver hsl_driver = { + .name = "hsl", + .want_write = ts_want_write, + .line_update = hsl_line_update, + .default_delay = 0, +}; + +/* callback of the OML listening filedescriptor */ +static int listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what) +{ + int ret; + int idx = 0; + int i; + struct e1inp_line *line = listen_bfd->data; + struct e1inp_ts *e1i_ts; + struct osmo_fd *bfd; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (!(what & BSC_FD_READ)) + return 0; + + ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (ret < 0) { + perror("accept"); + return ret; + } + LOGP(DINP, LOGL_NOTICE, "accept()ed new HSL link from %s\n", + inet_ntoa(sa.sin_addr)); + + /* create virrtual E1 timeslots for signalling */ + e1inp_ts_config_sign(&line->ts[1-1], line); + + /* initialize the fds */ + for (i = 0; i < ARRAY_SIZE(line->ts); ++i) + line->ts[i].driver.ipaccess.fd.fd = -1; + + e1i_ts = &line->ts[idx]; + + bfd = &e1i_ts->driver.ipaccess.fd; + bfd->fd = ret; + bfd->data = line; + bfd->priv_nr = PRIV_OML; + bfd->cb = hsl_fd_cb; + bfd->when = BSC_FD_READ; + ret = osmo_fd_register(bfd); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not register FD\n"); + close(bfd->fd); + talloc_free(line); + return ret; + } + + return ret; +} + +static int +hsl_line_update(struct e1inp_line *line, enum e1inp_line_role role) +{ + int ret = -ENOENT; + + switch(role) { + case E1INP_LINE_R_BSC: + ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, INADDR_ANY, + HSL_TCP_PORT, 0, listen_fd_cb, line); + break; + case E1INP_LINE_R_BTS: + /* XXX: not implemented yet. */ + break; + default: + break; + } + return ret; +} + +int hsl_setup(struct gsm_network *gsmnet) +{ + e1h->gsmnet = gsmnet; + return 0; +} + +void e1inp_hsl_init(void) +{ + e1h = talloc_zero(tall_bsc_ctx, struct hsl_e1_handle); + if (!e1h) + return; + + e1inp_driver_register(&hsl_driver); +} diff --git a/src/input/ipaccess.c b/src/input/ipaccess.c new file mode 100644 index 0000000..5777d56 --- /dev/null +++ b/src/input/ipaccess.c @@ -0,0 +1,499 @@ +/* OpenBSC Abis input driver for ip.access */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * 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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/fcntl.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> + +#include <osmocom/core/select.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <talloc.h> +#include <osmocom/gsm/abis/e1_input.h> +#include <osmocom/gsm/abis/ipaccess.h> +/*#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/abis_nm.h> +#include <openbsc/abis_rsl.h> +#include <openbsc/subchan_demux.h> +#include <openbsc/e1_input.h> +#include <openbsc/ipaccess.h> +#include <openbsc/socket.h> +#include <openbsc/signal.h> */ + +#define PRIV_OML 1 +#define PRIV_RSL 2 + +static void *tall_bsc_ctx; + +/* data structure for one E1 interface with A-bis */ +struct ia_e1_handle { + struct osmo_fd listen_fd; + struct osmo_fd rsl_listen_fd; + struct gsm_network *gsmnet; +}; + +static struct ia_e1_handle *e1h; + + +#define TS1_ALLOC_SIZE 900 + +/* + * Common propietary IPA messages: + * - PONG: in reply to PING. + * - ID_REQUEST: first messages once OML has been established. + * - ID_ACK: in reply to ID_ACK. + */ +const uint8_t ipa_pong_msg[] = { + 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG +}; + +const uint8_t ipa_id_ack_msg[] = { + 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK +}; + +const uint8_t ipa_id_req_msg[] = { + 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET, + 0x01, IPAC_IDTAG_UNIT, + 0x01, IPAC_IDTAG_MACADDR, + 0x01, IPAC_IDTAG_LOCATION1, + 0x01, IPAC_IDTAG_LOCATION2, + 0x01, IPAC_IDTAG_EQUIPVERS, + 0x01, IPAC_IDTAG_SWVERSION, + 0x01, IPAC_IDTAG_UNITNAME, + 0x01, IPAC_IDTAG_SERNR, +}; + +static int ipaccess_send(int fd, const void *msg, size_t msglen) +{ + int ret; + + ret = write(fd, msg, msglen); + if (ret < 0) + return ret; + if (ret < msglen) { + LOGP(DINP, LOGL_ERROR, "ipaccess_send: short write\n"); + return -EIO; + } + return ret; +} + +int ipaccess_send_pong(int fd) +{ + return ipaccess_send(fd, ipa_pong_msg, sizeof(ipa_pong_msg)); +} + +int ipaccess_send_id_ack(int fd) +{ + return ipaccess_send(fd, ipa_id_ack_msg, sizeof(ipa_id_ack_msg)); +} + +int ipaccess_send_id_req(int fd) +{ + return ipaccess_send(fd, ipa_id_req_msg, sizeof(ipa_id_req_msg)); +} + +/* + * read one ipa message from the socket + * return NULL in case of error + */ +struct msgb *ipaccess_read_msg(struct osmo_fd *bfd, int *error) +{ + struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "Abis/IP"); + struct ipaccess_head *hh; + int len, ret = 0; + + if (!msg) { + *error = -ENOMEM; + return NULL; + } + + /* first read our 3-byte header */ + hh = (struct ipaccess_head *) msg->data; + ret = recv(bfd->fd, msg->data, sizeof(*hh), 0); + if (ret == 0) { + msgb_free(msg); + *error = ret; + return NULL; + } else if (ret != sizeof(*hh)) { + if (errno != EAGAIN) + LOGP(DINP, LOGL_ERROR, "recv error %d %s\n", ret, strerror(errno)); + msgb_free(msg); + *error = ret; + return NULL; + } + + msgb_put(msg, ret); + + /* then read te length as specified in header */ + msg->l2h = msg->data + sizeof(*hh); + len = ntohs(hh->len); + + if (len < 0 || TS1_ALLOC_SIZE < len + sizeof(*hh)) { + LOGP(DINP, LOGL_ERROR, "Can not read this packet. %d avail\n", len); + msgb_free(msg); + *error = -EIO; + return NULL; + } + + ret = recv(bfd->fd, msg->l2h, len, 0); + if (ret < len) { + LOGP(DINP, LOGL_ERROR, "short read! Got %d from %d\n", ret, len); + msgb_free(msg); + *error = -EIO; + return NULL; + } + msgb_put(msg, ret); + + return msg; +} + +static int handle_ts1_read(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct msgb *msg; + int ret = 0, error; + + msg = ipaccess_read_msg(bfd, &error); + if (!msg) { + if (e1i_ts->line->rx_err) + e1i_ts->line->rx_err(error); + if (error == 0) { + osmo_fd_unregister(bfd); + close(bfd->fd); + bfd->fd = -1; + } + return error; + } + DEBUGP(DMI, "RX %u: %s\n", ts_nr, osmo_hexdump(msgb_l2(msg), msgb_l2len(msg))); + + /* XXX better use e1inp_ts_rx. */ + + if (e1i_ts->line->rx) + e1i_ts->line->rx(msg, e1i_ts); + return ret; +} + +void ipaccess_prepend_header_ext(struct msgb *msg, int proto) +{ + struct ipaccess_head_ext *hh_ext; + + /* prepend the osmo ip.access header extension */ + hh_ext = (struct ipaccess_head_ext *) msgb_push(msg, sizeof(*hh_ext)); + hh_ext->proto = proto; +} + +void ipaccess_prepend_header(struct msgb *msg, int proto) +{ + struct ipaccess_head *hh; + + /* prepend the ip.access header */ + hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh)); + hh->len = htons(msg->len - sizeof(*hh)); + hh->proto = proto; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static int handle_ts1_write(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *sign_link; + struct msgb *msg; + uint8_t proto; + int ret; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + return 0; + } + + switch (sign_link->type) { + case E1INP_SIGN_OML: + proto = IPAC_PROTO_OML; + break; + case E1INP_SIGN_RSL: + proto = IPAC_PROTO_RSL; + break; + default: + msgb_free(msg); + bfd->when |= BSC_FD_WRITE; /* come back for more msg */ + return -EINVAL; + } + + msg->l2h = msg->data; + ipaccess_prepend_header(msg, sign_link->tei); + + DEBUGP(DMI, "TX %u: %s\n", ts_nr, osmo_hexdump(msg->l2h, msgb_l2len(msg))); + + ret = send(bfd->fd, msg->data, msg->len, 0); + msgb_free(msg); + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + + /* Reducing this might break the nanoBTS 900 init. */ + osmo_timer_schedule(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay); + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int ipaccess_fd_cb(struct osmo_fd *bfd, unsigned int what) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + unsigned int idx = ts_nr-1; + struct e1inp_ts *e1i_ts; + int rc = 0; + + /* In case of early RSL we might not yet have a line */ + + if (line) + e1i_ts = &line->ts[idx]; + + if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) { + if (what & BSC_FD_READ) + rc = handle_ts1_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_ts1_write(bfd); + } else + LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type); + + return rc; +} + +static int +ipaccess_line_update(struct e1inp_line *line, enum e1inp_line_role role); + +struct e1inp_driver ipaccess_driver = { + .name = "ipa", + .want_write = ts_want_write, + .line_update = ipaccess_line_update, + .default_delay = 0, +}; + +/* callback of the OML listening filedescriptor */ +static int listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what) +{ + int ret; + int idx = 0; + int i; + struct e1inp_line *line = listen_bfd->data; + struct e1inp_ts *e1i_ts; + struct osmo_fd *bfd; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (!(what & BSC_FD_READ)) + return 0; + + ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (ret < 0) { + perror("accept"); + return ret; + } + LOGP(DINP, LOGL_NOTICE, "accept()ed new OML link from %s\n", + inet_ntoa(sa.sin_addr)); + + /* create virrtual E1 timeslots for signalling */ + e1inp_ts_config_sign(&line->ts[1-1], line); + + /* initialize the fds */ + for (i = 0; i < ARRAY_SIZE(line->ts); ++i) + line->ts[i].driver.ipaccess.fd.fd = -1; + + e1i_ts = &line->ts[idx]; + + bfd = &e1i_ts->driver.ipaccess.fd; + bfd->fd = ret; + bfd->data = line; + bfd->priv_nr = PRIV_OML; + bfd->cb = ipaccess_fd_cb; + bfd->when = BSC_FD_READ; + ret = osmo_fd_register(bfd); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not register FD\n"); + close(bfd->fd); + return ret; + } + + /* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */ + ret = ipaccess_send_id_req(bfd->fd); + + return ret; +} + +static int rsl_listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what) +{ + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + struct osmo_fd *bfd; + int ret; + + if (!(what & BSC_FD_READ)) + return 0; + + bfd = talloc_zero(tall_bsc_ctx, struct osmo_fd); + if (!bfd) + return -ENOMEM; + + /* Some BTS has connected to us, but we don't know yet which line + * (as created by the OML link) to associate it with. Thus, we + * allocate a temporary bfd until we have received ID from BTS */ + + bfd->fd = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (bfd->fd < 0) { + perror("accept"); + return bfd->fd; + } + LOGP(DINP, LOGL_NOTICE, "accept()ed new RSL link from %s\n", inet_ntoa(sa.sin_addr)); + bfd->priv_nr = PRIV_RSL; + bfd->cb = ipaccess_fd_cb; + bfd->when = BSC_FD_READ; + ret = osmo_fd_register(bfd); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not register FD\n"); + close(bfd->fd); + talloc_free(bfd); + return ret; + } + /* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */ + ret = ipaccess_send_id_req(bfd->fd); + + return 0; +} + +static int +ipaccess_line_update(struct e1inp_line *line, enum e1inp_line_role role) +{ + int ret = -ENOENT; + + switch(role) { + case E1INP_LINE_R_BSC: + /* Listen for OML connections */ + ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, INADDR_ANY, + IPA_TCP_PORT_OML, 0, listen_fd_cb, line); + if (ret < 0) + return ret; + + /* Listen for RSL connections */ + ret = make_sock(&e1h->rsl_listen_fd, IPPROTO_TCP, INADDR_ANY, + IPA_TCP_PORT_RSL, 0, rsl_listen_fd_cb, NULL); + if (ret < 0) + return ret; + break; + case E1INP_LINE_R_BTS: + /* XXX: no implemented yet. */ + break; + default: + break; + } + return ret; +} + +/* Actively connect to a BTS. Currently used by ipaccess-config.c */ +int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa) +{ + struct e1inp_ts *e1i_ts = &line->ts[0]; + struct osmo_fd *bfd = &e1i_ts->driver.ipaccess.fd; + int ret, on = 1; + + bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + bfd->cb = ipaccess_fd_cb; + bfd->when = BSC_FD_READ | BSC_FD_WRITE; + bfd->data = line; + bfd->priv_nr = PRIV_OML; + + if (bfd->fd < 0) { + LOGP(DINP, LOGL_ERROR, "could not create TCP socket.\n"); + return -EIO; + } + + setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa)); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not connect socket\n"); + close(bfd->fd); + return ret; + } + + ret = osmo_fd_register(bfd); + if (ret < 0) { + close(bfd->fd); + return ret; + } + + line->driver = &ipaccess_driver; + + return ret; + //return e1inp_line_register(line); +} + +int ipaccess_setup(struct gsm_network *gsmnet) +{ + e1h->gsmnet = gsmnet; + return 0; +} + +void e1inp_ipaccess_init(void) +{ + e1h = talloc_zero(tall_bsc_ctx, struct ia_e1_handle); + if (!e1h) + return; + + e1inp_driver_register(&ipaccess_driver); +} diff --git a/src/input/lapd.c b/src/input/lapd.c new file mode 100644 index 0000000..060edb6 --- /dev/null +++ b/src/input/lapd.c @@ -0,0 +1,713 @@ +/* OpenBSC minimal LAPD implementation */ + +/* (C) 2009 by oystein@homelien.no + * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com> + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/* TODO: + * detect RR timeout and set SAP state back to SABM_RETRANSMIT + * use of value_string + * further code cleanup (spaghetti) + */ + +#include "internal.h" + +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <errno.h> + +#include "lapd.h" + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/logging.h> +#include <talloc.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/timer.h> +//#include <openbsc/debug.h> + +#define SABM_INTERVAL 0, 300000 + +typedef enum { + LAPD_TEI_NONE = 0, + LAPD_TEI_ASSIGNED, + LAPD_TEI_ACTIVE, +} lapd_tei_state; + +const char *lapd_tei_states[] = { + "NONE", + "ASSIGNED", + "ACTIVE", +}; + +typedef enum { + LAPD_TYPE_NONE = 0, + + LAPD_TYPE_I, + LAPD_TYPE_S, + LAPD_TYPE_U, +} lapd_msg_type; + +typedef enum { + /* commands/responses */ + LAPD_CMD_NONE = 0, + + LAPD_CMD_I, + LAPD_CMD_RR, + LAPD_CMD_RNR, + LAPD_CMD_REJ, + + LAPD_CMD_SABME, + LAPD_CMD_DM, + LAPD_CMD_UI, + LAPD_CMD_DISC, + LAPD_CMD_UA, + LAPD_CMD_FRMR, + LAPD_CMD_XID, +} lapd_cmd_type; + +const char *lapd_cmd_types[] = { + "NONE", + + "I", + "RR", + "RNR", + "REJ", + + "SABME", + "DM", + "UI", + "DISC", + "UA", + "FRMR", + "XID", + +}; + +enum lapd_sap_state { + SAP_STATE_INACTIVE, + SAP_STATE_SABM_RETRANS, + SAP_STATE_ACTIVE, +}; + +const char *lapd_sap_states[] = { + "INACTIVE", + "SABM_RETRANS", + "ACTIVE", +}; + +const char *lapd_msg_types = "?ISU"; + +/* structure representing an allocated TEI within a LAPD instance */ +struct lapd_tei { + struct llist_head list; + struct lapd_instance *li; + uint8_t tei; + lapd_tei_state state; + + struct llist_head sap_list; +}; + +/* Structure representing a SAP within a TEI. We use this for TE-mode to + * re-transmit SABM */ +struct lapd_sap { + struct llist_head list; + struct lapd_tei *tei; + uint8_t sapi; + enum lapd_sap_state state; + + /* A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ V(S). */ + int vs; /* next to be transmitted */ + int va; /* last acked by peer */ + int vr; /* next expected to be received */ + + struct osmo_timer_list sabme_timer; /* timer to re-transmit SABM message */ +}; + +/* 3.5.2.2 Send state variable V(S) + * Each point-to-point data link connection endpoint shall have an associated V(S) when using I frame + * commands. V(S) denotes the sequence number of the next I frame to be transmitted. The V(S) can + * take on the value 0 through n minus 1. The value of V(S) shall be incremented by 1 with each + * successive I frame transmission, and shall not exceed V(A) by more than the maximum number of + * outstanding I frames k. The value of k may be in the range of 1 ≤ k ≤ 127. + * + * 3.5.2.3 Acknowledge state variable V(A) + * Each point-to-point data link connection endpoint shall have an associated V(A) when using I frame + * commands and supervisory frame commands/responses. V(A) identifies the last I frame that has been + * acknowledged by its peer [V(A) − 1 equals the N(S) of the last acknowledged I frame]. V(A) can + * take on the value 0 through n minus 1. The value of V(A) shall be updated by the valid N(R) values + * received from its peer (see 3.5.2.6). A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ + * V(S). + * + * 3.5.2.5 Receive state variable V(R) + * Each point-to-point data link connection endpoint shall have an associated V(R) when using I frame + * commands and supervisory frame commands/responses. V(R) denotes the sequence number of the + * next in-sequence I frame expected to be received. V(R) can take on the value 0 through n minus 1. + * The value of V(R) shall be incremented by one with the receipt of an error-free, in-sequence I frame + * whose N(S) equals V(R). + */ +#define LAPD_NS(sap) (sap->vs) +#define LAPD_NR(sap) (sap->vr) + +/* 3.5.2.4 Send sequence number N(S) + * Only I frames contain N(S), the send sequence number of transmitted I frames. At the time that an in- + * sequence I frame is designated for transmission, the value of N(S) is set equal to V(S). + * + * 3.5.2.6 Receive sequence number N(R) + * All I frames and supervisory frames contain N(R), the expected send sequence number of the next + * received I frame. At the time that a frame of the above types is designated for transmission, the value + * of N(R) is set equal to V(R). N(R) indicates that the data link layer entity transmitting the N(R) has + * correctly received all I frames numbered up to and including N(R) − 1. + */ + +/* Resolve TEI structure from given numeric TEI */ +static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei) +{ + struct lapd_tei *lt; + + llist_for_each_entry(lt, &li->tei_list, list) { + if (lt->tei == tei) + return lt; + } + return NULL; +}; + +static void lapd_tei_set_state(struct lapd_tei *teip, int newstate) +{ + DEBUGP(DMI, "state change on TEI %d: %s -> %s\n", teip->tei, + lapd_tei_states[teip->state], lapd_tei_states[newstate]); + teip->state = newstate; +}; + +/* Allocate a new TEI */ +struct lapd_tei *lapd_tei_alloc(struct lapd_instance *li, uint8_t tei) +{ + struct lapd_tei *teip; + + teip = talloc_zero(li, struct lapd_tei); + if (!teip) + return NULL; + + teip->li = li; + teip->tei = tei; + llist_add(&teip->list, &li->tei_list); + INIT_LLIST_HEAD(&teip->sap_list); + + lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); + + return teip; +} + +/* Find a SAP within a given TEI */ +static struct lapd_sap *lapd_sap_find(struct lapd_tei *teip, uint8_t sapi) +{ + struct lapd_sap *sap; + + llist_for_each_entry(sap, &teip->sap_list, list) { + if (sap->sapi == sapi) + return sap; + } + + return NULL; +} + +static void sabme_timer_cb(void *_sap); + +/* Allocate a new SAP within a given TEI */ +static struct lapd_sap *lapd_sap_alloc(struct lapd_tei *teip, uint8_t sapi) +{ + struct lapd_sap *sap = talloc_zero(teip, struct lapd_sap); + + LOGP(DMI, LOGL_INFO, "Allocating SAP for SAPI=%u / TEI=%u\n", + sapi, teip->tei); + + sap->sapi = sapi; + sap->tei = teip; + sap->sabme_timer.cb = &sabme_timer_cb; + sap->sabme_timer.data = sap; + + llist_add(&sap->list, &teip->sap_list); + + return sap; +} + +static void lapd_sap_set_state(struct lapd_tei *teip, uint8_t sapi, + enum lapd_sap_state newstate) +{ + struct lapd_sap *sap = lapd_sap_find(teip, sapi); + if (!sap) + return; + + DEBUGP(DMI, "state change on TEI %u / SAPI %u: %s -> %s\n", teip->tei, + sapi, lapd_sap_states[sap->state], lapd_sap_states[newstate]); + switch (sap->state) { + case SAP_STATE_SABM_RETRANS: + if (newstate != SAP_STATE_SABM_RETRANS) + osmo_timer_del(&sap->sabme_timer); + break; + default: + if (newstate == SAP_STATE_SABM_RETRANS) + osmo_timer_schedule(&sap->sabme_timer, SABM_INTERVAL); + break; + } + + sap->state = newstate; +}; + +/* Input function into TEI manager */ +static void lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len) +{ + uint8_t entity = data[0]; + uint8_t ref = data[1]; + uint8_t mt = data[3]; + uint8_t action = data[4] >> 1; + uint8_t e = data[4] & 1; + uint8_t resp[8]; + struct lapd_tei *teip; + + DEBUGP(DMI, "TEIMGR: entity %x, ref %x, mt %x, action %x, e %x\n", entity, ref, mt, action, e); + + switch (mt) { + case 0x01: /* IDENTITY REQUEST */ + DEBUGP(DMI, "TEIMGR: identity request for TEI %u\n", action); + + teip = teip_from_tei(li, action); + if (!teip) { + LOGP(DMI, LOGL_INFO, "TEI MGR: New TEI %u\n", action); + teip = lapd_tei_alloc(li, action); + } + + /* Send ACCEPT */ + memmove(resp, "\xfe\xff\x03\x0f\x00\x00\x02\x00", 8); + resp[7] = (action << 1) | 1; + li->transmit_cb(resp, 8, li->cbdata); + + if (teip->state == LAPD_TEI_NONE) + lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); + break; + default: + LOGP(DMI, LOGL_NOTICE, "TEIMGR: unknown mt %x action %x\n", + mt, action); + break; + }; +}; + +/* General input function for any data received for this LAPD instance */ +uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len, + int *ilen, lapd_mph_type *prim) +{ + uint8_t sapi, cr, tei, command; + int pf, ns, nr; + uint8_t *contents; + struct lapd_tei *teip; + struct lapd_sap *sap; + + uint8_t resp[8]; + int l = 0; + + *ilen = 0; + *prim = 0; + + if (len < 2) { + DEBUGP(DMI, "len %d < 2\n", len); + return NULL; + }; + + if ((data[0] & 1) != 0 || (data[1] & 1) != 1) { + DEBUGP(DMI, "address field %x/%x not well formed\n", data[0], + data[1]); + return NULL; + }; + + sapi = data[0] >> 2; + cr = (data[0] >> 1) & 1; + tei = data[1] >> 1; + command = li->network_side ^ cr; + //DEBUGP(DMI, " address sapi %x tei %d cmd %d cr %d\n", sapi, tei, command, cr); + + if (len < 3) { + DEBUGP(DMI, "len %d < 3\n", len); + return NULL; + }; + + lapd_msg_type typ = 0; + lapd_cmd_type cmd = 0; + pf = -1; + ns = -1; + nr = -1; + if ((data[2] & 1) == 0) { + typ = LAPD_TYPE_I; + assert(len >= 4); + ns = data[2] >> 1; + nr = data[3] >> 1; + pf = data[3] & 1; + cmd = LAPD_CMD_I; + } else if ((data[2] & 3) == 1) { + typ = LAPD_TYPE_S; + assert(len >= 4); + nr = data[3] >> 1; + pf = data[3] & 1; + switch (data[2]) { + case 0x1: + cmd = LAPD_CMD_RR; + break; + case 0x5: + cmd = LAPD_CMD_RNR; + break; + case 0x9: + cmd = LAPD_CMD_REJ; + break; + default: + LOGP(DMI, LOGL_ERROR, "unknown LAPD S cmd %x\n", data[2]); + return NULL; + }; + } else if ((data[2] & 3) == 3) { + typ = LAPD_TYPE_U; + pf = (data[2] >> 4) & 1; + int val = data[2] & ~(1 << 4); + switch (val) { + case 0x6f: + cmd = LAPD_CMD_SABME; + break; + case 0x0f: + cmd = LAPD_CMD_DM; + break; + case 0x03: + cmd = LAPD_CMD_UI; + break; + case 0x43: + cmd = LAPD_CMD_DISC; + break; + case 0x63: + cmd = LAPD_CMD_UA; + break; + case 0x87: + cmd = LAPD_CMD_FRMR; + break; + case 0xaf: + cmd = LAPD_CMD_XID; + break; + + default: + LOGP(DMI, LOGL_ERROR, "unknown U cmd %x " + "(pf %x data %x)\n", val, pf, data[2]); + return NULL; + }; + }; + + contents = &data[4]; + if (typ == LAPD_TYPE_U) + contents--; + *ilen = len - (contents - data); + + if (tei == 127) + lapd_tei_receive(li, contents, *ilen); + + teip = teip_from_tei(li, tei); + if (!teip) { + LOGP(DMI, LOGL_NOTICE, "Unknown TEI %u\n", tei); + return NULL; + } + + sap = lapd_sap_find(teip, sapi); + if (!sap) { + LOGP(DMI, LOGL_INFO, "No SAP for TEI=%u / SAPI=%u, " + "allocating\n", tei, sapi); + sap = lapd_sap_alloc(teip, sapi); + } + + DEBUGP(DMI, "<- %c %s sapi %x tei %3d cmd %x pf %x ns %3d nr %3d " + "ilen %d teip %p vs %d va %d vr %d len %d\n", + lapd_msg_types[typ], lapd_cmd_types[cmd], sapi, tei, command, pf, + ns, nr, *ilen, teip, sap->vs, sap->va, sap->vr, len); + + switch (cmd) { + case LAPD_CMD_I: + if (ns != sap->vr) { + DEBUGP(DMI, "ns %d != vr %d\n", ns, sap->vr); + if (ns == ((sap->vr - 1) & 0x7f)) { + DEBUGP(DMI, "DOUBLE FRAME, ignoring\n"); + cmd = 0; // ignore + } else { + assert(0); + }; + } else { + //printf("IN SEQUENCE\n"); + sap->vr = (ns + 1) & 0x7f; // FIXME: hack! + }; + + break; + case LAPD_CMD_UI: + break; + case LAPD_CMD_SABME: + sap->vs = 0; + sap->vr = 0; + sap->va = 0; + + // ua + resp[l++] = data[0]; + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x73; + li->transmit_cb(resp, l, li->cbdata); + if (teip->state != LAPD_TEI_ACTIVE) { + if (teip->state == LAPD_TEI_ASSIGNED) { + lapd_tei_set_state(teip, + LAPD_TEI_ACTIVE); + //printf("ASSIGNED and ACTIVE\n"); + } else { +#if 0 + DEBUGP(DMI, "rr in strange state, send rej\n"); + + // rej + resp[l++] = (sap-> sapi << 2) | (li->network_side ? 0 : 2); + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x09; //rej + resp[l++] = ((sap->vr + 1) << 1) | 0; + li->transmit_cb(resp, l, li->cbdata); + pf = 0; // dont reply +#endif + }; + }; + + *prim = LAPD_MPH_ACTIVATE_IND; + break; + case LAPD_CMD_UA: + sap->vs = 0; + sap->vr = 0; + sap->va = 0; + lapd_tei_set_state(teip, LAPD_TEI_ACTIVE); + lapd_sap_set_state(teip, sapi, SAP_STATE_ACTIVE); + *prim = LAPD_MPH_ACTIVATE_IND; + break; + case LAPD_CMD_RR: + sap->va = (nr & 0x7f); +#if 0 + if (teip->state != LAPD_TEI_ACTIVE) { + if (teip->state == LAPD_TEI_ASSIGNED) { + lapd_tei_set_state(teip, LAPD_TEI_ACTIVE); + *prim = LAPD_MPH_ACTIVATE_IND; + //printf("ASSIGNED and ACTIVE\n"); + } else { +#if 0 + DEBUGP(DMI, "rr in strange " "state, send rej\n"); + + // rej + resp[l++] = (sap-> sapi << 2) | (li->network_side ? 0 : 2); + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x09; //rej + resp[l++] = + ((sap->vr + 1) << 1) | 0; + li->transmit_cb(resp, l, li->cbdata); + pf = 0; // dont reply +#endif + }; + }; +#endif + if (pf) { + // interrogating us, send rr + resp[l++] = data[0]; + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x01; // rr + resp[l++] = (LAPD_NR(sap) << 1) | (data[3] & 1); // pf bit from req + + li->transmit_cb(resp, l, li->cbdata); + + }; + break; + case LAPD_CMD_FRMR: + // frame reject +#if 0 + if (teip->state == LAPD_TEI_ACTIVE) + *prim = LAPD_MPH_DEACTIVATE_IND; + lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); +#endif + LOGP(DMI, LOGL_NOTICE, "frame reject, ignoring\n"); + break; + case LAPD_CMD_DISC: + // disconnect + resp[l++] = data[0]; + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x73; + li->transmit_cb(resp, l, li->cbdata); + lapd_tei_set_state(teip, LAPD_TEI_NONE); + break; + default: + LOGP(DMI, LOGL_NOTICE, "unknown cmd for tei %d (cmd %x)\n", + tei, cmd); + break; + } + + if (typ == LAPD_TYPE_I) { + /* send rr + * Thu Jan 22 19:17:13 2009 <4000> sangoma.c:340 read (62/25) 4: fa 33 01 0a + * lapd <- S RR sapi 3e tei 25 cmd 0 pf 0 ns -1 nr 5 ilen 0 teip 0x613800 vs 7 va 5 vr 2 len 4 + */ + + /* interrogating us, send rr */ + DEBUGP(DMI, "Sending RR response\n"); + resp[l++] = data[0]; + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x01; // rr + resp[l++] = (LAPD_NR(sap) << 1) | (data[3] & 1); // pf bit from req + + li->transmit_cb(resp, l, li->cbdata); + + if (cmd != 0) { + *prim = LAPD_DL_DATA_IND; + return contents; + } + } else if (tei != 127 && typ == LAPD_TYPE_U && cmd == LAPD_CMD_UI) { + *prim = LAPD_DL_UNITDATA_IND; + return contents; + } + + return NULL; +}; + +/* low-level function to send a single SABM message */ +static int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi) +{ + struct msgb *msg = msgb_alloc_headroom(1024, 128, "LAPD SABM"); + if (!msg) + return -ENOMEM; + + DEBUGP(DMI, "Sending SABM for TEI=%u, SAPI=%u\n", tei, sapi); + + msgb_put_u8(msg, (sapi << 2) | (li->network_side ? 2 : 0)); + msgb_put_u8(msg, (tei << 1) | 1); + msgb_put_u8(msg, 0x7F); + + li->transmit_cb(msg->data, msg->len, li->cbdata); + + msgb_free(msg); + + return 0; +} + +/* timer call-back function for SABM re-transmission */ +static void sabme_timer_cb(void *_sap) +{ + struct lapd_sap *sap = _sap; + + lapd_send_sabm(sap->tei->li, sap->tei->tei, sap->sapi); + + if (sap->state == SAP_STATE_SABM_RETRANS) + osmo_timer_schedule(&sap->sabme_timer, SABM_INTERVAL); +} + +/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ +int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi) +{ + struct lapd_sap *sap; + struct lapd_tei *teip; + + teip = teip_from_tei(li, tei); + if (!teip) + teip = lapd_tei_alloc(li, tei); + + sap = lapd_sap_find(teip, sapi); + if (sap) + return -EEXIST; + + sap = lapd_sap_alloc(teip, sapi); + + lapd_sap_set_state(teip, sapi, SAP_STATE_SABM_RETRANS); + + return 0; +} + +/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ +int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi) +{ + struct lapd_tei *teip; + struct lapd_sap *sap; + + teip = teip_from_tei(li, tei); + if (!teip) + return -ENODEV; + + sap = lapd_sap_find(teip, sapi); + if (!sap) + return -ENODEV; + + lapd_sap_set_state(teip, sapi, SAP_STATE_INACTIVE); + + llist_del(&sap->list); + talloc_free(sap); + + return 0; +} + +/* Transmit Data (I-Frame) on the given LAPD Instance / TEI / SAPI */ +void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi, + uint8_t *data, unsigned int len) +{ + struct lapd_tei *teip = teip_from_tei(li, tei); + struct lapd_sap *sap; + + if (!teip) { + LOGP(DMI, LOGL_ERROR, "Cannot transmit on non-existing " + "TEI %u\n", tei); + return; + } + + sap = lapd_sap_find(teip, sapi); + if (!sap) { + LOGP(DMI, LOGL_INFO, "Tx on unknown SAPI=%u in TEI=%u, " + "allocating\n", sapi, tei); + sap = lapd_sap_alloc(teip, sapi); + } + + /* prepend stuff */ + uint8_t buf[10000]; + memset(buf, 0, sizeof(buf)); + memmove(buf + 4, data, len); + len += 4; + + buf[0] = (sapi << 2) | (li->network_side ? 2 : 0); + buf[1] = (tei << 1) | 1; + buf[2] = (LAPD_NS(sap) << 1); + buf[3] = (LAPD_NR(sap) << 1) | 0; + + sap->vs = (sap->vs + 1) & 0x7f; + + li->transmit_cb(buf, len, li->cbdata); +}; + +/* Allocate a new LAPD instance */ +struct lapd_instance *lapd_instance_alloc(int network_side, + void (*tx_cb)(uint8_t *data, int len, + void *cbdata), void *cbdata) +{ + struct lapd_instance *li; + + li = talloc_zero(NULL, struct lapd_instance); + if (!li) + return NULL; + + li->transmit_cb = tx_cb; + li->cbdata = cbdata; + li->network_side = network_side; + INIT_LLIST_HEAD(&li->tei_list); + + return li; +} diff --git a/src/input/misdn.c b/src/input/misdn.c new file mode 100644 index 0000000..45d5e06 --- /dev/null +++ b/src/input/misdn.c @@ -0,0 +1,547 @@ +/* OpenBSC Abis input driver for mISDNuser */ + +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * + * All Rights Reserved + * + * 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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/fcntl.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <mISDNif.h> + +//#define AF_COMPATIBILITY_FUNC +//#include <compat_af_isdn.h> +#ifndef AF_ISDN +#define AF_ISDN 34 +#define PF_ISDN AF_ISDN +#endif + +#include <osmocom/core/select.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/abis/e1_input.h> +#include <talloc.h> +/*#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/abis_nm.h> +#include <openbsc/abis_rsl.h> +#include <openbsc/subchan_demux.h> +#include <openbsc/e1_input.h> +#include <openbsc/signal.h> */ + +#define TS1_ALLOC_SIZE 300 + +struct prim_name { + unsigned int prim; + const char *name; +}; + +const struct prim_name prim_names[] = { + { PH_CONTROL_IND, "PH_CONTROL_IND" }, + { PH_DATA_IND, "PH_DATA_IND" }, + { PH_DATA_CNF, "PH_DATA_CNF" }, + { PH_ACTIVATE_IND, "PH_ACTIVATE_IND" }, + { DL_ESTABLISH_IND, "DL_ESTABLISH_IND" }, + { DL_ESTABLISH_CNF, "DL_ESTABLISH_CNF" }, + { DL_RELEASE_IND, "DL_RELEASE_IND" }, + { DL_RELEASE_CNF, "DL_RELEASE_CNF" }, + { DL_DATA_IND, "DL_DATA_IND" }, + { DL_UNITDATA_IND, "DL_UNITDATA_IND" }, + { DL_INFORMATION_IND, "DL_INFORMATION_IND" }, + { MPH_ACTIVATE_IND, "MPH_ACTIVATE_IND" }, + { MPH_DEACTIVATE_IND, "MPH_DEACTIVATE_IND" }, +}; + +const char *get_prim_name(unsigned int prim) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(prim_names); i++) { + if (prim_names[i].prim == prim) + return prim_names[i].name; + } + + return "UNKNOWN"; +} + +static int handle_ts1_read(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *link; + struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "mISDN TS1"); + struct sockaddr_mISDN l2addr; + struct mISDNhead *hh; + socklen_t alen; + int ret; + + if (!msg) + return -ENOMEM; + + hh = (struct mISDNhead *) msg->data; + + alen = sizeof(l2addr); + ret = recvfrom(bfd->fd, msg->data, 300, 0, + (struct sockaddr *) &l2addr, &alen); + if (ret < 0) { + fprintf(stderr, "recvfrom error %s\n", strerror(errno)); + return ret; + } + + if (alen != sizeof(l2addr)) { + fprintf(stderr, "%s error len\n", __func__); + return -EINVAL; + } + + msgb_put(msg, ret); + + DEBUGP(DMI, "alen =%d, dev(%d) channel(%d) sapi(%d) tei(%d)\n", + alen, l2addr.dev, l2addr.channel, l2addr.sapi, l2addr.tei); + + DEBUGP(DMI, "<= len = %d, prim(0x%x) id(0x%x): %s\n", + ret, hh->prim, hh->id, get_prim_name(hh->prim)); + + switch (hh->prim) { + case DL_INFORMATION_IND: + /* mISDN tells us which channel number is allocated for this + * tuple of (SAPI, TEI). */ + DEBUGP(DMI, "DL_INFORMATION_IND: use channel(%d) sapi(%d) tei(%d) for now\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi); + if (!link) { + DEBUGPC(DMI, "mISDN message for unknown sign_link\n"); + msgb_free(msg); + return -EINVAL; + } + /* save the channel number in the driver private struct */ + link->driver.misdn.channel = l2addr.channel; + break; + case DL_ESTABLISH_IND: + DEBUGP(DMI, "DL_ESTABLISH_IND: channel(%d) sapi(%d) tei(%d)\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + /* For some strange reason, sometimes the DL_INFORMATION_IND tells + * us the wrong channel, and we only get the real channel number + * during the DL_ESTABLISH_IND */ + link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi); + if (!link) { + DEBUGPC(DMI, "mISDN message for unknown sign_link\n"); + msgb_free(msg); + return -EINVAL; + } + /* save the channel number in the driver private struct */ + link->driver.misdn.channel = l2addr.channel; + ret = e1inp_event(e1i_ts, S_INP_TEI_UP, l2addr.tei, l2addr.sapi); + break; + case DL_RELEASE_IND: + DEBUGP(DMI, "DL_RELEASE_IND: channel(%d) sapi(%d) tei(%d)\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + ret = e1inp_event(e1i_ts, S_INP_TEI_DN, l2addr.tei, l2addr.sapi); + break; + case DL_DATA_IND: + case DL_UNITDATA_IND: + msg->l2h = msg->data + MISDN_HEADER_LEN; + DEBUGP(DMI, "RX: %s\n", osmo_hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN)); + ret = e1inp_rx_ts(e1i_ts, msg, l2addr.tei, l2addr.sapi); + break; + case PH_ACTIVATE_IND: + DEBUGP(DMI, "PH_ACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + break; + case PH_DEACTIVATE_IND: + DEBUGP(DMI, "PH_DEACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + break; + default: + break; + } + return ret; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + /* We never include the mISDN B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + if (e1i_ts->type == E1INP_TS_TYPE_TRAU) + return 0; + + e1i_ts->driver.misdn.fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static int handle_ts1_write(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *sign_link; + struct sockaddr_mISDN sa; + struct msgb *msg; + struct mISDNhead *hh; + uint8_t *l2_data; + int ret; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + return 0; + } + + l2_data = msg->data; + + /* prepend the mISDNhead */ + hh = (struct mISDNhead *) msgb_push(msg, sizeof(*hh)); + hh->prim = DL_DATA_REQ; + + DEBUGP(DMI, "TX channel(%d) TEI(%d) SAPI(%d): %s\n", + sign_link->driver.misdn.channel, sign_link->tei, + sign_link->sapi, osmo_hexdump(l2_data, msg->len - MISDN_HEADER_LEN)); + + /* construct the sockaddr */ + sa.family = AF_ISDN; + sa.sapi = sign_link->sapi; + sa.dev = sign_link->tei; + sa.channel = sign_link->driver.misdn.channel; + + ret = sendto(bfd->fd, msg->data, msg->len, 0, + (struct sockaddr *)&sa, sizeof(sa)); + if (ret < 0) + fprintf(stderr, "%s sendto failed %d\n", __func__, ret); + msgb_free(msg); + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + osmo_timer_schedule(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay); + + return ret; +} + +#define BCHAN_TX_GRAN 160 +/* write to a B channel TS */ +static int handle_tsX_write(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct mISDNhead *hh; + uint8_t tx_buf[BCHAN_TX_GRAN + sizeof(*hh)]; + struct subch_mux *mx = &e1i_ts->trau.mux; + int ret; + + hh = (struct mISDNhead *) tx_buf; + hh->prim = PH_DATA_REQ; + + subchan_mux_out(mx, tx_buf+sizeof(*hh), BCHAN_TX_GRAN); + + DEBUGP(DMIB, "BCHAN TX: %s\n", + osmo_hexdump(tx_buf+sizeof(*hh), BCHAN_TX_GRAN)); + + ret = send(bfd->fd, tx_buf, sizeof(*hh) + BCHAN_TX_GRAN, 0); + if (ret < sizeof(*hh) + BCHAN_TX_GRAN) + DEBUGP(DMIB, "send returns %d instead of %zu\n", ret, + sizeof(*hh) + BCHAN_TX_GRAN); + + return ret; +} + +#define TSX_ALLOC_SIZE 4096 +/* FIXME: read from a B channel TS */ +static int handle_tsX_read(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct msgb *msg = msgb_alloc(TSX_ALLOC_SIZE, "mISDN TSx"); + struct mISDNhead *hh; + int ret; + + if (!msg) + return -ENOMEM; + + hh = (struct mISDNhead *) msg->data; + + ret = recv(bfd->fd, msg->data, TSX_ALLOC_SIZE, 0); + if (ret < 0) { + fprintf(stderr, "recvfrom error %s\n", strerror(errno)); + return ret; + } + + msgb_put(msg, ret); + + if (hh->prim != PH_CONTROL_IND) + DEBUGP(DMIB, "<= BCHAN len = %d, prim(0x%x) id(0x%x): %s\n", + ret, hh->prim, hh->id, get_prim_name(hh->prim)); + + switch (hh->prim) { + case PH_DATA_IND: + msg->l2h = msg->data + MISDN_HEADER_LEN; + DEBUGP(DMIB, "BCHAN RX: %s\n", + osmo_hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN)); + ret = e1inp_rx_ts(e1i_ts, msg, 0, 0); + break; + case PH_ACTIVATE_IND: + case PH_DATA_CNF: + /* physical layer indicates that data has been sent, + * we thus can send some more data */ + ret = handle_tsX_write(bfd); + default: + break; + } + /* FIXME: why do we free signalling msgs in the caller, and trau not? */ + msgb_free(msg); + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int misdn_fd_cb(struct osmo_fd *bfd, unsigned int what) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + unsigned int idx = ts_nr-1; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + int rc = 0; + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_SIGN: + if (what & BSC_FD_READ) + rc = handle_ts1_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_ts1_write(bfd); + break; + case E1INP_TS_TYPE_TRAU: + if (what & BSC_FD_READ) + rc = handle_tsX_read(bfd); + /* We never include the mISDN B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + break; + default: + fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type); + break; + } + + return rc; +} + +static int activate_bchan(struct e1inp_line *line, int ts, int act) +{ + struct mISDNhead hh; + int ret; + unsigned int idx = ts-1; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + struct osmo_fd *bfd = &e1i_ts->driver.misdn.fd; + + fprintf(stdout, "activate bchan\n"); + if (act) + hh.prim = PH_ACTIVATE_REQ; + else + hh.prim = PH_DEACTIVATE_REQ; + + hh.id = MISDN_ID_ANY; + ret = sendto(bfd->fd, &hh, sizeof(hh), 0, NULL, 0); + if (ret < 0) { + fprintf(stdout, "could not send ACTIVATE_RQ %s\n", + strerror(errno)); + } + + return ret; +} + +static int +mi_e1_line_update(struct e1inp_line *line, enum e1inp_line_role role); + +struct e1inp_driver misdn_driver = { + .name = "misdn", + .want_write = ts_want_write, + .default_delay = 50000, + .line_update = &mi_e1_line_update, +}; + +static int mi_e1_setup(struct e1inp_line *line, int release_l2) +{ + int ts, ret; + + /* TS0 is CRC4, don't need any fd for it */ + for (ts = 1; ts < NUM_E1_TS; ts++) { + unsigned int idx = ts-1; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + struct osmo_fd *bfd = &e1i_ts->driver.misdn.fd; + struct sockaddr_mISDN addr; + + bfd->data = line; + bfd->priv_nr = ts; + bfd->cb = misdn_fd_cb; + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_NONE: + continue; + break; + case E1INP_TS_TYPE_SIGN: + bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_NT); + bfd->when = BSC_FD_READ; + break; + case E1INP_TS_TYPE_TRAU: + bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_B_RAW); + /* We never include the mISDN B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + bfd->when = BSC_FD_READ; + break; + } + + if (bfd->fd < 0) { + fprintf(stderr, "%s could not open socket %s\n", + __func__, strerror(errno)); + return bfd->fd; + } + + memset(&addr, 0, sizeof(addr)); + addr.family = AF_ISDN; + addr.dev = line->num; + switch (e1i_ts->type) { + case E1INP_TS_TYPE_SIGN: + addr.channel = 0; + /* SAPI not supported yet in kernel */ + //addr.sapi = e1inp_ts->sign.sapi; + addr.sapi = 0; + addr.tei = GROUP_TEI; + break; + case E1INP_TS_TYPE_TRAU: + addr.channel = ts; + break; + default: + DEBUGP(DMI, "unsupported E1 TS type: %u\n", + e1i_ts->type); + break; + } + + ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (ret < 0) { + fprintf(stderr, "could not bind l2 socket %s\n", + strerror(errno)); + return -EIO; + } + + if (e1i_ts->type == E1INP_TS_TYPE_SIGN) { + ret = ioctl(bfd->fd, IMCLEAR_L2, &release_l2); + if (ret < 0) { + fprintf(stderr, "could not send IOCTL IMCLEAN_L2 %s\n", strerror(errno)); + return -EIO; + } + } + + /* FIXME: only activate B-Channels once we start to + * use them to conserve CPU power */ + if (e1i_ts->type == E1INP_TS_TYPE_TRAU) + activate_bchan(line, ts, 1); + + ret = osmo_fd_register(bfd); + if (ret < 0) { + fprintf(stderr, "could not register FD: %s\n", + strerror(ret)); + return ret; + } + } + + return 0; +} + +static int +mi_e1_line_update(struct e1inp_line *line, enum e1inp_line_role role) +{ + struct mISDN_devinfo devinfo; + int sk, ret, cnt; + + if (line->driver != &misdn_driver) + return -EINVAL; + + /* open the ISDN card device */ + sk = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE); + if (sk < 0) { + fprintf(stderr, "%s could not open socket %s\n", + __func__, strerror(errno)); + return sk; + } + + ret = ioctl(sk, IMGETCOUNT, &cnt); + if (ret) { + fprintf(stderr, "%s error getting interf count: %s\n", + __func__, strerror(errno)); + close(sk); + return -ENODEV; + } + //DEBUGP(DMI,"%d device%s found\n", cnt, (cnt==1)?"":"s"); + printf("%d device%s found\n", cnt, (cnt==1)?"":"s"); +#if 1 + devinfo.id = line->num; + ret = ioctl(sk, IMGETDEVINFO, &devinfo); + if (ret < 0) { + fprintf(stdout, "error getting info for device %d: %s\n", + line->num, strerror(errno)); + return -ENODEV; + } + fprintf(stdout, " id: %d\n", devinfo.id); + fprintf(stdout, " Dprotocols: %08x\n", devinfo.Dprotocols); + fprintf(stdout, " Bprotocols: %08x\n", devinfo.Bprotocols); + fprintf(stdout, " protocol: %d\n", devinfo.protocol); + fprintf(stdout, " nrbchan: %d\n", devinfo.nrbchan); + fprintf(stdout, " name: %s\n", devinfo.name); +#endif + + if (!(devinfo.Dprotocols & (1 << ISDN_P_NT_E1))) { + fprintf(stderr, "error: card is not of type E1 (NT-mode)\n"); + return -EINVAL; + } + + ret = mi_e1_setup(line, 1); + if (ret) + return ret; + + return 0; +} + +void e1inp_misdn_init(void) +{ + /* register the driver with the core */ + e1inp_driver_register(&misdn_driver); +} |