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 |
initial commit of libosmo-abis
still many things to get fixed
-rw-r--r-- | .gitignore | 26 | ||||
-rw-r--r-- | Makefile.am | 14 | ||||
-rw-r--r-- | configure.ac | 51 | ||||
-rwxr-xr-x | git-version-gen | 151 | ||||
-rw-r--r-- | include/Makefile.am | 3 | ||||
-rw-r--r-- | include/internal.h | 102 | ||||
-rw-r--r-- | include/lapd.h | 46 | ||||
-rw-r--r-- | include/mISDNif.h | 387 | ||||
-rw-r--r-- | include/osmocom/Makefile.am | 1 | ||||
-rw-r--r-- | include/osmocom/gsm/Makefile.am | 1 | ||||
-rw-r--r-- | include/osmocom/gsm/abis/Makefile.am | 3 | ||||
-rw-r--r-- | include/osmocom/gsm/abis/e1_input.h | 226 | ||||
-rw-r--r-- | include/osmocom/gsm/abis/ipaccess.h | 93 | ||||
-rw-r--r-- | include/osmocom/gsm/abis/subchan_demux.h | 101 | ||||
-rw-r--r-- | include/osmocom/gsm/abis/trau_frame.h | 63 | ||||
-rw-r--r-- | libosmoabis.pc.in | 11 | ||||
-rw-r--r-- | src/Makefile.am | 17 | ||||
-rw-r--r-- | src/e1_input.c | 628 | ||||
-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 | ||||
-rw-r--r-- | src/socket.c | 109 | ||||
-rw-r--r-- | src/subchan_demux.c | 323 | ||||
-rw-r--r-- | src/trau_frame.c | 260 | ||||
-rw-r--r-- | tests/Makefile.am | 10 | ||||
-rw-r--r-- | tests/e1inp_ipa_bsc_test.c | 69 |
29 files changed, 5256 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b299baa --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +Makefile +Makefile.in +.deps +.libs +*.o +*.lo +*.la +*.pc +aclocal.m4 +m4/*.m4 +autom4te.cache +config.h* +config.sub +config.log +config.status +config.guess +configure +depcomp +missing +ltmain.sh +install-sh +stamp-h1 +libtool + +.tarball-version +.version diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..17b9dbd --- /dev/null +++ b/Makefile.am @@ -0,0 +1,14 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 +ACLOCAL_AMFLAGS = -I m4 + +INCLUDES = $(all_includes) -I$(top_srcdir)/include +SUBDIRS = include src tests + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libosmoabis.pc + +BUILT_SOURCES = $(top_srcdir)/.version +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..02ecdb4 --- /dev/null +++ b/configure.ac @@ -0,0 +1,51 @@ +AC_INIT([libosmo-abis], + m4_esyscmd([./git-version-gen .tarball-version]), + [openbsc-devel@lists.openbsc.org]) + +AM_INIT_AUTOMAKE([dist-bzip2]) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +LT_INIT +AC_PROG_LIBTOOL + +AC_CONFIG_MACRO_DIR([m4]) + +dnl checks for header files +AC_HEADER_STDC +AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h) + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden"], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST(SYMBOL_VISIBILITY) + +dnl Generate the output +AM_CONFIG_HEADER(config.h) + +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.3.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.3.0) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.3.0) +PKG_CHECK_MODULES([LIBTALLOC], talloc >= 2.0.1) + +AC_OUTPUT( + libosmoabis.pc + include/Makefile + include/osmocom/Makefile + include/osmocom/gsm/Makefile + include/osmocom/gsm/abis/Makefile + src/Makefile + src/input/Makefile + tests/Makefile + Makefile) diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..c94d3ee --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,3 @@ +noinst_HEADERS=mISDNif.h internal.h lapd.h + +SUBDIRS=osmocom diff --git a/include/internal.h b/include/internal.h new file mode 100644 index 0000000..131e12b --- /dev/null +++ b/include/internal.h @@ -0,0 +1,102 @@ +#ifndef _INTERNAL_H_ +#define _INTERNAL_H_ + +/* things I don't know what to do with yet. */ + +/* extracted from include/openbsc/debug.h. */ +#define BSC_CTX_BTS 2 + +/* from include/openbsc/signal.h, we need SS_INPUT and S_GLOBAL_SHUTDOWN. */ +enum signal_subsystems { + SS_PAGING, + SS_SMS, + SS_ABISIP, + SS_NM, + SS_LCHAN, + SS_SUBSCR, + SS_SCALL, + SS_GLOBAL, + SS_CHALLOC, + SS_NS, + SS_IPAC_NWL, + SS_RF, + SS_MSC, + SS_HO, + SS_INPUT, +}; + +enum signal_global { + S_GLOBAL_SHUTDOWN, + S_GLOBAL_BTS_CLOSE_OM, +}; + +/* from include/openbsc/vty.h, we need E1INP_NODE */ +#include <osmocom/vty/vty.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/command.h> + +enum bsc_vty_node { + GSMNET_NODE = _LAST_OSMOVTY_NODE + 1, + BTS_NODE, + TRX_NODE, + TS_NODE, + SUBSCR_NODE, + MGCP_NODE, + GBPROXY_NODE, + SGSN_NODE, + NS_NODE, + BSSGP_NODE, + OML_NODE, + E1INP_NODE, + NAT_NODE, + NAT_BSC_NODE, + MSC_NODE, + OM2K_NODE, + TRUNK_NODE, + PGROUP_NODE, +}; + +/* from include/openbsc/debug.h */ +enum { + DRLL, + DCC, + DMM, + DRR, + DRSL, + DNM, + DMNCC, + DSMS, + DPAG, + DMEAS, + DMI, + DMIB, + DMUX, + DINP, + DSCCP, + DMSC, + DMGCP, + DHO, + DDB, + DREF, + DGPRS, + DNS, + DBSSGP, + DLLC, + DSNDCP, + DNAT, + Debug_LastEntry, +}; + +struct osmo_fd; +struct msgb *ipaccess_read_msg(struct osmo_fd *bfd, int *error); +void ipaccess_prepend_header(struct msgb *msg, int proto); + +#include <stdint.h> + +int make_sock(struct osmo_fd *bfd, int proto, + uint32_t ip, uint16_t port, int priv_nr, + int (*cb)(struct osmo_fd *fd, unsigned int what), void *data); + +uint8_t *trau_idle_frame(void); + +#endif diff --git a/include/lapd.h b/include/lapd.h new file mode 100644 index 0000000..fb980d1 --- /dev/null +++ b/include/lapd.h @@ -0,0 +1,46 @@ +#ifndef OPENBSC_LAPD_H +#define OPENBSC_LAPD_H + +#include <stdint.h> + +#include <osmocom/core/linuxlist.h> + +typedef enum { + LAPD_MPH_NONE = 0, + + LAPD_MPH_ACTIVATE_IND, + LAPD_MPH_DEACTIVATE_IND, + + LAPD_DL_DATA_IND, + LAPD_DL_UNITDATA_IND, + +} lapd_mph_type; + +struct lapd_instance { + struct llist_head list; /* list of LAPD instances */ + int network_side; + + void (*transmit_cb)(uint8_t *data, int len, void *cbdata); + void *cbdata; + + struct llist_head tei_list; /* list of TEI in this LAPD instance */ +}; + +extern uint8_t *lapd_receive(struct lapd_instance *li, uint8_t *data, unsigned int len, + int *ilen, lapd_mph_type *prim); + +extern void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi, + uint8_t *data, unsigned int len); + +struct lapd_instance *lapd_instance_alloc(int network_side, + void (*tx_cb)(uint8_t *data, int len, + void *cbdata), void *cbdata); + + +/* 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); + +/* 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); + +#endif /* OPENBSC_LAPD_H */ diff --git a/include/mISDNif.h b/include/mISDNif.h new file mode 100644 index 0000000..8e065d2 --- /dev/null +++ b/include/mISDNif.h @@ -0,0 +1,387 @@ +/* + * + * Author Karsten Keil <kkeil@novell.com> + * + * Copyright 2008 by Karsten Keil <kkeil@novell.com> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE + * version 2.1 as published by the Free Software Foundation. + * + * This code 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 LESSER GENERAL PUBLIC LICENSE for more details. + * + */ + +#ifndef mISDNIF_H +#define mISDNIF_H + +#include <stdarg.h> +#ifdef linux +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/socket.h> +#else +#include <sys/types.h> +#include <sys/errno.h> +#include <sys/socket.h> +#endif + +/* + * ABI Version 32 bit + * + * <8 bit> Major version + * - changed if any interface become backwards incompatible + * + * <8 bit> Minor version + * - changed if any interface is extended but backwards compatible + * + * <16 bit> Release number + * - should be incremented on every checkin + */ +#define MISDN_MAJOR_VERSION 1 +#define MISDN_MINOR_VERSION 1 +#define MISDN_RELEASE 20 + +/* primitives for information exchange + * generell format + * <16 bit 0 > + * <8 bit command> + * BIT 8 = 1 LAYER private + * BIT 7 = 1 answer + * BIT 6 = 1 DATA + * <8 bit target layer mask> + * + * Layer = 00 is reserved for general commands + Layer = 01 L2 -> HW + Layer = 02 HW -> L2 + Layer = 04 L3 -> L2 + Layer = 08 L2 -> L3 + * Layer = FF is reserved for broadcast commands + */ + +#define MISDN_CMDMASK 0xff00 +#define MISDN_LAYERMASK 0x00ff + +/* generell commands */ +#define OPEN_CHANNEL 0x0100 +#define CLOSE_CHANNEL 0x0200 +#define CONTROL_CHANNEL 0x0300 +#define CHECK_DATA 0x0400 + +/* layer 2 -> layer 1 */ +#define PH_ACTIVATE_REQ 0x0101 +#define PH_DEACTIVATE_REQ 0x0201 +#define PH_DATA_REQ 0x2001 +#define MPH_ACTIVATE_REQ 0x0501 +#define MPH_DEACTIVATE_REQ 0x0601 +#define MPH_INFORMATION_REQ 0x0701 +#define PH_CONTROL_REQ 0x0801 + +/* layer 1 -> layer 2 */ +#define PH_ACTIVATE_IND 0x0102 +#define PH_ACTIVATE_CNF 0x4102 +#define PH_DEACTIVATE_IND 0x0202 +#define PH_DEACTIVATE_CNF 0x4202 +#define PH_DATA_IND 0x2002 +#define PH_DATA_E_IND 0x3002 +#define MPH_ACTIVATE_IND 0x0502 +#define MPH_DEACTIVATE_IND 0x0602 +#define MPH_INFORMATION_IND 0x0702 +#define PH_DATA_CNF 0x6002 +#define PH_CONTROL_IND 0x0802 +#define PH_CONTROL_CNF 0x4802 + +/* layer 3 -> layer 2 */ +#define DL_ESTABLISH_REQ 0x1004 +#define DL_RELEASE_REQ 0x1104 +#define DL_DATA_REQ 0x3004 +#define DL_UNITDATA_REQ 0x3104 +#define DL_INFORMATION_REQ 0x0004 + +/* layer 2 -> layer 3 */ +#define DL_ESTABLISH_IND 0x1008 +#define DL_ESTABLISH_CNF 0x5008 +#define DL_RELEASE_IND 0x1108 +#define DL_RELEASE_CNF 0x5108 +#define DL_DATA_IND 0x3008 +#define DL_UNITDATA_IND 0x3108 +#define DL_INFORMATION_IND 0x0008 + +/* intern layer 2 managment */ +#define MDL_ASSIGN_REQ 0x1804 +#define MDL_ASSIGN_IND 0x1904 +#define MDL_REMOVE_REQ 0x1A04 +#define MDL_REMOVE_IND 0x1B04 +#define MDL_STATUS_UP_IND 0x1C04 +#define MDL_STATUS_DOWN_IND 0x1D04 +#define MDL_STATUS_UI_IND 0x1E04 +#define MDL_ERROR_IND 0x1F04 +#define MDL_ERROR_RSP 0x5F04 + +/* DL_INFORMATION_IND types */ +#define DL_INFO_L2_CONNECT 0x0001 +#define DL_INFO_L2_REMOVED 0x0002 + +/* PH_CONTROL types */ +/* TOUCH TONE IS 0x20XX XX "0"..."9", "A","B","C","D","*","#" */ +#define DTMF_TONE_VAL 0x2000 +#define DTMF_TONE_MASK 0x007F +#define DTMF_TONE_START 0x2100 +#define DTMF_TONE_STOP 0x2200 +#define DTMF_HFC_COEF 0x4000 +#define DSP_CONF_JOIN 0x2403 +#define DSP_CONF_SPLIT 0x2404 +#define DSP_RECEIVE_OFF 0x2405 +#define DSP_RECEIVE_ON 0x2406 +#define DSP_ECHO_ON 0x2407 +#define DSP_ECHO_OFF 0x2408 +#define DSP_MIX_ON 0x2409 +#define DSP_MIX_OFF 0x240a +#define DSP_DELAY 0x240b +#define DSP_JITTER 0x240c +#define DSP_TXDATA_ON 0x240d +#define DSP_TXDATA_OFF 0x240e +#define DSP_TX_DEJITTER 0x240f +#define DSP_TX_DEJ_OFF 0x2410 +#define DSP_TONE_PATT_ON 0x2411 +#define DSP_TONE_PATT_OFF 0x2412 +#define DSP_VOL_CHANGE_TX 0x2413 +#define DSP_VOL_CHANGE_RX 0x2414 +#define DSP_BF_ENABLE_KEY 0x2415 +#define DSP_BF_DISABLE 0x2416 +#define DSP_BF_ACCEPT 0x2416 +#define DSP_BF_REJECT 0x2417 +#define DSP_PIPELINE_CFG 0x2418 +#define HFC_VOL_CHANGE_TX 0x2601 +#define HFC_VOL_CHANGE_RX 0x2602 +#define HFC_SPL_LOOP_ON 0x2603 +#define HFC_SPL_LOOP_OFF 0x2604 + +/* DSP_TONE_PATT_ON parameter */ +#define TONE_OFF 0x0000 +#define TONE_GERMAN_DIALTONE 0x0001 +#define TONE_GERMAN_OLDDIALTONE 0x0002 +#define TONE_AMERICAN_DIALTONE 0x0003 +#define TONE_GERMAN_DIALPBX 0x0004 +#define TONE_GERMAN_OLDDIALPBX 0x0005 +#define TONE_AMERICAN_DIALPBX 0x0006 +#define TONE_GERMAN_RINGING 0x0007 +#define TONE_GERMAN_OLDRINGING 0x0008 +#define TONE_AMERICAN_RINGPBX 0x000b +#define TONE_GERMAN_RINGPBX 0x000c +#define TONE_GERMAN_OLDRINGPBX 0x000d +#define TONE_AMERICAN_RINGING 0x000e +#define TONE_GERMAN_BUSY 0x000f +#define TONE_GERMAN_OLDBUSY 0x0010 +#define TONE_AMERICAN_BUSY 0x0011 +#define TONE_GERMAN_HANGUP 0x0012 +#define TONE_GERMAN_OLDHANGUP 0x0013 +#define TONE_AMERICAN_HANGUP 0x0014 +#define TONE_SPECIAL_INFO 0x0015 +#define TONE_GERMAN_GASSENBESETZT 0x0016 +#define TONE_GERMAN_AUFSCHALTTON 0x0016 + +/* MPH_INFORMATION_IND */ +#define L1_SIGNAL_LOS_OFF 0x0010 +#define L1_SIGNAL_LOS_ON 0x0011 +#define L1_SIGNAL_AIS_OFF 0x0012 +#define L1_SIGNAL_AIS_ON 0x0013 +#define L1_SIGNAL_RDI_OFF 0x0014 +#define L1_SIGNAL_RDI_ON 0x0015 +#define L1_SIGNAL_SLIP_RX 0x0020 +#define L1_SIGNAL_SLIP_TX 0x0021 + +/* + * protocol ids + * D channel 1-31 + * B channel 33 - 63 + */ + +#define ISDN_P_NONE 0 +#define ISDN_P_BASE 0 +#define ISDN_P_TE_S0 0x01 +#define ISDN_P_NT_S0 0x02 +#define ISDN_P_TE_E1 0x03 +#define ISDN_P_NT_E1 0x04 +#define ISDN_P_TE_UP0 0x05 +#define ISDN_P_NT_UP0 0x06 + +#define IS_ISDN_P_TE(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_TE_E1) || \ + (p == ISDN_P_TE_UP0) || (p == ISDN_P_LAPD_TE)) +#define IS_ISDN_P_NT(p) ((p == ISDN_P_NT_S0) || (p == ISDN_P_NT_E1) || \ + (p == ISDN_P_NT_UP0) || (p == ISDN_P_LAPD_NT)) +#define IS_ISDN_P_S0(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_NT_S0)) +#define IS_ISDN_P_E1(p) ((p == ISDN_P_TE_E1) || (p == ISDN_P_NT_E1)) +#define IS_ISDN_P_UP0(p) ((p == ISDN_P_TE_UP0) || (p == ISDN_P_NT_UP0)) + + +#define ISDN_P_LAPD_TE 0x10 +#define ISDN_P_LAPD_NT 0x11 + +#define ISDN_P_B_MASK 0x1f +#define ISDN_P_B_START 0x20 + +#define ISDN_P_B_RAW 0x21 +#define ISDN_P_B_HDLC 0x22 +#define ISDN_P_B_X75SLP 0x23 +#define ISDN_P_B_L2DTMF 0x24 +#define ISDN_P_B_L2DSP 0x25 +#define ISDN_P_B_L2DSPHDLC 0x26 + +#define OPTION_L2_PMX 1 +#define OPTION_L2_PTP 2 +#define OPTION_L2_FIXEDTEI 3 +#define OPTION_L2_CLEANUP 4 + +/* should be in sync with linux/kobject.h:KOBJ_NAME_LEN */ +#define MISDN_MAX_IDLEN 20 + +struct mISDNhead { + unsigned int prim; + unsigned int id; +} __attribute__((packed)); + +#define MISDN_HEADER_LEN sizeof(struct mISDNhead) +#define MAX_DATA_SIZE 2048 +#define MAX_DATA_MEM (MAX_DATA_SIZE + MISDN_HEADER_LEN) +#define MAX_DFRAME_LEN 260 + +#define MISDN_ID_ADDR_MASK 0xFFFF +#define MISDN_ID_TEI_MASK 0xFF00 +#define MISDN_ID_SAPI_MASK 0x00FF +#define MISDN_ID_TEI_ANY 0x7F00 + +#define MISDN_ID_ANY 0xFFFF +#define MISDN_ID_NONE 0xFFFE + +#define GROUP_TEI 127 +#define TEI_SAPI 63 +#define CTRL_SAPI 0 + +#define MISDN_MAX_CHANNEL 127 +#define MISDN_CHMAP_SIZE ((MISDN_MAX_CHANNEL + 1) >> 3) + +#define SOL_MISDN 0 + +struct sockaddr_mISDN { + sa_family_t family; + unsigned char dev; + unsigned char channel; + unsigned char sapi; + unsigned char tei; +}; + +struct mISDNversion { + unsigned char major; + unsigned char minor; + unsigned short release; +}; + +#define MAX_DEVICE_ID 63 + +struct mISDN_devinfo { + u_int id; + u_int Dprotocols; + u_int Bprotocols; + u_int protocol; + u_char channelmap[MISDN_CHMAP_SIZE]; + u_int nrbchan; + char name[MISDN_MAX_IDLEN]; +}; + +struct mISDN_devrename { + u_int id; + char name[MISDN_MAX_IDLEN]; +}; + +struct ph_info_ch { + int32_t protocol; + int64_t Flags; +}; + +struct ph_info_dch { + struct ph_info_ch ch; + int16_t state; + int16_t num_bch; +}; + +struct ph_info { + struct ph_info_dch dch; + struct ph_info_ch bch[]; +}; + +/* timer device ioctl */ +#define IMADDTIMER _IOR('I', 64, int) +#define IMDELTIMER _IOR('I', 65, int) +/* socket ioctls */ +#define IMGETVERSION _IOR('I', 66, int) +#define IMGETCOUNT _IOR('I', 67, int) +#define IMGETDEVINFO _IOR('I', 68, int) +#define IMCTRLREQ _IOR('I', 69, int) +#define IMCLEAR_L2 _IOR('I', 70, int) +#define IMSETDEVNAME _IOR('I', 71, struct mISDN_devrename) + +static inline int +test_channelmap(u_int nr, u_char *map) +{ + if (nr <= MISDN_MAX_CHANNEL) + return map[nr >> 3] & (1 << (nr & 7)); + else + return 0; +} + +static inline void +set_channelmap(u_int nr, u_char *map) +{ + map[nr >> 3] |= (1 << (nr & 7)); +} + +static inline void +clear_channelmap(u_int nr, u_char *map) +{ + map[nr >> 3] &= ~(1 << (nr & 7)); +} + +/* CONTROL_CHANNEL parameters */ +#define MISDN_CTRL_GETOP 0x0000 +#define MISDN_CTRL_LOOP 0x0001 +#define MISDN_CTRL_CONNECT 0x0002 +#define MISDN_CTRL_DISCONNECT 0x0004 +#define MISDN_CTRL_PCMCONNECT 0x0010 +#define MISDN_CTRL_PCMDISCONNECT 0x0020 +#define MISDN_CTRL_SETPEER 0x0040 +#define MISDN_CTRL_UNSETPEER 0x0080 +#define MISDN_CTRL_RX_OFF 0x0100 +#define MISDN_CTRL_FILL_EMPTY 0x0200 +#define MISDN_CTRL_GETPEER 0x0400 +#define MISDN_CTRL_HW_FEATURES_OP 0x2000 +#define MISDN_CTRL_HW_FEATURES 0x2001 +#define MISDN_CTRL_HFC_OP 0x4000 +#define MISDN_CTRL_HFC_PCM_CONN 0x4001 +#define MISDN_CTRL_HFC_PCM_DISC 0x4002 +#define MISDN_CTRL_HFC_CONF_JOIN 0x4003 +#define MISDN_CTRL_HFC_CONF_SPLIT 0x4004 +#define MISDN_CTRL_HFC_RECEIVE_OFF 0x4005 +#define MISDN_CTRL_HFC_RECEIVE_ON 0x4006 +#define MISDN_CTRL_HFC_ECHOCAN_ON 0x4007 +#define MISDN_CTRL_HFC_ECHOCAN_OFF 0x4008 + + +/* socket options */ +#define MISDN_TIME_STAMP 0x0001 + +struct mISDN_ctrl_req { + int op; + int channel; + int p1; + int p2; +}; + +/* muxer options */ +#define MISDN_OPT_ALL 1 +#define MISDN_OPT_TEIMGR 2 + +#endif /* mISDNIF_H */ diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am new file mode 100644 index 0000000..999a3ee --- /dev/null +++ b/include/osmocom/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=gsm diff --git a/include/osmocom/gsm/Makefile.am b/include/osmocom/gsm/Makefile.am new file mode 100644 index 0000000..f9d5e7c --- /dev/null +++ b/include/osmocom/gsm/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=abis diff --git a/include/osmocom/gsm/abis/Makefile.am b/include/osmocom/gsm/abis/Makefile.am new file mode 100644 index 0000000..8bdcc96 --- /dev/null +++ b/include/osmocom/gsm/abis/Makefile.am @@ -0,0 +1,3 @@ +osmoabis_HEADERS = e1_input.h subchan_demux.h ipaccess.h trau_frame.h + +osmoabisdir = $(includedir)/osmocom/gsm/abis diff --git a/include/osmocom/gsm/abis/e1_input.h b/include/osmocom/gsm/abis/e1_input.h new file mode 100644 index 0000000..1911568 --- /dev/null +++ b/include/osmocom/gsm/abis/e1_input.h @@ -0,0 +1,226 @@ +#ifndef _E1_INPUT_H +#define _E1_INPUT_H + +#include <stdlib.h> +#include <netinet/in.h> + +#include <osmocom/core/linuxlist.h> +//#include <openbsc/gsm_data.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/gsm/abis/subchan_demux.h> + +#define NUM_E1_TS 32 + +enum e1inp_sign_type { + E1INP_SIGN_NONE, + E1INP_SIGN_OML, + E1INP_SIGN_RSL, +}; +const char *e1inp_signtype_name(enum e1inp_sign_type tp); + +struct e1inp_ts; + +struct e1inp_sign_link { + /* list of signalling links */ + struct llist_head list; + + /* to which timeslot do we belong? */ + struct e1inp_ts *ts; + + enum e1inp_sign_type type; + + /* trx for msg->trx of received msgs */ + struct gsm_bts_trx *trx; + + /* msgb queue of to-be-transmitted msgs */ + struct llist_head tx_list; + + /* SAPI and TEI on the E1 TS */ + uint8_t sapi; + uint8_t tei; + + union { + struct { + uint8_t channel; + } misdn; + } driver; +}; + +enum e1inp_ts_type { + E1INP_TS_TYPE_NONE, + E1INP_TS_TYPE_SIGN, + E1INP_TS_TYPE_TRAU, +}; +const char *e1inp_tstype_name(enum e1inp_ts_type tp); + +/* A timeslot in the E1 interface */ +struct e1inp_ts { + enum e1inp_ts_type type; + int num; + + /* to which line do we belong ? */ + struct e1inp_line *line; + + union { + struct { + /* list of all signalling links on this TS */ + struct llist_head sign_links; + /* delay for the queue */ + int delay; + /* timer when to dequeue next frame */ + struct osmo_timer_list tx_timer; + } sign; + struct { + /* subchannel demuxer for frames from E1 */ + struct subch_demux demux; + /* subchannel muxer for frames to E1 */ + struct subch_mux mux; + } trau; + }; + union { + struct { + /* mISDN driver has one fd for each ts */ + struct osmo_fd fd; + } misdn; + struct { + /* ip.access driver has one fd for each ts */ + struct osmo_fd fd; + } ipaccess; + struct { + /* DAHDI driver has one fd for each ts */ + struct osmo_fd fd; + struct lapd_instance *lapd; + } dahdi; + } driver; +}; + +struct gsm_e1_subslot { + /* Number of E1 link */ + uint8_t e1_nr; + /* Number of E1 TS inside E1 link */ + uint8_t e1_ts; + /* Sub-slot within the E1 TS, 0xff if full TS */ + uint8_t e1_ts_ss; +}; + +enum e1inp_line_role { + E1INP_LINE_R_NONE, + E1INP_LINE_R_BSC, + E1INP_LINE_R_BTS, + E1INP_LINE_R_MAX +}; + +struct e1inp_driver { + struct llist_head list; + const char *name; + int (*want_write)(struct e1inp_ts *ts); + int (*line_update)(struct e1inp_line *line, enum e1inp_line_role role); + int default_delay; +}; + +struct e1inp_line { + struct llist_head list; + int refcnt; + + unsigned int num; + const char *name; + + /* array of timestlots */ + struct e1inp_ts ts[NUM_E1_TS]; + + int (*rx)(struct msgb *msg, struct e1inp_ts *ts); + int (*rx_err)(int error); + + struct e1inp_driver *driver; + void *driver_data; +}; + +/* SS_INPUT signals */ +enum signal_input { + S_INP_NONE, + S_INP_TEI_UP, + S_INP_TEI_DN, + S_INP_LINE_INIT, + S_INP_LINE_ALARM, + S_INP_LINE_NOALARM, +}; + +/* register a driver with the E1 core */ +int e1inp_driver_register(struct e1inp_driver *drv); + +/* fine a previously registered driver */ +struct e1inp_driver *e1inp_driver_find(const char *name); + +/* register a line with the E1 core */ +int e1inp_line_register(struct e1inp_line *line); + +/* get a line by its ID */ +struct e1inp_line *e1inp_line_get(uint8_t e1_nr); + +/* create a line in the E1 input core */ +struct e1inp_line *e1inp_line_create(uint8_t e1_nr, const char *driver_name, int (*rx)(struct msgb *msg, struct e1inp_ts *ts), int (*rx_err)(int error)); + +/* find a sign_link for given TEI and SAPI in a TS */ +struct e1inp_sign_link * +e1inp_lookup_sign_link(struct e1inp_ts *ts, uint8_t tei, + uint8_t sapi); + +/* 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); + +/* configure and initialize one e1inp_ts */ +int e1inp_ts_config_sign(struct e1inp_ts *ts, struct e1inp_line *line); +int e1inp_ts_config_trau(struct e1inp_ts *ts, struct e1inp_line *line, + int (*subch_cb)(struct subch_demux *dmx, int ch, + uint8_t *data, int len, void *_priv)); + +/* Call from the Stack: configuration of this TS has changed */ +int e1inp_update_ts(struct e1inp_ts *ts); + +/* Receive a packet from the E1 driver */ +int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg, + uint8_t tei, uint8_t sapi); + +/* 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); + +/* 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); + +/* Write LAPD frames to the fd. */ +void e1_set_pcap_fd(int fd); + +/* called by TRAU muxer to obtain the destination mux entity */ +struct subch_mux *e1inp_get_mux(uint8_t e1_nr, uint8_t ts_nr); + +void e1inp_sign_link_destroy(struct e1inp_sign_link *link); +int e1inp_line_update(struct e1inp_line *line, enum e1inp_line_role role); + +struct gsm_network; +int ipaccess_setup(struct gsm_network *gsmnet); +int hsl_setup(struct gsm_network *gsmnet); + +extern struct llist_head e1inp_driver_list; +extern struct llist_head e1inp_line_list; + +int e1inp_vty_init(void); +void e1inp_init(void); + +int _abis_nm_sendmsg(struct msgb *msg, int to_trx_oml); + +/* XXX */ +struct input_signal_data { + int link_type; + uint8_t tei; + uint8_t sapi; + struct gsm_bts_trx *trx; + struct e1inp_line *line; +}; + +#endif /* _E1_INPUT_H */ diff --git a/include/osmocom/gsm/abis/ipaccess.h b/include/osmocom/gsm/abis/ipaccess.h new file mode 100644 index 0000000..2792572 --- /dev/null +++ b/include/osmocom/gsm/abis/ipaccess.h @@ -0,0 +1,93 @@ +#ifndef _OSMO_PROTO_IPACCESS_H +#define _OSMO_PROTO_IPACCESS_H + +#include <stdint.h> + +#define IPA_TCP_PORT_OML 3002 +#define IPA_TCP_PORT_RSL 3003 + +struct ipaccess_head { + uint16_t len; /* network byte order */ + uint8_t proto; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct ipaccess_head_ext { + uint8_t proto; + uint8_t data[0]; +} __attribute__ ((packed)); + +enum ipaccess_proto { + IPAC_PROTO_RSL = 0x00, + IPAC_PROTO_IPACCESS = 0xfe, + IPAC_PROTO_SCCP = 0xfd, + IPAC_PROTO_OML = 0xff, + + + /* OpenBSC extensions */ + IPAC_PROTO_OSMO = 0xee, + IPAC_PROTO_MGCP_OLD = 0xfc, +}; + +enum ipaccess_proto_ext { + IPAC_PROTO_EXT_CTRL = 0x00, + IPAC_PROTO_EXT_MGCP = 0x01, + IPAC_PROTO_EXT_LAC = 0x02, +}; + +enum ipaccess_msgtype { + IPAC_MSGT_PING = 0x00, + IPAC_MSGT_PONG = 0x01, + IPAC_MSGT_ID_GET = 0x04, + IPAC_MSGT_ID_RESP = 0x05, + IPAC_MSGT_ID_ACK = 0x06, + + /* OpenBSC extension */ + IPAC_MSGT_SCCP_OLD = 0xff, +}; + +enum ipaccess_id_tags { + IPAC_IDTAG_SERNR = 0x00, + IPAC_IDTAG_UNITNAME = 0x01, + IPAC_IDTAG_LOCATION1 = 0x02, + IPAC_IDTAG_LOCATION2 = 0x03, + IPAC_IDTAG_EQUIPVERS = 0x04, + IPAC_IDTAG_SWVERSION = 0x05, + IPAC_IDTAG_IPADDR = 0x06, + IPAC_IDTAG_MACADDR = 0x07, + IPAC_IDTAG_UNIT = 0x08, +}; + +/* + * Firmware specific header + */ +struct sdp_firmware { + char magic[4]; + char more_magic[2]; + uint16_t more_more_magic; + uint32_t header_length; + uint32_t file_length; + char sw_part[20]; + char text1[64]; + char time[12]; + char date[14]; + char text2[10]; + char version[20]; + uint16_t table_offset; + /* stuff i don't know */ +} __attribute__((packed)); + +struct sdp_header_entry { + uint16_t something1; + char text1[64]; + char time[12]; + char date[14]; + char text2[10]; + char version[20]; + uint32_t length; + uint32_t addr1; + uint32_t addr2; + uint32_t start; +} __attribute__((packed)); + +#endif /* _OSMO_PROTO_IPACCESS_H */ diff --git a/include/osmocom/gsm/abis/subchan_demux.h b/include/osmocom/gsm/abis/subchan_demux.h new file mode 100644 index 0000000..b71c856 --- /dev/null +++ b/include/osmocom/gsm/abis/subchan_demux.h @@ -0,0 +1,101 @@ +#ifndef _SUBCH_DEMUX_H +#define _SUBCH_DEMUX_H +/* A E1 sub-channel (de)multiplexer with TRAU frame sync */ + +/* (C) 2009 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 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 <stdint.h> +#include <osmocom/core/linuxlist.h> + +#define NR_SUBCH 4 +#define TRAU_FRAME_SIZE 40 +#define TRAU_FRAME_BITS (TRAU_FRAME_SIZE*8) + +/***********************************************************************/ +/* DEMULTIPLEXER */ +/***********************************************************************/ + +struct demux_subch { + uint8_t out_bitbuf[TRAU_FRAME_BITS]; + uint16_t out_idx; /* next bit to be written in out_bitbuf */ + /* number of consecutive zeros that we have received (for sync) */ + unsigned int consecutive_zeros; + /* are we in TRAU frame sync or not? */ + unsigned int in_sync; +}; + +struct subch_demux { + /* bitmask of currently active subchannels */ + uint8_t chan_activ; + /* one demux_subch struct for every subchannel */ + struct demux_subch subch[NR_SUBCH]; + /* callback to be called once we have received a complete + * frame on a given subchannel */ + int (*out_cb)(struct subch_demux *dmx, int ch, uint8_t *data, int len, + void *); + /* user-provided data, transparently passed to out_cb() */ + void *data; +}; + +/* initialize one demultiplexer instance */ +int subch_demux_init(struct subch_demux *dmx); + +/* feed 'len' number of muxed bytes into the demultiplexer */ +int subch_demux_in(struct subch_demux *dmx, uint8_t *data, int len); + +/* activate decoding/processing for one subchannel */ +int subch_demux_activate(struct subch_demux *dmx, int subch); + +/* deactivate decoding/processing for one subchannel */ +int subch_demux_deactivate(struct subch_demux *dmx, int subch); + +/***********************************************************************/ +/* MULTIPLEXER */ +/***********************************************************************/ + +/* one element in the tx_queue of a muxer sub-channel */ +struct subch_txq_entry { + struct llist_head list; + + unsigned int bit_len; /* total number of bits in 'bits' */ + unsigned int next_bit; /* next bit to be transmitted */ + + uint8_t bits[0]; /* one bit per byte */ +}; + +struct mux_subch { + struct llist_head tx_queue; +}; + +/* structure representing one instance of the subchannel muxer */ +struct subch_mux { + struct mux_subch subch[NR_SUBCH]; +}; + +/* initialize a subchannel muxer instance */ +int subchan_mux_init(struct subch_mux *mx); + +/* request the output of 'len' multiplexed bytes */ +int subchan_mux_out(struct subch_mux *mx, uint8_t *data, int len); + +/* enqueue some data into one sub-channel of the muxer */ +int subchan_mux_enqueue(struct subch_mux *mx, int s_nr, const uint8_t *data, + int len); + +#endif /* _SUBCH_DEMUX_H */ diff --git a/include/osmocom/gsm/abis/trau_frame.h b/include/osmocom/gsm/abis/trau_frame.h new file mode 100644 index 0000000..a76da33 --- /dev/null +++ b/include/osmocom/gsm/abis/trau_frame.h @@ -0,0 +1,63 @@ +#ifndef _TRAU_FRAME_H +#define _TRAU_FRAME_H +/* TRAU frame handling according to GSM TS 08.60 */ + +/* (C) 2009 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 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 <stdint.h> + +/* 21 for FR/EFR, 25 for AMR, 15 for OM, 15 for data, 13 for E-data, 21 idle */ +#define MAX_C_BITS 25 +/* 260 for FR/EFR, 256 for AMR, 264 for OM, 288 for E-data */ +#define MAX_D_BITS 288 +/* for all speech frames */ +#define MAX_T_BITS 4 +/* for OM */ +#define MAX_S_BITS 6 +/* for E-data */ +#define MAX_M_BITS 2 + +struct decoded_trau_frame { + uint8_t c_bits[MAX_C_BITS]; + uint8_t d_bits[MAX_D_BITS]; + uint8_t t_bits[MAX_T_BITS]; + uint8_t s_bits[MAX_S_BITS]; + uint8_t m_bits[MAX_M_BITS]; +}; + +#define TRAU_FT_FR_UP 0x02 /* 0 0 0 1 0 - 3.5.1.1.1 */ +#define TRAU_FT_FR_DOWN 0x1c /* 1 1 1 0 0 - 3.5.1.1.1 */ +#define TRAU_FT_EFR 0x1a /* 1 1 0 1 0 - 3.5.1.1.1 */ +#define TRAU_FT_AMR 0x06 /* 0 0 1 1 0 - 3.5.1.2 */ +#define TRAU_FT_OM_UP 0x07 /* 0 0 1 0 1 - 3.5.2 */ +#define TRAU_FT_OM_DOWN 0x1b /* 1 1 0 1 1 - 3.5.2 */ +#define TRAU_FT_DATA_UP 0x08 /* 0 1 0 0 0 - 3.5.3 */ +#define TRAU_FT_DATA_DOWN 0x16 /* 1 0 1 1 0 - 3.5.3 */ +#define TRAU_FT_D145_SYNC 0x14 /* 1 0 1 0 0 - 3.5.3 */ +#define TRAU_FT_EDATA 0x1f /* 1 1 1 1 1 - 3.5.4 */ +#define TRAU_FT_IDLE_UP 0x10 /* 1 0 0 0 0 - 3.5.5 */ +#define TRAU_FT_IDLE_DOWN 0x0e /* 0 1 1 1 0 - 3.5.5 */ + + +int decode_trau_frame(struct decoded_trau_frame *fr, const uint8_t *trau_bits); +int encode_trau_frame(uint8_t *trau_bits, const struct decoded_trau_frame *fr); +int trau_frame_up2down(struct decoded_trau_frame *fr); + + +#endif /* _TRAU_FRAME_H */ diff --git a/libosmoabis.pc.in b/libosmoabis.pc.in new file mode 100644 index 0000000..751149d --- /dev/null +++ b/libosmoabis.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: A-bis Core Library +Description: C Utility Library +Version: @VERSION@ +Libs: -L${libdir} -losmoabis +Cflags: -I${includedir}/ + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..292d01e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,17 @@ +SUBDIRS=input + +# This is _NOT_ the library release version, it's an API version. +# Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification +LIBVERSION=0:0:0 + +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) + +lib_LTLIBRARIES = libosmoabis.la + +libosmoabis_la_LIBADD = input/libosmoabis-input.la +libosmoabis_la_SOURCES = e1_input.c \ + subchan_demux.c \ + trau_frame.c \ + socket.c diff --git a/src/e1_input.c b/src/e1_input.c new file mode 100644 index 0000000..d806766 --- /dev/null +++ b/src/e1_input.c @@ -0,0 +1,628 @@ +/* OpenBSC Abis interface to E1 */ + +/* (C) 2008-2009 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 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/core/signal.h> +//#include <openbsc/debug.h> +//#include <openbsc/gsm_data.h> +#include <osmocom/gsm/abis/e1_input.h> +//#include <openbsc/abis_nm.h> +//#include <openbsc/abis_rsl.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/abis/subchan_demux.h> +//#include <openbsc/trau_frame.h> +//#include <openbsc/trau_mux.h> +#include <talloc.h> +//#include <openbsc/signal.h> +//#include <openbsc/misdn.h> + +//#include "../../bscconfig.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; + +/* + * 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 { + 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 ... */ +} __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); + + +static int pcap_fd = -1; + +void e1_set_pcap_fd(int fd) +{ + int ret; + struct pcap_hdr header = { + .magic_number = 0xa1b2c3d4, + .version_major = 2, + .version_minor = 4, + .thiszone = 0, + .sigfigs = 0, + .snaplen = 65535, + .network = DLT_LINUX_LAPD, + }; + + pcap_fd = fd; + ret = write(pcap_fd, &header, sizeof(header)); +} + +/* This currently only works for the D-Channel */ +static void write_pcap_packet(int direction, int sapi, int tei, + struct msgb *msg) { + if (pcap_fd < 0) + return; + + int ret; + 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); + + ret = write(pcap_fd, &payload_header, sizeof(payload_header)); + ret = write(pcap_fd, &header, sizeof(header)); + ret = write(pcap_fd, &lapd_header, sizeof(lapd_header)); + ret = write(pcap_fd, msg->l2h, msgb_l2len(msg)); +} + +static const char *sign_types[] = { + [E1INP_SIGN_NONE] = "None", + [E1INP_SIGN_OML] = "OML", + [E1INP_SIGN_RSL] = "RSL", +}; +const char *e1inp_signtype_name(enum e1inp_sign_type tp) +{ + if (tp >= ARRAY_SIZE(sign_types)) + return "undefined"; + return sign_types[tp]; +} + +static const char *ts_types[] = { + [E1INP_TS_TYPE_NONE] = "None", + [E1INP_TS_TYPE_SIGN] = "Signalling", + [E1INP_TS_TYPE_TRAU] = "TRAU", +}; + +const char *e1inp_tstype_name(enum e1inp_ts_type tp) +{ + if (tp >= ARRAY_SIZE(ts_types)) + return "undefined"; + return ts_types[tp]; +} + +int abis_rsl_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) { + LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx == 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); + + /* dump it */ + write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg); + + return 0; +} + +int abis_nm_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; + + if (!sign_link) { + LOGP(DNM, LOGL_ERROR, "nm_sendmsg: msg->trx == NULL\n"); + 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); + + /* dump it */ + write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg); + + return 0; +} + +/* Timeslot */ +int e1inp_ts_config_trau(struct e1inp_ts *ts, struct e1inp_line *line, + int (*subch_cb)(struct subch_demux *dmx, int ch, + uint8_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 = subch_cb; + ts->trau.demux.data = ts; + subch_demux_init(&ts->trau.demux); + return 0; +} + +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; +} + +struct e1inp_line *e1inp_line_get(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; +} + +struct e1inp_line * +e1inp_line_create(uint8_t e1_nr, const char *driver_name, + int (*rx)(struct msgb *msgb, struct e1inp_ts *ts), + int (*rx_err)(int error)) +{ + struct e1inp_driver *driver; + struct e1inp_line *line; + int i; + + line = e1inp_line_get(e1_nr); + if (line) { + LOGP(DINP, LOGL_ERROR, "E1 Line %u already exists\n", + e1_nr); + return NULL; + } + + driver = e1inp_driver_find(driver_name); + if (!driver) { + LOGP(DINP, 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->rx = rx; + line->rx_err = rx_err; + + 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; +} + +#if 0 +struct e1inp_line *e1inp_line_get_create(uint8_t e1_nr) +{ + struct e1inp_line *line; + int i; + + line = e1inp_line_get(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_get(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; + + 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); + + 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: + /* consult the list of signalling links */ + write_pcap_packet(PCAP_INPUT, sapi, tei, msg); + link = e1inp_lookup_sign_link(ts, tei, sapi); + if (!link) { + LOGP(DMI, LOGL_ERROR, "didn't find signalling link for " + "tei %d, sapi %d\n", tei, sapi); + return -EINVAL; + } +// log_set_context(BSC_CTX_BTS, link->trx->bts); + if (!ts->line->rx) + printf("XXX Error, horror\n"); + + ts->line->rx(msg, ts); + break; + case E1INP_TS_TYPE_TRAU: + ts->line->rx(msg, ts); +// ret = subch_demux_in(&ts->trau.demux, msg->l2h, msgb_l2len(msg)); + break; + default: + ret = -EINVAL; + LOGP(DMI, LOGL_ERROR, "unknown TS type %u\n", ts->type); + break; + } + + return ret; +} + +#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); + msgb_put(msg, 40); + break; + default: + LOGP(DMI, LOGL_ERROR, "unsupported E1 TS type %u\n", e1i_ts->type); + return NULL; + } + return msg; +} + +/* 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; + struct input_signal_data isd; + + link = e1inp_lookup_sign_link(ts, tei, sapi); + if (!link) + return -EINVAL; + + isd.link_type = link->type; + isd.trx = link->trx; + isd.tei = tei; + isd.sapi = sapi; + + /* report further upwards */ + osmo_signal_dispatch(SS_INPUT, evt, &isd); + return 0; +} + +/* 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, enum e1inp_line_role role) +{ + struct input_signal_data isd; + int rc; + + /* This line has been already initialized, skip this. */ + if (++line->refcnt > 1) + return 0; + + if (line->driver && line->driver->line_update) + rc = line->driver->line_update(line, role); + else + rc = 0; + + /* 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_INPUT, S_INP_LINE_INIT, &isd); + + return rc; +} + +static int e1i_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + if (subsys != SS_GLOBAL || + signal != S_GLOBAL_SHUTDOWN) + return 0; + + if (pcap_fd) { + close(pcap_fd); + pcap_fd = -1; + } + + return 0; +} + +void e1inp_misdn_init(void); +void e1inp_dahdi_init(void); +void e1inp_ipaccess_init(void); +void e1inp_hsl_init(void); + +void e1inp_init(void) +{ + tall_sigl_ctx = talloc_named_const(tall_e1inp_ctx, 1, + "e1inp_sign_link"); + osmo_signal_register_handler(SS_GLOBAL, e1i_sig_cb, NULL); + + e1inp_misdn_init(); +#ifdef HAVE_DAHDI_USER_H + e1inp_dahdi_init(); +#endif + e1inp_ipaccess_init(); + e1inp_hsl_init(); +} 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); +} diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..f0f8fe3 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,109 @@ +/* OpenBSC sokcet code, taken from 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/core/msgb.h> +#include <osmocom/core/logging.h> +#include <talloc.h> + +int make_sock(struct osmo_fd *bfd, int proto, + uint32_t ip, uint16_t port, int priv_nr, + int (*cb)(struct osmo_fd *fd, unsigned int what), void *data) +{ + struct sockaddr_in addr; + int ret, on = 1; + int type = SOCK_STREAM; + + switch (proto) { + case IPPROTO_TCP: + type = SOCK_STREAM; + break; + case IPPROTO_UDP: + type = SOCK_DGRAM; + break; + case IPPROTO_GRE: + type = SOCK_RAW; + break; + default: + return -EINVAL; + } + + bfd->fd = socket(AF_INET, type, proto); + bfd->cb = cb; + bfd->when = BSC_FD_READ; + bfd->data = data; + bfd->priv_nr = priv_nr; + + if (bfd->fd < 0) { + LOGP(DINP, LOGL_ERROR, "could not create socket.\n"); + return -EIO; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (ip != INADDR_ANY) + addr.sin_addr.s_addr = htonl(ip); + else + addr.sin_addr.s_addr = INADDR_ANY; + + setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not bind socket %s\n", + strerror(errno)); + close(bfd->fd); + return -EIO; + } + + if (proto == IPPROTO_TCP) { + ret = listen(bfd->fd, 1); + if (ret < 0) { + perror("listen"); + close(bfd->fd); + return ret; + } + } + + ret = osmo_fd_register(bfd); + if (ret < 0) { + perror("register_listen_fd"); + close(bfd->fd); + return ret; + } + return 0; +} diff --git a/src/subchan_demux.c b/src/subchan_demux.c new file mode 100644 index 0000000..6ff66bc --- /dev/null +++ b/src/subchan_demux.c @@ -0,0 +1,323 @@ +/* A E1 sub-channel (de)multiplexer with TRAU frame sync */ + +/* (C) 2009 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 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 <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/gsm/abis/subchan_demux.h> +#include <osmocom/gsm/abis/trau_frame.h> +//#include <openbsc/debug.h> +#include <talloc.h> +//#include <openbsc/gsm_data.h> + +void *tall_tqe_ctx; + +static inline void append_bit(struct demux_subch *sch, uint8_t bit) +{ + sch->out_bitbuf[sch->out_idx++] = bit; +} + +#define SYNC_HDR_BITS 16 +static const uint8_t nullbytes[SYNC_HDR_BITS]; + +/* check if we have just completed the 16 bit zero sync header, + * in accordance with GSM TS 08.60 Chapter 4.8.1 */ +static int sync_hdr_complete(struct demux_subch *sch, uint8_t bit) +{ + if (bit == 0) + sch->consecutive_zeros++; + else + sch->consecutive_zeros = 0; + + if (sch->consecutive_zeros >= SYNC_HDR_BITS) { + sch->consecutive_zeros = 0; + return 1; + } + + return 0; +} + +/* resynchronize to current location */ +static void resync_to_here(struct demux_subch *sch) +{ + memset(sch->out_bitbuf, 0, SYNC_HDR_BITS); + + /* set index in a way that we can continue receiving bits after + * the end of the SYNC header */ + sch->out_idx = SYNC_HDR_BITS; + sch->in_sync = 1; +} + +int subch_demux_init(struct subch_demux *dmx) +{ + int i; + + dmx->chan_activ = 0; + for (i = 0; i < NR_SUBCH; i++) { + struct demux_subch *sch = &dmx->subch[i]; + sch->out_idx = 0; + memset(sch->out_bitbuf, 0xff, sizeof(sch->out_bitbuf)); + } + return 0; +} + +/* input some arbitrary (modulo 4) number of bytes of a 64k E1 channel, + * split it into the 16k subchannels */ +int subch_demux_in(struct subch_demux *dmx, uint8_t *data, int len) +{ + int i, c; + + /* we avoid partially filled bytes in outbuf */ + if (len % 4) + return -EINVAL; + + for (i = 0; i < len; i++) { + uint8_t inbyte = data[i]; + + for (c = 0; c < NR_SUBCH; c++) { + struct demux_subch *sch = &dmx->subch[c]; + uint8_t inbits; + uint8_t bit; + + /* ignore inactive subchannels */ + if (!(dmx->chan_activ & (1 << c))) + continue; + + inbits = inbyte >> (c << 1); + + /* two bits for each subchannel */ + if (inbits & 0x01) + bit = 1; + else + bit = 0; + append_bit(sch, bit); + + if (sync_hdr_complete(sch, bit)) + resync_to_here(sch); + + if (inbits & 0x02) + bit = 1; + else + bit = 0; + append_bit(sch, bit); + + if (sync_hdr_complete(sch, bit)) + resync_to_here(sch); + + /* FIXME: verify the first bit in octet 2, 4, 6, ... + * according to TS 08.60 4.8.1 */ + + /* once we have reached TRAU_FRAME_BITS, call + * the TRAU frame handler callback function */ + if (sch->out_idx >= TRAU_FRAME_BITS) { + if (sch->in_sync) { + dmx->out_cb(dmx, c, sch->out_bitbuf, + sch->out_idx, dmx->data); + sch->in_sync = 0; + } + sch->out_idx = 0; + } + } + } + return i; +} + +int subch_demux_activate(struct subch_demux *dmx, int subch) +{ + if (subch >= NR_SUBCH) + return -EINVAL; + + dmx->chan_activ |= (1 << subch); + return 0; +} + +int subch_demux_deactivate(struct subch_demux *dmx, int subch) +{ + if (subch >= NR_SUBCH) + return -EINVAL; + + dmx->chan_activ &= ~(1 << subch); + return 0; +} + +/* MULTIPLEXER */ + +static int alloc_add_idle_frame(struct subch_mux *mx, int sch_nr) +{ + /* allocate and initialize with idle pattern */ + return subchan_mux_enqueue(mx, sch_nr, trau_idle_frame(), + TRAU_FRAME_BITS); +} + +/* return the requested number of bits from the specified subchannel */ +static int get_subch_bits(struct subch_mux *mx, int subch, + uint8_t *bits, int num_requested) +{ + struct mux_subch *sch = &mx->subch[subch]; + int num_bits = 0; + + while (num_bits < num_requested) { + struct subch_txq_entry *txe; + int num_bits_left; + int num_bits_thistime; + + /* make sure we have a valid entry at top of tx queue. + * if not, add an idle frame */ + if (llist_empty(&sch->tx_queue)) + alloc_add_idle_frame(mx, subch); + + if (llist_empty(&sch->tx_queue)) + return -EIO; + + txe = llist_entry(sch->tx_queue.next, struct subch_txq_entry, list); + num_bits_left = txe->bit_len - txe->next_bit; + + if (num_bits_left < num_requested) + num_bits_thistime = num_bits_left; + else + num_bits_thistime = num_requested; + + /* pull the bits from the txe */ + memcpy(bits + num_bits, txe->bits + txe->next_bit, num_bits_thistime); + txe->next_bit += num_bits_thistime; + + /* free the tx_queue entry if it is fully consumed */ + if (txe->next_bit >= txe->bit_len) { + llist_del(&txe->list); + talloc_free(txe); + } + + /* increment global number of bits dequeued */ + num_bits += num_bits_thistime; + } + + return num_requested; +} + +/* compact an array of 8 single-bit bytes into one byte of 8 bits */ +static uint8_t compact_bits(const uint8_t *bits) +{ + uint8_t ret = 0; + int i; + + for (i = 0; i < 8; i++) + ret |= (bits[i] ? 1 : 0) << i; + + return ret; +} + +/* obtain a single output byte from the subchannel muxer */ +static int mux_output_byte(struct subch_mux *mx, uint8_t *byte) +{ + uint8_t bits[8]; + int rc; + + /* combine two bits of every subchan */ + rc = get_subch_bits(mx, 0, &bits[0], 2); + rc = get_subch_bits(mx, 1, &bits[2], 2); + rc = get_subch_bits(mx, 2, &bits[4], 2); + rc = get_subch_bits(mx, 3, &bits[6], 2); + + *byte = compact_bits(bits); + + return rc; +} + +/* Request the output of some muxed bytes from the subchan muxer */ +int subchan_mux_out(struct subch_mux *mx, uint8_t *data, int len) +{ + int i; + + for (i = 0; i < len; i++) { + int rc; + rc = mux_output_byte(mx, &data[i]); + if (rc < 0) + break; + } + return i; +} + +static int llist_len(struct llist_head *head) +{ + struct llist_head *entry; + int i = 0; + + llist_for_each(entry, head) + i++; + + return i; +} + +/* evict the 'num_evict' number of oldest entries in the queue */ +static void tx_queue_evict(struct mux_subch *sch, int num_evict) +{ + struct subch_txq_entry *tqe; + int i; + + for (i = 0; i < num_evict; i++) { + if (llist_empty(&sch->tx_queue)) + return; + + tqe = llist_entry(sch->tx_queue.next, struct subch_txq_entry, list); + llist_del(&tqe->list); + talloc_free(tqe); + } +} + +/* enqueue some data into the tx_queue of a given subchannel */ +int subchan_mux_enqueue(struct subch_mux *mx, int s_nr, const uint8_t *data, + int len) +{ + struct mux_subch *sch = &mx->subch[s_nr]; + int list_len = llist_len(&sch->tx_queue); + struct subch_txq_entry *tqe = talloc_zero_size(tall_tqe_ctx, + sizeof(*tqe) + len); + if (!tqe) + return -ENOMEM; + + tqe->bit_len = len; + memcpy(tqe->bits, data, len); + + if (list_len > 2) + tx_queue_evict(sch, list_len-2); + + llist_add_tail(&tqe->list, &sch->tx_queue); + + return 0; +} + +/* initialize one subchannel muxer instance */ +int subchan_mux_init(struct subch_mux *mx) +{ + int i; + + memset(mx, 0, sizeof(*mx)); + for (i = 0; i < NR_SUBCH; i++) { + struct mux_subch *sch = &mx->subch[i]; + INIT_LLIST_HEAD(&sch->tx_queue); + } + + return 0; +} diff --git a/src/trau_frame.c b/src/trau_frame.c new file mode 100644 index 0000000..7be6e8f --- /dev/null +++ b/src/trau_frame.c @@ -0,0 +1,260 @@ +/* TRAU frame handling according to GSM TS 08.60 */ + +/* (C) 2009 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 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 <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/gsm/abis/trau_frame.h> +#include <osmocom/gsm/abis/subchan_demux.h> +//#include <openbsc/debug.h> + +static uint32_t get_bits(const uint8_t *bitbuf, int offset, int num) +{ + int i; + uint32_t ret = 0; + + for (i = offset; i < offset + num; i++) { + ret = ret << 1; + if (bitbuf[i]) + ret |= 1; + } + return ret; +} + +/* Decode according to 3.1.1 */ +static void decode_fr(struct decoded_trau_frame *fr, const uint8_t *trau_bits) +{ + int i; + int d_idx = 0; + + /* C1 .. C15 */ + memcpy(fr->c_bits+0, trau_bits+17, 15); + /* C16 .. C21 */ + memcpy(fr->c_bits+15, trau_bits+310, 6); + /* T1 .. T4 */ + memcpy(fr->t_bits+0, trau_bits+316, 4); + /* D1 .. D255 */ + for (i = 32; i < 304; i+= 16) { + memcpy(fr->d_bits + d_idx, trau_bits+i+1, 15); + d_idx += 15; + } + /* D256 .. D260 */ + memcpy(fr->d_bits + d_idx, trau_bits + 305, 5); +} + +/* Decode according to 3.1.2 */ +static void decode_amr(struct decoded_trau_frame *fr, const uint8_t *trau_bits) +{ + int i; + int d_idx = 0; + + /* C1 .. C15 */ + memcpy(fr->c_bits+0, trau_bits+17, 15); + /* C16 .. C25 */ + memcpy(fr->c_bits+15, trau_bits+33, 10); + /* T1 .. T4 */ + memcpy(fr->t_bits+0, trau_bits+316, 4); + /* D1 .. D5 */ + memcpy(fr->d_bits, trau_bits+43, 5); + /* D6 .. D245 */ + for (i = 48; i < 304; i += 16) { + memcpy(fr->d_bits + d_idx, trau_bits+i+1, 15); + d_idx += 15; + } + /* D246 .. D256 */ + memcpy(fr->d_bits + d_idx, trau_bits + 305, 11); +} + +int decode_trau_frame(struct decoded_trau_frame *fr, const uint8_t *trau_bits) +{ + uint8_t cbits5 = get_bits(trau_bits, 17, 5); + + switch (cbits5) { + case TRAU_FT_FR_UP: + case TRAU_FT_FR_DOWN: + case TRAU_FT_IDLE_UP: + case TRAU_FT_IDLE_DOWN: + case TRAU_FT_EFR: + decode_fr(fr, trau_bits); + break; + case TRAU_FT_AMR: + decode_amr(fr, trau_bits); + break; + case TRAU_FT_OM_UP: + case TRAU_FT_OM_DOWN: + case TRAU_FT_DATA_UP: + case TRAU_FT_DATA_DOWN: + case TRAU_FT_D145_SYNC: + case TRAU_FT_EDATA: + LOGP(DMUX, LOGL_NOTICE, "can't decode unimplemented TRAU " + "Frame Type 0x%02x\n", cbits5); + return -1; + break; + default: + LOGP(DMUX, LOGL_NOTICE, "can't decode unknown TRAU " + "Frame Type 0x%02x\n", cbits5); + return -1; + break; + } + + return 0; +} + +const uint8_t ft_fr_down_bits[] = { 1, 1, 1, 0, 0 }; +const uint8_t ft_idle_down_bits[] = { 0, 1, 1, 1, 0 }; + +/* modify an uplink TRAU frame so we can send it downlink */ +int trau_frame_up2down(struct decoded_trau_frame *fr) +{ + uint8_t cbits5 = get_bits(fr->c_bits, 0, 5); + + switch (cbits5) { + case TRAU_FT_FR_UP: + memcpy(fr->c_bits, ft_fr_down_bits, 5); + /* clear time alignment */ + memset(fr->c_bits+5, 0, 6); + /* FIXME: SP / BFI in case of DTx */ + /* C12 .. C21 are spare and coded as '1' */ + memset(fr->c_bits+11, 0x01, 10); + break; + case TRAU_FT_EFR: + /* clear time alignment */ + memset(fr->c_bits+5, 0, 6); + /* FIXME: set UFE appropriately */ + /* FIXME: SP / BFI in case of DTx */ + break; + case TRAU_FT_IDLE_UP: + memcpy(fr->c_bits, ft_idle_down_bits, 5); + /* clear time alignment */ + memset(fr->c_bits+5, 0, 6); + /* FIXME: SP / BFI in case of DTx */ + /* C12 .. C21 are spare and coded as '1' */ + memset(fr->c_bits+11, 0x01, 10); + break; + case TRAU_FT_FR_DOWN: + case TRAU_FT_IDLE_DOWN: + case TRAU_FT_OM_DOWN: + case TRAU_FT_DATA_DOWN: + /* we cannot convert a downlink to a downlink frame */ + return -EINVAL; + break; + case TRAU_FT_AMR: + case TRAU_FT_OM_UP: + case TRAU_FT_DATA_UP: + case TRAU_FT_D145_SYNC: + case TRAU_FT_EDATA: + LOGP(DMUX, LOGL_NOTICE, "unimplemented TRAU Frame Type " + "0x%02x\n", cbits5); + return -1; + break; + default: + LOGP(DMUX, LOGL_NOTICE, "unknown TRAU Frame Type " + "0x%02x\n", cbits5); + return -1; + break; + } + + return 0; + +} + +static void encode_fr(uint8_t *trau_bits, const struct decoded_trau_frame *fr) +{ + int i; + int d_idx = 0; + + trau_bits[16] = 1; + /* C1 .. C15 */ + memcpy(trau_bits+17, fr->c_bits+0, 15); + /* D1 .. D255 */ + for (i = 32; i < 304; i+= 16) { + trau_bits[i] = 1; + memcpy(trau_bits+i+1, fr->d_bits + d_idx, 15); + d_idx += 15; + } + /* D256 .. D260 */ + trau_bits[304] = 1; + memcpy(trau_bits + 305, fr->d_bits + d_idx, 5); + /* C16 .. C21 */ + memcpy(trau_bits+310, fr->c_bits+15, 6); + + /* FIXME: handle timing adjustment */ + + /* T1 .. T4 */ + memcpy(trau_bits+316, fr->t_bits+0, 4); +} + +int encode_trau_frame(uint8_t *trau_bits, const struct decoded_trau_frame *fr) +{ + uint8_t cbits5 = get_bits(fr->c_bits, 0, 5); + + /* 16 bits of sync header */ + memset(trau_bits, 0, 16); + + switch (cbits5) { + case TRAU_FT_FR_UP: + case TRAU_FT_FR_DOWN: + case TRAU_FT_IDLE_UP: + case TRAU_FT_IDLE_DOWN: + case TRAU_FT_EFR: + encode_fr(trau_bits, fr); + break; + case TRAU_FT_AMR: + case TRAU_FT_OM_UP: + case TRAU_FT_OM_DOWN: + case TRAU_FT_DATA_UP: + case TRAU_FT_DATA_DOWN: + case TRAU_FT_D145_SYNC: + case TRAU_FT_EDATA: + LOGP(DMUX, LOGL_NOTICE, "unimplemented TRAU Frame Type " + "0x%02x\n", cbits5); + return -1; + break; + default: + LOGP(DMUX, LOGL_NOTICE, "unknown TRAU Frame Type " + "0x%02x\n", cbits5); + return -1; + break; + } + + return 0; +} + +static struct decoded_trau_frame fr_idle_frame = { + .c_bits = { 0, 1, 1, 1, 0 }, /* IDLE DOWNLINK 3.5.5 */ + .t_bits = { 1, 1, 1, 1 }, +}; +static uint8_t encoded_idle_frame[TRAU_FRAME_BITS]; +static int dbits_initted; + +uint8_t *trau_idle_frame(void) +{ + /* only initialize during the first call */ + if (!dbits_initted) { + /* set all D-bits to 1 */ + memset(&fr_idle_frame.d_bits, 0x01, 260); + encode_fr(encoded_idle_frame, &fr_idle_frame); + } + return encoded_idle_frame; +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..c9986fd --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,10 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) + +noinst_PROGRAMS = e1inp_ipa_bsc_test + +e1inp_ipa_bsc_test_SOURCES = e1inp_ipa_bsc_test.c + +e1inp_ipa_bsc_test_LDADD = $(top_builddir)/src/libosmoabis.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) diff --git a/tests/e1inp_ipa_bsc_test.c b/tests/e1inp_ipa_bsc_test.c new file mode 100644 index 0000000..8985e98 --- /dev/null +++ b/tests/e1inp_ipa_bsc_test.c @@ -0,0 +1,69 @@ +#include <stdio.h> +#include <osmocom/gsm/abis/e1_input.h> + +static int rx_cb(struct msgb *msg, struct e1inp_ts *ts) +{ + printf("received data\n"); + + /* For ISDN: from the timeslot type, we can get if it's: + * - E1INP_TS_TYPE_SIGN: in this case, check if we have a signal + * link in this timeslot, then, check if it's OML or RSL and pass + * it to the upper layer. + * - E1INP_TS_TYPE_TRAU. + * + * For INET: we have to check if this is a control message, if it's + * a response to previous request id, we enable the signal links for + * OML and RSL. Otherwise, look up for the existing signal link and + * pass it to the upper layer. We don't receive TRAU frames, we use + * RTP. + */ + return 0; +} + +static int rx_err_cb(int error) +{ + printf("error, malformed message\n"); + return 0; +} + +int main(void) +{ + struct e1inp_line *line; + struct e1inp_ts *sign_ts; + + e1inp_init(); + +#define LINENR 0 + + line = e1inp_line_create(LINENR, "ipa", rx_cb, rx_err_cb); + if (line == NULL) { + fprintf(stderr, "problem creating E1 line\n"); + exit(EXIT_FAILURE); + } + sign_ts = &line->ts[0]; + if (e1inp_ts_config_sign(sign_ts, line) < 0) { + fprintf(stderr, "problem setting timeslot in E1 line\n"); + exit(EXIT_FAILURE); + } + + /* + * Depending if this is a real or virtual E1 lines: + * - real (ISDN): create signal link for OML and RSL before line up. + * - vitual (INET): we create it once we have seen response to REQ_ID. + * + * The signal link is created via e1inp_sign_link_create(...) + * + * See e1_reconfig_trx and e1_reconfig_bts in libbsc/e1_config.c, + * it explains how this is done with ISDN. + */ + + if (e1inp_line_update(line, E1INP_LINE_R_BSC) < 0) { + fprintf(stderr, "problem enabling E1 line\n"); + exit(EXIT_FAILURE); + } + + while (1) { + osmo_select_main(0); + } + return 0; +} |